Skip to content

Commit cb8c3db

Browse files
refactor(HttpServer): stop manually instead of interruption (#4)
1 parent 6cf7093 commit cb8c3db

File tree

3 files changed

+51
-28
lines changed

3 files changed

+51
-28
lines changed

src/main/kotlin/net/ccbluex/netty/http/HttpServer.kt

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
package net.ccbluex.netty.http
2121

2222
import io.netty.bootstrap.ServerBootstrap
23+
import io.netty.channel.Channel
2324
import io.netty.channel.ChannelOption
25+
import io.netty.channel.EventLoopGroup
2426
import io.netty.channel.epoll.Epoll
2527
import io.netty.channel.epoll.EpollEventLoopGroup
2628
import io.netty.channel.epoll.EpollServerSocketChannel
@@ -32,6 +34,9 @@ import net.ccbluex.netty.http.middleware.Middleware
3234
import net.ccbluex.netty.http.rest.RouteController
3335
import net.ccbluex.netty.http.websocket.WebSocketController
3436
import org.apache.logging.log4j.LogManager
37+
import java.net.InetSocketAddress
38+
import java.util.concurrent.locks.ReentrantLock
39+
import kotlin.concurrent.withLock
3540

3641

3742
/**
@@ -44,22 +49,32 @@ class HttpServer {
4449
val routeController = RouteController()
4550
val webSocketController = WebSocketController()
4651

47-
val middlewares = mutableListOf<Middleware>()
52+
private val lock = ReentrantLock()
53+
54+
internal val middlewares = mutableListOf<Middleware>()
55+
56+
private var bossGroup: EventLoopGroup? = null
57+
private var workerGroup: EventLoopGroup? = null
58+
private var serverChannel: Channel? = null
4859

4960
companion object {
5061
internal val logger = LogManager.getLogger("HttpServer")
5162
}
5263

53-
fun middleware(middleware: Middleware) {
64+
fun middleware(middleware: Middleware) = apply {
5465
middlewares += middleware
5566
}
5667

5768
/**
5869
* Starts the Netty server on the specified port.
70+
*
71+
* @param port The port of HTTP server. `0` means to auto select one.
72+
*
73+
* @return actual port of server.
5974
*/
60-
fun start(port: Int) {
61-
val bossGroup = if (Epoll.isAvailable()) EpollEventLoopGroup() else NioEventLoopGroup()
62-
val workerGroup = if (Epoll.isAvailable()) EpollEventLoopGroup() else NioEventLoopGroup()
75+
fun start(port: Int): Int = lock.withLock {
76+
bossGroup = if (Epoll.isAvailable()) EpollEventLoopGroup(1) else NioEventLoopGroup(1)
77+
workerGroup = if (Epoll.isAvailable()) EpollEventLoopGroup() else NioEventLoopGroup()
6378

6479
try {
6580
logger.info("Starting Netty server...")
@@ -70,21 +85,35 @@ class HttpServer {
7085
.handler(LoggingHandler(LogLevel.INFO))
7186
.childHandler(HttpChannelInitializer(this))
7287
val ch = b.bind(port).sync().channel()
88+
serverChannel = ch
7389

7490
logger.info("Netty server started on port $port.")
75-
ch.closeFuture().sync()
76-
} catch (e: InterruptedException) {
77-
logger.error("Netty server interrupted", e)
91+
92+
return@withLock (ch.localAddress() as InetSocketAddress).port
7893
} catch (t: Throwable) {
7994
logger.error("Netty server failed - $port", t)
80-
95+
stop()
8196
// Forward the exception because we ran into an unexpected error
8297
throw t
83-
} finally {
84-
bossGroup.shutdownGracefully()
85-
workerGroup.shutdownGracefully()
8698
}
99+
}
87100

101+
/**
102+
* Stops the Netty server gracefully.
103+
*/
104+
fun stop() = lock.withLock {
105+
logger.info("Shutting down Netty server...")
106+
try {
107+
serverChannel?.close()?.sync()
108+
bossGroup?.shutdownGracefully()?.sync()
109+
workerGroup?.shutdownGracefully()?.sync()
110+
} catch (e: Exception) {
111+
logger.warn("Error during shutdown", e)
112+
} finally {
113+
serverChannel = null
114+
bossGroup = null
115+
workerGroup = null
116+
}
88117
logger.info("Netty server stopped.")
89118
}
90119

src/test/kotlin/HttpMiddlewareServerTest.kt

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@ import okhttp3.OkHttpClient
77
import okhttp3.Request
88
import okhttp3.Response
99
import org.junit.jupiter.api.*
10-
import java.io.File
11-
import java.nio.file.Files
12-
import kotlin.concurrent.thread
1310
import kotlin.test.assertEquals
1411
import kotlin.test.assertNotNull
1512
import kotlin.test.assertTrue
@@ -21,7 +18,7 @@ import kotlin.test.assertTrue
2118
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
2219
class HttpMiddlewareServerTest {
2320

24-
private lateinit var serverThread: Thread
21+
private lateinit var server: HttpServer
2522
private val client = OkHttpClient()
2623

2724
/**
@@ -32,9 +29,7 @@ class HttpMiddlewareServerTest {
3229
@BeforeAll
3330
fun initialize() {
3431
// Start the HTTP server in a separate thread
35-
serverThread = thread {
36-
startHttpServer()
37-
}
32+
server = startHttpServer()
3833

3934
// Allow the server some time to start
4035
Thread.sleep(1000)
@@ -46,14 +41,14 @@ class HttpMiddlewareServerTest {
4641
*/
4742
@AfterAll
4843
fun cleanup() {
49-
serverThread.interrupt()
44+
server.stop()
5045
}
5146

5247
/**
5348
* This function starts the HTTP server with routing configured for
5449
* different difficulty levels.
5550
*/
56-
private fun startHttpServer() {
51+
private fun startHttpServer(): HttpServer {
5752
val server = HttpServer()
5853

5954
server.routeController.apply {
@@ -74,6 +69,7 @@ class HttpMiddlewareServerTest {
7469
}
7570

7671
server.start(8080) // Start the server on port 8080
72+
return server
7773
}
7874

7975
@Suppress("UNUSED_PARAMETER")

src/test/kotlin/HttpServerTest.kt

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import okhttp3.Response
99
import org.junit.jupiter.api.*
1010
import java.io.File
1111
import java.nio.file.Files
12-
import kotlin.concurrent.thread
1312
import kotlin.test.assertEquals
1413
import kotlin.test.assertNotNull
1514
import kotlin.test.assertTrue
@@ -22,7 +21,7 @@ import kotlin.test.assertTrue
2221
class HttpServerTest {
2322

2423
private lateinit var folder: File
25-
private lateinit var serverThread: Thread
24+
private lateinit var server: HttpServer
2625
private val client = OkHttpClient()
2726

2827
/**
@@ -44,9 +43,7 @@ class HttpServerTest {
4443
File(subFolder, "index.html").writeText("Hello, World!")
4544

4645
// Start the HTTP server in a separate thread
47-
serverThread = thread {
48-
startHttpServer(folder)
49-
}
46+
server = startHttpServer(folder)
5047

5148
// Allow the server some time to start
5249
Thread.sleep(1000)
@@ -58,15 +55,15 @@ class HttpServerTest {
5855
*/
5956
@AfterAll
6057
fun cleanup() {
61-
serverThread.interrupt()
58+
server.stop()
6259
folder.deleteRecursively() // Clean up the temporary folder
6360
}
6461

6562
/**
6663
* This function starts the HTTP server with routing configured for
6764
* different difficulty levels.
6865
*/
69-
private fun startHttpServer(folder: File) {
66+
private fun startHttpServer(folder: File): HttpServer {
7067
val server = HttpServer()
7168

7269
server.routeController.apply {
@@ -97,6 +94,7 @@ class HttpServerTest {
9794
}
9895

9996
server.start(8080) // Start the server on port 8080
97+
return server
10098
}
10199

102100
@Suppress("UNUSED_PARAMETER")

0 commit comments

Comments
 (0)