Skip to content

Commit 2818977

Browse files
committed
release: 2.0.0
- Introduces support for inline URI parameters - Rewritten path processing - Fixes websocket channels broadcast and close
1 parent d8c458e commit 2818977

File tree

15 files changed

+717
-166
lines changed

15 files changed

+717
-166
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Netty HttpServer is a Kotlin-based library for building web REST APIs on top of
99
To include Netty HttpServer in your project, add the following dependency to your `build.gradle` file:
1010

1111
```gradle
12-
implementation 'com.github.CCBlueX:netty-httpserver:1.0.0'
12+
implementation 'com.github.CCBlueX:netty-httpserver:2.0.0'
1313
```
1414

1515
### Basic Usage

build.gradle.kts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ val authorName = "ccbluex"
1111
val projectUrl = "https://github.com/ccbluex/netty-httpserver"
1212

1313
group = "net.ccbluex"
14-
version = "1.0.0"
14+
version = "2.0.0"
1515

1616
repositories {
1717
mavenCentral()
@@ -37,8 +37,8 @@ dependencies {
3737
implementation("org.apache.tika:tika-core:2.9.2")
3838

3939
testImplementation(kotlin("test"))
40-
// https://mvnrepository.com/artifact/org.mockito/mockito-core
41-
testImplementation("org.mockito:mockito-core:5.13.0")
40+
testImplementation("com.squareup.retrofit2:retrofit:2.9.0")
41+
testImplementation("com.squareup.retrofit2:converter-gson:2.9.0")
4242
}
4343

4444
tasks.test {
Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
import com.google.gson.JsonObject
2+
import io.netty.handler.codec.http.FullHttpResponse
23
import net.ccbluex.netty.http.HttpServer
4+
import net.ccbluex.netty.http.model.RequestObject
35
import net.ccbluex.netty.http.util.httpOk
46

57
fun main() {
68
val server = HttpServer()
79

810
server.routeController.apply {
9-
post("/echo") { request ->
10-
httpOk(request.asJson<JsonObject>())
11-
}
11+
post("/echo", ::postEcho) // /echo
1212
}
1313

1414
server.start(8080) // Start the server on port 8080
1515
}
16+
17+
fun postEcho(requestObject: RequestObject): FullHttpResponse {
18+
return httpOk(requestObject.asJson<JsonObject>())
19+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
hello!
File renamed without changes.
Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,78 @@
1+
import com.google.gson.JsonObject
2+
import io.netty.handler.codec.http.FullHttpResponse
13
import net.ccbluex.netty.http.HttpServer
4+
import net.ccbluex.netty.http.model.RequestObject
5+
import net.ccbluex.netty.http.util.httpBadRequest
6+
import net.ccbluex.netty.http.util.httpOk
27
import java.io.File
38

9+
const val FOLDER_NAME = "files"
10+
val folder = File(FOLDER_NAME)
11+
412
fun main() {
513
val server = HttpServer()
6-
val folder = File("files")
14+
715
println("Serving files from: ${folder.absolutePath}")
816

917
server.routeController.apply {
10-
// Serve files from the "files" directory
11-
file("/files", folder)
18+
get("/", ::getRoot)
19+
get("/conflicting", ::getConflictingPath)
20+
get("/a/b/c", ::getConflictingPath)
21+
get("/file/:name", ::getFileInformation)
22+
post("/file/:name", ::postFile)
23+
24+
// register file serving at the bottom of the routing tree
25+
// to avoid overwriting other routes
26+
file("/", folder)
1227
}
1328

1429
server.start(8080) // Start the server on port 8080
1530
}
31+
32+
@Suppress("UNUSED_PARAMETER")
33+
fun getRoot(requestObject: RequestObject): FullHttpResponse {
34+
// Count the number of files in the folder
35+
// Walk the folder and count the number of files
36+
return httpOk(JsonObject().apply {
37+
addProperty("path", folder.absolutePath)
38+
addProperty("files", folder.walk().count())
39+
})
40+
}
41+
42+
@Suppress("UNUSED_PARAMETER")
43+
fun getConflictingPath(requestObject: RequestObject): FullHttpResponse {
44+
return httpOk(JsonObject().apply {
45+
addProperty("message", "This is a conflicting path")
46+
})
47+
}
48+
49+
@Suppress("UNUSED_PARAMETER")
50+
fun getFileInformation(requestObject: RequestObject): FullHttpResponse {
51+
val name = requestObject.params["name"] ?: return httpBadRequest("Missing name parameter")
52+
val file = File(folder, name)
53+
54+
if (!file.exists()) {
55+
return httpBadRequest("File not found")
56+
}
57+
58+
return httpOk(JsonObject().apply {
59+
addProperty("name", file.name)
60+
addProperty("size", file.length())
61+
addProperty("lastModified", file.lastModified())
62+
})
63+
}
64+
65+
@Suppress("UNUSED_PARAMETER")
66+
fun postFile(requestObject: RequestObject): FullHttpResponse {
67+
val name = requestObject.params["name"] ?: return httpBadRequest("Missing name parameter")
68+
val file = File(folder, name)
69+
70+
if (file.exists()) {
71+
return httpBadRequest("File already exists")
72+
}
73+
74+
file.writeText(requestObject.body)
75+
return httpOk(JsonObject().apply {
76+
addProperty("message", "File written")
77+
})
78+
}
Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,41 @@
11
import com.google.gson.JsonObject
2+
import io.netty.handler.codec.http.FullHttpResponse
23
import net.ccbluex.netty.http.HttpServer
4+
import net.ccbluex.netty.http.model.RequestObject
35
import net.ccbluex.netty.http.util.httpOk
46

57
fun main() {
68
val server = HttpServer()
79

810
server.routeController.apply {
9-
get("/hello") {
10-
httpOk(JsonObject().apply {
11-
addProperty("message", "Hello, World!")
12-
})
13-
}
11+
get("/", ::getRoot)
12+
get("/hello", ::getHello) // /hello?name=World
13+
get("/hello/:name", ::getHello) // /hello/World
14+
get("/hello/:name/:age", ::getHelloWithAge) // /hello/World/20
1415
}
1516

1617
server.start(8080) // Start the server on port 8080
1718
}
19+
20+
@Suppress("UNUSED_PARAMETER")
21+
fun getRoot(requestObject: RequestObject): FullHttpResponse {
22+
return httpOk(JsonObject().apply {
23+
addProperty("root", true)
24+
})
25+
}
26+
27+
fun getHello(requestObject: RequestObject): FullHttpResponse {
28+
val name = requestObject.params["name"] ?: requestObject.queryParams["name"] ?: "World"
29+
return httpOk(JsonObject().apply {
30+
addProperty("message", "Hello, $name!")
31+
})
32+
}
33+
34+
fun getHelloWithAge(requestObject: RequestObject): FullHttpResponse {
35+
val name = requestObject.params["name"] ?: "World"
36+
val age = requestObject.params["age"] ?: "0"
37+
return httpOk(JsonObject().apply {
38+
addProperty("message", "Hello, $name! You are $age years old.")
39+
})
40+
}
41+

settings.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ rootProject.name = "netty-httpserver"
55

66
include(":examples:hello-world")
77
include(":examples:echo-server")
8-
include(":examples:file-server")
8+
include(":examples:file-server")

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

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ package net.ccbluex.netty.http
2222
import io.netty.buffer.Unpooled
2323
import io.netty.handler.codec.http.*
2424
import net.ccbluex.netty.http.HttpServer.Companion.logger
25+
import net.ccbluex.netty.http.model.RequestContext
2526
import net.ccbluex.netty.http.util.httpBadRequest
2627
import net.ccbluex.netty.http.util.httpInternalServerError
2728
import net.ccbluex.netty.http.util.httpNotFound
@@ -30,20 +31,20 @@ import net.ccbluex.netty.http.model.RequestObject
3031
internal class HttpConductor(private val server: HttpServer) {
3132

3233
/**
33-
* Processes the incoming request object and returns the response.
34+
* Processes the incoming request context and returns the response.
3435
*
35-
* @param requestObject The incoming request object.
36+
* @param context The request context to process.
3637
* @return The response to the request.
3738
*/
38-
fun processRequestObject(requestObject: RequestObject) = runCatching {
39-
val context = requestObject.context
39+
fun processRequestContext(context: RequestContext) = runCatching {
40+
val content = context.contentBuffer.toString()
4041
val method = context.httpMethod
4142

42-
logger.debug("Request {}", requestObject)
43+
logger.debug("Request {}", context)
4344

4445
if (!context.headers["content-length"].isNullOrEmpty() &&
45-
context.headers["content-length"]?.toInt() != requestObject.content.toByteArray(Charsets.UTF_8).size) {
46-
logger.warn("Received incomplete request: $requestObject")
46+
context.headers["content-length"]?.toInt() != content.toByteArray(Charsets.UTF_8).size) {
47+
logger.warn("Received incomplete request: $context")
4748
return@runCatching httpBadRequest("Incomplete request")
4849
}
4950

@@ -63,21 +64,24 @@ internal class HttpConductor(private val server: HttpServer) {
6364
return@runCatching response
6465
}
6566

66-
server.routeController.findRoute(context.path, method)?.let { route ->
67-
logger.debug("Found route {}", route)
68-
return@runCatching route.handler(requestObject)
69-
}
67+
val (node, params, remaining) = server.routeController.processPath(context.path, method) ?:
68+
return@runCatching httpNotFound(context.path, "Route not found")
7069

71-
if (method == HttpMethod.GET) {
72-
server.routeController.findFileServant(context.path)?.let { (fileServant, path) ->
73-
logger.debug("Found file servant {}", fileServant)
74-
return@runCatching fileServant.handleFileRequest(path)
75-
}
76-
}
70+
logger.debug("Found destination {}", node)
71+
val requestObject = RequestObject(
72+
uri = context.uri,
73+
path = context.path,
74+
remainingPath = remaining,
75+
method = method,
76+
body = content,
77+
params = params,
78+
queryParams = context.params,
79+
headers = context.headers
80+
)
7781

78-
httpNotFound(context.path, "Route not found")
82+
return@runCatching node.handleRequest(requestObject)
7983
}.getOrElse {
80-
logger.error("Error while processing request object: $requestObject", it)
84+
logger.error("Error while processing request object: $context", it)
8185
httpInternalServerError(it.message ?: "Unknown error")
8286
}
8387

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import io.netty.handler.codec.http.LastHttpContent
2828
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory
2929
import net.ccbluex.netty.http.HttpServer.Companion.logger
3030
import net.ccbluex.netty.http.model.RequestContext
31-
import net.ccbluex.netty.http.model.RequestObject
3231
import net.ccbluex.netty.http.websocket.WebSocketHandler
3332
import java.net.URLDecoder
3433

@@ -109,11 +108,10 @@ internal class HttpServerHandler(private val server: HttpServer) : ChannelInboun
109108

110109
// If this is the last content, process the request
111110
if (msg is LastHttpContent) {
112-
val requestObject = RequestObject(requestContext)
113111
localRequestContext.remove()
114112

115113
val httpConductor = HttpConductor(server)
116-
val response = httpConductor.processRequestObject(requestObject)
114+
val response = httpConductor.processRequestContext(requestContext)
117115
ctx.writeAndFlush(response)
118116
}
119117
}

0 commit comments

Comments
 (0)