From 7f20c7fe6a34023fc9a52f0e5858753fd84c48e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=A8=E8=91=89=20Scarlet?= <93977077+mukjepscarlet@users.noreply.github.com> Date: Fri, 23 May 2025 00:05:53 +0800 Subject: [PATCH 1/2] refactor: stop HttpServer manually instead of interruption This refactoring makes HttpServer#start not blocking anymore. And now we should call HttpServer#stop to stop this server (instead of interrupting). This can make it easier to be used in OAuthServer(mc-authlib) and OAuthClient(LiquidBounce). --- .../net/ccbluex/netty/http/HttpServer.kt | 51 +++++++++++++++---- src/test/kotlin/HttpMiddlewareServerTest.kt | 14 ++--- src/test/kotlin/HttpServerTest.kt | 12 ++--- 3 files changed, 50 insertions(+), 27 deletions(-) diff --git a/src/main/kotlin/net/ccbluex/netty/http/HttpServer.kt b/src/main/kotlin/net/ccbluex/netty/http/HttpServer.kt index 2f8ce2b..a8fda5b 100644 --- a/src/main/kotlin/net/ccbluex/netty/http/HttpServer.kt +++ b/src/main/kotlin/net/ccbluex/netty/http/HttpServer.kt @@ -20,7 +20,9 @@ package net.ccbluex.netty.http import io.netty.bootstrap.ServerBootstrap +import io.netty.channel.Channel import io.netty.channel.ChannelOption +import io.netty.channel.EventLoopGroup import io.netty.channel.epoll.Epoll import io.netty.channel.epoll.EpollEventLoopGroup import io.netty.channel.epoll.EpollServerSocketChannel @@ -32,6 +34,9 @@ import net.ccbluex.netty.http.middleware.Middleware import net.ccbluex.netty.http.rest.RouteController import net.ccbluex.netty.http.websocket.WebSocketController import org.apache.logging.log4j.LogManager +import java.net.InetSocketAddress +import java.util.concurrent.locks.ReentrantLock +import kotlin.concurrent.withLock /** @@ -44,7 +49,13 @@ class HttpServer { val routeController = RouteController() val webSocketController = WebSocketController() - val middlewares = mutableListOf() + private val lock = ReentrantLock() + + internal val middlewares = mutableListOf() + + private var bossGroup: EventLoopGroup? = null + private var workerGroup: EventLoopGroup? = null + private var serverChannel: Channel? = null companion object { internal val logger = LogManager.getLogger("HttpServer") @@ -56,10 +67,14 @@ class HttpServer { /** * Starts the Netty server on the specified port. + * + * @param port The port of HTTP server. `0` means to auto select one. + * + * @return actual port of server. */ - fun start(port: Int) { - val bossGroup = if (Epoll.isAvailable()) EpollEventLoopGroup() else NioEventLoopGroup() - val workerGroup = if (Epoll.isAvailable()) EpollEventLoopGroup() else NioEventLoopGroup() + fun start(port: Int): Int = lock.withLock { + bossGroup = if (Epoll.isAvailable()) EpollEventLoopGroup(1) else NioEventLoopGroup(1) + workerGroup = if (Epoll.isAvailable()) EpollEventLoopGroup() else NioEventLoopGroup() try { logger.info("Starting Netty server...") @@ -70,21 +85,35 @@ class HttpServer { .handler(LoggingHandler(LogLevel.INFO)) .childHandler(HttpChannelInitializer(this)) val ch = b.bind(port).sync().channel() + serverChannel = ch logger.info("Netty server started on port $port.") - ch.closeFuture().sync() - } catch (e: InterruptedException) { - logger.error("Netty server interrupted", e) + + return@withLock (ch.localAddress() as InetSocketAddress).port } catch (t: Throwable) { logger.error("Netty server failed - $port", t) - + stop() // Forward the exception because we ran into an unexpected error throw t - } finally { - bossGroup.shutdownGracefully() - workerGroup.shutdownGracefully() } + } + /** + * Stops the Netty server gracefully. + */ + fun stop() = lock.withLock { + logger.info("Shutting down Netty server...") + try { + serverChannel?.close()?.sync() + bossGroup?.shutdownGracefully()?.sync() + workerGroup?.shutdownGracefully()?.sync() + } catch (e: Exception) { + logger.warn("Error during shutdown", e) + } finally { + serverChannel = null + bossGroup = null + workerGroup = null + } logger.info("Netty server stopped.") } diff --git a/src/test/kotlin/HttpMiddlewareServerTest.kt b/src/test/kotlin/HttpMiddlewareServerTest.kt index f21367b..8100a01 100644 --- a/src/test/kotlin/HttpMiddlewareServerTest.kt +++ b/src/test/kotlin/HttpMiddlewareServerTest.kt @@ -7,9 +7,6 @@ import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response import org.junit.jupiter.api.* -import java.io.File -import java.nio.file.Files -import kotlin.concurrent.thread import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertTrue @@ -21,7 +18,7 @@ import kotlin.test.assertTrue @TestInstance(TestInstance.Lifecycle.PER_CLASS) class HttpMiddlewareServerTest { - private lateinit var serverThread: Thread + private lateinit var server: HttpServer private val client = OkHttpClient() /** @@ -32,9 +29,7 @@ class HttpMiddlewareServerTest { @BeforeAll fun initialize() { // Start the HTTP server in a separate thread - serverThread = thread { - startHttpServer() - } + server = startHttpServer() // Allow the server some time to start Thread.sleep(1000) @@ -46,14 +41,14 @@ class HttpMiddlewareServerTest { */ @AfterAll fun cleanup() { - serverThread.interrupt() + server.stop() } /** * This function starts the HTTP server with routing configured for * different difficulty levels. */ - private fun startHttpServer() { + private fun startHttpServer(): HttpServer { val server = HttpServer() server.routeController.apply { @@ -74,6 +69,7 @@ class HttpMiddlewareServerTest { } server.start(8080) // Start the server on port 8080 + return server } @Suppress("UNUSED_PARAMETER") diff --git a/src/test/kotlin/HttpServerTest.kt b/src/test/kotlin/HttpServerTest.kt index 9fb5ee4..d6ae183 100644 --- a/src/test/kotlin/HttpServerTest.kt +++ b/src/test/kotlin/HttpServerTest.kt @@ -9,7 +9,6 @@ import okhttp3.Response import org.junit.jupiter.api.* import java.io.File import java.nio.file.Files -import kotlin.concurrent.thread import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertTrue @@ -22,7 +21,7 @@ import kotlin.test.assertTrue class HttpServerTest { private lateinit var folder: File - private lateinit var serverThread: Thread + private lateinit var server: HttpServer private val client = OkHttpClient() /** @@ -44,9 +43,7 @@ class HttpServerTest { File(subFolder, "index.html").writeText("Hello, World!") // Start the HTTP server in a separate thread - serverThread = thread { - startHttpServer(folder) - } + server = startHttpServer(folder) // Allow the server some time to start Thread.sleep(1000) @@ -58,7 +55,7 @@ class HttpServerTest { */ @AfterAll fun cleanup() { - serverThread.interrupt() + server.stop() folder.deleteRecursively() // Clean up the temporary folder } @@ -66,7 +63,7 @@ class HttpServerTest { * This function starts the HTTP server with routing configured for * different difficulty levels. */ - private fun startHttpServer(folder: File) { + private fun startHttpServer(folder: File): HttpServer { val server = HttpServer() server.routeController.apply { @@ -97,6 +94,7 @@ class HttpServerTest { } server.start(8080) // Start the server on port 8080 + return server } @Suppress("UNUSED_PARAMETER") From d6a77375f71d27a1358522615c7628df952a8843 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=A8=E8=91=89=20Scarlet?= <93977077+mukjepscarlet@users.noreply.github.com> Date: Sun, 25 May 2025 00:05:55 +0800 Subject: [PATCH 2/2] apply --- src/main/kotlin/net/ccbluex/netty/http/HttpServer.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/net/ccbluex/netty/http/HttpServer.kt b/src/main/kotlin/net/ccbluex/netty/http/HttpServer.kt index a8fda5b..d1c7932 100644 --- a/src/main/kotlin/net/ccbluex/netty/http/HttpServer.kt +++ b/src/main/kotlin/net/ccbluex/netty/http/HttpServer.kt @@ -61,7 +61,7 @@ class HttpServer { internal val logger = LogManager.getLogger("HttpServer") } - fun middleware(middleware: Middleware) { + fun middleware(middleware: Middleware) = apply { middlewares += middleware }