Skip to content

Commit 62d791f

Browse files
authored
Improved Undertow HTTP server contracts (#658)
* Improved Undertow HTTP server contracts like: Optimized `server` header value Fixed potential issue with buffer position Optimized UndertowHttpHeaders retrieval and caching Disabled `KEEP_ALIVE` header by default as useless bytes for each request Moved KoraVirtualThreadHttpHandler apply to method factory to unhardened its declaration in UndertowHttpServer
1 parent bd03f57 commit 62d791f

17 files changed

Lines changed: 126 additions & 81 deletions

File tree

experimental/camunda-rest-undertow/src/main/java/io/koraframework/camunda/rest/undertow/UndertowCamundaRestHttpHandler.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
import io.koraframework.common.telemetry.Observation;
99
import io.koraframework.common.telemetry.OpentelemetryContext;
1010
import io.koraframework.common.util.TimeUtils;
11-
import io.koraframework.http.server.undertow.RequestProcessingHttpHandler;
11+
import io.koraframework.http.server.undertow.handler.KoraRequestProcessingHttpHandler;
1212
import io.koraframework.http.server.undertow.UndertowContext;
13-
import io.koraframework.http.server.undertow.VirtualThreadHttpHandler;
13+
import io.koraframework.http.server.undertow.handler.KoraVirtualThreadDispatchHttpHandler;
1414
import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
1515
import io.undertow.server.HttpHandler;
1616
import io.undertow.server.handlers.PathHandler;
@@ -108,7 +108,7 @@ public void init() throws Exception {
108108

109109
var restHandler = deploymentManager.start();
110110
root.addPrefixPath(camundaRestConfig.path(), exchange -> {
111-
var rootCtx = W3CTraceContextPropagator.getInstance().extract(io.opentelemetry.context.Context.root(), exchange.getRequestHeaders(), RequestProcessingHttpHandler.HttpServerExchangeMapGetter.INSTANCE);
111+
var rootCtx = W3CTraceContextPropagator.getInstance().extract(io.opentelemetry.context.Context.root(), exchange.getRequestHeaders(), KoraRequestProcessingHttpHandler.HttpServerExchangeMapGetter.INSTANCE);
112112
ScopedValue
113113
.where(UndertowContext.VALUE, new UndertowContext(exchange))
114114
.where(io.koraframework.logging.common.MDC.VALUE, new io.koraframework.logging.common.MDC())
@@ -129,7 +129,7 @@ public void init() throws Exception {
129129
W3CTraceContextPropagator.getInstance().inject(
130130
ctx,
131131
exchange.getResponseHeaders(),
132-
RequestProcessingHttpHandler.HttpServerExchangeMapGetter.INSTANCE
132+
KoraRequestProcessingHttpHandler.HttpServerExchangeMapGetter.INSTANCE
133133
);
134134

135135
ScopedValue
@@ -149,7 +149,7 @@ public void init() throws Exception {
149149
});
150150

151151
root.addPrefixPath("/", new OpenApiHttpHandler(camundaRestConfig));
152-
this.realhttpHandler = new VirtualThreadHttpHandler("camunda-rest", root);
152+
this.realhttpHandler = new KoraVirtualThreadDispatchHttpHandler("camunda-rest", root);
153153

154154
logger.info("Camunda Rest Handler (Undertow) configured in {}", TimeUtils.tookForLogging(started));
155155
}

http/http-server-common/src/main/java/io/koraframework/http/server/common/HttpServerConfig.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
import io.koraframework.common.util.Size;
44
import io.koraframework.config.common.annotation.ConfigValueExtractor;
55
import io.koraframework.http.server.common.telemetry.HttpServerTelemetryConfig;
6+
import org.jspecify.annotations.Nullable;
67

78
import java.time.Duration;
9+
import java.util.List;
810

911
@ConfigValueExtractor
1012
public interface HttpServerConfig {
@@ -29,13 +31,21 @@ default boolean socketKeepAliveEnabled() {
2931
return false;
3032
}
3133

34+
default boolean headerKeepAliveEnabled() {
35+
return false;
36+
}
37+
38+
default boolean headerServerDateEnabled() {
39+
return true;
40+
}
41+
3242
default Duration shutdownWait() {
3343
return Duration.ofSeconds(30);
3444
}
3545

36-
HttpServerTelemetryConfig telemetry();
37-
3846
default Size maxRequestBodySize() {
3947
return Size.of(256, Size.Type.MiB);
4048
}
49+
50+
HttpServerTelemetryConfig telemetry();
4151
}

http/http-server-common/src/main/java/io/koraframework/http/server/common/HttpServerModule.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
import io.koraframework.http.server.common.response.mapper.HttpServerResponseMapperModule;
1818
import io.koraframework.http.server.common.router.HttpServerHandler;
1919
import io.koraframework.http.server.common.system.*;
20-
import io.koraframework.http.server.common.telemetry.impl.DefaultHttpServerBodyConverter;
2120
import io.koraframework.http.server.common.telemetry.HttpServerTelemetryFactory;
21+
import io.koraframework.http.server.common.telemetry.impl.DefaultHttpServerBodyConverter;
2222
import io.koraframework.http.server.common.telemetry.impl.DefaultHttpServerLoggerFactory;
2323
import io.koraframework.http.server.common.telemetry.impl.DefaultHttpServerMetricsFactory;
2424
import io.koraframework.http.server.common.telemetry.impl.DefaultHttpServerTelemetryFactory;
@@ -48,10 +48,10 @@ default HttpServerHandler publicHttpServerHandler(All<HttpServerRequestHandler>
4848

4949
@DefaultComponent
5050
default HttpServerTelemetryFactory defaultHttpServerTelemetryFactory(@Nullable MeterRegistry meterRegistry,
51-
@Nullable Tracer tracer,
52-
@Nullable DefaultHttpServerLoggerFactory loggerFactory,
53-
@Nullable DefaultHttpServerMetricsFactory metricsFactory,
54-
@Nullable DefaultHttpServerBodyConverter bodyLogger) {
51+
@Nullable Tracer tracer,
52+
@Nullable DefaultHttpServerLoggerFactory loggerFactory,
53+
@Nullable DefaultHttpServerMetricsFactory metricsFactory,
54+
@Nullable DefaultHttpServerBodyConverter bodyLogger) {
5555
return new DefaultHttpServerTelemetryFactory(meterRegistry, tracer, loggerFactory, metricsFactory, bodyLogger);
5656
}
5757

@@ -81,7 +81,7 @@ default HttpServerRequestHandler systemMetricsHandler(@SystemApi ValueOf<HttpSer
8181
}
8282

8383
@SystemApi
84-
default HttpServerHandler systemHttpServerHandler(@Tag(SystemApi.class) All<HttpServerRequestHandler> handlers, @Tag(SystemApi.class) All<HttpServerInterceptor> interceptors, HttpServerConfig config) {
84+
default HttpServerHandler systemHttpServerHandler(@SystemApi All<HttpServerRequestHandler> handlers, @SystemApi All<HttpServerInterceptor> interceptors, HttpServerConfig config) {
8585
return new HttpServerHandler(handlers, interceptors, config);
8686
}
8787
}

http/http-server-common/src/main/java/io/koraframework/http/server/common/router/HttpServerHandler.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,16 @@
22

33

44
import io.koraframework.http.common.header.HttpHeaders;
5-
import io.koraframework.http.server.common.*;
5+
import io.koraframework.http.server.common.HttpServerConfig;
66
import io.koraframework.http.server.common.interceptor.HttpServerInterceptor;
7-
import io.koraframework.http.server.common.request.HttpServerRequestHandler;
87
import io.koraframework.http.server.common.request.HttpServerRequest;
8+
import io.koraframework.http.server.common.request.HttpServerRequestHandler;
99
import io.koraframework.http.server.common.request.RouterHttpServerRequest;
1010
import io.koraframework.http.server.common.response.HttpServerResponse;
1111
import io.koraframework.http.server.common.response.HttpServerResponseException;
1212
import org.jspecify.annotations.Nullable;
1313

14-
import java.util.ArrayList;
15-
import java.util.HashMap;
16-
import java.util.List;
17-
import java.util.Map;
14+
import java.util.*;
1815
import java.util.concurrent.atomic.AtomicReference;
1916

2017
/**
@@ -68,10 +65,13 @@ public HttpServerHandler(Iterable<HttpServerRequestHandler> handlers, Iterable<H
6865
otherMethods.addAll(oldAllMethodValue.getValue());
6966
}
7067
}
68+
7169
var interceptorsList = new ArrayList<HttpServerInterceptor>();
7270
for (var interceptor : interceptors) {
7371
interceptorsList.add(interceptor);
7472
}
73+
interceptorsList.sort(Comparator.comparing(i -> i.getClass().getSimpleName()));
74+
7575
if (interceptorsList.isEmpty()) {
7676
this.requestHandler.set(new SimpleRequestHandler());
7777
} else {

http/http-server-common/src/test/java/io/koraframework/http/server/common/router/HttpServerHandlerProcessTests.java

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
package io.koraframework.http.server.common.router;
22

3-
import io.koraframework.http.server.common.telemetry.*;
4-
import io.koraframework.http.server.common.telemetry.impl.NoopHttpServerTelemetry;
5-
import org.junit.jupiter.api.Test;
6-
import org.junit.jupiter.params.ParameterizedTest;
7-
import org.junit.jupiter.params.provider.Arguments;
8-
import org.junit.jupiter.params.provider.MethodSource;
9-
import org.mockito.Mockito;
103
import io.koraframework.application.graph.All;
114
import io.koraframework.common.util.Size;
125
import io.koraframework.http.common.body.HttpBody;
136
import io.koraframework.http.common.body.HttpBodyInput;
147
import io.koraframework.http.common.header.HttpHeaders;
158
import io.koraframework.http.server.common.$HttpServerConfig_ConfigValueExtractor.HttpServerConfig_Impl;
169
import io.koraframework.http.server.common.HttpServerConfig;
17-
import io.koraframework.http.server.common.response.HttpServerResponse;
1810
import io.koraframework.http.server.common.request.HttpServerRequestHandler;
1911
import io.koraframework.http.server.common.request.HttpServerRequestHandlerImpl;
12+
import io.koraframework.http.server.common.response.HttpServerResponse;
13+
import io.koraframework.http.server.common.telemetry.*;
14+
import io.koraframework.http.server.common.telemetry.impl.NoopHttpServerTelemetry;
15+
import org.junit.jupiter.api.Test;
16+
import org.junit.jupiter.params.ParameterizedTest;
17+
import org.junit.jupiter.params.provider.Arguments;
18+
import org.junit.jupiter.params.provider.MethodSource;
19+
import org.mockito.Mockito;
2020

2121
import java.time.Duration;
2222
import java.util.List;
@@ -152,13 +152,15 @@ private HttpServerConfig config(boolean ignoreTrailingSlash) {
152152
Duration.ofSeconds(1),
153153
Duration.ofSeconds(1),
154154
false,
155+
false,
156+
true,
155157
Duration.ofMillis(100),
158+
Size.ofBytesBinary(1024),
156159
new $HttpServerTelemetryConfig_ConfigValueExtractor.HttpServerTelemetryConfig_Impl(
157160
new $HttpServerTelemetryConfig_HttpServerLoggingConfig_ConfigValueExtractor.HttpServerLoggingConfig_Defaults(),
158161
new $HttpServerTelemetryConfig_HttpServerMetricsConfig_ConfigValueExtractor.HttpServerMetricsConfig_Defaults(),
159162
new $HttpServerTelemetryConfig_HttpServerTracingConfig_ConfigValueExtractor.HttpServerTracingConfig_Defaults()
160-
),
161-
Size.ofBytesBinary(1024)
163+
)
162164
);
163165
}
164166

http/http-server-common/src/test/java/io/koraframework/http/server/common/router/HttpServerHandlerTest.java

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
package io.koraframework.http.server.common.router;
22

3-
import io.koraframework.http.server.common.telemetry.*;
4-
import org.assertj.core.api.Assertions;
5-
import org.junit.jupiter.api.Test;
6-
import org.mockito.Mockito;
73
import io.koraframework.common.util.Size;
84
import io.koraframework.http.server.common.$HttpServerConfig_ConfigValueExtractor;
95
import io.koraframework.http.server.common.HttpServerConfig;
10-
import io.koraframework.http.server.common.response.HttpServerResponse;
116
import io.koraframework.http.server.common.request.HttpServerRequestHandler;
127
import io.koraframework.http.server.common.request.HttpServerRequestHandlerImpl;
8+
import io.koraframework.http.server.common.response.HttpServerResponse;
9+
import io.koraframework.http.server.common.telemetry.*;
10+
import org.assertj.core.api.Assertions;
11+
import org.junit.jupiter.api.Test;
12+
import org.mockito.Mockito;
1313

1414
import java.time.Duration;
1515
import java.util.List;
@@ -147,13 +147,15 @@ private HttpServerConfig config(boolean ignoreTrailingSlash) {
147147
Duration.ofSeconds(1),
148148
Duration.ofSeconds(1),
149149
false,
150+
false,
151+
true,
150152
Duration.ofMillis(1),
153+
Size.ofBytesBinary(1024),
151154
new $HttpServerTelemetryConfig_ConfigValueExtractor.HttpServerTelemetryConfig_Impl(
152155
new $HttpServerTelemetryConfig_HttpServerLoggingConfig_ConfigValueExtractor.HttpServerLoggingConfig_Defaults(),
153156
new $HttpServerTelemetryConfig_HttpServerMetricsConfig_ConfigValueExtractor.HttpServerMetricsConfig_Defaults(),
154157
new $HttpServerTelemetryConfig_HttpServerTracingConfig_ConfigValueExtractor.HttpServerTracingConfig_Defaults()
155-
),
156-
Size.ofBytesBinary(1024)
158+
)
157159
);
158160
}
159161

http/http-server-common/src/testFixtures/java/io/koraframework/http/server/common/HttpServerTestKit.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -971,13 +971,15 @@ protected void startServer(boolean ignoreTrailingSlash, List<HttpServerIntercept
971971
Duration.ofSeconds(1),
972972
Duration.ofSeconds(1),
973973
false,
974+
false,
975+
true,
974976
Duration.ofMillis(1),
977+
Size.of(1, Size.Type.GiB),
975978
new $HttpServerTelemetryConfig_ConfigValueExtractor.HttpServerTelemetryConfig_Impl(
976979
new $HttpServerTelemetryConfig_HttpServerLoggingConfig_ConfigValueExtractor.HttpServerLoggingConfig_Defaults(),
977980
new $HttpServerTelemetryConfig_HttpServerMetricsConfig_ConfigValueExtractor.HttpServerMetricsConfig_Defaults(),
978981
new $HttpServerTelemetryConfig_HttpServerTracingConfig_ConfigValueExtractor.HttpServerTracingConfig_Defaults()
979-
),
980-
Size.of(1, Size.Type.GiB)
982+
)
981983
);
982984
var publicApiHandler = new HttpServerHandler(List.of(handlers), interceptors, config);
983985
this.httpServer = this.httpServer(valueOf(config), publicApiHandler, this.telemetry);

http/http-server-undertow/src/main/java/io/koraframework/http/server/undertow/UndertowContext.java

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,7 @@
33
import io.undertow.server.HttpServerExchange;
44
import org.jspecify.annotations.Nullable;
55

6-
public final class UndertowContext {
7-
8-
public final HttpServerExchange exchange;
9-
10-
public UndertowContext(HttpServerExchange exchange) {
11-
this.exchange = exchange;
12-
}
6+
public record UndertowContext(HttpServerExchange exchange) {
137

148
public static final ScopedValue<UndertowContext> VALUE = ScopedValue.newInstance();
159

http/http-server-undertow/src/main/java/io/koraframework/http/server/undertow/UndertowHttpServer.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ public UndertowHttpServer(String name,
4747
this.name = name;
4848
this.xnioWorker = xnioWorker;
4949
this.configurer = configurer;
50-
var handler = (HttpHandler) new VirtualThreadHttpHandler(name, (exchange) -> httpHandler.get().handleRequest(exchange));
50+
51+
var handler = httpHandler.get();
5152
if (handlerConfigurer != null) {
5253
handler = handlerConfigurer.configure(handler);
5354
}
@@ -108,6 +109,8 @@ private Undertow createServer() {
108109
.setServerOption(Options.READ_TIMEOUT, ((int) config.socketReadTimeout().toMillis()))
109110
.setServerOption(Options.WRITE_TIMEOUT, ((int) config.socketWriteTimeout().toMillis()))
110111
.setServerOption(Options.KEEP_ALIVE, config.socketKeepAliveEnabled())
112+
.setServerOption(UndertowOptions.ALWAYS_SET_KEEP_ALIVE, config.headerKeepAliveEnabled())
113+
.setServerOption(UndertowOptions.ALWAYS_SET_DATE, config.headerServerDateEnabled())
111114
.setServerOption(UndertowOptions.MAX_ENTITY_SIZE, config.maxRequestBodySize().toBytes());
112115

113116
if (this.configurer != null) {

http/http-server-undertow/src/main/java/io/koraframework/http/server/undertow/UndertowPublicHttpServerModule.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import io.koraframework.http.server.common.HttpServerConfig;
1111
import io.koraframework.http.server.common.router.HttpServerHandler;
1212
import io.koraframework.http.server.common.telemetry.HttpServerTelemetryFactory;
13+
import io.koraframework.http.server.undertow.handler.KoraRequestProcessingHttpHandler;
14+
import io.koraframework.http.server.undertow.handler.KoraVirtualThreadDispatchHttpHandler;
1315
import io.undertow.Undertow;
1416
import io.undertow.server.HttpHandler;
1517
import org.jspecify.annotations.Nullable;
@@ -31,7 +33,9 @@ default HttpHandler undertowPublicHttpHandler(HttpServerConfig config,
3133
HttpServerHandler publicApiHandler,
3234
HttpServerTelemetryFactory telemetryFactory) {
3335
var telemetry = telemetryFactory.get(config.telemetry());
34-
return new RequestProcessingHttpHandler(telemetry, publicApiHandler);
36+
var handler = (HttpHandler) new KoraRequestProcessingHttpHandler(telemetry, publicApiHandler);
37+
handler = new KoraVirtualThreadDispatchHttpHandler("kora-undertow", handler);
38+
return handler;
3539
}
3640

3741
default UndertowConfig undertowHttpServerConfig(Config config, ConfigValueExtractor<UndertowConfig> extractor) {

0 commit comments

Comments
 (0)