@@ -65,7 +65,9 @@ record HandlerConfig(
6565 ExceptionHandler exceptionHandler ,
6666 Map <String , RequestHandler > extras ,
6767 boolean externalAuth ,
68- List <AfterResponseHook > afterHooks ) {}
68+ List <AfterResponseHook > afterHooks ,
69+ long maxDecompressedRequestBytes ,
70+ long minimumGzipResponseBytes ) {}
6971
7072 OpenApiServer (
7173 List <SpecBinding > bindings ,
@@ -93,9 +95,10 @@ record HandlerConfig(
9395 this .httpServer = createHttpServer (socketAddress , sslContext );
9496 httpServer .setExecutor (newThreadPerTaskExecutor (ofVirtual ().name ("http-" , 0 ).factory ()));
9597
96- ResponseRenderer renderer = new ResponseRenderer (bodyMappers );
98+ ResponseRenderer renderer =
99+ new ResponseRenderer (bodyMappers , handlerConfig .minimumGzipResponseBytes ());
97100 RequestBodyReader bodyReader =
98- new RequestBodyReader (RequestBodyReader . DEFAULT_MAX_DECOMPRESSED_BYTES );
101+ new RequestBodyReader (handlerConfig . maxDecompressedRequestBytes () );
99102 boolean anyBindingAtRoot =
100103 wireBindings (
101104 httpServer ,
@@ -278,6 +281,8 @@ public static final class Builder {
278281 private final LinkedHashMap <String , RequestHandler > extras = new LinkedHashMap <>();
279282 private final Map <String , SchemeValidator > securityValidators = new LinkedHashMap <>();
280283 private boolean externalAuth = false ;
284+ private long maxDecompressedRequestBytes = RequestBodyReader .DEFAULT_MAX_DECOMPRESSED_BYTES ;
285+ private long minimumGzipResponseBytes = ResponseRenderer .DEFAULT_MINIMUM_GZIP_BYTES ;
281286 private final List <SpecBinding > bindings = new ArrayList <>();
282287
283288 private Builder () {}
@@ -427,6 +432,42 @@ public Builder https(Path certificateChainPem, Path privateKeyPem) {
427432 * stops immediately; positive values wait up to that many seconds for in-flight exchanges to
428433 * finish.
429434 */
435+ /**
436+ * Ceiling on the inflated size of a gzip request body, 10 MiB by default. A compressed payload
437+ * can expand by orders of magnitude, so this bounds what a single request may allocate;
438+ * exceeding it fails the request with 413. Bodies that arrive uncompressed are not affected.
439+ */
440+ public Builder maxDecompressedRequestBytes (long maxDecompressedRequestBytes ) {
441+ if (maxDecompressedRequestBytes <= 0 ) {
442+ throw new IllegalArgumentException (
443+ "maxDecompressedRequestBytes must be positive, got " + maxDecompressedRequestBytes );
444+ }
445+ if (maxDecompressedRequestBytes > Integer .MAX_VALUE ) {
446+ throw new IllegalArgumentException (
447+ "maxDecompressedRequestBytes must not exceed "
448+ + Integer .MAX_VALUE
449+ + ", got "
450+ + maxDecompressedRequestBytes );
451+ }
452+ this .maxDecompressedRequestBytes = maxDecompressedRequestBytes ;
453+ return this ;
454+ }
455+
456+ /**
457+ * Smallest response body worth gzipping, 1 KiB by default. Below this, the coding costs more
458+ * than it saves. Set it to 0 to compress every compressible body, or high enough to exceed any
459+ * response this server produces to stop compressing altogether — useful when a proxy in front
460+ * already terminates compression.
461+ */
462+ public Builder minimumGzipResponseBytes (long minimumGzipResponseBytes ) {
463+ if (minimumGzipResponseBytes < 0 ) {
464+ throw new IllegalArgumentException (
465+ "minimumGzipResponseBytes must be non-negative, got " + minimumGzipResponseBytes );
466+ }
467+ this .minimumGzipResponseBytes = minimumGzipResponseBytes ;
468+ return this ;
469+ }
470+
430471 public Builder shutdownTimeoutSeconds (int shutdownTimeoutSeconds ) {
431472 if (shutdownTimeoutSeconds < 0 ) {
432473 throw new IllegalArgumentException (
@@ -468,7 +509,9 @@ public OpenApiServer build() throws IOException {
468509 effectiveExceptionHandler ,
469510 extras ,
470511 externalAuth ,
471- List .copyOf (afterHooks ));
512+ List .copyOf (afterHooks ),
513+ maxDecompressedRequestBytes ,
514+ minimumGzipResponseBytes );
472515 int resolvedPort = resolvePort ();
473516 SSLContext sslContext =
474517 httpsCertChain != null ? PemSslContext .load (httpsCertChain , httpsPrivateKey ) : null ;
0 commit comments