From 43c5ce13ed1b5d6386d2e2a70d6a587fab01dea8 Mon Sep 17 00:00:00 2001 From: tursom Date: Tue, 8 Oct 2019 18:51:38 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AF=B9=E9=A1=B9=E7=9B=AE=E8=BF=9B=E8=A1=8C?= =?UTF-8?q?=E8=BF=9B=E4=B8=80=E6=AD=A5=E6=8B=86=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../utils/cache/AsyncSqlStringCacheMap.kt | 61 ++++++ web/netty-web/build.gradle | 5 + .../tursom/web/netty/HttpContextExchange.kt | 20 ++ .../web/netty/NettyAdvanceByteBuffer.kt | 143 +++++++++++++ .../web/netty/NettyChunkedByteBuffer.kt | 39 ++++ .../cn/tursom/web/netty/NettyChunkedInput.kt | 28 +++ .../tursom/web/netty/NettyExceptionContent.kt | 30 +++ .../cn/tursom/web/netty/NettyHttpContent.kt | 200 ++++++++++++++++++ .../cn/tursom/web/netty/NettyHttpHandler.kt | 32 +++ .../cn/tursom/web/netty/NettyHttpServer.kt | 75 +++++++ .../cn/tursom/web/netty/RequestParser.kt | 48 +++++ 11 files changed, 681 insertions(+) create mode 100644 database/database-async/src/main/kotlin/cn/tursom/utils/cache/AsyncSqlStringCacheMap.kt create mode 100644 web/netty-web/build.gradle create mode 100644 web/netty-web/src/main/kotlin/cn/tursom/web/netty/HttpContextExchange.kt create mode 100644 web/netty-web/src/main/kotlin/cn/tursom/web/netty/NettyAdvanceByteBuffer.kt create mode 100644 web/netty-web/src/main/kotlin/cn/tursom/web/netty/NettyChunkedByteBuffer.kt create mode 100644 web/netty-web/src/main/kotlin/cn/tursom/web/netty/NettyChunkedInput.kt create mode 100644 web/netty-web/src/main/kotlin/cn/tursom/web/netty/NettyExceptionContent.kt create mode 100644 web/netty-web/src/main/kotlin/cn/tursom/web/netty/NettyHttpContent.kt create mode 100644 web/netty-web/src/main/kotlin/cn/tursom/web/netty/NettyHttpHandler.kt create mode 100644 web/netty-web/src/main/kotlin/cn/tursom/web/netty/NettyHttpServer.kt create mode 100644 web/netty-web/src/main/kotlin/cn/tursom/web/netty/RequestParser.kt diff --git a/database/database-async/src/main/kotlin/cn/tursom/utils/cache/AsyncSqlStringCacheMap.kt b/database/database-async/src/main/kotlin/cn/tursom/utils/cache/AsyncSqlStringCacheMap.kt new file mode 100644 index 0000000..e7a1663 --- /dev/null +++ b/database/database-async/src/main/kotlin/cn/tursom/utils/cache/AsyncSqlStringCacheMap.kt @@ -0,0 +1,61 @@ +package cn.tursom.utils.cache + +import cn.tursom.database.async.AsyncSqlAdapter +import cn.tursom.database.async.AsyncSqlHelper +import cn.tursom.database.clauses.clause +import cn.tursom.utils.background +import cn.tursom.utils.cache.interfaces.AsyncPotableCacheMap +import java.util.logging.Level +import java.util.logging.Logger + +@Suppress("CanBeParameter", "MemberVisibilityCanBePrivate", "SpellCheckingInspection") +class AsyncSqlStringCacheMap( + val db: AsyncSqlHelper, + val timeout: Long, + val table: String, + val updateDelay: Long = 60 * 1000, + val prevCacheMap: AsyncPotableCacheMap = DefaultAsyncPotableCacheMap(timeout), + val logger: Logger? = null +) : AsyncPotableCacheMap by prevCacheMap { + + override suspend fun get(key: String): String? { + return prevCacheMap.get(key) ?: try { + val storage = db.select(AsyncSqlAdapter(StorageData::class.java), where = clause { !StorageData::key equal !key }, maxCount = 1, table = table) + if (storage.isNotEmpty() && storage[0].cacheTime + timeout > System.currentTimeMillis()) { + val value = storage[0].value + set(key, value) + value + } else null + } catch (e: Exception) { + e.printStackTrace() + null + } + } + + override suspend fun get(key: String, constructor: suspend () -> String): String { + val memCache = get(key) + return if (memCache != null) memCache + else { + val newValue = constructor() + this.set(key, newValue) + newValue + } + } + + override suspend fun set(key: String, value: String): String? { + prevCacheMap.set(key, value) + background { updateStorage(key, value) } + return value + } + + suspend fun updateStorage(key: String, value: String) { + try { + val updated = db.replace(table, StorageData(key, value)) + logger?.log(Level.INFO, "AsyncSqlStringCacheMap update $updated coloums: $key") + } catch (e: Exception) { + System.err.println("AsyncSqlStringCacheMap cause an Exception on update cn.tusom.database") + e.printStackTrace() + } + } +} + diff --git a/web/netty-web/build.gradle b/web/netty-web/build.gradle new file mode 100644 index 0000000..fce6d20 --- /dev/null +++ b/web/netty-web/build.gradle @@ -0,0 +1,5 @@ +dependencies { + implementation project(":") + implementation project(":web") + implementation group: "io.netty", name: "netty-all", version: "4.1.33.Final" +} diff --git a/web/netty-web/src/main/kotlin/cn/tursom/web/netty/HttpContextExchange.kt b/web/netty-web/src/main/kotlin/cn/tursom/web/netty/HttpContextExchange.kt new file mode 100644 index 0000000..74124ed --- /dev/null +++ b/web/netty-web/src/main/kotlin/cn/tursom/web/netty/HttpContextExchange.kt @@ -0,0 +1,20 @@ +package cn.tursom.web.netty + +import cn.tursom.web.HttpContent +import cn.tursom.web.utils.Cookie +import io.netty.handler.codec.DateFormatter +import io.netty.handler.codec.http.cookie.ServerCookieDecoder +import java.util.* +import kotlin.collections.HashMap + + +fun HttpContent.parseHttpDate(date: CharSequence, start: Int = 0, end: Int = date.length): Date = DateFormatter.parseHttpDate(date, start, end) +fun HttpContent.format(date: Date): String = DateFormatter.format(date) +fun HttpContent.append(date: Date, sb: StringBuilder): StringBuilder = DateFormatter.append(date, sb) +fun HttpContent.decodeCookie(cookie: String): Map { + val cookieMap = HashMap() + ServerCookieDecoder.STRICT.decode(cookie).forEach { + cookieMap[it.name()] = Cookie(it.name(), it.value(), it.domain(), it.path(), it.maxAge()) + } + return cookieMap +} \ No newline at end of file diff --git a/web/netty-web/src/main/kotlin/cn/tursom/web/netty/NettyAdvanceByteBuffer.kt b/web/netty-web/src/main/kotlin/cn/tursom/web/netty/NettyAdvanceByteBuffer.kt new file mode 100644 index 0000000..8668c96 --- /dev/null +++ b/web/netty-web/src/main/kotlin/cn/tursom/web/netty/NettyAdvanceByteBuffer.kt @@ -0,0 +1,143 @@ +package cn.tursom.web.netty + +import cn.tursom.core.bytebuffer.AdvanceByteBuffer +import io.netty.buffer.ByteBuf +import java.io.OutputStream +import java.nio.ByteBuffer + +class NettyAdvanceByteBuffer(val byteBuf: ByteBuf) : AdvanceByteBuffer { + override val hasArray: Boolean get() = byteBuf.hasArray() + override val readOnly: Boolean get() = byteBuf.isReadOnly + override val nioBuffer: ByteBuffer + get() = if (readMode) byteBuf.nioBuffer() + else byteBuf.nioBuffer(writePosition, limit) + + override val bufferCount: Int get() = byteBuf.nioBufferCount() + override val nioBuffers: Array get() = byteBuf.nioBuffers() + + override var writePosition: Int + get() = byteBuf.writerIndex() + set(value) { + byteBuf.writerIndex(value) + } + override var limit: Int + get() = byteBuf.capacity() + set(value) { + byteBuf.capacity(value) + } + override val capacity get() = byteBuf.maxCapacity() + override val array: ByteArray get() = byteBuf.array() + override val arrayOffset: Int get() = byteBuf.arrayOffset() + override var readPosition: Int + get() = byteBuf.readerIndex() + set(value) { + byteBuf.readerIndex(value) + } + override val readOffset: Int get() = byteBuf.arrayOffset() + byteBuf.readerIndex() + override val readableSize: Int + get() = byteBuf.readableBytes() + override val available: Int get() = readableSize + override val writeOffset: Int get() = writePosition + override val writeableSize: Int + get() = limit - writePosition + override val size: Int get() = capacity + override var readMode: Boolean = false + + override fun readMode() { + readMode = true + } + + override fun resumeWriteMode(usedSize: Int) { + readPosition += usedSize + readMode = false + } + + override fun needReadSize(size: Int) { + if (size > limit) { + if (size < capacity) byteBuf.capacity(size) + else throw IndexOutOfBoundsException() + } + } + + override fun clear() { + byteBuf.clear() + } + + override fun reset() { + byteBuf.discardReadBytes() + } + + override fun reset(outputStream: OutputStream) { + byteBuf.readBytes(outputStream, readableSize) + byteBuf.clear() + } + + override fun get(): Byte = byteBuf.readByte() + override fun getChar(): Char = byteBuf.readChar() + override fun getShort(): Short = byteBuf.readShort() + override fun getInt(): Int = byteBuf.readInt() + override fun getLong(): Long = byteBuf.readLong() + override fun getFloat(): Float = byteBuf.readFloat() + override fun getDouble(): Double = byteBuf.readDouble() + + override fun getBytes(): ByteArray { + val bytes = ByteArray(byteBuf.readableBytes()) + byteBuf.readBytes(bytes) + return bytes + } + + override fun getString(size: Int): String { + val str = byteBuf.toString(readPosition, size, Charsets.UTF_8) + readPosition += size + return str + } + + override fun writeTo(buffer: ByteArray, bufferOffset: Int, size: Int): Int { + byteBuf.readBytes(buffer, bufferOffset, size) + return size + } + + override fun writeTo(os: OutputStream): Int { + val size = readableSize + byteBuf.readBytes(os, size) + reset() + return size + } + + override fun put(byte: Byte) { + byteBuf.writeByte(byte.toInt()) + } + + override fun put(char: Char) { + byteBuf.writeChar(char.toInt()) + } + + override fun put(short: Short) { + byteBuf.writeShort(short.toInt()) + } + + override fun put(int: Int) { + byteBuf.writeInt(int) + } + + override fun put(long: Long) { + byteBuf.writeLong(long) + } + + override fun put(float: Float) { + byteBuf.writeFloat(float) + } + + override fun put(double: Double) { + byteBuf.writeDouble(double) + } + + override fun put(str: String) { + byteBuf.writeCharSequence(str, Charsets.UTF_8) + } + + override fun put(byteArray: ByteArray, startIndex: Int, endIndex: Int) { + byteBuf.writeBytes(byteArray, startIndex, endIndex - startIndex) + } + +} \ No newline at end of file diff --git a/web/netty-web/src/main/kotlin/cn/tursom/web/netty/NettyChunkedByteBuffer.kt b/web/netty-web/src/main/kotlin/cn/tursom/web/netty/NettyChunkedByteBuffer.kt new file mode 100644 index 0000000..a247101 --- /dev/null +++ b/web/netty-web/src/main/kotlin/cn/tursom/web/netty/NettyChunkedByteBuffer.kt @@ -0,0 +1,39 @@ +package cn.tursom.web.netty + +import cn.tursom.core.bytebuffer.AdvanceByteBuffer +import io.netty.buffer.ByteBuf +import io.netty.buffer.ByteBufAllocator +import io.netty.buffer.Unpooled +import io.netty.channel.ChannelHandlerContext +import io.netty.handler.stream.ChunkedInput + +class NettyChunkedByteBuffer(val bufList: List) : ChunkedInput { + constructor(vararg bufList: AdvanceByteBuffer) : this(bufList.asList()) + + var iterator = bufList.iterator() + var progress: Long = 0 + val length = run { + var len = 0L + bufList.forEach { + len += it.readableSize + } + len + } + + override fun progress(): Long = progress + override fun length(): Long = length + override fun isEndOfInput(): Boolean = !iterator.hasNext() + + override fun readChunk(ctx: ChannelHandlerContext?): ByteBuf = readChunk() + override fun readChunk(allocator: ByteBufAllocator?): ByteBuf = readChunk() + + private fun readChunk(): ByteBuf { + val next = iterator.next() + progress += next.readableSize + return if (next is NettyAdvanceByteBuffer) next.byteBuf + else Unpooled.wrappedBuffer(next.nioBuffer) + } + + override fun close() {} +} + diff --git a/web/netty-web/src/main/kotlin/cn/tursom/web/netty/NettyChunkedInput.kt b/web/netty-web/src/main/kotlin/cn/tursom/web/netty/NettyChunkedInput.kt new file mode 100644 index 0000000..747e728 --- /dev/null +++ b/web/netty-web/src/main/kotlin/cn/tursom/web/netty/NettyChunkedInput.kt @@ -0,0 +1,28 @@ +package cn.tursom.web.netty + +import cn.tursom.web.utils.Chunked +import io.netty.buffer.ByteBuf +import io.netty.buffer.ByteBufAllocator +import io.netty.buffer.Unpooled +import io.netty.channel.ChannelHandlerContext +import io.netty.handler.stream.ChunkedInput + +class NettyChunkedInput(val chunked: Chunked) : ChunkedInput { + override fun progress(): Long = chunked.progress + override fun length() = chunked.length + override fun isEndOfInput(): Boolean = chunked.endOfInput + + override fun readChunk(ctx: ChannelHandlerContext?): ByteBuf { + val buf = chunked.readChunk() + return if (buf is NettyAdvanceByteBuffer) buf.byteBuf + else Unpooled.wrappedBuffer(buf.nioBuffer) + } + + override fun readChunk(allocator: ByteBufAllocator?): ByteBuf { + val buf = chunked.readChunk() + return if (buf is NettyAdvanceByteBuffer) buf.byteBuf + else Unpooled.wrappedBuffer(buf.nioBuffer) + } + + override fun close() = chunked.close() +} \ No newline at end of file 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 new file mode 100644 index 0000000..6af0604 --- /dev/null +++ b/web/netty-web/src/main/kotlin/cn/tursom/web/netty/NettyExceptionContent.kt @@ -0,0 +1,30 @@ +package cn.tursom.web.netty + +import cn.tursom.core.bytebuffer.AdvanceByteBuffer +import cn.tursom.web.ExceptionContent +import io.netty.buffer.Unpooled +import io.netty.channel.ChannelHandlerContext + +class NettyExceptionContent( + val ctx: ChannelHandlerContext, + override val cause: Throwable +) : ExceptionContent { + override fun write(message: String) { + ctx.write(Unpooled.wrappedBuffer(message.toByteArray())) + } + + override fun write(bytes: ByteArray, offset: Int, length: Int) { + ctx.write(Unpooled.wrappedBuffer(bytes, offset, length)) + } + + override fun write(buffer: AdvanceByteBuffer) { + when (buffer) { + is NettyAdvanceByteBuffer -> ctx.write(buffer.byteBuf) + else -> write(buffer.getBytes()) + } + } + + override fun finish() { + ctx.flush() + } +} \ 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 new file mode 100644 index 0000000..d1fbd6b --- /dev/null +++ b/web/netty-web/src/main/kotlin/cn/tursom/web/netty/NettyHttpContent.kt @@ -0,0 +1,200 @@ +package cn.tursom.web.netty + +import cn.tursom.core.buf +import cn.tursom.core.bytebuffer.AdvanceByteBuffer +import cn.tursom.web.AdvanceHttpContent +import cn.tursom.web.utils.Chunked +import io.netty.buffer.ByteBuf +import io.netty.buffer.Unpooled +import io.netty.channel.ChannelHandlerContext +import io.netty.handler.codec.http.* +import io.netty.handler.stream.ChunkedFile +import java.io.ByteArrayOutputStream +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 { + override val uri: String by lazy { + var uri = msg.uri() + while (uri.contains("//")) { + uri = uri.replace("//", "/") + } + uri + } + override val clientIp get() = ctx.channel().remoteAddress()!! + override val realIp: String = super.realIp + val httpMethod: HttpMethod get() = msg.method() + val protocolVersion: HttpVersion get() = msg.protocolVersion() + val headers: HttpHeaders get() = msg.headers() + protected val paramMap by lazy { RequestParser.parse(msg) } + override val cookieMap by lazy { getHeader("Cookie")?.let { decodeCookie(it) } ?: mapOf() } + override val body = msg.content()?.let { NettyAdvanceByteBuffer(it) } + + val responseMap = HashMap() + val responseListMap = HashMap>() + override val responseBody = ByteArrayOutputStream() + override var responseCode: Int = 200 + override var responseMessage: String? = null + override val method: String get() = httpMethod.name() + val chunkedList = ArrayList() + + override fun getHeader(header: String): String? { + return headers[header] + } + + override fun getHeaders(): List> { + return headers.toList() + } + + override fun getParam(param: String): String? { + return paramMap[param]?.get(0) + } + + override fun getParams(): Map> { + return paramMap + } + + override fun getParams(param: String): List? { + return paramMap[param] + } + + override fun addParam(key: String, value: String) { + if (!paramMap.containsKey(key)) { + paramMap[key] = ArrayList() + } + (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() + responseListMap[name] = newList + newList + } + list.add(value) + } + + override fun write(message: String) { + responseBody.write(message.toByteArray()) + } + + override fun write(byte: Byte) { + responseBody.write(byte.toInt()) + } + + override fun write(bytes: ByteArray, offset: Int, size: Int) { + responseBody.write(bytes, offset, size) + } + + override fun write(buffer: AdvanceByteBuffer) { + buffer.writeTo(responseBody) + } + + override fun reset() { + responseBody.reset() + } + + override fun finish() { + finish(responseBody.buf, 0, responseBody.size()) + } + + override fun finish(buffer: ByteArray, offset: Int, size: Int) { + finish(Unpooled.wrappedBuffer(buffer, offset, size)) + } + + override fun finish(buffer: AdvanceByteBuffer) { + if (buffer is NettyAdvanceByteBuffer) { + finish(buffer.byteBuf) + } else { + super.finish(buffer) + } + } + + fun finish(buf: ByteBuf) = finish(buf, HttpResponseStatus.valueOf(responseCode)) + 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) + } + + fun addHeaders(heads: HttpHeaders, defaultHeaders: Map) { + 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) + val heads = response.headers() + addHeaders( + heads, mapOf( + HttpHeaderNames.CONTENT_TYPE to "${HttpHeaderValues.TEXT_PLAIN}; charset=UTF-8", + HttpHeaderNames.CONNECTION to HttpHeaderValues.KEEP_ALIVE, + HttpHeaderNames.TRANSFER_ENCODING to "chunked" + ) + ) + ctx.write(response) + } + + override fun addChunked(buffer: AdvanceByteBuffer) { + chunkedList.add(buffer) + } + + override fun finishChunked() { + val httpChunkWriter = HttpChunkedInput(NettyChunkedByteBuffer(chunkedList)) + ctx.writeAndFlush(httpChunkWriter) + } + + override fun finishChunked(chunked: Chunked) { + val httpChunkWriter = HttpChunkedInput(NettyChunkedInput(chunked)) + ctx.writeAndFlush(httpChunkWriter) + } + + override fun finishFile(file: File, chunkSize: Int) { + writeChunkedHeader() + ctx.writeAndFlush(HttpChunkedInput(ChunkedFile(file, chunkSize))) + } + + override fun finishFile(file: RandomAccessFile, offset: Long, length: Long, chunkSize: Int) { + writeChunkedHeader() + ctx.writeAndFlush(HttpChunkedInput(ChunkedFile(file, offset, length, chunkSize))) + } +} + diff --git a/web/netty-web/src/main/kotlin/cn/tursom/web/netty/NettyHttpHandler.kt b/web/netty-web/src/main/kotlin/cn/tursom/web/netty/NettyHttpHandler.kt new file mode 100644 index 0000000..57be940 --- /dev/null +++ b/web/netty-web/src/main/kotlin/cn/tursom/web/netty/NettyHttpHandler.kt @@ -0,0 +1,32 @@ +package cn.tursom.web.netty + +import cn.tursom.web.HttpHandler +import io.netty.channel.ChannelHandler +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import io.netty.handler.codec.http.FullHttpRequest + +@ChannelHandler.Sharable +class NettyHttpHandler( + private val handler: HttpHandler +) : SimpleChannelInboundHandler() { + + override fun channelRead0(ctx: ChannelHandlerContext, msg: FullHttpRequest) { + val handlerContext = NettyHttpContent(ctx, msg) + try { + handler.handle(handlerContext) + } catch (e: Throwable) { + handlerContext.write("${e.javaClass}: ${e.message}") + } + } + + override fun channelReadComplete(ctx: ChannelHandlerContext) { + super.channelReadComplete(ctx) + ctx.flush() + } + + override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable?) { + if (cause != null) handler.exception(NettyExceptionContent(ctx, cause)) + ctx.close() + } +} \ No newline at end of file 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 new file mode 100644 index 0000000..c77a579 --- /dev/null +++ b/web/netty-web/src/main/kotlin/cn/tursom/web/netty/NettyHttpServer.kt @@ -0,0 +1,75 @@ +package cn.tursom.web.netty + +import cn.tursom.web.HttpHandler +import cn.tursom.web.HttpServer +import io.netty.bootstrap.ServerBootstrap +import io.netty.channel.ChannelFuture +import io.netty.channel.ChannelInitializer +import io.netty.channel.ChannelOption +import io.netty.channel.nio.NioEventLoopGroup +import io.netty.channel.socket.SocketChannel +import io.netty.channel.socket.nio.NioServerSocketChannel +import io.netty.handler.codec.http.HttpObjectAggregator +import io.netty.handler.codec.http.HttpRequestDecoder +import io.netty.handler.codec.http.HttpResponseEncoder +import io.netty.handler.stream.ChunkedWriteHandler + +class NettyHttpServer( + override val port: Int, + handler: HttpHandler, + bodySize: Int = 512 * 1024, + autoRun: Boolean = false +) : HttpServer { + constructor( + port: Int, + bodySize: Int = 512 * 1024, + autoRun: Boolean = false, + handler: (content: NettyHttpContent) -> Unit + ) : this( + port, + object : HttpHandler { + override fun handle(content: NettyHttpContent) { + handler(content) + } + + override fun exception(e: NettyExceptionContent) { + e.cause.printStackTrace() + } + }, + bodySize, + autoRun + ) + + val httpHandler = NettyHttpHandler(handler) + private val group = NioEventLoopGroup() + private val b = ServerBootstrap().group(group) + .channel(NioServerSocketChannel::class.java) + .childHandler(object : ChannelInitializer() { + override fun initChannel(ch: SocketChannel) { + ch.pipeline() + .addLast("decoder", HttpRequestDecoder()) + .addLast("encoder", HttpResponseEncoder()) + .addLast("aggregator", HttpObjectAggregator(bodySize)) + .addLast("http-chunked", ChunkedWriteHandler()) + .addLast("handle", httpHandler) + } + }) + .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 + + init { + if (autoRun) run() + } + + override fun run() { + future = b.bind(port) + future.sync() + } + + override fun close() { + future.cancel(false) + future.channel().close() + } +} \ No newline at end of file diff --git a/web/netty-web/src/main/kotlin/cn/tursom/web/netty/RequestParser.kt b/web/netty-web/src/main/kotlin/cn/tursom/web/netty/RequestParser.kt new file mode 100644 index 0000000..8ca05b8 --- /dev/null +++ b/web/netty-web/src/main/kotlin/cn/tursom/web/netty/RequestParser.kt @@ -0,0 +1,48 @@ +package cn.tursom.web.netty + +import io.netty.handler.codec.http.FullHttpRequest +import io.netty.handler.codec.http.HttpMethod +import io.netty.handler.codec.http.QueryStringDecoder +import io.netty.handler.codec.http.multipart.Attribute +import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder +import java.util.HashMap + +/** + * HTTP请求参数解析器, 支持GET, POST + */ +object RequestParser { + fun parse(fullReq: FullHttpRequest): HashMap> { + val method = fullReq.method() + + val paramMap = HashMap>() + + when { + HttpMethod.GET === method -> try { + // 是GET请求 + val decoder = QueryStringDecoder(fullReq.uri()) + decoder.parameters().entries.forEach { entry -> + paramMap[entry.key] = entry.value + } + } catch (e: Exception) { + } + HttpMethod.POST === method -> try { + // 是POST请求 + val decoder = HttpPostRequestDecoder(fullReq) + decoder.offer(fullReq) + + val paramList = decoder.bodyHttpDatas + + for (param in paramList) { + val data = param as Attribute + if (!paramMap.containsKey(data.name)) { + paramMap[data.name] = ArrayList() + } + (paramMap[data.name] as ArrayList).add(data.value) + } + } catch (e: Exception) { + } + } + + return paramMap + } +} \ No newline at end of file