mirror of
https://github.com/tursom/TursomServer.git
synced 2025-04-01 15:11:08 +08:00
独立出请求与响应的 HeaderAdapter
This commit is contained in:
parent
9e765dc997
commit
2629b93c23
@ -53,7 +53,6 @@ allprojects {
|
|||||||
name = "GitHubPackages"
|
name = "GitHubPackages"
|
||||||
url = uri("https://maven.pkg.github.com/tursom/TursomServer")
|
url = uri("https://maven.pkg.github.com/tursom/TursomServer")
|
||||||
credentials {
|
credentials {
|
||||||
println project.findProperty("gpr.user")
|
|
||||||
//username = project.findProperty("gpr.user") ?: System.getenv("USERNAME")
|
//username = project.findProperty("gpr.user") ?: System.getenv("USERNAME")
|
||||||
username = "tursom"
|
username = "tursom"
|
||||||
password = project.findProperty("gpr.key") ?: System.getenv("PASSWORD")
|
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.core.buffer.ByteBuffer
|
||||||
import cn.tursom.web.ExceptionContent
|
import cn.tursom.web.ExceptionContent
|
||||||
|
import io.netty.buffer.ByteBuf
|
||||||
|
import io.netty.buffer.CompositeByteBuf
|
||||||
import io.netty.buffer.Unpooled
|
import io.netty.buffer.Unpooled
|
||||||
import io.netty.channel.ChannelHandlerContext
|
import io.netty.channel.ChannelHandlerContext
|
||||||
|
import io.netty.handler.codec.http.*
|
||||||
|
|
||||||
|
@Suppress("MemberVisibilityCanBePrivate")
|
||||||
class NettyExceptionContent(
|
class NettyExceptionContent(
|
||||||
val ctx: ChannelHandlerContext,
|
val ctx: ChannelHandlerContext,
|
||||||
override val cause: Throwable
|
override val cause: Throwable
|
||||||
) : ExceptionContent {
|
) : ExceptionContent, NettyResponseHeaderAdapter() {
|
||||||
override fun write(message: String) {
|
val responseBodyBuf: CompositeByteBuf = ctx.alloc().compositeBuffer()!!
|
||||||
ctx.write(Unpooled.wrappedBuffer(message.toByteArray()))
|
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) {
|
override fun write(message: String) {
|
||||||
ctx.write(Unpooled.wrappedBuffer(bytes, offset, length))
|
responseBodyBuf.addComponent(Unpooled.wrappedBuffer(message.toByteArray()))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun write(buffer: ByteBuffer) {
|
override fun write(bytes: ByteArray, offset: Int, length: Int) {
|
||||||
when (buffer) {
|
responseBodyBuf.addComponent(Unpooled.wrappedBuffer(bytes, offset, length))
|
||||||
is NettyByteBuffer -> ctx.write(buffer.byteBuf)
|
}
|
||||||
else -> write(buffer.getBytes())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun finish() {
|
override fun write(buffer: ByteBuffer) {
|
||||||
ctx.flush()
|
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 io.netty.handler.stream.ChunkedFile
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.RandomAccessFile
|
import java.io.RandomAccessFile
|
||||||
import java.util.*
|
|
||||||
import kotlin.collections.ArrayList
|
|
||||||
import kotlin.collections.component1
|
|
||||||
import kotlin.collections.component2
|
|
||||||
import kotlin.collections.set
|
import kotlin.collections.set
|
||||||
|
|
||||||
@Suppress("MemberVisibilityCanBePrivate", "unused")
|
@Suppress("MemberVisibilityCanBePrivate", "unused")
|
||||||
open class NettyHttpContent(
|
open class NettyHttpContent(
|
||||||
val ctx: ChannelHandlerContext,
|
val ctx: ChannelHandlerContext,
|
||||||
val msg: FullHttpRequest
|
val msg: FullHttpRequest
|
||||||
) : AdvanceHttpContent {
|
) : AdvanceHttpContent, NettyResponseHeaderAdapter() {
|
||||||
override val uri: String by lazy {
|
override val uri: String by lazy {
|
||||||
var uri = msg.uri()
|
var uri = msg.uri()
|
||||||
while (uri.contains("//")) {
|
while (uri.contains("//")) {
|
||||||
@ -38,10 +34,13 @@ open class NettyHttpContent(
|
|||||||
override val cookieMap by lazy { getHeader("Cookie")?.let { decodeCookie(it) } ?: mapOf() }
|
override val cookieMap by lazy { getHeader("Cookie")?.let { decodeCookie(it) } ?: mapOf() }
|
||||||
override val body = msg.content()?.let { NettyByteBuffer(it) }
|
override val body = msg.content()?.let { NettyByteBuffer(it) }
|
||||||
|
|
||||||
val responseMap = HashMap<String, Any>()
|
|
||||||
val responseListMap = HashMap<String, ArrayList<Any>>()
|
|
||||||
//override val responseBody = ByteArrayOutputStream()
|
//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 var responseMessage: String? = null
|
||||||
override val method: String get() = httpMethod.name()
|
override val method: String get() = httpMethod.name()
|
||||||
val chunkedList = ArrayList<ByteBuffer>()
|
val chunkedList = ArrayList<ByteBuffer>()
|
||||||
@ -74,19 +73,6 @@ open class NettyHttpContent(
|
|||||||
(paramMap[key] as ArrayList).add(value)
|
(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) {
|
override fun write(message: String) {
|
||||||
responseBodyBuf.addComponent(Unpooled.wrappedBuffer(message.toByteArray()))
|
responseBodyBuf.addComponent(Unpooled.wrappedBuffer(message.toByteArray()))
|
||||||
//responseBody.write(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) {
|
fun finish(buf: ByteBuf, responseCode: HttpResponseStatus) {
|
||||||
val response = DefaultFullHttpResponse(HttpVersion.HTTP_1_1, responseCode, buf)
|
val response = DefaultFullHttpResponse(HttpVersion.HTTP_1_1, responseCode, buf)
|
||||||
finish(response)
|
finish(response)
|
||||||
@ -153,26 +139,10 @@ open class NettyHttpContent(
|
|||||||
ctx.writeAndFlush(response)
|
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() {
|
override fun writeChunkedHeader() {
|
||||||
val response = DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK)
|
val response = DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK)
|
||||||
response.status = if (responseMessage != null) HttpResponseStatus(responseCode, responseMessage)
|
response.status = if (responseMessage != null) HttpResponseStatus(responseCode, responseMessage)
|
||||||
else HttpResponseStatus.valueOf(responseCode)
|
else responseStatus
|
||||||
val heads = response.headers()
|
val heads = response.headers()
|
||||||
addHeaders(
|
addHeaders(
|
||||||
heads, mapOf(
|
heads, mapOf(
|
||||||
|
@ -51,14 +51,13 @@ class NettyHttpServer(
|
|||||||
.option(ChannelOption.SO_BACKLOG, 1024) // determining the number of connections queued
|
.option(ChannelOption.SO_BACKLOG, 1024) // determining the number of connections queued
|
||||||
.option(ChannelOption.SO_REUSEADDR, true)
|
.option(ChannelOption.SO_REUSEADDR, true)
|
||||||
.childOption(ChannelOption.SO_KEEPALIVE, java.lang.Boolean.TRUE)
|
.childOption(ChannelOption.SO_KEEPALIVE, java.lang.Boolean.TRUE)
|
||||||
private lateinit var future: ChannelFuture
|
private val future: ChannelFuture = b.bind(port)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (autoRun) run()
|
if (autoRun) run()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun run() {
|
override fun run() {
|
||||||
future = b.bind(port)
|
|
||||||
future.sync()
|
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
|
import cn.tursom.core.buffer.ByteBuffer
|
||||||
|
|
||||||
|
|
||||||
interface ExceptionContent {
|
interface ExceptionContent : ResponseHeaderAdapter {
|
||||||
val cause: Throwable
|
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 finish()
|
||||||
fun write(bytes: ByteArray, offset: Int = 0, length: Int = bytes.size - offset)
|
|
||||||
fun write(buffer: ByteBuffer) {
|
|
||||||
write(buffer.getBytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun finish()
|
|
||||||
}
|
}
|
@ -10,7 +10,7 @@ import java.io.File
|
|||||||
import java.io.RandomAccessFile
|
import java.io.RandomAccessFile
|
||||||
import java.net.SocketAddress
|
import java.net.SocketAddress
|
||||||
|
|
||||||
interface HttpContent {
|
interface HttpContent : ResponseHeaderAdapter, RequestHeaderAdapter {
|
||||||
val uri: String
|
val uri: String
|
||||||
var responseCode: Int
|
var responseCode: Int
|
||||||
var responseMessage: String?
|
var responseMessage: String?
|
||||||
@ -24,17 +24,12 @@ interface HttpContent {
|
|||||||
str.substring(1, str.indexOf(':').let { if (it < 1) str.length else it - 1 })
|
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 getParam(param: String): String?
|
||||||
fun getParams(): Map<String, List<String>>
|
fun getParams(): Map<String, List<String>>
|
||||||
fun getParams(param: String): List<String>?
|
fun getParams(param: String): List<String>?
|
||||||
|
|
||||||
operator fun get(name: String) = (getHeader(name) ?: getParam(name))?.urlDecode
|
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)
|
operator fun set(name: String, value: Any) = setResponseHeader(name, value)
|
||||||
|
|
||||||
fun write(message: String)
|
fun write(message: String)
|
||||||
@ -115,39 +110,8 @@ interface HttpContent {
|
|||||||
|
|
||||||
fun usingCache() = finish(304)
|
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 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(
|
fun setCookie(
|
||||||
name: String,
|
name: String,
|
||||||
value: Any,
|
value: Any,
|
||||||
|
@ -5,6 +5,7 @@ interface HttpHandler<in T : HttpContent, in E : ExceptionContent> {
|
|||||||
|
|
||||||
fun exception(e: E) {
|
fun exception(e: E) {
|
||||||
e.cause.printStackTrace()
|
e.cause.printStackTrace()
|
||||||
|
e.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun invoke(content: T) {
|
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