独立出请求与响应的 HeaderAdapter

This commit is contained in:
tursom 2019-11-22 09:47:54 +08:00
parent 9e765dc997
commit 2629b93c23
10 changed files with 169 additions and 106 deletions

View File

@ -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")

View File

@ -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)
}
}

View File

@ -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(

View File

@ -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()
}

View File

@ -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)
}
}
}
}

View File

@ -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()
}

View File

@ -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,

View File

@ -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) {

View File

@ -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")
}

View 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)
}
}