Repository: javalin/javalin
Pull request #2368 (2025-02-04 19:48:25)

[core] Improve `HttpStatus.forStatus` by using map lookup
The current `HttpStatus.forStatus` implementation appears to be a brute force search:
https://github.com/javalin/javalin/blob/2b63b76fd4b262c4eb8ff5187926e83bd2dd6129/javalin/src/main/java/io/javalin/http/HttpStatus.kt#L153-L154
This PR changes the `HttpStatus.forStatus` implementation to use a map lookup, similar to [`HandlerType.findByName`](https://github.com/javalin/javalin/blob/2b63b76fd4b262c4eb8ff5187926e83bd2dd6129/javalin/src/main/java/io/javalin/http/HandlerType.kt#L106-L112).

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/javalin/javalin/pull/2368?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=javalin) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 87.13%. Comparing base [(`2b63b76`)](https://app.codecov.io/gh/javalin/javalin/commit/2b63b76fd4b262c4eb8ff5187926e83bd2dd6129?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=javalin) to head [(`d0aa954`)](https://app.codecov.io/gh/javalin/javalin/commit/d0aa954ab60d14b0725e882e4fb89c5900c1baa3?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=javalin).

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff            @@
##             master    #2368   +/-   ##
=========================================
  Coverage     87.12%   87.13%           
  Complexity     1320     1320           
=========================================
  Files           148      148           
  Lines          4467     4468    +1     
  Branches        512      512           
=========================================
+ Hits           3892     3893    +1     
  Misses          354      354           
  Partials        221      221           
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/javalin/javalin/pull/2368?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=javalin).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=javalin).


