From 9cf2623c7095326743a5bda69eddb072b624d09a Mon Sep 17 00:00:00 2001 From: Maxim Schuwalow Date: Thu, 16 Nov 2023 14:07:43 +0100 Subject: [PATCH 1/2] Allow specifying maxIninitialLineLength and maxChunkSize --- zio-http/src/main/scala/zio/http/Server.scala | 26 ++++++++++++++++++- .../server/ServerChannelInitializer.scala | 3 +-- .../src/test/scala/zio/http/ServerSpec.scala | 19 +++++++++++--- 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/zio-http/src/main/scala/zio/http/Server.scala b/zio-http/src/main/scala/zio/http/Server.scala index ff540b68ce..42ce58aff9 100644 --- a/zio-http/src/main/scala/zio/http/Server.scala +++ b/zio-http/src/main/scala/zio/http/Server.scala @@ -26,6 +26,8 @@ import zio.http.Server.Config.ResponseCompressionConfig import zio.http.netty.NettyConfig import zio.http.netty.server._ +import io.netty.handler.codec.http.HttpObjectDecoder.{DEFAULT_MAX_CHUNK_SIZE, DEFAULT_MAX_HEADER_SIZE, DEFAULT_MAX_INITIAL_LINE_LENGTH} + /** * Represents a server, which is capable of serving zero or more HTTP * applications. @@ -54,7 +56,9 @@ object Server { requestDecompression: Decompression, responseCompression: Option[ResponseCompressionConfig], requestStreaming: RequestStreaming, + maxInitialLineLength: Int, maxHeaderSize: Int, + maxChunkSize: Int, logWarningOnFatalError: Boolean, gracefulShutdownTimeout: Duration, webSocketConfig: WebSocketConfig, @@ -112,12 +116,24 @@ object Server { */ def logWarningOnFatalError(enable: Boolean): Config = self.copy(logWarningOnFatalError = enable) + /** + * Configure the server to use `maxInitialLineLength` value when + * encode/decode headers. + */ + def maxInitialLineLength(lineLength: Int): Config = self.copy(maxInitialLineLength = lineLength) + /** * Configure the server to use `maxHeaderSize` value when encode/decode * headers. */ def maxHeaderSize(headerSize: Int): Config = self.copy(maxHeaderSize = headerSize) + /** + * Configure the server to use `maxChunkSize` value when encode/decode + * headers. + */ + def maxChunkSize(chunkSize: Int): Config = self.copy(maxChunkSize = chunkSize) + def noIdleTimeout: Config = self.copy(idleTimeout = None) /** @@ -169,7 +185,9 @@ object Server { Decompression.config.nested("request-decompression").withDefault(Config.default.requestDecompression) ++ ResponseCompressionConfig.config.nested("response-compression").optional ++ RequestStreaming.config.nested("request-streaming").withDefault(Config.default.requestStreaming) ++ + zio.Config.int("max-initial-line-length").withDefault(Config.default.maxInitialLineLength) ++ zio.Config.int("max-header-size").withDefault(Config.default.maxHeaderSize) ++ + zio.Config.int("max-chunk-size").withDefault(Config.default.maxChunkSize) ++ zio.Config.boolean("log-warning-on-fatal-error").withDefault(Config.default.logWarningOnFatalError) ++ zio.Config.duration("graceful-shutdown-timeout").withDefault(Config.default.gracefulShutdownTimeout) ++ zio.Config.duration("idle-timeout").optional.withDefault(Config.default.idleTimeout) @@ -183,7 +201,9 @@ object Server { requestDecompression, responseCompression, requestStreaming, + maxInitialLineLength, maxHeaderSize, + maxChunkSize, logWarningOnFatalError, gracefulShutdownTimeout, idleTimeout, @@ -196,7 +216,9 @@ object Server { requestDecompression = requestDecompression, responseCompression = responseCompression, requestStreaming = requestStreaming, + maxInitialLineLength = maxInitialLineLength, maxHeaderSize = maxHeaderSize, + maxChunkSize = maxChunkSize, logWarningOnFatalError = logWarningOnFatalError, gracefulShutdownTimeout = gracefulShutdownTimeout, idleTimeout = idleTimeout, @@ -211,7 +233,9 @@ object Server { requestDecompression = Decompression.No, responseCompression = None, requestStreaming = RequestStreaming.Disabled(1024 * 100), - maxHeaderSize = 8192, + maxInitialLineLength = DEFAULT_MAX_INITIAL_LINE_LENGTH, + maxHeaderSize = DEFAULT_MAX_HEADER_SIZE, + maxChunkSize = DEFAULT_MAX_CHUNK_SIZE, logWarningOnFatalError = true, gracefulShutdownTimeout = 10.seconds, webSocketConfig = WebSocketConfig.default, diff --git a/zio-http/src/main/scala/zio/http/netty/server/ServerChannelInitializer.scala b/zio-http/src/main/scala/zio/http/netty/server/ServerChannelInitializer.scala index 418626c99f..52630ca16a 100644 --- a/zio-http/src/main/scala/zio/http/netty/server/ServerChannelInitializer.scala +++ b/zio-http/src/main/scala/zio/http/netty/server/ServerChannelInitializer.scala @@ -28,7 +28,6 @@ import zio.http.netty.model.Conversions import io.netty.channel.ChannelHandler.Sharable import io.netty.channel._ -import io.netty.handler.codec.http.HttpObjectDecoder.{DEFAULT_MAX_CHUNK_SIZE, DEFAULT_MAX_INITIAL_LINE_LENGTH} import io.netty.handler.codec.http._ import io.netty.handler.flush.FlushConsolidationHandler import io.netty.handler.timeout.ReadTimeoutHandler @@ -60,7 +59,7 @@ private[zio] final case class ServerChannelInitializer( // Instead of ServerCodec, we should use Decoder and Encoder separately to have more granular control over performance. pipeline.addLast( Names.HttpRequestDecoder, - new HttpRequestDecoder(DEFAULT_MAX_INITIAL_LINE_LENGTH, cfg.maxHeaderSize, DEFAULT_MAX_CHUNK_SIZE, false), + new HttpRequestDecoder(cfg.maxInitialLineLength, cfg.maxHeaderSize, cfg.maxChunkSize, false), ) pipeline.addLast(Names.HttpResponseEncoder, new HttpResponseEncoder()) diff --git a/zio-http/src/test/scala/zio/http/ServerSpec.scala b/zio-http/src/test/scala/zio/http/ServerSpec.scala index c9dcefcd54..4ec694d73d 100644 --- a/zio-http/src/test/scala/zio/http/ServerSpec.scala +++ b/zio-http/src/test/scala/zio/http/ServerSpec.scala @@ -36,13 +36,16 @@ object ServerSpec extends HttpRunnableSpec { content <- HttpGen.nonEmptyBody(Gen.const(data)) } yield (data.mkString(""), content) - private val port = 8080 - private val MaxSize = 1024 * 10 - val configApp = Server.Config.default + private val port = 8080 + private val MaxSize = 1024 * 10 + private val MaxPathLength = 6144 + + val configApp = Server.Config.default .requestDecompression(true) .disableRequestStreaming(MaxSize) .port(port) .responseCompression() + .maxInitialLineLength(MaxPathLength * 2) private val app = serve @@ -297,6 +300,16 @@ object ServerSpec extends HttpRunnableSpec { .toHttpApp val res = app.deploy.status.run(method = Method.POST, body = Body.fromString("some text")) assertZIO(res)(equalTo(Status.Ok)) + } + test("can support long urls") { + check(Gen.alphaNumericStringBounded(0, MaxPathLength)) { path => + val app = Routes + .singleton(handler { (_: Path, req: Request) => req.body.asChunk.as(Response.ok) }) + .sandbox + .toHttpApp + val res = + app.deploy.status.run(method = Method.POST, path = Path.root / path, body = Body.fromString("some text")) + assertZIO(res)(equalTo(Status.Ok)) + } } } From 658cfc89370a4e25e31f8002fd7281357a1ce1ab Mon Sep 17 00:00:00 2001 From: Maxim Schuwalow Date: Thu, 16 Nov 2023 15:02:33 +0100 Subject: [PATCH 2/2] fmt --- zio-http/src/main/scala/zio/http/Server.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/zio-http/src/main/scala/zio/http/Server.scala b/zio-http/src/main/scala/zio/http/Server.scala index 42ce58aff9..0d735934d9 100644 --- a/zio-http/src/main/scala/zio/http/Server.scala +++ b/zio-http/src/main/scala/zio/http/Server.scala @@ -26,7 +26,11 @@ import zio.http.Server.Config.ResponseCompressionConfig import zio.http.netty.NettyConfig import zio.http.netty.server._ -import io.netty.handler.codec.http.HttpObjectDecoder.{DEFAULT_MAX_CHUNK_SIZE, DEFAULT_MAX_HEADER_SIZE, DEFAULT_MAX_INITIAL_LINE_LENGTH} +import io.netty.handler.codec.http.HttpObjectDecoder.{ + DEFAULT_MAX_CHUNK_SIZE, + DEFAULT_MAX_HEADER_SIZE, + DEFAULT_MAX_INITIAL_LINE_LENGTH, +} /** * Represents a server, which is capable of serving zero or more HTTP