Skip to content

Commit 3772376

Browse files
refactor(rest): improve response creation (#3)
Directly serialize JSON into ByteBuf.
1 parent 711dea0 commit 3772376

8 files changed

Lines changed: 220 additions & 99 deletions

File tree

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

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@
1919
*/
2020
package net.ccbluex.netty.http
2121

22-
import io.netty.buffer.Unpooled
2322
import io.netty.handler.codec.http.*
2423
import net.ccbluex.netty.http.HttpServer.Companion.logger
2524
import net.ccbluex.netty.http.model.RequestContext
2625
import net.ccbluex.netty.http.util.httpBadRequest
2726
import net.ccbluex.netty.http.util.httpInternalServerError
2827
import net.ccbluex.netty.http.util.httpNotFound
2928
import net.ccbluex.netty.http.model.RequestObject
29+
import net.ccbluex.netty.http.util.httpNoContent
3030

3131
internal class HttpConductor(private val server: HttpServer) {
3232

@@ -48,22 +48,13 @@ internal class HttpConductor(private val server: HttpServer) {
4848
return@runCatching httpBadRequest("Incomplete request")
4949
}
5050

51-
if (method == HttpMethod.OPTIONS) {
52-
val response = DefaultFullHttpResponse(
53-
HttpVersion.HTTP_1_1,
54-
HttpResponseStatus.OK,
55-
Unpooled.wrappedBuffer(ByteArray(0))
56-
)
57-
58-
val httpHeaders = response.headers()
59-
httpHeaders[HttpHeaderNames.CONTENT_TYPE] = "text/plain"
60-
httpHeaders[HttpHeaderNames.CONTENT_LENGTH] = response.content().readableBytes()
61-
return@runCatching response
62-
}
63-
6451
val (node, params, remaining) = server.routeController.processPath(context.path, method) ?:
6552
return@runCatching httpNotFound(context.path, "Route not found")
6653

54+
if (method == HttpMethod.OPTIONS) {
55+
return@runCatching httpNoContent()
56+
}
57+
6758
logger.debug("Found destination {}", node)
6859
val requestObject = RequestObject(
6960
uri = context.uri,

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ internal class HttpServerHandler(private val server: HttpServer) : ChannelInboun
8686
} else {
8787
val requestContext = RequestContext(
8888
msg.method(),
89-
URLDecoder.decode(msg.uri(), "UTF-8"),
89+
URLDecoder.decode(msg.uri(), Charsets.UTF_8),
9090
msg.headers().associate { it.key to it.value },
9191
)
9292

@@ -95,21 +95,20 @@ internal class HttpServerHandler(private val server: HttpServer) : ChannelInboun
9595
}
9696

9797
is HttpContent -> {
98-
if (localRequestContext.get() == null) {
98+
val requestContext = localRequestContext.get() ?: run {
9999
logger.warn("Received HttpContent without HttpRequest")
100100
return
101101
}
102102

103103
// Append content to the buffer
104-
val requestContext = localRequestContext.get()
105104
requestContext
106105
.contentBuffer
107106
.append(msg.content().toString(Charsets.UTF_8))
108107

109108
// If this is the last content, process the request
110109
if (msg is LastHttpContent) {
111110
localRequestContext.remove()
112-
111+
113112
val httpConductor = HttpConductor(server)
114113
val response = httpConductor.processRequestContext(requestContext)
115114
val httpResponse = server.middlewares.fold(response) { acc, f -> f(requestContext, acc) }

src/main/kotlin/net/ccbluex/netty/http/model/RequestContext.kt

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,37 +20,32 @@
2020
package net.ccbluex.netty.http.model
2121

2222
import io.netty.handler.codec.http.HttpMethod
23-
import java.util.*
24-
import java.util.stream.Collectors
2523

2624
data class RequestContext(var httpMethod: HttpMethod, var uri: String, var headers: Map<String, String>) {
2725
val contentBuffer = StringBuilder()
28-
val path = if (uri.contains("?")) uri.substring(0, uri.indexOf('?')) else uri
26+
val path = uri.substringBefore('?', uri)
2927
val params = getUriParams(uri)
3028
}
3129

3230
/**
3331
* The received uri should be like: '...?param1=value&param2=value'
3432
*/
3533
private fun getUriParams(uri: String): Map<String, String> {
36-
if (uri.contains("?")) {
37-
val paramsString = uri.substring(uri.indexOf('?') + 1)
34+
val queryString = uri.substringAfter('?', "")
3835

39-
// in case of duplicated params, will be used la last value
40-
return Arrays.stream(
41-
if (paramsString.contains("&")) paramsString.split("&".toRegex()).dropLastWhile { it.isEmpty() }
42-
.toTypedArray() else arrayOf(paramsString))
43-
.map { value: String ->
44-
value.split("=".toRegex()).dropLastWhile { it.isEmpty() }
45-
.toTypedArray()
46-
}
47-
.collect(
48-
Collectors.toMap(
49-
{ paramValue: Array<String> -> paramValue[0] },
50-
{ paramValue: Array<String> -> paramValue[1] },
51-
{ v1: String?, v2: String -> v2 })
52-
)
36+
if (queryString.isEmpty()) {
37+
return emptyMap()
5338
}
5439

55-
return emptyMap()
40+
// in case of duplicated params, will be used the last value
41+
return queryString.split('&')
42+
.mapNotNull { param ->
43+
val index = param.indexOf('=')
44+
if (index == -1) null
45+
else {
46+
val key = param.substring(0, index)
47+
val value = param.substring(index + 1)
48+
if (key.isNotEmpty()) key to value else null
49+
}
50+
}.toMap()
5651
}

src/main/kotlin/net/ccbluex/netty/http/model/RequestObject.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919
*/
2020
package net.ccbluex.netty.http.model
2121

22-
import com.google.gson.Gson
2322
import io.netty.handler.codec.http.HttpMethod
23+
import net.ccbluex.netty.http.util.gson
2424

2525
/**
2626
* Represents an HTTP request object.
@@ -51,7 +51,12 @@ data class RequestObject(
5151
* @return The JSON object of the specified type.
5252
*/
5353
inline fun <reified T> asJson(): T {
54-
return Gson().fromJson(body, T::class.java)
54+
return GSON_INSTANCE.fromJson(body, T::class.java)
55+
}
56+
57+
companion object {
58+
@JvmField
59+
val GSON_INSTANCE = gson
5560
}
5661

5762
}

src/main/kotlin/net/ccbluex/netty/http/rest/RouteControl.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ class RouteController : Node("") {
5555
*/
5656
internal fun processPath(path: String, method: HttpMethod): Destination? {
5757
val pathArray = path.asPathArray()
58-
.also { if (it.isEmpty()) throw IllegalArgumentException("Path cannot be empty") }
58+
require(pathArray.isNotEmpty()) { "Path cannot be empty" }
5959

6060
return travelNode(this, pathArray, method, 0, mutableMapOf())
6161
}
@@ -282,4 +282,8 @@ class FileServant(part: String, private val baseFolder: File) : Node(part) {
282282
*
283283
* @return An array of path parts.
284284
*/
285-
private fun String.asPathArray() = split("/").drop(1).toTypedArray()
285+
private fun String.asPathArray(): Array<String> {
286+
val parts = split("/")
287+
return if (parts.size <= 1) emptyArray()
288+
else parts.subList(1, parts.size).toTypedArray()
289+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* This file is part of Netty-Rest (https://github.com/CCBlueX/netty-rest)
3+
*
4+
* Copyright (c) 2024 CCBlueX
5+
*
6+
* LiquidBounce is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU General Public License as published by
8+
* the Free Software Foundation, either version 3 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* Netty-Rest is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU General Public License
17+
* along with Netty-Rest. If not, see <https://www.gnu.org/licenses/>.
18+
*
19+
*/
20+
@file:Suppress("NOTHING_TO_INLINE")
21+
package net.ccbluex.netty.http.util
22+
23+
import io.netty.buffer.ByteBuf
24+
import io.netty.buffer.ByteBufInputStream
25+
import io.netty.buffer.ByteBufOutputStream
26+
27+
inline fun ByteBuf.inputStream() = ByteBufInputStream(this)
28+
29+
inline fun ByteBuf.outputStream() = ByteBufOutputStream(this)

0 commit comments

Comments
 (0)