Patch:
diff --git a/javalin/src/main/java/io/javalin/config/HttpConfig.kt b/javalin/src/main/java/io/javalin/config/HttpConfig.kt
index 09cc72c1..efbde710 100644
--- a/javalin/src/main/java/io/javalin/config/HttpConfig.kt
+++ b/javalin/src/main/java/io/javalin/config/HttpConfig.kt
@@ -17,6 +17,7 @@ class HttpConfig(private val cfg: JavalinConfig) {
     @JvmField var prefer405over404 = false
     @JvmField var strictContentTypes = false
     @JvmField var maxRequestSize = 1_000_000L // increase this or use inputstream to handle large requests
+    @JvmField var responseBufferSize: Int? = null
     @JvmField var defaultContentType = ContentType.PLAIN
     @JvmField var asyncTimeout = 0L
     //@formatter:on
diff --git a/javalin/src/main/java/io/javalin/http/HttpStatus.kt b/javalin/src/main/java/io/javalin/http/HttpStatus.kt
index ddbc1218..e30da1de 100644
--- a/javalin/src/main/java/io/javalin/http/HttpStatus.kt
+++ b/javalin/src/main/java/io/javalin/http/HttpStatus.kt
@@ -149,9 +149,10 @@ enum class HttpStatus(val code: Int, val message: String) {
 
     companion object {
 
+        private val statusMap = HttpStatus.entries.associateBy { it.code }
+
         @JvmStatic
-        fun forStatus(status: Int): HttpStatus =
-            HttpStatus.entries.find { it.code == status } ?: UNKNOWN
+        fun forStatus(status: Int): HttpStatus = statusMap[status] ?: UNKNOWN
 
     }
 
diff --git a/javalin/src/main/java/io/javalin/http/servlet/JavalinServlet.kt b/javalin/src/main/java/io/javalin/http/servlet/JavalinServlet.kt
index 2889b6e3..a52d7d99 100644
--- a/javalin/src/main/java/io/javalin/http/servlet/JavalinServlet.kt
+++ b/javalin/src/main/java/io/javalin/http/servlet/JavalinServlet.kt
@@ -109,7 +109,7 @@ class JavalinServlet(val cfg: JavalinConfig) : HttpServlet() {
             if (responseWritten.getAndSet(true)) return // prevent writing more than once, it's required because timeout listener can terminate the flow at any time
             resultInputStream()?.use { resultStream ->
                 val etagWritten = ETagGenerator.tryWriteEtagAndClose(cfg.http.generateEtags, this, resultStream)
-                if (!etagWritten) resultStream.copyTo(outputStream(), 4096)
+                if (!etagWritten) resultStream.copyTo(outputStream(), cfg.http.responseBufferSize ?: 32_768) // default should never happen, we add a fallback just in case
             }
             cfg.pvt.requestLogger?.handle(this, executionTimeMs())
         } catch (throwable: Throwable) {
diff --git a/javalin/src/main/java/io/javalin/jetty/JettyServer.kt b/javalin/src/main/java/io/javalin/jetty/JettyServer.kt
index ad9283b3..fd203dcb 100644
--- a/javalin/src/main/java/io/javalin/jetty/JettyServer.kt
+++ b/javalin/src/main/java/io/javalin/jetty/JettyServer.kt
@@ -79,6 +79,10 @@ class JettyServer(private val cfg: JavalinConfig) {
             })
             val httpConfiguration = defaultHttpConfiguration()
             cfg.pvt.jetty.httpConfigurationConfigs.forEach { it.accept(httpConfiguration) } // apply user config (before connectors)
+            // use the jetty value, either the default or something the user has specified with the cfg.jetty.modifyHttpConfiguration option if there is no value set with the new api.
+            if (cfg.http.responseBufferSize == null) {
+                cfg.http.responseBufferSize = httpConfiguration.outputBufferSize
+            }
             cfg.pvt.jetty.connectors.map { it.apply(this, httpConfiguration) }.forEach(this::addConnector) // add user connectors
             if (connectors.isEmpty()) { // add default connector if no connectors are specified
                 connectors = arrayOf(ServerConnector(server, HttpConnectionFactory(httpConfiguration)).apply {


Test patch:
diff --git a/javalin/src/test/java/io/javalin/TestCustomJettyHttpConfiguration.kt b/javalin/src/test/java/io/javalin/TestCustomJettyHttpConfiguration.kt
index de29cd3c..9ceb50dc 100644
--- a/javalin/src/test/java/io/javalin/TestCustomJettyHttpConfiguration.kt
+++ b/javalin/src/test/java/io/javalin/TestCustomJettyHttpConfiguration.kt
@@ -79,4 +79,31 @@ class TestCustomJettyHttpConfiguration {
 
     }
 
+    @Test
+    fun `responseBufferSize - default is set to the jetty default for outputBufferSize`() = TestUtil.test { app, http ->
+        val firstHttpConnectionFactory = app.jettyServer().server().connectors.firstNotNullOfOrNull { (it as? ServerConnector)?.defaultConnectionFactory as? HttpConnectionFactory }
+        assertThat(app.unsafeConfig().http.responseBufferSize)
+            .isNotNull
+            .isEqualTo(firstHttpConnectionFactory?.httpConfiguration?.outputBufferSize)
+    }
+
+    @Test
+    fun `responseBufferSize - outputBufferSize set via jetty httpConfiguration is respected`() = TestUtil.test(Javalin.create { config ->
+        config.jetty.modifyHttpConfiguration { http -> http.outputBufferSize = 42_007 }
+    }) { app, http ->
+        assertThat(app.unsafeConfig().http.responseBufferSize)
+            .isNotNull
+            .isEqualTo(42_007)
+    }
+
+    @Test
+    fun `responseBufferSize - setting the javalin option has a higher priority than the jetty option`() = TestUtil.test(Javalin.create { config ->
+        config.http.responseBufferSize = 777_777
+        config.jetty.modifyHttpConfiguration { http -> http.outputBufferSize = 555_555 }
+    }) { app, http ->
+        assertThat(app.unsafeConfig().http.responseBufferSize)
+            .isNotNull
+            .isEqualTo(777_777)
+    }
+
 }