From 55c50b07174d7acaac1cd9f55994ac9f448fb54f Mon Sep 17 00:00:00 2001 From: "nathphon.jeamjit" Date: Tue, 24 Sep 2024 17:20:34 +0700 Subject: [PATCH 01/10] Refactor controller to use functional routing Replaces the annotation-based controller with a functional routing approach. Introduces a `RouterFunction` bean to handle HTTP requests, eliminates redundant response creation, and improves efficiency by directly parsing path variables. --- .../controller/BenchmarkController.java | 48 ++++++++++++------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/java/spring-webflux/src/main/java/framework/benchmark/controller/BenchmarkController.java b/java/spring-webflux/src/main/java/framework/benchmark/controller/BenchmarkController.java index 510b2faa16b..fdd31380900 100644 --- a/java/spring-webflux/src/main/java/framework/benchmark/controller/BenchmarkController.java +++ b/java/spring-webflux/src/main/java/framework/benchmark/controller/BenchmarkController.java @@ -1,28 +1,42 @@ -package framework.benchmark.controller; +package framework.benchmark.router; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.context.annotation.Bean; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.ServerRequest; +import org.springframework.web.reactive.function.server.ServerResponse; import reactor.core.publisher.Mono; -@RestController -public class BenchmarkController { +import static org.springframework.web.reactive.function.server.RequestPredicates.GET; +import static org.springframework.web.reactive.function.server.RequestPredicates.POST; +import static org.springframework.web.reactive.function.server.RouterFunctions.route; - private static final String EMPTY = ""; +@Component +public class BenchmarkRouter { - @GetMapping("/") - public Mono root() { - return Mono.just(EMPTY); + private static final Mono EMPTY_RESPONSE = ServerResponse.ok() + .contentType(MediaType.TEXT_PLAIN) + .body(Mono.empty(), String.class); + + @Bean + public RouterFunction routes() { + return route(GET("/"), this::root) + .andRoute(GET("/user/{id}"), this::userId) + .andRoute(POST("/user"), this::user); + } + + // Reuse empty response to avoid constant re-creation + public Mono root(ServerRequest request) { + return EMPTY_RESPONSE; } - @GetMapping("/user/{id}") - public Mono userId(@PathVariable Integer id) { - return Mono.just(id); + public Mono userId(ServerRequest request) { + // Directly parse the id from path variable without extra boxing/unboxing + return ServerResponse.ok().bodyValue(Integer.parseInt(request.pathVariable("id"))); } - @PostMapping("/user") - public Mono user() { - return Mono.just(EMPTY); + public Mono user(ServerRequest request) { + return EMPTY_RESPONSE; } } From 044f19522394653272f24afde932be1340dddadb Mon Sep 17 00:00:00 2001 From: "nathphon.jeamjit" Date: Tue, 24 Sep 2024 18:16:29 +0700 Subject: [PATCH 02/10] Refactor route definitions in BenchmarkRouter Simplified the route definitions by using lambda expressions directly in the RouterFunction. This reduces unnecessary method declarations and enhances readability. --- .../controller/BenchmarkController.java | 26 +++++-------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/java/spring-webflux/src/main/java/framework/benchmark/controller/BenchmarkController.java b/java/spring-webflux/src/main/java/framework/benchmark/controller/BenchmarkController.java index fdd31380900..a61814b9c4f 100644 --- a/java/spring-webflux/src/main/java/framework/benchmark/controller/BenchmarkController.java +++ b/java/spring-webflux/src/main/java/framework/benchmark/controller/BenchmarkController.java @@ -15,28 +15,14 @@ @Component public class BenchmarkRouter { - private static final Mono EMPTY_RESPONSE = ServerResponse.ok() - .contentType(MediaType.TEXT_PLAIN) - .body(Mono.empty(), String.class); + private static final Mono EMPTY_RESPONSE = ServerResponse.ok().build(); @Bean public RouterFunction routes() { - return route(GET("/"), this::root) - .andRoute(GET("/user/{id}"), this::userId) - .andRoute(POST("/user"), this::user); - } - - // Reuse empty response to avoid constant re-creation - public Mono root(ServerRequest request) { - return EMPTY_RESPONSE; - } - - public Mono userId(ServerRequest request) { - // Directly parse the id from path variable without extra boxing/unboxing - return ServerResponse.ok().bodyValue(Integer.parseInt(request.pathVariable("id"))); - } - - public Mono user(ServerRequest request) { - return EMPTY_RESPONSE; + return route() + .GET("/", request -> EMPTY_RESPONSE) + .GET("/user/{id}", request -> ServerResponse.ok().bodyValue(request.pathVariable("id"))) + .POST("/user", request -> EMPTY_RESPONSE) + .build(); } } From 7a6b283637287a425305b87c7f0a761dfd3c315b Mon Sep 17 00:00:00 2001 From: "nathphon.jeamjit" Date: Wed, 25 Sep 2024 02:33:20 +0700 Subject: [PATCH 03/10] Rename BenchmarkRouter class to BenchmarkController This change aligns the class name with its intended functionality. The class was previously named incorrectly, which could cause confusion. The new name clarifies its role as a controller within the Spring WebFlux framework. --- .../framework/benchmark/controller/BenchmarkController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/spring-webflux/src/main/java/framework/benchmark/controller/BenchmarkController.java b/java/spring-webflux/src/main/java/framework/benchmark/controller/BenchmarkController.java index a61814b9c4f..4d0461eb1d2 100644 --- a/java/spring-webflux/src/main/java/framework/benchmark/controller/BenchmarkController.java +++ b/java/spring-webflux/src/main/java/framework/benchmark/controller/BenchmarkController.java @@ -13,7 +13,7 @@ import static org.springframework.web.reactive.function.server.RouterFunctions.route; @Component -public class BenchmarkRouter { +public class BenchmarkController { private static final Mono EMPTY_RESPONSE = ServerResponse.ok().build(); From f7b38cba2335300cc3f281e2719b8b9dbafc1ded Mon Sep 17 00:00:00 2001 From: "nathphon.jeamjit" Date: Wed, 25 Sep 2024 02:36:07 +0700 Subject: [PATCH 04/10] Rename package from router to controller This change corrects a package naming inconsistency in the BenchmarkController class. The package was previously listed incorrectly as "router" and has been updated to "controller" to reflect its actual content and functionality. --- .../framework/benchmark/controller/BenchmarkController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/spring-webflux/src/main/java/framework/benchmark/controller/BenchmarkController.java b/java/spring-webflux/src/main/java/framework/benchmark/controller/BenchmarkController.java index 4d0461eb1d2..40dd1f1e307 100644 --- a/java/spring-webflux/src/main/java/framework/benchmark/controller/BenchmarkController.java +++ b/java/spring-webflux/src/main/java/framework/benchmark/controller/BenchmarkController.java @@ -1,4 +1,4 @@ -package framework.benchmark.router; +package framework.benchmark.controller; import org.springframework.context.annotation.Bean; import org.springframework.http.MediaType; From cb98516082f283b580253caa3fd1998280be533d Mon Sep 17 00:00:00 2001 From: "nathphon.jeamjit" Date: Tue, 1 Oct 2024 10:37:38 +0700 Subject: [PATCH 05/10] Enable caching and optimize Netty server configuration Added a 15-second cache control to key GET endpoints in `BenchmarkController`. Enhanced Netty server settings for performance, including connection and idle timeouts, header validation, buffer size, and response compression. --- .../benchmark/controller/BenchmarkController.java | 13 +++++++------ .../src/main/resources/application.properties | 13 +++++++++++++ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/java/spring-webflux/src/main/java/framework/benchmark/controller/BenchmarkController.java b/java/spring-webflux/src/main/java/framework/benchmark/controller/BenchmarkController.java index 40dd1f1e307..7914d5ee558 100644 --- a/java/spring-webflux/src/main/java/framework/benchmark/controller/BenchmarkController.java +++ b/java/spring-webflux/src/main/java/framework/benchmark/controller/BenchmarkController.java @@ -1,27 +1,28 @@ package framework.benchmark.controller; import org.springframework.context.annotation.Bean; -import org.springframework.http.MediaType; +import org.springframework.http.CacheControl; import org.springframework.stereotype.Component; import org.springframework.web.reactive.function.server.RouterFunction; -import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.reactive.function.server.ServerResponse; import reactor.core.publisher.Mono; -import static org.springframework.web.reactive.function.server.RequestPredicates.GET; -import static org.springframework.web.reactive.function.server.RequestPredicates.POST; +import java.time.Duration; + import static org.springframework.web.reactive.function.server.RouterFunctions.route; @Component public class BenchmarkController { - private static final Mono EMPTY_RESPONSE = ServerResponse.ok().build(); + private static final CacheControl MAX_AGE_15_SECONDS = CacheControl.maxAge(Duration.ofSeconds(15)); + private static final Mono EMPTY_RESPONSE = ServerResponse.ok().cacheControl(MAX_AGE_15_SECONDS).build(); + @Bean public RouterFunction routes() { return route() .GET("/", request -> EMPTY_RESPONSE) - .GET("/user/{id}", request -> ServerResponse.ok().bodyValue(request.pathVariable("id"))) + .GET("/user/{id}", request -> ServerResponse.ok().cacheControl(MAX_AGE_15_SECONDS).bodyValue(request.pathVariable("id"))) .POST("/user", request -> EMPTY_RESPONSE) .build(); } diff --git a/java/spring-webflux/src/main/resources/application.properties b/java/spring-webflux/src/main/resources/application.properties index 7a2e4d519ba..8305fed404c 100644 --- a/java/spring-webflux/src/main/resources/application.properties +++ b/java/spring-webflux/src/main/resources/application.properties @@ -2,3 +2,16 @@ server.port=3000 server.error.whitelabel.enabled=false logging.level.root=ERROR spring.threads.virtual.enabled=true +spring.main.lazy-initialization=true +spring.main.keep-alive=true +server.netty.validate-headers=false +server.netty.connection-timeout=1s +server.netty.idle-timeout=15s +server.netty.h2c-max-content-length=1MB +server.netty.initial-buffer-size=64KB +server.netty.max-initial-line-length=8KB +server.netty.max-keep-alive-requests=1000 +spring.netty.leak-detection=disabled +server.compression.min-response-size=1KB +server.compression.enabled=true +spring.reactor.netty.shutdown-quiet-period=0s From fc92cfa554b14addcc59d7f1f4d591e2197913a3 Mon Sep 17 00:00:00 2001 From: "nathphon.jeamjit" Date: Tue, 1 Oct 2024 12:25:49 +0700 Subject: [PATCH 06/10] Remove DATE and SERVER headers from responses Updated EMPTY_RESPONSE and user/{id} route to exclude DATE and SERVER headers. This change improves security by not exposing server information and standardizes response headers. --- .../benchmark/controller/BenchmarkController.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/java/spring-webflux/src/main/java/framework/benchmark/controller/BenchmarkController.java b/java/spring-webflux/src/main/java/framework/benchmark/controller/BenchmarkController.java index 7914d5ee558..12c438c4ea4 100644 --- a/java/spring-webflux/src/main/java/framework/benchmark/controller/BenchmarkController.java +++ b/java/spring-webflux/src/main/java/framework/benchmark/controller/BenchmarkController.java @@ -15,14 +15,25 @@ public class BenchmarkController { private static final CacheControl MAX_AGE_15_SECONDS = CacheControl.maxAge(Duration.ofSeconds(15)); - private static final Mono EMPTY_RESPONSE = ServerResponse.ok().cacheControl(MAX_AGE_15_SECONDS).build(); + private static final Mono EMPTY_RESPONSE = ServerResponse.ok() + .cacheControl(MAX_AGE_15_SECONDS) + .headers(h -> { + h.remove(HttpHeaders.DATE); + h.remove(HttpHeaders.SERVER); + }) + .build(); @Bean public RouterFunction routes() { return route() .GET("/", request -> EMPTY_RESPONSE) - .GET("/user/{id}", request -> ServerResponse.ok().cacheControl(MAX_AGE_15_SECONDS).bodyValue(request.pathVariable("id"))) + .GET("/user/{id}", request -> ServerResponse.ok() + .cacheControl(MAX_AGE_15_SECONDS) + .headers(h -> { + h.remove(HttpHeaders.DATE); + h.remove(HttpHeaders.SERVER); + }).bodyValue(request.pathVariable("id"))) .POST("/user", request -> EMPTY_RESPONSE) .build(); } From 6ff1db8a36ab58b75db2d7a18c0642e2c01877e4 Mon Sep 17 00:00:00 2001 From: "nathphon.jeamjit" Date: Tue, 1 Oct 2024 14:39:30 +0700 Subject: [PATCH 07/10] Simplify response headers handling for cache control Removed unnecessary DATE header manipulations and streamlined SERVER header removal in response preparation. Changed content type to TEXT_PLAIN and added shared caching logic for consistent behavior. --- .../controller/BenchmarkController.java | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/java/spring-webflux/src/main/java/framework/benchmark/controller/BenchmarkController.java b/java/spring-webflux/src/main/java/framework/benchmark/controller/BenchmarkController.java index 12c438c4ea4..a481cacbd50 100644 --- a/java/spring-webflux/src/main/java/framework/benchmark/controller/BenchmarkController.java +++ b/java/spring-webflux/src/main/java/framework/benchmark/controller/BenchmarkController.java @@ -16,12 +16,10 @@ public class BenchmarkController { private static final CacheControl MAX_AGE_15_SECONDS = CacheControl.maxAge(Duration.ofSeconds(15)); private static final Mono EMPTY_RESPONSE = ServerResponse.ok() + .contentType(MediaType.TEXT_PLAIN) .cacheControl(MAX_AGE_15_SECONDS) - .headers(h -> { - h.remove(HttpHeaders.DATE); - h.remove(HttpHeaders.SERVER); - }) - .build(); + .headers(h -> h.remove(HttpHeaders.SERVER)) + .build().share().cache(Duration.ofSeconds(15)); @Bean @@ -29,12 +27,12 @@ public RouterFunction routes() { return route() .GET("/", request -> EMPTY_RESPONSE) .GET("/user/{id}", request -> ServerResponse.ok() + .contentType(MediaType.TEXT_PLAIN) .cacheControl(MAX_AGE_15_SECONDS) - .headers(h -> { - h.remove(HttpHeaders.DATE); - h.remove(HttpHeaders.SERVER); - }).bodyValue(request.pathVariable("id"))) + .headers(h -> h.remove(HttpHeaders.SERVER)) + .bodyValue(request.pathVariable("id"))) .POST("/user", request -> EMPTY_RESPONSE) .build(); } + } From e0d9b9c269a4fa19c0180a7b423fe269b81e42c6 Mon Sep 17 00:00:00 2001 From: "nathphon.jeamjit" Date: Tue, 1 Oct 2024 14:53:43 +0700 Subject: [PATCH 08/10] Remove HttpHeaders.CONNECTION and tweak server settings Removed HttpHeaders.CONNECTION for added security in response headers. Enabled HTTP/2 and adjusted connection and idle timeouts in application properties for optimized server performance. Changed root logging level from ERROR to WARN to capture more detailed logs. --- .../benchmark/controller/BenchmarkController.java | 10 ++++++++-- .../src/main/resources/application.properties | 7 ++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/java/spring-webflux/src/main/java/framework/benchmark/controller/BenchmarkController.java b/java/spring-webflux/src/main/java/framework/benchmark/controller/BenchmarkController.java index a481cacbd50..5aca398c185 100644 --- a/java/spring-webflux/src/main/java/framework/benchmark/controller/BenchmarkController.java +++ b/java/spring-webflux/src/main/java/framework/benchmark/controller/BenchmarkController.java @@ -18,7 +18,10 @@ public class BenchmarkController { private static final Mono EMPTY_RESPONSE = ServerResponse.ok() .contentType(MediaType.TEXT_PLAIN) .cacheControl(MAX_AGE_15_SECONDS) - .headers(h -> h.remove(HttpHeaders.SERVER)) + .headers(h -> { + h.remove(HttpHeaders.SERVER); + h.remove(HttpHeaders.CONNECTION); + }) .build().share().cache(Duration.ofSeconds(15)); @@ -29,7 +32,10 @@ public RouterFunction routes() { .GET("/user/{id}", request -> ServerResponse.ok() .contentType(MediaType.TEXT_PLAIN) .cacheControl(MAX_AGE_15_SECONDS) - .headers(h -> h.remove(HttpHeaders.SERVER)) + .headers(h -> { + h.remove(HttpHeaders.SERVER); + h.remove(HttpHeaders.CONNECTION); + }) .bodyValue(request.pathVariable("id"))) .POST("/user", request -> EMPTY_RESPONSE) .build(); diff --git a/java/spring-webflux/src/main/resources/application.properties b/java/spring-webflux/src/main/resources/application.properties index 8305fed404c..2d6f3637724 100644 --- a/java/spring-webflux/src/main/resources/application.properties +++ b/java/spring-webflux/src/main/resources/application.properties @@ -1,12 +1,13 @@ server.port=3000 server.error.whitelabel.enabled=false -logging.level.root=ERROR +logging.level.root=WARN +server.http2.enabled=true spring.threads.virtual.enabled=true spring.main.lazy-initialization=true spring.main.keep-alive=true server.netty.validate-headers=false -server.netty.connection-timeout=1s -server.netty.idle-timeout=15s +server.netty.connection-timeout=5s +server.netty.idle-timeout=30s server.netty.h2c-max-content-length=1MB server.netty.initial-buffer-size=64KB server.netty.max-initial-line-length=8KB From a6acf8376bd5cda72764f7ca0741498212ca0345 Mon Sep 17 00:00:00 2001 From: "nathphon.jeamjit" Date: Wed, 2 Oct 2024 09:48:46 +0700 Subject: [PATCH 09/10] Remove cache control from BenchmarkController This commit eliminates the `CacheControl` mechanism from the `BenchmarkController`. The `maxAge` duration and related caching properties were removed for both the empty response and the user-specific response. This change might impact performance and response handling in caching-sensitive environments. --- .../framework/benchmark/controller/BenchmarkController.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/java/spring-webflux/src/main/java/framework/benchmark/controller/BenchmarkController.java b/java/spring-webflux/src/main/java/framework/benchmark/controller/BenchmarkController.java index 5aca398c185..bc61ce51800 100644 --- a/java/spring-webflux/src/main/java/framework/benchmark/controller/BenchmarkController.java +++ b/java/spring-webflux/src/main/java/framework/benchmark/controller/BenchmarkController.java @@ -14,15 +14,13 @@ @Component public class BenchmarkController { - private static final CacheControl MAX_AGE_15_SECONDS = CacheControl.maxAge(Duration.ofSeconds(15)); private static final Mono EMPTY_RESPONSE = ServerResponse.ok() .contentType(MediaType.TEXT_PLAIN) - .cacheControl(MAX_AGE_15_SECONDS) .headers(h -> { h.remove(HttpHeaders.SERVER); h.remove(HttpHeaders.CONNECTION); }) - .build().share().cache(Duration.ofSeconds(15)); + .build().share(); @Bean @@ -31,7 +29,6 @@ public RouterFunction routes() { .GET("/", request -> EMPTY_RESPONSE) .GET("/user/{id}", request -> ServerResponse.ok() .contentType(MediaType.TEXT_PLAIN) - .cacheControl(MAX_AGE_15_SECONDS) .headers(h -> { h.remove(HttpHeaders.SERVER); h.remove(HttpHeaders.CONNECTION); From dd2cebee3f57c8fba1343844beedc34a70a3f13e Mon Sep 17 00:00:00 2001 From: "nathphon.jeamjit" Date: Wed, 2 Oct 2024 09:53:19 +0700 Subject: [PATCH 10/10] Refactor imports in BenchmarkController Replaced CacheControl and Duration imports with HttpHeaders and MediaType imports. This change addresses unnecessary imports and streamlines the code dependencies. --- .../framework/benchmark/controller/BenchmarkController.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/java/spring-webflux/src/main/java/framework/benchmark/controller/BenchmarkController.java b/java/spring-webflux/src/main/java/framework/benchmark/controller/BenchmarkController.java index bc61ce51800..ae517123fd6 100644 --- a/java/spring-webflux/src/main/java/framework/benchmark/controller/BenchmarkController.java +++ b/java/spring-webflux/src/main/java/framework/benchmark/controller/BenchmarkController.java @@ -1,14 +1,13 @@ package framework.benchmark.controller; import org.springframework.context.annotation.Bean; -import org.springframework.http.CacheControl; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; import org.springframework.stereotype.Component; import org.springframework.web.reactive.function.server.RouterFunction; import org.springframework.web.reactive.function.server.ServerResponse; import reactor.core.publisher.Mono; -import java.time.Duration; - import static org.springframework.web.reactive.function.server.RouterFunctions.route; @Component