From 2629b93c23e7a8f60570e6e354cba74b0223977e Mon Sep 17 00:00:00 2001 From: tursom <tursom@foxmail.com> Date: Fri, 22 Nov 2019 09:47:54 +0800 Subject: [PATCH] =?UTF-8?q?=E7=8B=AC=E7=AB=8B=E5=87=BA=E8=AF=B7=E6=B1=82?= =?UTF-8?q?=E4=B8=8E=E5=93=8D=E5=BA=94=E7=9A=84=20HeaderAdapter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 - .../tursom/web/netty/NettyExceptionContent.kt | 67 ++++++++++++++----- .../cn/tursom/web/netty/NettyHttpContent.kt | 48 +++---------- .../cn/tursom/web/netty/NettyHttpServer.kt | 3 +- .../web/netty/NettyResponseHeaderAdapter.kt | 47 +++++++++++++ .../kotlin/cn/tursom/web/ExceptionContent.kt | 18 ++--- .../main/kotlin/cn/tursom/web/HttpContent.kt | 38 +---------- .../main/kotlin/cn/tursom/web/HttpHandler.kt | 1 + .../cn/tursom/web/RequestHeaderAdapter.kt | 9 +++ .../cn/tursom/web/ResponseHeaderAdapter.kt | 43 ++++++++++++ 10 files changed, 169 insertions(+), 106 deletions(-) create mode 100644 web/netty-web/src/main/kotlin/cn/tursom/web/netty/NettyResponseHeaderAdapter.kt create mode 100644 web/src/main/kotlin/cn/tursom/web/RequestHeaderAdapter.kt create mode 100644 web/src/main/kotlin/cn/tursom/web/ResponseHeaderAdapter.kt diff --git a/build.gradle b/build.gradle index 8b77081..e2f2c44 100644 --- a/build.gradle +++ b/build.gradle @@ -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") diff --git a/web/netty-web/src/main/kotlin/cn/tursom/web/netty/NettyExceptionContent.kt b/web/netty-web/src/main/kotlin/cn/tursom/web/netty/NettyExceptionContent.kt index 5aab353..c8db82e 100644 --- a/web/netty-web/src/main/kotlin/cn/tursom/web/netty/NettyExceptionContent.kt +++ b/web/netty-web/src/main/kotlin/cn/tursom/web/netty/NettyExceptionContent.kt @@ -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) + } } \ No newline at end of file diff --git a/web/netty-web/src/main/kotlin/cn/tursom/web/netty/NettyHttpContent.kt b/web/netty-web/src/main/kotlin/cn/tursom/web/netty/NettyHttpContent.kt index bb72cb1..ecc1cc2 100644 --- a/web/netty-web/src/main/kotlin/cn/tursom/web/netty/NettyHttpContent.kt +++ b/web/netty-web/src/main/kotlin/cn/tursom/web/netty/NettyHttpContent.kt @@ -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( diff --git a/web/netty-web/src/main/kotlin/cn/tursom/web/netty/NettyHttpServer.kt b/web/netty-web/src/main/kotlin/cn/tursom/web/netty/NettyHttpServer.kt index d73a4dd..9a3b556 100644 --- a/web/netty-web/src/main/kotlin/cn/tursom/web/netty/NettyHttpServer.kt +++ b/web/netty-web/src/main/kotlin/cn/tursom/web/netty/NettyHttpServer.kt @@ -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() } diff --git a/web/netty-web/src/main/kotlin/cn/tursom/web/netty/NettyResponseHeaderAdapter.kt b/web/netty-web/src/main/kotlin/cn/tursom/web/netty/NettyResponseHeaderAdapter.kt new file mode 100644 index 0000000..b3338b8 --- /dev/null +++ b/web/netty-web/src/main/kotlin/cn/tursom/web/netty/NettyResponseHeaderAdapter.kt @@ -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) + } + } + } +} \ No newline at end of file diff --git a/web/src/main/kotlin/cn/tursom/web/ExceptionContent.kt b/web/src/main/kotlin/cn/tursom/web/ExceptionContent.kt index b3457a3..7bc8d62 100644 --- a/web/src/main/kotlin/cn/tursom/web/ExceptionContent.kt +++ b/web/src/main/kotlin/cn/tursom/web/ExceptionContent.kt @@ -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() } \ No newline at end of file diff --git a/web/src/main/kotlin/cn/tursom/web/HttpContent.kt b/web/src/main/kotlin/cn/tursom/web/HttpContent.kt index fa2b8d7..a84a14a 100644 --- a/web/src/main/kotlin/cn/tursom/web/HttpContent.kt +++ b/web/src/main/kotlin/cn/tursom/web/HttpContent.kt @@ -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, diff --git a/web/src/main/kotlin/cn/tursom/web/HttpHandler.kt b/web/src/main/kotlin/cn/tursom/web/HttpHandler.kt index f9f4963..d9115c1 100644 --- a/web/src/main/kotlin/cn/tursom/web/HttpHandler.kt +++ b/web/src/main/kotlin/cn/tursom/web/HttpHandler.kt @@ -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) { diff --git a/web/src/main/kotlin/cn/tursom/web/RequestHeaderAdapter.kt b/web/src/main/kotlin/cn/tursom/web/RequestHeaderAdapter.kt new file mode 100644 index 0000000..75a1a66 --- /dev/null +++ b/web/src/main/kotlin/cn/tursom/web/RequestHeaderAdapter.kt @@ -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") +} \ No newline at end of file diff --git a/web/src/main/kotlin/cn/tursom/web/ResponseHeaderAdapter.kt b/web/src/main/kotlin/cn/tursom/web/ResponseHeaderAdapter.kt new file mode 100644 index 0000000..1cbe068 --- /dev/null +++ b/web/src/main/kotlin/cn/tursom/web/ResponseHeaderAdapter.kt @@ -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) + } +} \ No newline at end of file