mirror of
https://github.com/tursom/TursomServer.git
synced 2025-01-01 07:50:47 +08:00
独立出请求与响应的 HeaderAdapter
This commit is contained in:
parent
9e765dc997
commit
2629b93c23
@ -53,7 +53,6 @@ allprojects {
|
||||
name = "GitHubPackages"
|
||||
url = uri("https://maven.pkg.github.com/tursom/TursomServer")
|
||||
credentials {
|
||||
println project.findProperty("gpr.user")
|
||||
//username = project.findProperty("gpr.user") ?: System.getenv("USERNAME")
|
||||
username = "tursom"
|
||||
password = project.findProperty("gpr.key") ?: System.getenv("PASSWORD")
|
||||
|
@ -2,29 +2,60 @@ package cn.tursom.web.netty
|
||||
|
||||
import cn.tursom.core.buffer.ByteBuffer
|
||||
import cn.tursom.web.ExceptionContent
|
||||
import io.netty.buffer.ByteBuf
|
||||
import io.netty.buffer.CompositeByteBuf
|
||||
import io.netty.buffer.Unpooled
|
||||
import io.netty.channel.ChannelHandlerContext
|
||||
import io.netty.handler.codec.http.*
|
||||
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
class NettyExceptionContent(
|
||||
val ctx: ChannelHandlerContext,
|
||||
override val cause: Throwable
|
||||
) : ExceptionContent {
|
||||
override fun write(message: String) {
|
||||
ctx.write(Unpooled.wrappedBuffer(message.toByteArray()))
|
||||
}
|
||||
val ctx: ChannelHandlerContext,
|
||||
override val cause: Throwable
|
||||
) : ExceptionContent, NettyResponseHeaderAdapter() {
|
||||
val responseBodyBuf: CompositeByteBuf = ctx.alloc().compositeBuffer()!!
|
||||
var responseStatus: HttpResponseStatus = HttpResponseStatus.INTERNAL_SERVER_ERROR
|
||||
override var responseCode: Int
|
||||
get() = responseStatus.code()
|
||||
set(value) {
|
||||
responseStatus = HttpResponseStatus.valueOf(value)
|
||||
}
|
||||
|
||||
override fun write(bytes: ByteArray, offset: Int, length: Int) {
|
||||
ctx.write(Unpooled.wrappedBuffer(bytes, offset, length))
|
||||
}
|
||||
override fun write(message: String) {
|
||||
responseBodyBuf.addComponent(Unpooled.wrappedBuffer(message.toByteArray()))
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
when (buffer) {
|
||||
is NettyByteBuffer -> ctx.write(buffer.byteBuf)
|
||||
else -> write(buffer.getBytes())
|
||||
}
|
||||
}
|
||||
override fun write(bytes: ByteArray, offset: Int, length: Int) {
|
||||
responseBodyBuf.addComponent(Unpooled.wrappedBuffer(bytes, offset, length))
|
||||
}
|
||||
|
||||
override fun finish() {
|
||||
ctx.flush()
|
||||
}
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
when (buffer) {
|
||||
is NettyByteBuffer -> responseBodyBuf.addComponent(buffer.byteBuf)
|
||||
else -> write(buffer.getBytes())
|
||||
}
|
||||
}
|
||||
|
||||
override fun finish() {
|
||||
finish(responseBodyBuf)
|
||||
}
|
||||
|
||||
fun finish(buf: ByteBuf) = finish(buf, responseStatus)
|
||||
fun finish(buf: ByteBuf, responseCode: HttpResponseStatus) {
|
||||
val response = DefaultFullHttpResponse(HttpVersion.HTTP_1_1, responseCode, buf)
|
||||
finish(response)
|
||||
}
|
||||
|
||||
fun finish(response: FullHttpResponse) {
|
||||
val heads = response.headers()
|
||||
addHeaders(
|
||||
heads,
|
||||
mapOf(
|
||||
HttpHeaderNames.CONTENT_TYPE to "${HttpHeaderValues.TEXT_PLAIN}; charset=UTF-8",
|
||||
HttpHeaderNames.CONTENT_LENGTH to response.content().readableBytes(),
|
||||
HttpHeaderNames.CONNECTION to HttpHeaderValues.KEEP_ALIVE
|
||||
)
|
||||
)
|
||||
ctx.writeAndFlush(response)
|
||||
}
|
||||
}
|
@ -11,17 +11,13 @@ import io.netty.handler.codec.http.*
|
||||
import io.netty.handler.stream.ChunkedFile
|
||||
import java.io.File
|
||||
import java.io.RandomAccessFile
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.collections.component1
|
||||
import kotlin.collections.component2
|
||||
import kotlin.collections.set
|
||||
|
||||
@Suppress("MemberVisibilityCanBePrivate", "unused")
|
||||
open class NettyHttpContent(
|
||||
val ctx: ChannelHandlerContext,
|
||||
val msg: FullHttpRequest
|
||||
) : AdvanceHttpContent {
|
||||
) : AdvanceHttpContent, NettyResponseHeaderAdapter() {
|
||||
override val uri: String by lazy {
|
||||
var uri = msg.uri()
|
||||
while (uri.contains("//")) {
|
||||
@ -38,10 +34,13 @@ open class NettyHttpContent(
|
||||
override val cookieMap by lazy { getHeader("Cookie")?.let { decodeCookie(it) } ?: mapOf() }
|
||||
override val body = msg.content()?.let { NettyByteBuffer(it) }
|
||||
|
||||
val responseMap = HashMap<String, Any>()
|
||||
val responseListMap = HashMap<String, ArrayList<Any>>()
|
||||
//override val responseBody = ByteArrayOutputStream()
|
||||
override var responseCode: Int = 200
|
||||
var responseStatus: HttpResponseStatus = HttpResponseStatus.INTERNAL_SERVER_ERROR
|
||||
override var responseCode: Int
|
||||
get() = responseStatus.code()
|
||||
set(value) {
|
||||
responseStatus = HttpResponseStatus.valueOf(value)
|
||||
}
|
||||
override var responseMessage: String? = null
|
||||
override val method: String get() = httpMethod.name()
|
||||
val chunkedList = ArrayList<ByteBuffer>()
|
||||
@ -74,19 +73,6 @@ open class NettyHttpContent(
|
||||
(paramMap[key] as ArrayList).add(value)
|
||||
}
|
||||
|
||||
override fun setResponseHeader(name: String, value: Any) {
|
||||
responseMap[name] = value
|
||||
}
|
||||
|
||||
override fun addResponseHeader(name: String, value: Any) {
|
||||
val list = responseListMap[name] ?: run {
|
||||
val newList = ArrayList<Any>()
|
||||
responseListMap[name] = newList
|
||||
newList
|
||||
}
|
||||
list.add(value)
|
||||
}
|
||||
|
||||
override fun write(message: String) {
|
||||
responseBodyBuf.addComponent(Unpooled.wrappedBuffer(message.toByteArray()))
|
||||
//responseBody.write(message.toByteArray())
|
||||
@ -134,7 +120,7 @@ open class NettyHttpContent(
|
||||
}
|
||||
}
|
||||
|
||||
fun finish(buf: ByteBuf) = finish(buf, HttpResponseStatus.valueOf(responseCode))
|
||||
fun finish(buf: ByteBuf) = finish(buf, responseStatus)
|
||||
fun finish(buf: ByteBuf, responseCode: HttpResponseStatus) {
|
||||
val response = DefaultFullHttpResponse(HttpVersion.HTTP_1_1, responseCode, buf)
|
||||
finish(response)
|
||||
@ -153,26 +139,10 @@ open class NettyHttpContent(
|
||||
ctx.writeAndFlush(response)
|
||||
}
|
||||
|
||||
fun addHeaders(heads: HttpHeaders, defaultHeaders: Map<out CharSequence, Any>) {
|
||||
responseListMap.forEach { (t, u) ->
|
||||
u.forEach {
|
||||
heads.add(t, it)
|
||||
}
|
||||
}
|
||||
|
||||
defaultHeaders.forEach { (t, u) ->
|
||||
heads.set(t, u)
|
||||
}
|
||||
|
||||
responseMap.forEach { (t, u) ->
|
||||
heads.set(t, u)
|
||||
}
|
||||
}
|
||||
|
||||
override fun writeChunkedHeader() {
|
||||
val response = DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK)
|
||||
response.status = if (responseMessage != null) HttpResponseStatus(responseCode, responseMessage)
|
||||
else HttpResponseStatus.valueOf(responseCode)
|
||||
else responseStatus
|
||||
val heads = response.headers()
|
||||
addHeaders(
|
||||
heads, mapOf(
|
||||
|
@ -51,14 +51,13 @@ class NettyHttpServer(
|
||||
.option(ChannelOption.SO_BACKLOG, 1024) // determining the number of connections queued
|
||||
.option(ChannelOption.SO_REUSEADDR, true)
|
||||
.childOption(ChannelOption.SO_KEEPALIVE, java.lang.Boolean.TRUE)
|
||||
private lateinit var future: ChannelFuture
|
||||
private val future: ChannelFuture = b.bind(port)
|
||||
|
||||
init {
|
||||
if (autoRun) run()
|
||||
}
|
||||
|
||||
override fun run() {
|
||||
future = b.bind(port)
|
||||
future.sync()
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,47 @@
|
||||
package cn.tursom.web.netty
|
||||
|
||||
import cn.tursom.web.ResponseHeaderAdapter
|
||||
import io.netty.handler.codec.http.HttpHeaders
|
||||
import java.util.HashMap
|
||||
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
open class NettyResponseHeaderAdapter : ResponseHeaderAdapter {
|
||||
val responseMap = HashMap<String, Any>()
|
||||
val responseListMap = HashMap<String, ArrayList<Any>>()
|
||||
|
||||
override fun setResponseHeader(name: String, value: Any) {
|
||||
responseMap[name] = value
|
||||
responseListMap.remove(name)
|
||||
}
|
||||
|
||||
override fun addResponseHeader(name: String, value: Any) {
|
||||
val list = responseListMap[name] ?: run {
|
||||
val newList = ArrayList<Any>()
|
||||
responseListMap[name] = newList
|
||||
newList
|
||||
}
|
||||
responseMap[name]?.let {
|
||||
responseMap.remove(name)
|
||||
list.add(it)
|
||||
}
|
||||
list.add(value)
|
||||
}
|
||||
|
||||
protected fun addHeaders(heads: HttpHeaders, defaultHeaders: Map<out CharSequence, Any>) {
|
||||
responseListMap.forEach { (t, u) ->
|
||||
u.forEach {
|
||||
heads.add(t, it)
|
||||
}
|
||||
}
|
||||
|
||||
responseMap.forEach { (t, u) ->
|
||||
heads.set(t, u)
|
||||
}
|
||||
|
||||
defaultHeaders.forEach { (t, u) ->
|
||||
if (!heads.contains(t)) {
|
||||
heads.set(t, u)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -3,14 +3,14 @@ package cn.tursom.web
|
||||
import cn.tursom.core.buffer.ByteBuffer
|
||||
|
||||
|
||||
interface ExceptionContent {
|
||||
val cause: Throwable
|
||||
interface ExceptionContent : ResponseHeaderAdapter {
|
||||
val cause: Throwable
|
||||
var responseCode: Int
|
||||
fun write(message: String)
|
||||
fun write(bytes: ByteArray, offset: Int = 0, length: Int = bytes.size - offset)
|
||||
fun write(buffer: ByteBuffer) {
|
||||
write(buffer.getBytes())
|
||||
}
|
||||
|
||||
fun write(message: String)
|
||||
fun write(bytes: ByteArray, offset: Int = 0, length: Int = bytes.size - offset)
|
||||
fun write(buffer: ByteBuffer) {
|
||||
write(buffer.getBytes())
|
||||
}
|
||||
|
||||
fun finish()
|
||||
fun finish()
|
||||
}
|
@ -10,7 +10,7 @@ import java.io.File
|
||||
import java.io.RandomAccessFile
|
||||
import java.net.SocketAddress
|
||||
|
||||
interface HttpContent {
|
||||
interface HttpContent : ResponseHeaderAdapter, RequestHeaderAdapter {
|
||||
val uri: String
|
||||
var responseCode: Int
|
||||
var responseMessage: String?
|
||||
@ -24,17 +24,12 @@ interface HttpContent {
|
||||
str.substring(1, str.indexOf(':').let { if (it < 1) str.length else it - 1 })
|
||||
}
|
||||
|
||||
fun getHeader(header: String): String?
|
||||
fun getHeaders(): List<Map.Entry<String, String>>
|
||||
|
||||
fun getParam(param: String): String?
|
||||
fun getParams(): Map<String, List<String>>
|
||||
fun getParams(param: String): List<String>?
|
||||
|
||||
operator fun get(name: String) = (getHeader(name) ?: getParam(name))?.urlDecode
|
||||
|
||||
fun setResponseHeader(name: String, value: Any)
|
||||
fun addResponseHeader(name: String, value: Any)
|
||||
operator fun set(name: String, value: Any) = setResponseHeader(name, value)
|
||||
|
||||
fun write(message: String)
|
||||
@ -115,39 +110,8 @@ interface HttpContent {
|
||||
|
||||
fun usingCache() = finish(304)
|
||||
|
||||
fun setCacheTag(tag: Any) = setResponseHeader("Etag", tag)
|
||||
fun getCacheTag(): String? = getHeader("If-None-Match")
|
||||
|
||||
fun cacheControl(
|
||||
cacheControl: CacheControl,
|
||||
maxAge: Int? = null,
|
||||
mustRevalidate: Boolean = false
|
||||
) = setResponseHeader(
|
||||
"Cache-Control", "$cacheControl${
|
||||
if (maxAge != null && maxAge > 0) ", max-age=$maxAge" else ""}${
|
||||
if (mustRevalidate) ", must-revalidate" else ""
|
||||
}"
|
||||
)
|
||||
|
||||
fun getCookie(name: String): Cookie? = cookieMap[name]
|
||||
|
||||
fun addCookie(
|
||||
name: String,
|
||||
value: Any,
|
||||
maxAge: Int = 0,
|
||||
domain: String? = null,
|
||||
path: String? = null,
|
||||
sameSite: SameSite? = null
|
||||
) = addResponseHeader(
|
||||
"Set-Cookie",
|
||||
"$name=$value${
|
||||
if (maxAge > 0) "; Max-Age=$maxAge" else ""}${
|
||||
if (domain != null) "; Domain=$domain" else ""}${
|
||||
if (path != null) "; Path=$path" else ""}${
|
||||
if (sameSite != null) ": SameSite=$sameSite" else ""
|
||||
}"
|
||||
)
|
||||
|
||||
fun setCookie(
|
||||
name: String,
|
||||
value: Any,
|
||||
|
@ -5,6 +5,7 @@ interface HttpHandler<in T : HttpContent, in E : ExceptionContent> {
|
||||
|
||||
fun exception(e: E) {
|
||||
e.cause.printStackTrace()
|
||||
e.finish()
|
||||
}
|
||||
|
||||
operator fun invoke(content: T) {
|
||||
|
@ -0,0 +1,9 @@
|
||||
package cn.tursom.web
|
||||
|
||||
interface RequestHeaderAdapter {
|
||||
val requestHost: String? get() = getHeader("Host")
|
||||
fun getHeader(header: String): String?
|
||||
fun getHeaders(): List<Map.Entry<String, String>>
|
||||
|
||||
fun getCacheTag(): String? = getHeader("If-None-Match")
|
||||
}
|
43
web/src/main/kotlin/cn/tursom/web/ResponseHeaderAdapter.kt
Normal file
43
web/src/main/kotlin/cn/tursom/web/ResponseHeaderAdapter.kt
Normal file
@ -0,0 +1,43 @@
|
||||
package cn.tursom.web
|
||||
|
||||
import cn.tursom.web.utils.CacheControl
|
||||
import cn.tursom.web.utils.SameSite
|
||||
|
||||
interface ResponseHeaderAdapter {
|
||||
fun setResponseHeader(name: String, value: Any)
|
||||
fun addResponseHeader(name: String, value: Any)
|
||||
|
||||
fun setCacheTag(tag: Any) = setResponseHeader("Etag", tag)
|
||||
|
||||
fun cacheControl(
|
||||
cacheControl: CacheControl,
|
||||
maxAge: Int? = null,
|
||||
mustRevalidate: Boolean = false
|
||||
) = setResponseHeader(
|
||||
"Cache-Control", "$cacheControl${
|
||||
if (maxAge != null && maxAge > 0) ", max-age=$maxAge" else ""}${
|
||||
if (mustRevalidate) ", must-revalidate" else ""
|
||||
}"
|
||||
)
|
||||
|
||||
fun addCookie(
|
||||
name: String,
|
||||
value: Any,
|
||||
maxAge: Int = 0,
|
||||
domain: String? = null,
|
||||
path: String? = null,
|
||||
sameSite: SameSite? = null
|
||||
) = addResponseHeader(
|
||||
"Set-Cookie",
|
||||
"$name=$value${
|
||||
if (maxAge > 0) "; Max-Age=$maxAge" else ""}${
|
||||
if (domain != null) "; Domain=$domain" else ""}${
|
||||
if (path != null) "; Path=$path" else ""}${
|
||||
if (sameSite != null) ": SameSite=$sameSite" else ""
|
||||
}"
|
||||
)
|
||||
|
||||
fun setLanguage(language: String) {
|
||||
setResponseHeader("Content-Language", language)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user