From 3749b9ba3a201b858b5a4c99750a11663245e15e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Prante?= Date: Fri, 7 Apr 2023 22:35:24 +0200 Subject: [PATCH] refactoring code for easier API, update to Netty 4.1.92, begin of file upload work --- gradle.properties | 2 +- .../client/netty/secure/HttpsRequest.java | 5 +- .../netty/secure/HttpsRequestBuilder.java | 3 +- .../netty/HttpChunkContentCompressor.java | 3 +- .../net/http/client/netty/HttpRequest.java | 21 +- .../http/client/netty/HttpRequestBuilder.java | 68 +++--- .../client/netty/NettyHttpClientConfig.java | 29 ++- .../netty/http1/Http1ChannelInitializer.java | 18 +- .../http/client/netty/http1/Http1Handler.java | 4 +- .../client/netty/http1/Http1Interaction.java | 81 +++++--- .../http2/Http2ChildChannelInitializer.java | 16 +- .../net/http/client/HttpRequestBuilder.java | 2 + .../java/org/xbib/net/http/client/Part.java | 49 +++++ .../server/application/web/Bootstrap.java | 85 ++++---- .../web/WebApplicationBuilder.java | 38 +++- .../http1/Https1ChannelInitializer.java | 19 +- .../netty/secure/http1/Https1Handler.java | 2 +- .../http2/Https2ChannelInitializer.java | 21 +- .../netty/secure/http2/Https2Handler.java | 4 +- ...NettyHttps2ServerMultiRequestLoadTest.java | 64 +++--- .../secure/test/NettyHttps2ServerTest.java | 64 +++--- .../NettyHttpsServerMultiRequestLoadTest.java | 135 ++++++------ .../secure/test/NettyHttpsServerTest.java | 196 ++++++++++-------- .../net/http/server/netty/HttpRequest.java | 16 +- .../http/server/netty/HttpRequestBuilder.java | 56 +++-- .../http/server/netty/NettyHttpServer.java | 65 +++++- .../server/netty/NettyHttpServerConfig.java | 72 ++++++- .../netty/http1/Http1ChannelInitializer.java | 17 +- .../http/server/netty/http1/Http1Handler.java | 14 +- .../netty/http1/HttpFileUploadHandler.java | 120 +++++++++++ .../netty/http2/Http2ChannelInitializer.java | 15 +- .../http/server/netty/http2/Http2Handler.java | 2 +- .../NettyHttp2ServerMultiRequestLoadTest.java | 50 +++-- .../http/netty/test/NettyHttp2ServerTest.java | 46 ++-- .../netty/test/NettyHttpServerBodyTest.java | 100 +++++++++ .../test/NettyHttpServerFailureTest.java | 50 +++-- .../test/NettyHttpServerFileUploadTest.java | 110 ++++++++++ .../NettyHttpServerMultiRequestLoadTest.java | 50 +++-- .../http/netty/test/NettyHttpServerTest.java | 55 +++-- .../net/http/server/nio/NioHttpServer.java | 55 ++++- .../net/http/nio/test/NioHttpServerTest.java | 88 ++++---- .../simple/secure/SimpleHttpsServerTest.java | 66 +++--- .../http/server/simple/SimpleHttpServer.java | 62 +++++- .../simple/test/SimpleHttpServerTest.java | 89 ++++---- .../src/main/java/module-info.java | 1 + .../xbib/net/http/server/BaseHttpRequest.java | 6 + .../http/server/BaseHttpRequestBuilder.java | 13 ++ .../org/xbib/net/http/server/HttpRequest.java | 4 + .../net/http/server/HttpRequestBuilder.java | 2 + .../org/xbib/net/http/server/HttpServer.java | 14 +- .../java/org/xbib/net/http/server/Part.java | 55 +++++ .../http/server/application/Application.java | 27 +-- .../application/ApplicationBuilder.java | 19 +- .../application/ApplicationCallable.java | 7 - .../server/application/BaseApplication.java | 128 +++++------- .../application/BaseApplicationBuilder.java | 166 ++++----------- .../http/server/executor/BaseExecutor.java | 41 ++++ .../server/executor/BaseExecutorBuilder.java | 80 +++++++ .../BaseThreadPoolExecutor.java} | 26 +-- .../server/executor/CallableReleasable.java | 7 + .../net/http/server/executor/Executor.java | 13 ++ .../http/server/executor/ExecutorBuilder.java | 21 ++ .../Task.java} | 6 +- .../net/http/server/route/BaseHttpRouter.java | 22 +- .../server/route/BaseHttpRouterBuilder.java | 30 +++ .../http/server/route/HttpRouterBuilder.java | 3 + settings.gradle | 2 +- 67 files changed, 1903 insertions(+), 917 deletions(-) create mode 100644 net-http-client/src/main/java/org/xbib/net/http/client/Part.java create mode 100644 net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/http1/HttpFileUploadHandler.java create mode 100644 net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttpServerBodyTest.java create mode 100644 net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttpServerFileUploadTest.java create mode 100644 net-http-server/src/main/java/org/xbib/net/http/server/Part.java delete mode 100644 net-http-server/src/main/java/org/xbib/net/http/server/application/ApplicationCallable.java create mode 100644 net-http-server/src/main/java/org/xbib/net/http/server/executor/BaseExecutor.java create mode 100644 net-http-server/src/main/java/org/xbib/net/http/server/executor/BaseExecutorBuilder.java rename net-http-server/src/main/java/org/xbib/net/http/server/{application/ApplicationThreadPoolExecutor.java => executor/BaseThreadPoolExecutor.java} (63%) create mode 100644 net-http-server/src/main/java/org/xbib/net/http/server/executor/CallableReleasable.java create mode 100644 net-http-server/src/main/java/org/xbib/net/http/server/executor/Executor.java create mode 100644 net-http-server/src/main/java/org/xbib/net/http/server/executor/ExecutorBuilder.java rename net-http-server/src/main/java/org/xbib/net/http/server/{application/ApplicationTask.java => executor/Task.java} (62%) diff --git a/gradle.properties b/gradle.properties index 8064cf2..97d5d74 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ group = org.xbib name = net-http -version = 3.2.0 +version = 3.3.0 org.gradle.warning.mode = ALL diff --git a/net-http-client-netty-secure/src/main/java/org/xbib/net/http/client/netty/secure/HttpsRequest.java b/net-http-client-netty-secure/src/main/java/org/xbib/net/http/client/netty/secure/HttpsRequest.java index b09d26e..63fc0bd 100644 --- a/net-http-client-netty-secure/src/main/java/org/xbib/net/http/client/netty/secure/HttpsRequest.java +++ b/net-http-client-netty-secure/src/main/java/org/xbib/net/http/client/netty/secure/HttpsRequest.java @@ -1,15 +1,14 @@ package org.xbib.net.http.client.netty.secure; import javax.net.ssl.SSLSession; -import org.xbib.net.http.HttpHeaders; import org.xbib.net.http.client.netty.HttpRequest; public class HttpsRequest extends HttpRequest { private final HttpsRequestBuilder builder; - protected HttpsRequest(HttpsRequestBuilder builder, HttpHeaders headers) { - super(builder, headers); + protected HttpsRequest(HttpsRequestBuilder builder) { + super(builder); this.builder = builder; } diff --git a/net-http-client-netty-secure/src/main/java/org/xbib/net/http/client/netty/secure/HttpsRequestBuilder.java b/net-http-client-netty-secure/src/main/java/org/xbib/net/http/client/netty/secure/HttpsRequestBuilder.java index 66376b0..9c02eb7 100644 --- a/net-http-client-netty-secure/src/main/java/org/xbib/net/http/client/netty/secure/HttpsRequestBuilder.java +++ b/net-http-client-netty-secure/src/main/java/org/xbib/net/http/client/netty/secure/HttpsRequestBuilder.java @@ -16,6 +16,7 @@ public class HttpsRequestBuilder extends HttpRequestBuilder { } public HttpsRequest build() { - return new HttpsRequest(this, validateHeaders()); + this.headers = validateHeaders(headers); + return new HttpsRequest(this); } } diff --git a/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/HttpChunkContentCompressor.java b/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/HttpChunkContentCompressor.java index 71a0fc1..bad8b42 100644 --- a/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/HttpChunkContentCompressor.java +++ b/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/HttpChunkContentCompressor.java @@ -16,8 +16,7 @@ public class HttpChunkContentCompressor extends HttpContentCompressor { @Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { - if (msg instanceof ByteBuf) { - ByteBuf byteBuf = (ByteBuf) msg; + if (msg instanceof ByteBuf byteBuf) { if (byteBuf.isReadable()) { msg = new DefaultHttpContent(byteBuf); } diff --git a/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/HttpRequest.java b/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/HttpRequest.java index 3b79cda..9223197 100644 --- a/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/HttpRequest.java +++ b/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/HttpRequest.java @@ -2,7 +2,6 @@ package org.xbib.net.http.client.netty; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.PooledByteBufAllocator; -import io.netty.handler.codec.http.multipart.InterfaceHttpData; import java.io.Closeable; import java.io.IOException; import java.net.InetSocketAddress; @@ -23,6 +22,7 @@ import org.xbib.net.http.HttpVersion; import org.xbib.net.http.client.BackOff; import org.xbib.net.http.client.ExceptionListener; import org.xbib.net.http.client.HttpResponse; +import org.xbib.net.http.client.Part; import org.xbib.net.http.client.ResponseListener; import org.xbib.net.http.client.TimeoutListener; import org.xbib.net.http.cookie.Cookie; @@ -34,15 +34,12 @@ public class HttpRequest implements org.xbib.net.http.client.HttpRequest, Closea private final HttpRequestBuilder builder; - private final HttpHeaders headers; - private CompletableFuture completableFuture; private int redirectCount; - protected HttpRequest(HttpRequestBuilder builder, HttpHeaders headers) { + protected HttpRequest(HttpRequestBuilder builder) { this.builder = builder; - this.headers = headers; } @Override @@ -62,7 +59,7 @@ public class HttpRequest implements org.xbib.net.http.client.HttpRequest, Closea @Override public HttpHeaders getHeaders() { - return headers; + return builder.headers; } @Override @@ -95,11 +92,11 @@ public class HttpRequest implements org.xbib.net.http.client.HttpRequest, Closea @Override public CharBuffer getBodyAsChars(Charset charset) { - return charset.decode(getBody()); + return charset.decode(builder.body); } public CharBuffer getBodyAsChars(Charset charset, int offset, int size) { - ByteBuffer slicedBuffer = (getBody().duplicate().position(offset)).slice(); + ByteBuffer slicedBuffer = (builder.body.duplicate().position(offset)).slice(); slicedBuffer.limit(size); return charset.decode(slicedBuffer); } @@ -110,8 +107,8 @@ public class HttpRequest implements org.xbib.net.http.client.HttpRequest, Closea return (R) this; } - public List getBodyData() { - return builder.bodyData; + public List getParts() { + return builder.parts; } public boolean isFollowRedirect() { @@ -151,7 +148,7 @@ public class HttpRequest implements org.xbib.net.http.client.HttpRequest, Closea return "HttpNettyRequest[url=" + builder.url + ",version=" + builder.httpVersion + ",method=" + builder.httpMethod + - ",headers=" + headers.entries() + + ",headers=" + builder.headers.entries() + ",content=" + (builder.body != null && builder.body.remaining() >= 16 ? getBodyAsChars(StandardCharsets.UTF_8, 0, 16) + "..." : builder.body != null ? getBodyAsChars(StandardCharsets.UTF_8) : "") + @@ -254,7 +251,7 @@ public class HttpRequest implements org.xbib.net.http.client.HttpRequest, Closea return builder(PooledByteBufAllocator.DEFAULT, httpMethod) .setVersion(httpRequest.builder.httpVersion) .setURL(httpRequest.builder.url) - .setHeaders(httpRequest.headers) + .setHeaders(httpRequest.builder.headers) .content(httpRequest.builder.body) .setResponseListener(httpRequest.builder.responseListener) .setTimeoutListener(httpRequest.builder.timeoutListener, httpRequest.builder.timeoutMillis) diff --git a/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/HttpRequestBuilder.java b/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/HttpRequestBuilder.java index 6c07e53..7e55936 100644 --- a/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/HttpRequestBuilder.java +++ b/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/HttpRequestBuilder.java @@ -2,7 +2,6 @@ package org.xbib.net.http.client.netty; import io.netty.buffer.ByteBufAllocator; import io.netty.handler.codec.http.HttpUtil; -import io.netty.handler.codec.http.multipart.InterfaceHttpData; import io.netty.handler.codec.http2.HttpConversionUtil; import java.io.IOException; import java.io.UncheckedIOException; @@ -33,6 +32,7 @@ import org.xbib.net.http.HttpVersion; import org.xbib.net.http.client.BackOff; import org.xbib.net.http.client.ExceptionListener; import org.xbib.net.http.client.HttpResponse; +import org.xbib.net.http.client.Part; import org.xbib.net.http.client.ResponseListener; import org.xbib.net.http.client.TimeoutListener; import org.xbib.net.http.cookie.Cookie; @@ -43,53 +43,53 @@ public class HttpRequestBuilder implements org.xbib.net.http.client.HttpRequestB private static final String DEFAULT_FORM_CONTENT_TYPE = "application/x-www-form-urlencoded; charset=utf-8"; - final ByteBufAllocator allocator; + protected final ByteBufAllocator allocator; - HttpAddress httpAddress; + protected HttpAddress httpAddress; - URL url; + protected URL url; - String requestPath; + protected String requestPath; - final Collection cookies; + protected final Collection cookies; - HttpMethod httpMethod; + protected HttpMethod httpMethod; - HttpHeaders headers; + protected HttpHeaders headers; - HttpVersion httpVersion; + protected HttpVersion httpVersion; - final List removeHeaders; + protected final List removeHeaders; - String userAgent; + protected String userAgent; - boolean keepalive; + protected boolean keepalive; - boolean gzip; + protected boolean gzip; - String contentType; + protected String contentType; - ParameterBuilder parameterBuilder; + protected ParameterBuilder parameterBuilder; - ByteBuffer body; + protected ByteBuffer body; - final List bodyData; + protected boolean followRedirect; - boolean followRedirect; + protected int maxRedirects; - int maxRedirects; + protected boolean enableBackOff; - boolean enableBackOff; + protected BackOff backOff; - BackOff backOff; + protected ResponseListener responseListener; - ResponseListener responseListener; + protected ExceptionListener exceptionListener; - ExceptionListener exceptionListener; + protected TimeoutListener timeoutListener; - TimeoutListener timeoutListener; + protected long timeoutMillis; - long timeoutMillis; + protected final List parts; protected HttpRequestBuilder() { this(ByteBufAllocator.DEFAULT); @@ -108,10 +108,10 @@ public class HttpRequestBuilder implements org.xbib.net.http.client.HttpRequestB this.headers = new HttpHeaders(); this.removeHeaders = new ArrayList<>(); this.cookies = new HashSet<>(); - this.bodyData = new ArrayList<>(); this.contentType = DEFAULT_FORM_CONTENT_TYPE; this.parameterBuilder = Parameter.builder(); this.timeoutMillis = 0L; + this.parts = new ArrayList<>(); } @Override @@ -245,14 +245,9 @@ public class HttpRequestBuilder implements org.xbib.net.http.client.HttpRequestB return this; } - /** - * For multipart MIME body data. - * - * @param data a mime body - * @return this - */ - public HttpRequestBuilder addBodyData(InterfaceHttpData data) { - bodyData.add(data); + @Override + public HttpRequestBuilder addPart(Part part) { + parts.add(part); return this; } @@ -382,10 +377,11 @@ public class HttpRequestBuilder implements org.xbib.net.http.client.HttpRequestB } public HttpRequest build() { - return new HttpRequest(this, validateHeaders()); + this.headers = validateHeaders(headers); + return new HttpRequest(this); } - protected HttpHeaders validateHeaders() { + protected HttpHeaders validateHeaders(HttpHeaders httpHeaders) { Parameter parameter = parameterBuilder.build(); HttpHeaders validatedHeaders = HttpHeaders.of(headers); if (url != null) { diff --git a/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/NettyHttpClientConfig.java b/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/NettyHttpClientConfig.java index 04c9765..e6dcdfe 100644 --- a/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/NettyHttpClientConfig.java +++ b/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/NettyHttpClientConfig.java @@ -26,7 +26,7 @@ public class NettyHttpClientConfig { */ private LogLevel debugLogLevel = LogLevel.DEBUG; - SocketConfig socketConfig = new SocketConfig(); + protected SocketConfig socketConfig = new SocketConfig(); private String transportProviderName = null; @@ -67,7 +67,7 @@ public class NettyHttpClientConfig { /** * Default for gzip codec is false */ - private boolean gzipEnabled = false; + private boolean isGzipEnabled = false; private ByteBufAllocator byteBufAllocator; @@ -95,6 +95,10 @@ public class NettyHttpClientConfig { private BackOff backOff = BackOff.ZERO_BACKOFF; + private Boolean isChunkWriteEnabled = true; + + private Boolean isObjectAggregationEnabled = true; + public NettyHttpClientConfig() { this.byteBufAllocator = ByteBufAllocator.DEFAULT; } @@ -208,12 +212,12 @@ public class NettyHttpClientConfig { } public NettyHttpClientConfig setGzipEnabled(boolean gzipEnabled) { - this.gzipEnabled = gzipEnabled; + this.isGzipEnabled = gzipEnabled; return this; } public boolean isGzipEnabled() { - return gzipEnabled; + return isGzipEnabled; } public NettyHttpClientConfig setHttp2Settings(Http2Settings http2Settings) { @@ -330,4 +334,21 @@ public class NettyHttpClientConfig { return backOff; } + public NettyHttpClientConfig setChunkWriteEnabled(boolean isChunkWriteEnabled) { + this.isChunkWriteEnabled = isChunkWriteEnabled; + return this; + } + + public Boolean isChunkWriteEnabled() { + return isChunkWriteEnabled; + } + + public NettyHttpClientConfig setObjectAggregationEnabled(boolean isObjectAggregationEnabled) { + this.isObjectAggregationEnabled = isObjectAggregationEnabled; + return this; + } + + public Boolean isObjectAggregationEnabled() { + return isObjectAggregationEnabled; + } } diff --git a/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/http1/Http1ChannelInitializer.java b/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/http1/Http1ChannelInitializer.java index 303ad13..2fff338 100644 --- a/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/http1/Http1ChannelInitializer.java +++ b/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/http1/Http1ChannelInitializer.java @@ -85,17 +85,21 @@ public class Http1ChannelInitializer implements HttpChannelInitializer { Interaction interaction) throws IOException { NettyHttpClientConfig nettyHttpClientConfig = nettyHttpClient.getClientConfig(); ChannelPipeline pipeline = channel.pipeline(); - pipeline.addLast("http-client-chunk-writer", - new ChunkedWriteHandler()); - pipeline.addLast("http-client-codec", new HttpClientCodec(nettyHttpClientConfig.getMaxInitialLineLength(), + pipeline.addLast("http-client-codec", new HttpClientCodec(nettyHttpClientConfig.getMaxInitialLineLength(), nettyHttpClientConfig.getMaxHeadersSize(), nettyHttpClientConfig.getMaxChunkSize())); if (nettyHttpClientConfig.isGzipEnabled()) { pipeline.addLast("http-client-decompressor", new HttpContentDecompressor()); } - HttpObjectAggregator httpObjectAggregator = - new HttpObjectAggregator(nettyHttpClientConfig.getMaxContentLength(), false); - httpObjectAggregator.setMaxCumulationBufferComponents(nettyHttpClientConfig.getMaxCompositeBufferComponents()); - pipeline.addLast("http-client-aggregator", httpObjectAggregator); + if (nettyHttpClientConfig.isObjectAggregationEnabled()) { + HttpObjectAggregator httpObjectAggregator = + new HttpObjectAggregator(nettyHttpClientConfig.getMaxContentLength(), false); + httpObjectAggregator.setMaxCumulationBufferComponents(nettyHttpClientConfig.getMaxCompositeBufferComponents()); + pipeline.addLast("http-client-aggregator", httpObjectAggregator); + } + if (nettyHttpClientConfig.isChunkWriteEnabled()) { + //pipeline.addLast("http-client-chunk-content-compressor", new HttpChunkContentCompressor()); + pipeline.addLast("http-client-chunked-writer", new ChunkedWriteHandler()); + } pipeline.addLast("http-client-response", new Http1Handler(interaction)); interaction.settingsReceived(null); } diff --git a/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/http1/Http1Handler.java b/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/http1/Http1Handler.java index 1d083c8..0a67191 100644 --- a/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/http1/Http1Handler.java +++ b/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/http1/Http1Handler.java @@ -21,8 +21,7 @@ public class Http1Handler extends ChannelDuplexHandler { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { - if (msg instanceof FullHttpResponse) { - FullHttpResponse httpResponse = (FullHttpResponse) msg; + if (msg instanceof FullHttpResponse httpResponse) { try { interaction.responseReceived(ctx.channel(), null, httpResponse); } finally { @@ -35,6 +34,7 @@ public class Http1Handler extends ChannelDuplexHandler { @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { + logger.log(Level.FINEST, "event " + evt.getClass().getName()); ctx.fireUserEventTriggered(evt); } diff --git a/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/http1/Http1Interaction.java b/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/http1/Http1Interaction.java index 4ed2e2c..60fdf54 100644 --- a/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/http1/Http1Interaction.java +++ b/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/http1/Http1Interaction.java @@ -1,19 +1,26 @@ package org.xbib.net.http.client.netty.http1; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.handler.codec.http.DefaultFullHttpRequest; import io.netty.handler.codec.http.FullHttpResponse; +import io.netty.handler.codec.http.HttpChunkedInput; import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory; +import io.netty.handler.codec.http.multipart.FileUpload; import io.netty.handler.codec.http.multipart.HttpDataFactory; import io.netty.handler.codec.http.multipart.HttpPostRequestEncoder; import io.netty.handler.codec.http2.Http2Headers; import io.netty.handler.codec.http2.Http2Settings; import java.io.IOException; import java.net.ConnectException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.concurrent.CompletableFuture; @@ -26,6 +33,7 @@ import org.xbib.net.URLSyntaxException; import org.xbib.net.http.HttpAddress; import org.xbib.net.http.HttpHeaders; import org.xbib.net.http.HttpResponseStatus; +import org.xbib.net.http.client.Part; import org.xbib.net.http.cookie.Cookie; import org.xbib.net.http.client.cookie.CookieDecoder; import org.xbib.net.http.client.cookie.CookieEncoder; @@ -42,11 +50,8 @@ public class Http1Interaction extends BaseInteraction { private static final Logger logger = Logger.getLogger(Http1Interaction.class.getName()); - private final HttpDataFactory httpDataFactory; - public Http1Interaction(NettyHttpClient nettyHttpClient, HttpAddress httpAddress) { super(nettyHttpClient, httpAddress); - this.httpDataFactory = new DefaultHttpDataFactory(); } @Override @@ -72,6 +77,10 @@ public class Http1Interaction extends BaseInteraction { } public Interaction executeRequest(HttpRequest request, Channel channel) throws IOException { + if (!channel.isWritable()) { + logger.log(Level.WARNING, "sorry, channel not writable"); + return this; + } final String channelId = channel.id().toString(); streamIds.putIfAbsent(channelId, new StreamIds()); // Some HTTP 1 servers do not understand URIs in HTTP command line in spite of RFC 7230. @@ -84,7 +93,6 @@ public class Http1Interaction extends BaseInteraction { DefaultFullHttpRequest fullHttpRequest = request.getBody() == null ? new DefaultFullHttpRequest(httpVersion, httpMethod, uri) : new DefaultFullHttpRequest(httpVersion, httpMethod, uri, Unpooled.wrappedBuffer(request.getBody())); - HttpPostRequestEncoder httpPostRequestEncoder = null; final Integer streamId = streamIds.get(channelId).nextStreamId(); if (streamId == null) { throw new IllegalStateException("stream id is null"); @@ -96,27 +104,52 @@ public class Http1Interaction extends BaseInteraction { if (!cookies.isEmpty()) { request.getHeaders().set(HttpHeaderNames.COOKIE, CookieEncoder.STRICT.encode(cookies)); } + // headers request.getHeaders().entries().forEach(p -> fullHttpRequest.headers().add(p.getKey(), p.getValue())); - if (request.getBody() == null && !request.getBodyData().isEmpty()) { - try { - httpPostRequestEncoder = new HttpPostRequestEncoder(httpDataFactory, fullHttpRequest, true); - httpPostRequestEncoder.setBodyHttpDatas(request.getBodyData()); - httpPostRequestEncoder.finalizeRequest(); - } catch (HttpPostRequestEncoder.ErrorDataEncoderException e) { - throw new IOException(e); + // file upload + HttpDataFactory httpDataFactory = new DefaultHttpDataFactory(); + HttpPostRequestEncoder httpPostRequestEncoder = null; + try { + if (!request.getParts().isEmpty()) { + httpPostRequestEncoder = new HttpPostRequestEncoder(httpDataFactory, + fullHttpRequest, + true, + StandardCharsets.UTF_8, + HttpPostRequestEncoder.EncoderMode.RFC1738); + for (Part part : request.getParts()) { + Path path = part.getPath(); + if (Files.exists(path)) { + FileUpload fileUpload = httpDataFactory.createFileUpload(fullHttpRequest, part.getName(), + path.toFile().getName(), part.getContentType(), part.getContentTransferEncoding(), + part.getCharset(), Files.size(path)); + fileUpload.setContent(path.toFile()); + logger.log(Level.FINEST, "HTTP FORM file upload = " + fileUpload); + httpPostRequestEncoder.addBodyHttpData(fileUpload); + } else { + logger.log(Level.WARNING, " does not exist : " + path); + } + } + io.netty.handler.codec.http.HttpRequest httpRequest = httpPostRequestEncoder.finalizeRequest(); + channel.write(httpRequest); + } else { + channel.write(fullHttpRequest); } - } - if (!channel.isWritable()) { - logger.log(Level.WARNING, "channel not writable"); - return this; - } - channel.write(fullHttpRequest); - if (httpPostRequestEncoder != null && httpPostRequestEncoder.isChunked()) { - channel.write(httpPostRequestEncoder); - } - channel.flush(); - if (httpPostRequestEncoder != null) { - httpPostRequestEncoder.cleanFiles(); + if (httpPostRequestEncoder != null && httpPostRequestEncoder.isChunked()) { + logger.log(Level.FINEST, "finish chunked HTTP POST encoder"); + channel.write(httpPostRequestEncoder); + } else { + logger.log(Level.FINEST, "HTTP POST encoder not chunked"); + } + channel.flush(); + } catch (HttpPostRequestEncoder.ErrorDataEncoderException e) { + throw new IOException(e); + } finally { + if (httpPostRequestEncoder != null) { + logger.log(Level.FINEST, "cleaning files of HTTP POST encoder"); + //httpPostRequestEncoder.cleanFiles(); + } + logger.log(Level.FINEST, "clean all http data"); + //httpDataFactory.cleanAllHttpData(); } return this; } @@ -241,7 +274,7 @@ public class Http1Interaction extends BaseInteraction { @Override public void close() throws IOException { - httpDataFactory.cleanAllHttpData(); + //httpDataFactory.cleanAllHttpData(); super.close(); } diff --git a/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/http2/Http2ChildChannelInitializer.java b/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/http2/Http2ChildChannelInitializer.java index 76eed9f..cf10c01 100644 --- a/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/http2/Http2ChildChannelInitializer.java +++ b/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/http2/Http2ChildChannelInitializer.java @@ -6,19 +6,20 @@ import io.netty.channel.ChannelPipeline; import io.netty.handler.codec.http.HttpContentDecompressor; import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http2.Http2StreamFrameToHttpObjectCodec; +import io.netty.handler.stream.ChunkedWriteHandler; import org.xbib.net.http.client.netty.Interaction; import org.xbib.net.http.client.netty.NettyHttpClientConfig; public class Http2ChildChannelInitializer extends ChannelInitializer { - private final NettyHttpClientConfig clientConfig; + private final NettyHttpClientConfig nettyHttpClientConfig; private final Interaction interaction; protected final Channel parentChannel; - public Http2ChildChannelInitializer(NettyHttpClientConfig clientConfig, Interaction interaction, Channel parentChannel) { - this.clientConfig = clientConfig; + public Http2ChildChannelInitializer(NettyHttpClientConfig nettyHttpClientConfig, Interaction interaction, Channel parentChannel) { + this.nettyHttpClientConfig = nettyHttpClientConfig; this.interaction = interaction; this.parentChannel = parentChannel; } @@ -30,8 +31,13 @@ public class Http2ChildChannelInitializer extends ChannelInitializer { new Http2StreamFrameToHttpObjectCodec(false)); p.addLast("child-client-decompressor", new HttpContentDecompressor()); - p.addLast("child-client-chunk-aggregator", - new HttpObjectAggregator(clientConfig.getMaxContentLength())); + if (nettyHttpClientConfig.isChunkWriteEnabled()) { + p.addLast("child-chunk-write", new ChunkedWriteHandler()); + } + if (nettyHttpClientConfig.isObjectAggregationEnabled()) { + p.addLast("child-client-object-aggregator", + new HttpObjectAggregator(nettyHttpClientConfig.getMaxContentLength())); + } p.addLast("child-client-response-handler", new Http2Handler(interaction)); } diff --git a/net-http-client/src/main/java/org/xbib/net/http/client/HttpRequestBuilder.java b/net-http-client/src/main/java/org/xbib/net/http/client/HttpRequestBuilder.java index 7ba3d17..427b684 100644 --- a/net-http-client/src/main/java/org/xbib/net/http/client/HttpRequestBuilder.java +++ b/net-http-client/src/main/java/org/xbib/net/http/client/HttpRequestBuilder.java @@ -19,5 +19,7 @@ public interface HttpRequestBuilder { HttpRequestBuilder setBody(ByteBuffer byteBuffer); + HttpRequestBuilder addPart(Part part); + HttpRequest build() throws UnmappableCharacterException, MalformedInputException; } diff --git a/net-http-client/src/main/java/org/xbib/net/http/client/Part.java b/net-http-client/src/main/java/org/xbib/net/http/client/Part.java new file mode 100644 index 0000000..8baa277 --- /dev/null +++ b/net-http-client/src/main/java/org/xbib/net/http/client/Part.java @@ -0,0 +1,49 @@ +package org.xbib.net.http.client; + +import java.nio.charset.Charset; +import java.nio.file.Path; + +public class Part { + + private final String contentType; + + private final String contentTransferEncoding; + + private final String name; + + private final Path path; + + private final Charset charset; + + public Part(String contentType, + String contentTransferEncoding, + String name, + Path path, + Charset charset) { + this.contentType = contentType; + this.contentTransferEncoding = contentTransferEncoding; + this.name = name; + this.path = path; + this.charset = charset; + } + + public String getContentType() { + return contentType; + } + + public String getContentTransferEncoding() { + return contentTransferEncoding; + } + + public String getName() { + return name; + } + + public Path getPath() { + return path; + } + + public Charset getCharset() { + return charset; + } +} diff --git a/net-http-server-application-web/src/main/java/org/xbib/net/http/server/application/web/Bootstrap.java b/net-http-server-application-web/src/main/java/org/xbib/net/http/server/application/web/Bootstrap.java index 1b247e5..94757ce 100644 --- a/net-http-server-application-web/src/main/java/org/xbib/net/http/server/application/web/Bootstrap.java +++ b/net-http-server-application-web/src/main/java/org/xbib/net/http/server/application/web/Bootstrap.java @@ -17,6 +17,9 @@ import org.xbib.net.http.HttpVersion; import org.xbib.net.http.server.domain.BaseHttpDomain; import org.xbib.net.http.server.domain.BaseHttpSecurityDomain; import org.xbib.net.http.server.domain.HttpSecurityDomain; +import org.xbib.net.http.server.executor.BaseExecutor; +import org.xbib.net.http.server.executor.Executor; +import org.xbib.net.http.server.route.HttpRouter; import org.xbib.net.http.server.service.HttpService; import org.xbib.net.http.server.auth.BasicAuthenticationHandler; import org.xbib.net.http.server.auth.FormAuthenticationHandler; @@ -120,6 +123,7 @@ public final class Bootstrap { BasicAuthenticationHandler basicAuthenticationHandler = new BasicAuthenticationHandler(ldapRealm); + FormAuthenticationHandler formAuthenticationHandler = new FormAuthenticationHandler("j_username", "j_password", "j_remember", "demo/auth/form/index.gtpl", ldapRealm); @@ -148,48 +152,57 @@ public final class Bootstrap { }) .build(); - try (NettyHttpServer server = NettyHttpServer.builder() - .setHttpServerConfig(serverConfig) - .setApplication(WebApplication.builder() - .setSettings(settings) - .setSecret("1088e6b7ad58d64d09961e1357bf95544447051c6ad1332cd626e3a33bb5786b") - .setRouter(BaseHttpRouter.builder() - .setHandler(400, new GroovyHttpStatusHandler(HttpResponseStatus.BAD_REQUEST, "Bad request", "400.gtpl")) - .setHandler(401, new GroovyHttpStatusHandler(HttpResponseStatus.UNAUTHORIZED, "Unauthorized", "401.gtpl")) - .setHandler(403, new GroovyHttpStatusHandler(HttpResponseStatus.FORBIDDEN, "Forbidden", "403.gtpl")) - .setHandler(404, new GroovyHttpStatusHandler(HttpResponseStatus.NOT_FOUND, "Not found", "404.gtpl")) - .setHandler(500, new GroovyInternalServerErrorHandler("500.gtpl")) - .addDomain(BaseHttpDomain.builder() - .setHttpAddress(httpsAddress) - .addService(BaseHttpService.builder() - .setPath("/favicon.ico") - .setHandler(ctx -> { - ctx.response() - .setResponseStatus(HttpResponseStatus.OK) - .setHeader(HttpHeaderNames.CONTENT_TYPE, "image/x-icon") - .write(NettyDataBufferFactory.getInstance().wrap(Hex.fromHex(hexFavIcon))) - .build(); - ctx.done(); - }) - .build()) - .addService(BaseHttpService.builder() - .setPath("/webjars/**") - .setHandler(new ClassLoaderResourceHandler(Bootstrap.class.getClassLoader(), "META-INF/resources/")) - .build()) - .addService(httpService) - .addService(GroovyTemplateService.builder() - .setTemplateName("index.gtpl") - .setSecurityDomain(securityDomain) - .setPath("glob:**") - .setHandler(new GroovyTemplateResourceHandler()) - .build()) - .build()) + HttpRouter httpRouter = BaseHttpRouter.builder() + .setHandler(400, new GroovyHttpStatusHandler(HttpResponseStatus.BAD_REQUEST, "Bad request", "400.gtpl")) + .setHandler(401, new GroovyHttpStatusHandler(HttpResponseStatus.UNAUTHORIZED, "Unauthorized", "401.gtpl")) + .setHandler(403, new GroovyHttpStatusHandler(HttpResponseStatus.FORBIDDEN, "Forbidden", "403.gtpl")) + .setHandler(404, new GroovyHttpStatusHandler(HttpResponseStatus.NOT_FOUND, "Not found", "404.gtpl")) + .setHandler(500, new GroovyInternalServerErrorHandler("500.gtpl")) + .addDomain(BaseHttpDomain.builder() + .setHttpAddress(httpsAddress) + .addService(BaseHttpService.builder() + .setPath("/favicon.ico") + .setHandler(ctx -> { + ctx.response() + .setResponseStatus(HttpResponseStatus.OK) + .setHeader(HttpHeaderNames.CONTENT_TYPE, "image/x-icon") + .write(NettyDataBufferFactory.getInstance().wrap(Hex.fromHex(hexFavIcon))) + .build(); + ctx.done(); + }) + .build()) + .addService(BaseHttpService.builder() + .setPath("/webjars/**") + .setHandler(new ClassLoaderResourceHandler(Bootstrap.class.getClassLoader(), "META-INF/resources/")) + .build()) + .addService(httpService) + .addService(GroovyTemplateService.builder() + .setTemplateName("index.gtpl") + .setSecurityDomain(securityDomain) + .setPath("glob:**") + .setHandler(new GroovyTemplateResourceHandler()) .build()) .build()) + .build(); + + Executor executor = BaseExecutor.builder() + .build(); + + WebApplication application = WebApplication.builder() + .setSettings(settings) + .setSecret("1088e6b7ad58d64d09961e1357bf95544447051c6ad1332cd626e3a33bb5786b") + .setExecutor(executor) + .setRouter(httpRouter) + .build(); + + try (NettyHttpServer server = NettyHttpServer.builder() + .setHttpServerConfig(serverConfig) + .setApplication(application) .build()) { server.bind(); server.loop(); } + return 0; } diff --git a/net-http-server-application-web/src/main/java/org/xbib/net/http/server/application/web/WebApplicationBuilder.java b/net-http-server-application-web/src/main/java/org/xbib/net/http/server/application/web/WebApplicationBuilder.java index 3d48460..9934628 100644 --- a/net-http-server-application-web/src/main/java/org/xbib/net/http/server/application/web/WebApplicationBuilder.java +++ b/net-http-server-application-web/src/main/java/org/xbib/net/http/server/application/web/WebApplicationBuilder.java @@ -2,6 +2,8 @@ package org.xbib.net.http.server.application.web; import org.xbib.net.http.server.application.BaseApplicationBuilder; +import org.xbib.net.http.server.executor.Executor; +import org.xbib.net.http.server.route.HttpRouter; import org.xbib.settings.Settings; public class WebApplicationBuilder extends BaseApplicationBuilder { @@ -12,14 +14,8 @@ public class WebApplicationBuilder extends BaseApplicationBuilder { protected WebApplicationBuilder() { super(); - this.profile = System.getProperty("application.profile"); this.name = System.getProperty("application.name"); - } - - @Override - public WebApplicationBuilder setSettings(Settings settings) { - super.setSettings(settings); - return this; + this.profile = System.getProperty("application.profile"); } public WebApplicationBuilder setProfile(String profile) { @@ -32,10 +28,32 @@ public class WebApplicationBuilder extends BaseApplicationBuilder { return this; } + @Override + public WebApplicationBuilder setSettings(Settings settings) { + this.settings = settings; + return this; + } + + @Override + public WebApplicationBuilder setSecret(String secret) { + super.setSecret(secret); + return this; + } + + @Override + public WebApplicationBuilder setExecutor(Executor executor) { + super.setExecutor(executor); + return this; + } + + @Override + public WebApplicationBuilder setRouter(HttpRouter router) { + super.setRouter(router); + return this; + } + @Override public WebApplication build() { - WebApplication webApplication = new WebApplication(this); - setupApplication(webApplication); - return webApplication; + return new WebApplication(this); } } diff --git a/net-http-server-netty-secure/src/main/java/org/xbib/net/http/server/netty/secure/http1/Https1ChannelInitializer.java b/net-http-server-netty-secure/src/main/java/org/xbib/net/http/server/netty/secure/http1/Https1ChannelInitializer.java index 148a3da..37732d7 100644 --- a/net-http-server-netty-secure/src/main/java/org/xbib/net/http/server/netty/secure/http1/Https1ChannelInitializer.java +++ b/net-http-server-netty-secure/src/main/java/org/xbib/net/http/server/netty/secure/http1/Https1ChannelInitializer.java @@ -25,6 +25,7 @@ import org.xbib.net.http.server.netty.HttpChannelInitializer; import org.xbib.net.http.server.netty.NettyCustomizer; import org.xbib.net.http.server.netty.NettyHttpServer; import org.xbib.net.http.server.netty.NettyHttpServerConfig; +import org.xbib.net.http.server.netty.http1.HttpFileUploadHandler; import org.xbib.net.http.server.netty.http1.HttpPipeliningHandler; import org.xbib.net.http.server.netty.IdleTimeoutHandler; import org.xbib.net.http.server.netty.TrafficLoggingHandler; @@ -50,13 +51,12 @@ public class Https1ChannelInitializer implements HttpChannelInitializer { final NettyHttpsServerConfig nettyHttpsServerConfig = (NettyHttpsServerConfig) nettyHttpServer.getNettyHttpServerConfig(); final ServerNameIndicationHandler serverNameIndicationHandler = new ServerNameIndicationHandler(nettyHttpsServerConfig, httpAddress, - nettyHttpsServerConfig.getDomainNameMapping(nettyHttpServer.getApplication().getDomains())); + nettyHttpsServerConfig.getDomainNameMapping(nettyHttpServer.getDomains())); channel.attr(NettyHttpsServerConfig.ATTRIBUTE_KEY_SNI_HANDLER).set(serverNameIndicationHandler); ChannelPipeline pipeline = channel.pipeline(); pipeline.addLast("server-sni", serverNameIndicationHandler); HttpServerCodec httpServerCodec = new HttpServerCodec(nettyHttpsServerConfig.getMaxInitialLineLength(), nettyHttpsServerConfig.getMaxHeadersSize(), nettyHttpsServerConfig.getMaxChunkSize()); - pipeline.addLast("server-chunked-write", new ChunkedWriteHandler()); pipeline.addLast("server-codec", httpServerCodec); if (nettyHttpsServerConfig.isCompressionEnabled()) { pipeline.addLast("server-compressor", new HttpContentCompressor()); @@ -64,9 +64,18 @@ public class Https1ChannelInitializer implements HttpChannelInitializer { if (nettyHttpsServerConfig.isDecompressionEnabled()) { pipeline.addLast("server-decompressor", new HttpContentDecompressor()); } - HttpObjectAggregator httpObjectAggregator = new HttpObjectAggregator(nettyHttpsServerConfig.getMaxContentLength()); - httpObjectAggregator.setMaxCumulationBufferComponents(nettyHttpsServerConfig.getMaxCompositeBufferComponents()); - pipeline.addLast("server-aggregator", httpObjectAggregator); + if (nettyHttpsServerConfig.isObjectAggregationEnabled()) { + HttpObjectAggregator httpObjectAggregator = new HttpObjectAggregator(nettyHttpsServerConfig.getMaxContentLength()); + httpObjectAggregator.setMaxCumulationBufferComponents(nettyHttpsServerConfig.getMaxCompositeBufferComponents()); + pipeline.addLast("server-aggregator", httpObjectAggregator); + } + if (nettyHttpsServerConfig.isFileUploadEnabled()) { + HttpFileUploadHandler httpFileUploadHandler = new HttpFileUploadHandler(nettyHttpServer); + pipeline.addLast("server-file-upload", httpFileUploadHandler); + } + if (nettyHttpsServerConfig.isChunkedWriteEnabled()) { + pipeline.addLast("server-chunked-write", new ChunkedWriteHandler()); + } if (nettyHttpsServerConfig.isPipeliningEnabled()) { pipeline.addLast("server-pipelining", new HttpPipeliningHandler(nettyHttpsServerConfig.getPipeliningCapacity())); } diff --git a/net-http-server-netty-secure/src/main/java/org/xbib/net/http/server/netty/secure/http1/Https1Handler.java b/net-http-server-netty-secure/src/main/java/org/xbib/net/http/server/netty/secure/http1/Https1Handler.java index d2e49b5..600fd6e 100644 --- a/net-http-server-netty-secure/src/main/java/org/xbib/net/http/server/netty/secure/http1/Https1Handler.java +++ b/net-http-server-netty-secure/src/main/java/org/xbib/net/http/server/netty/secure/http1/Https1Handler.java @@ -89,7 +89,7 @@ public class Https1Handler extends ChannelDuplexHandler { serverRequestBuilder.setSNIHost(serverNameIndicationHandler.hostname()); serverRequestBuilder.setSSLSession(serverNameIndicationHandler.getSslHandler().engine().getSession()); } - nettyHttpServer.getApplication().dispatch(serverRequestBuilder, serverResponseBuilder); + nettyHttpServer.dispatch(serverRequestBuilder, serverResponseBuilder); } catch (Exception e) { logger.log(Level.SEVERE, "bad request: " + e.getMessage(), e); DefaultFullHttpResponse fullHttpResponse = new DefaultFullHttpResponse(io.netty.handler.codec.http.HttpVersion.valueOf(httpAddress.getVersion().text()), diff --git a/net-http-server-netty-secure/src/main/java/org/xbib/net/http/server/netty/secure/http2/Https2ChannelInitializer.java b/net-http-server-netty-secure/src/main/java/org/xbib/net/http/server/netty/secure/http2/Https2ChannelInitializer.java index 75c71e9..e1618be 100644 --- a/net-http-server-netty-secure/src/main/java/org/xbib/net/http/server/netty/secure/http2/Https2ChannelInitializer.java +++ b/net-http-server-netty-secure/src/main/java/org/xbib/net/http/server/netty/secure/http2/Https2ChannelInitializer.java @@ -16,6 +16,7 @@ import io.netty.handler.codec.http2.Http2MultiplexHandler; import io.netty.handler.codec.http2.Http2ServerUpgradeCodec; import io.netty.handler.codec.http2.Http2Settings; import io.netty.handler.logging.LogLevel; +import io.netty.handler.stream.ChunkedWriteHandler; import io.netty.util.AsciiString; import java.util.logging.Level; import java.util.logging.Logger; @@ -26,6 +27,7 @@ import org.xbib.net.http.server.netty.NettyCustomizer; import org.xbib.net.http.server.netty.NettyHttpServer; import org.xbib.net.http.server.netty.NettyHttpServerConfig; import org.xbib.net.http.server.netty.TrafficLoggingHandler; +import org.xbib.net.http.server.netty.http1.HttpFileUploadHandler; import org.xbib.net.http.server.netty.secure.NettyHttpsServerConfig; import org.xbib.net.http.server.netty.secure.ServerNameIndicationHandler; @@ -47,7 +49,7 @@ public class Https2ChannelInitializer implements HttpChannelInitializer { final NettyHttpsServerConfig nettyHttpsServerConfig = (NettyHttpsServerConfig) nettyHttpServer.getNettyHttpServerConfig(); final ServerNameIndicationHandler serverNameIndicationHandler = new ServerNameIndicationHandler(nettyHttpsServerConfig, httpAddress, - nettyHttpsServerConfig.getDomainNameMapping(nettyHttpServer.getApplication().getDomains())); + nettyHttpsServerConfig.getDomainNameMapping(nettyHttpServer.getDomains())); channel.attr(NettyHttpsServerConfig.ATTRIBUTE_KEY_SNI_HANDLER).set(serverNameIndicationHandler); ChannelPipeline pipeline = channel.pipeline(); pipeline.addLast("server-sni", serverNameIndicationHandler); @@ -55,8 +57,16 @@ public class Https2ChannelInitializer implements HttpChannelInitializer { pipeline.addLast("server-logger", new TrafficLoggingHandler(LogLevel.DEBUG)); } pipeline.addLast("server-upgrade", createUpgradeHandler(nettyHttpServer, httpAddress, serverNameIndicationHandler)); - // handler for HTTP1 - pipeline.addLast("server-object-aggregator", new HttpObjectAggregator(nettyHttpsServerConfig.getMaxContentLength())); + if (nettyHttpsServerConfig.isObjectAggregationEnabled()) { + pipeline.addLast("server-object-aggregator", new HttpObjectAggregator(nettyHttpsServerConfig.getMaxContentLength())); + } + if (nettyHttpsServerConfig.isFileUploadEnabled()) { + HttpFileUploadHandler httpFileUploadHandler = new HttpFileUploadHandler(nettyHttpServer); + pipeline.addLast("server-file-upload", httpFileUploadHandler); + } + if (nettyHttpsServerConfig.isChunkedWriteEnabled()) { + pipeline.addLast("server-chunked-write", new ChunkedWriteHandler()); + } pipeline.addLast("server-requests", new Https2Handler(nettyHttpServer)); pipeline.addLast("server-messages", new Https2Messages()); pipeline.addLast("server-idle-timeout", new IdleTimeoutHandler(nettyHttpsServerConfig.getTimeoutMillis())); @@ -77,6 +87,7 @@ public class Https2ChannelInitializer implements HttpChannelInitializer { NettyHttpServerConfig nettyHttpServerConfig = nettyHttpServer.getNettyHttpServerConfig(); Https2ChildChannelInitializer childHandler = new Https2ChildChannelInitializer(nettyHttpServer, httpAddress, serverNameIndicationHandler); + // TODO replace Http2MultiplexCodecBuilder Http2MultiplexCodecBuilder multiplexCodecBuilder = Http2MultiplexCodecBuilder.forServer(childHandler) .initialSettings(Http2Settings.defaultSettings()); if (nettyHttpServerConfig.isDebug()) { @@ -94,6 +105,10 @@ public class Https2ChannelInitializer implements HttpChannelInitializer { return new CleartextHttp2ServerUpgradeHandler(serverCodec, upgradeHandler, multiplexCodec); } + /** + * A new upgrade handler. + * Sadly, this does not work. + */ protected CleartextHttp2ServerUpgradeHandler createNewUpgradeHandler(NettyHttpServer nettyHttpServer, HttpAddress httpAddress, ServerNameIndicationHandler serverNameIndicationHandler) { diff --git a/net-http-server-netty-secure/src/main/java/org/xbib/net/http/server/netty/secure/http2/Https2Handler.java b/net-http-server-netty-secure/src/main/java/org/xbib/net/http/server/netty/secure/http2/Https2Handler.java index ae97c96..380d059 100644 --- a/net-http-server-netty-secure/src/main/java/org/xbib/net/http/server/netty/secure/http2/Https2Handler.java +++ b/net-http-server-netty-secure/src/main/java/org/xbib/net/http/server/netty/secure/http2/Https2Handler.java @@ -55,7 +55,7 @@ public class Https2Handler extends ChannelDuplexHandler { .setRemoteAddress((InetSocketAddress) ctx.channel().remoteAddress()) .setStreamId(streamId); if ("PRI".equals(fullHttpRequest.method().name())) { - nettyHttpServer.getApplication().dispatch(httpsRequestBuilder, httpsResponseBuilder, HttpResponseStatus.HTTP_VERSION_NOT_SUPPORTED); + nettyHttpServer.dispatch(httpsRequestBuilder, httpsResponseBuilder, HttpResponseStatus.HTTP_VERSION_NOT_SUPPORTED); return; } httpsResponseBuilder.shouldClose("close".equalsIgnoreCase(fullHttpRequest.headers().get(HttpHeaderNames.CONNECTION))); @@ -65,7 +65,7 @@ public class Https2Handler extends ChannelDuplexHandler { httpsRequestBuilder.setSNIHost(serverNameIndicationHandler.hostname()); httpsRequestBuilder.setSSLSession(serverNameIndicationHandler.getSslHandler().engine().getSession()); } - nettyHttpServer.getApplication().dispatch(httpsRequestBuilder, httpsResponseBuilder); + nettyHttpServer.dispatch(httpsRequestBuilder, httpsResponseBuilder); } catch (Exception e) { logger.log(Level.SEVERE, "bad request: " + e.getMessage(), e); DefaultFullHttpResponse fullHttpResponse = new DefaultFullHttpResponse(io.netty.handler.codec.http.HttpVersion.valueOf(httpAddress.getVersion().text()), diff --git a/net-http-server-netty-secure/src/test/java/org/xbib/net/http/server/netty/secure/test/NettyHttps2ServerMultiRequestLoadTest.java b/net-http-server-netty-secure/src/test/java/org/xbib/net/http/server/netty/secure/test/NettyHttps2ServerMultiRequestLoadTest.java index 91daf11..c1ced72 100644 --- a/net-http-server-netty-secure/src/test/java/org/xbib/net/http/server/netty/secure/test/NettyHttps2ServerMultiRequestLoadTest.java +++ b/net-http-server-netty-secure/src/test/java/org/xbib/net/http/server/netty/secure/test/NettyHttps2ServerMultiRequestLoadTest.java @@ -19,6 +19,9 @@ import org.xbib.net.http.client.netty.NettyHttpClientConfig; import org.xbib.net.http.client.netty.secure.NettyHttpsClientConfig; import org.xbib.net.http.server.application.BaseApplication; import org.xbib.net.http.server.domain.BaseHttpDomain; +import org.xbib.net.http.server.executor.BaseExecutor; +import org.xbib.net.http.server.executor.Executor; +import org.xbib.net.http.server.route.HttpRouter; import org.xbib.net.http.server.service.BaseHttpService; import org.xbib.net.http.server.netty.NettyHttpServer; import org.xbib.net.http.server.netty.secure.HttpsAddress; @@ -46,36 +49,43 @@ public class NettyHttps2ServerMultiRequestLoadTest { NettyHttpsServerConfig serverConfig = new NettyHttpsServerConfig(); serverConfig.setServerName("NettySecureHttpServer", Bootstrap.class.getPackage().getImplementationVersion()); serverConfig.setNetworkClass(NetworkClass.LOOPBACK); + + HttpRouter router = BaseHttpRouter.builder() + .addDomain(BaseHttpDomain.builder() + .setHttpAddress(httpsAddress) + .addService(BaseHttpService.builder() + .setPath("/favicon.ico") + .setHandler(ctx -> ctx.response() + .setResponseStatus(HttpResponseStatus.NOT_FOUND) + .build() + .flush()) + .build()) + .addService(BaseHttpService.builder() + .setPath("/secure") + .setHandler(ctx -> { ctx.response() + .setResponseStatus(HttpResponseStatus.OK) + .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) + .setCharset(StandardCharsets.UTF_8); + ctx.write("secure domain " + + " SNI host " + ctx.httpRequest().as(HttpsRequest.class).getSNIHost() + " " + + " SSL peer host " + ctx.httpRequest().as(HttpsRequest.class).getSSLSession() + " " + + " base URL = " + ctx.request().getBaseURL() + " " + + ctx.httpRequest().getParameter() + " " + + ctx.httpRequest().getLocalAddress() + " " + + ctx.httpRequest().getRemoteAddress()); + }) + .build()) + .build()) + .build(); + + Executor executor = BaseExecutor.builder() + .build(); + try (NettyHttpServer server = NettyHttpServer.builder() .setHttpServerConfig(serverConfig) .setApplication(BaseApplication.builder() - .setRouter(BaseHttpRouter.builder() - .addDomain(BaseHttpDomain.builder() - .setHttpAddress(httpsAddress) - .addService(BaseHttpService.builder() - .setPath("/favicon.ico") - .setHandler(ctx -> ctx.response() - .setResponseStatus(HttpResponseStatus.NOT_FOUND) - .build() - .flush()) - .build()) - .addService(BaseHttpService.builder() - .setPath("/secure") - .setHandler(ctx -> { ctx.response() - .setResponseStatus(HttpResponseStatus.OK) - .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) - .setCharset(StandardCharsets.UTF_8); - ctx.write("secure domain " + - " SNI host " + ctx.httpRequest().as(HttpsRequest.class).getSNIHost() + " " + - " SSL peer host " + ctx.httpRequest().as(HttpsRequest.class).getSSLSession() + " " + - " base URL = " + ctx.request().getBaseURL() + " " + - ctx.httpRequest().getParameter() + " " + - ctx.httpRequest().getLocalAddress() + " " + - ctx.httpRequest().getRemoteAddress()); - }) - .build()) - .build()) - .build()) + .setExecutor(executor) + .setRouter(router) .build()) .build()) { server.bind(); diff --git a/net-http-server-netty-secure/src/test/java/org/xbib/net/http/server/netty/secure/test/NettyHttps2ServerTest.java b/net-http-server-netty-secure/src/test/java/org/xbib/net/http/server/netty/secure/test/NettyHttps2ServerTest.java index c8f52b0..32188ee 100644 --- a/net-http-server-netty-secure/src/test/java/org/xbib/net/http/server/netty/secure/test/NettyHttps2ServerTest.java +++ b/net-http-server-netty-secure/src/test/java/org/xbib/net/http/server/netty/secure/test/NettyHttps2ServerTest.java @@ -19,6 +19,9 @@ import org.xbib.net.http.client.netty.NettyHttpClientConfig; import org.xbib.net.http.client.netty.secure.NettyHttpsClientConfig; import org.xbib.net.http.server.application.BaseApplication; import org.xbib.net.http.server.domain.BaseHttpDomain; +import org.xbib.net.http.server.executor.BaseExecutor; +import org.xbib.net.http.server.executor.Executor; +import org.xbib.net.http.server.route.HttpRouter; import org.xbib.net.http.server.service.BaseHttpService; import org.xbib.net.http.server.netty.NettyHttpServer; import org.xbib.net.http.server.netty.secure.HttpsAddress; @@ -47,36 +50,43 @@ public class NettyHttps2ServerTest { serverConfig.setServerName("NettySecureHttpServer", Bootstrap.class.getPackage().getImplementationVersion()); serverConfig.setNetworkClass(NetworkClass.LOOPBACK); serverConfig.setDebug(true); + + HttpRouter router = BaseHttpRouter.builder() + .addDomain(BaseHttpDomain.builder() + .setHttpAddress(httpsAddress) + .addService(BaseHttpService.builder() + .setPath("/favicon.ico") + .setHandler(ctx -> ctx.response() + .setResponseStatus(HttpResponseStatus.NOT_FOUND) + .build() + .flush()) + .build()) + .addService(BaseHttpService.builder() + .setPath("/secure") + .setHandler(ctx -> { ctx.response() + .setResponseStatus(HttpResponseStatus.OK) + .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) + .setCharset(StandardCharsets.UTF_8); + ctx.write("secure domain " + + " SNI host " + ctx.httpRequest().as(HttpsRequest.class).getSNIHost() + " " + + " SSL peer host " + ctx.httpRequest().as(HttpsRequest.class).getSSLSession() + " " + + " base URL = " + ctx.request().getBaseURL() + " " + + ctx.httpRequest().getParameter() + " " + + ctx.httpRequest().getLocalAddress() + " " + + ctx.httpRequest().getRemoteAddress()); + }) + .build()) + .build()) + .build(); + + Executor executor = BaseExecutor.builder() + .build(); + try (NettyHttpServer server = NettyHttpServer.builder() .setHttpServerConfig(serverConfig) .setApplication(BaseApplication.builder() - .setRouter(BaseHttpRouter.builder() - .addDomain(BaseHttpDomain.builder() - .setHttpAddress(httpsAddress) - .addService(BaseHttpService.builder() - .setPath("/favicon.ico") - .setHandler(ctx -> ctx.response() - .setResponseStatus(HttpResponseStatus.NOT_FOUND) - .build() - .flush()) - .build()) - .addService(BaseHttpService.builder() - .setPath("/secure") - .setHandler(ctx -> { ctx.response() - .setResponseStatus(HttpResponseStatus.OK) - .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) - .setCharset(StandardCharsets.UTF_8); - ctx.write("secure domain " + - " SNI host " + ctx.httpRequest().as(HttpsRequest.class).getSNIHost() + " " + - " SSL peer host " + ctx.httpRequest().as(HttpsRequest.class).getSSLSession() + " " + - " base URL = " + ctx.request().getBaseURL() + " " + - ctx.httpRequest().getParameter() + " " + - ctx.httpRequest().getLocalAddress() + " " + - ctx.httpRequest().getRemoteAddress()); - }) - .build()) - .build()) - .build()) + .setExecutor(executor) + .setRouter(router) .build()) .build()) { server.bind(); diff --git a/net-http-server-netty-secure/src/test/java/org/xbib/net/http/server/netty/secure/test/NettyHttpsServerMultiRequestLoadTest.java b/net-http-server-netty-secure/src/test/java/org/xbib/net/http/server/netty/secure/test/NettyHttpsServerMultiRequestLoadTest.java index 8dded73..c05c13e 100644 --- a/net-http-server-netty-secure/src/test/java/org/xbib/net/http/server/netty/secure/test/NettyHttpsServerMultiRequestLoadTest.java +++ b/net-http-server-netty-secure/src/test/java/org/xbib/net/http/server/netty/secure/test/NettyHttpsServerMultiRequestLoadTest.java @@ -15,6 +15,9 @@ import org.xbib.net.http.client.netty.NettyHttpClientConfig; import org.xbib.net.http.client.netty.secure.NettyHttpsClientConfig; import org.xbib.net.http.server.application.BaseApplication; import org.xbib.net.http.server.domain.BaseHttpDomain; +import org.xbib.net.http.server.executor.BaseExecutor; +import org.xbib.net.http.server.executor.Executor; +import org.xbib.net.http.server.route.HttpRouter; import org.xbib.net.http.server.service.BaseHttpService; import org.xbib.net.http.server.netty.NettyHttpServer; import org.xbib.net.http.server.netty.secure.HttpsAddress; @@ -48,39 +51,46 @@ public class NettyHttpsServerMultiRequestLoadTest { NettyHttpsServerConfig serverConfig = new NettyHttpsServerConfig(); serverConfig.setServerName("NettySecureHttpServer", Bootstrap.class.getPackage().getImplementationVersion()); serverConfig.setNetworkClass(NetworkClass.LOOPBACK); + + HttpRouter router = BaseHttpRouter.builder() + .addDomain(BaseHttpDomain.builder() + .setHttpAddress(httpsAddress) + .addService(BaseHttpService.builder() + .setPath("/favicon.ico") + .setHandler(ctx -> ctx.response() + .setResponseStatus(HttpResponseStatus.NOT_FOUND) + .build() + .flush()) + .build()) + .addService(BaseHttpService.builder() + .setPath("/secure") + .setHandler(ctx -> { + logger.log(Level.INFO, "executing /secure"); + ctx.response() + .setResponseStatus(HttpResponseStatus.OK) + .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) + .setCharset(StandardCharsets.UTF_8); + ctx.write("secure domain: " + + " SNI host = " + ctx.httpRequest().as(HttpsRequest.class).getSNIHost() + + " SSL peer host = " + ctx.httpRequest().as(HttpsRequest.class).getSSLSession() + + " base URL = " + ctx.httpRequest().getBaseURL() + + " parameter = " + ctx.httpRequest().getParameter().allToString() + + " attributes = " + ctx.getAttributes() + + " local address = " + ctx.httpRequest().getLocalAddress() + + " remote address = " + ctx.httpRequest().getRemoteAddress()); + }) + .build()) + .build()) + .build(); + + Executor executor = BaseExecutor.builder() + .build(); + try (NettyHttpServer server = NettyHttpServer.builder() .setHttpServerConfig(serverConfig) .setApplication(BaseApplication.builder() - .setRouter(BaseHttpRouter.builder() - .addDomain(BaseHttpDomain.builder() - .setHttpAddress(httpsAddress) - .addService(BaseHttpService.builder() - .setPath("/favicon.ico") - .setHandler(ctx -> ctx.response() - .setResponseStatus(HttpResponseStatus.NOT_FOUND) - .build() - .flush()) - .build()) - .addService(BaseHttpService.builder() - .setPath("/secure") - .setHandler(ctx -> { - logger.log(Level.INFO, "executing /secure"); - ctx.response() - .setResponseStatus(HttpResponseStatus.OK) - .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) - .setCharset(StandardCharsets.UTF_8); - ctx.write("secure domain: " + - " SNI host = " + ctx.httpRequest().as(HttpsRequest.class).getSNIHost() + - " SSL peer host = " + ctx.httpRequest().as(HttpsRequest.class).getSSLSession() + - " base URL = " + ctx.httpRequest().getBaseURL() + - " parameter = " + ctx.httpRequest().getParameter().allToString() + - " attributes = " + ctx.getAttributes() + - " local address = " + ctx.httpRequest().getLocalAddress() + - " remote address = " + ctx.httpRequest().getRemoteAddress()); - }) - .build()) - .build()) - .build()) + .setExecutor(executor) + .setRouter(router) .build()) .build()) { server.bind(); @@ -133,38 +143,45 @@ public class NettyHttpsServerMultiRequestLoadTest { NettyHttpsServerConfig serverConfig = new NettyHttpsServerConfig(); serverConfig.setServerName("NettySecureHttpServer", Bootstrap.class.getPackage().getImplementationVersion()); serverConfig.setNetworkClass(NetworkClass.LOOPBACK); + + HttpRouter router = BaseHttpRouter.builder() + .addDomain(BaseHttpDomain.builder() + .setHttpAddress(httpsAddress) + .addService(BaseHttpService.builder() + .setPath("/favicon.ico") + .setHandler(ctx -> ctx.response() + .setResponseStatus(HttpResponseStatus.NOT_FOUND) + .build() + .flush()) + .build()) + .addService(BaseHttpService.builder() + .setPath("/secure") + .setHandler(ctx -> { + ctx.response() + .setResponseStatus(HttpResponseStatus.OK) + .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) + .setCharset(StandardCharsets.UTF_8); + ctx.write("secure domain: " + + " SNI host = " + ctx.httpRequest().as(HttpsRequest.class).getSNIHost() + + " SSL session = " + ctx.httpRequest().as(HttpsRequest.class).getSSLSession() + + " base URL = " + ctx.httpRequest().getBaseURL() + + " parameter = " + ctx.httpRequest().getParameter().allToString() + + " attributes = " + ctx.getAttributes() + + " local address = " + ctx.httpRequest().getLocalAddress() + + " remote address = " + ctx.httpRequest().getRemoteAddress()); + }) + .build()) + .build()) + .build(); + + Executor executor = BaseExecutor.builder() + .build(); + try (NettyHttpServer server = NettyHttpServer.builder() .setHttpServerConfig(serverConfig) .setApplication(BaseApplication.builder() - .setRouter(BaseHttpRouter.builder() - .addDomain(BaseHttpDomain.builder() - .setHttpAddress(httpsAddress) - .addService(BaseHttpService.builder() - .setPath("/favicon.ico") - .setHandler(ctx -> ctx.response() - .setResponseStatus(HttpResponseStatus.NOT_FOUND) - .build() - .flush()) - .build()) - .addService(BaseHttpService.builder() - .setPath("/secure") - .setHandler(ctx -> { - ctx.response() - .setResponseStatus(HttpResponseStatus.OK) - .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) - .setCharset(StandardCharsets.UTF_8); - ctx.write("secure domain: " + - " SNI host = " + ctx.httpRequest().as(HttpsRequest.class).getSNIHost() + - " SSL session = " + ctx.httpRequest().as(HttpsRequest.class).getSSLSession() + - " base URL = " + ctx.httpRequest().getBaseURL() + - " parameter = " + ctx.httpRequest().getParameter().allToString() + - " attributes = " + ctx.getAttributes() + - " local address = " + ctx.httpRequest().getLocalAddress() + - " remote address = " + ctx.httpRequest().getRemoteAddress()); - }) - .build()) - .build()) - .build()) + .setExecutor(executor) + .setRouter(router) .build()) .build()) { server.bind(); diff --git a/net-http-server-netty-secure/src/test/java/org/xbib/net/http/server/netty/secure/test/NettyHttpsServerTest.java b/net-http-server-netty-secure/src/test/java/org/xbib/net/http/server/netty/secure/test/NettyHttpsServerTest.java index 9ae2877..9b2e95f 100644 --- a/net-http-server-netty-secure/src/test/java/org/xbib/net/http/server/netty/secure/test/NettyHttpsServerTest.java +++ b/net-http-server-netty-secure/src/test/java/org/xbib/net/http/server/netty/secure/test/NettyHttpsServerTest.java @@ -22,11 +22,14 @@ import org.xbib.net.http.client.netty.NettyHttpClientConfig; import org.xbib.net.http.client.netty.secure.NettyHttpsClientConfig; import org.xbib.net.http.server.application.BaseApplication; import org.xbib.net.http.server.domain.BaseHttpDomain; +import org.xbib.net.http.server.executor.BaseExecutor; +import org.xbib.net.http.server.executor.Executor; import org.xbib.net.http.server.netty.NettyHttpServer; import org.xbib.net.http.server.netty.secure.HttpsAddress; import org.xbib.net.http.server.netty.secure.HttpsRequest; import org.xbib.net.http.server.netty.secure.NettyHttpsServerConfig; import org.xbib.net.http.server.route.BaseHttpRouter; +import org.xbib.net.http.server.route.HttpRouter; import org.xbib.net.http.server.service.BaseHttpService; import java.nio.charset.StandardCharsets; @@ -52,38 +55,45 @@ public class NettyHttpsServerTest { serverConfig.setServerName("NettySecureHttpServer", Bootstrap.class.getPackage().getImplementationVersion()); serverConfig.setNetworkClass(NetworkClass.LOOPBACK); serverConfig.setDebug(true); + + HttpRouter router = BaseHttpRouter.builder() + .addDomain(BaseHttpDomain.builder() + .setHttpAddress(httpsAddress) + .addService(BaseHttpService.builder() + .setPath("/favicon.ico") + .setHandler(ctx -> ctx.response() + .setResponseStatus(HttpResponseStatus.NOT_FOUND) + .build() + .flush()) + .build()) + .addService(BaseHttpService.builder() + .setPath("/secure") + .setHandler(ctx -> { + ctx.response() + .setResponseStatus(HttpResponseStatus.OK) + .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) + .setCharset(StandardCharsets.UTF_8); + ctx.write("secure domain: " + + " SNI host = " + ctx.httpRequest().as(HttpsRequest.class).getSNIHost() + + " SSL peer host = " + ctx.httpRequest().as(HttpsRequest.class).getSSLSession() + + " base URL = " + ctx.httpRequest().getBaseURL() + + " parameter = " + ctx.httpRequest().getParameter().allToString() + + " attributes = " + ctx.getAttributes() + + " local address = " + ctx.httpRequest().getLocalAddress() + + " remote address = " + ctx.httpRequest().getRemoteAddress()); + }) + .build()) + .build()) + .build(); + + Executor executor = BaseExecutor.builder() + .build(); + try (NettyHttpServer server = NettyHttpServer.builder() .setHttpServerConfig(serverConfig) .setApplication(BaseApplication.builder() - .setRouter(BaseHttpRouter.builder() - .addDomain(BaseHttpDomain.builder() - .setHttpAddress(httpsAddress) - .addService(BaseHttpService.builder() - .setPath("/favicon.ico") - .setHandler(ctx -> ctx.response() - .setResponseStatus(HttpResponseStatus.NOT_FOUND) - .build() - .flush()) - .build()) - .addService(BaseHttpService.builder() - .setPath("/secure") - .setHandler(ctx -> { - ctx.response() - .setResponseStatus(HttpResponseStatus.OK) - .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) - .setCharset(StandardCharsets.UTF_8); - ctx.write("secure domain: " + - " SNI host = " + ctx.httpRequest().as(HttpsRequest.class).getSNIHost() + - " SSL peer host = " + ctx.httpRequest().as(HttpsRequest.class).getSSLSession() + - " base URL = " + ctx.httpRequest().getBaseURL() + - " parameter = " + ctx.httpRequest().getParameter().allToString() + - " attributes = " + ctx.getAttributes() + - " local address = " + ctx.httpRequest().getLocalAddress() + - " remote address = " + ctx.httpRequest().getRemoteAddress()); - }) - .build()) - .build()) - .build()) + .setExecutor(executor) + .setRouter(router) .build()) .build()) { server.bind(); @@ -127,38 +137,45 @@ public class NettyHttpsServerTest { Bootstrap.class.getPackage().getImplementationVersion()); serverConfig.setNetworkClass(NetworkClass.LOOPBACK); serverConfig.setDebug(true); + + HttpRouter router = BaseHttpRouter.builder() + .addDomain(BaseHttpDomain.builder() + .setHttpAddress(httpsAddress) + .addService(BaseHttpService.builder() + .setPath("/favicon.ico") + .setHandler(ctx -> ctx.response() + .setResponseStatus(HttpResponseStatus.NOT_FOUND) + .build() + .flush()) + .build()) + .addService(BaseHttpService.builder() + .setPath("/secure") + .setHandler(ctx -> { + ctx.response() + .setResponseStatus(HttpResponseStatus.OK) + .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) + .setCharset(StandardCharsets.UTF_8); + ctx.write("secure domain: " + + " SNI host = " + ctx.httpRequest().as(HttpsRequest.class).getSNIHost() + + " SSL session = " + ctx.httpRequest().as(HttpsRequest.class).getSSLSession() + + " base URL = " + ctx.httpRequest().getBaseURL() + + " parameter = " + ctx.httpRequest().getParameter().allToString() + + " attributes = " + ctx.getAttributes() + + " local address = " + ctx.httpRequest().getLocalAddress() + + " remote address = " + ctx.httpRequest().getRemoteAddress()); + }) + .build()) + .build()) + .build(); + + Executor executor = BaseExecutor.builder() + .build(); + try (NettyHttpServer server = NettyHttpServer.builder() .setHttpServerConfig(serverConfig) .setApplication(BaseApplication.builder() - .setRouter(BaseHttpRouter.builder() - .addDomain(BaseHttpDomain.builder() - .setHttpAddress(httpsAddress) - .addService(BaseHttpService.builder() - .setPath("/favicon.ico") - .setHandler(ctx -> ctx.response() - .setResponseStatus(HttpResponseStatus.NOT_FOUND) - .build() - .flush()) - .build()) - .addService(BaseHttpService.builder() - .setPath("/secure") - .setHandler(ctx -> { - ctx.response() - .setResponseStatus(HttpResponseStatus.OK) - .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) - .setCharset(StandardCharsets.UTF_8); - ctx.write("secure domain: " + - " SNI host = " + ctx.httpRequest().as(HttpsRequest.class).getSNIHost() + - " SSL session = " + ctx.httpRequest().as(HttpsRequest.class).getSSLSession() + - " base URL = " + ctx.httpRequest().getBaseURL() + - " parameter = " + ctx.httpRequest().getParameter().allToString() + - " attributes = " + ctx.getAttributes() + - " local address = " + ctx.httpRequest().getLocalAddress() + - " remote address = " + ctx.httpRequest().getRemoteAddress()); - }) - .build()) - .build()) - .build()) + .setExecutor(executor) + .setRouter(router) .build()) .build()) { server.bind(); @@ -201,37 +218,44 @@ public class NettyHttpsServerTest { serverConfig.setServerName("NettySecureHttpServer", Bootstrap.class.getPackage().getImplementationVersion()); serverConfig.setNetworkClass(NetworkClass.LOOPBACK); serverConfig.setDebug(true); + + HttpRouter router = BaseHttpRouter.builder() + .addDomain(BaseHttpDomain.builder() + .setHttpAddress(httpsAddress) + .addService(BaseHttpService.builder() + .setPath("/favicon.ico") + .setHandler(ctx -> ctx.response() + .setResponseStatus(HttpResponseStatus.NOT_FOUND) + .build() + .flush()) + .build()) + .addService(BaseHttpService.builder() + .setPath("/secure") + .setHandler(ctx -> { + ctx.response() + .setResponseStatus(HttpResponseStatus.OK) + .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) + .setCharset(StandardCharsets.UTF_8); + ctx.write("secure domain " + + " SNI host " + ctx.httpRequest().as(HttpsRequest.class).getSNIHost() + " " + + " SSL peer host " + ctx.httpRequest().as(HttpsRequest.class).getSSLSession() + " " + + " base URL = " + ctx.httpRequest().getBaseURL() + " " + + ctx.httpRequest().getParameter() + " " + + ctx.httpRequest().getLocalAddress() + " " + + ctx.httpRequest().getRemoteAddress()); + }) + .build()) + .build()) + .build(); + + Executor executor = BaseExecutor.builder() + .build(); + try (NettyHttpServer server = NettyHttpServer.builder() .setHttpServerConfig(serverConfig) .setApplication(BaseApplication.builder() - .setRouter(BaseHttpRouter.builder() - .addDomain(BaseHttpDomain.builder() - .setHttpAddress(httpsAddress) - .addService(BaseHttpService.builder() - .setPath("/favicon.ico") - .setHandler(ctx -> ctx.response() - .setResponseStatus(HttpResponseStatus.NOT_FOUND) - .build() - .flush()) - .build()) - .addService(BaseHttpService.builder() - .setPath("/secure") - .setHandler(ctx -> { - ctx.response() - .setResponseStatus(HttpResponseStatus.OK) - .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) - .setCharset(StandardCharsets.UTF_8); - ctx.write("secure domain " + - " SNI host " + ctx.httpRequest().as(HttpsRequest.class).getSNIHost() + " " + - " SSL peer host " + ctx.httpRequest().as(HttpsRequest.class).getSSLSession() + " " + - " base URL = " + ctx.httpRequest().getBaseURL() + " " + - ctx.httpRequest().getParameter() + " " + - ctx.httpRequest().getLocalAddress() + " " + - ctx.httpRequest().getRemoteAddress()); - }) - .build()) - .build()) - .build()) + .setExecutor(executor) + .setRouter(router) .build()) .build()) { server.bind(); diff --git a/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/HttpRequest.java b/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/HttpRequest.java index fbde81b..77293de 100644 --- a/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/HttpRequest.java +++ b/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/HttpRequest.java @@ -1,7 +1,5 @@ package org.xbib.net.http.server.netty; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufInputStream; import org.xbib.net.Request; import org.xbib.net.http.server.BaseHttpRequest; @@ -9,8 +7,8 @@ import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; import java.util.Objects; +import org.xbib.net.util.ByteBufferInputStream; public class HttpRequest extends BaseHttpRequest { @@ -27,7 +25,8 @@ public class HttpRequest extends BaseHttpRequest { @Override public InputStream getInputStream() { - return new ByteBufInputStream(builder.fullHttpRequest.content()); + //return new ByteBufInputStream(builder.fullHttpRequest.content()); + return builder.byteBuffer != null ? new ByteBufferInputStream(builder.byteBuffer) : null; } @Override @@ -48,13 +47,10 @@ public class HttpRequest extends BaseHttpRequest { @Override public String toString() { - return "HttpRequest[request=" + builder.fullHttpRequest + + return "HttpRequest[method=" + builder.getMethod() + + ",version=" + builder.getVersion() + ",parameter=" + builder.getParameter() + - ",body=" + builder.fullHttpRequest.content().toString(StandardCharsets.UTF_8) + + ",body=" + (builder.byteBuffer != null) + "]"; } - - public ByteBuf getByteBuf() { - return builder.fullHttpRequest.content(); - } } diff --git a/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/HttpRequestBuilder.java b/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/HttpRequestBuilder.java index 4aed893..67f5d4a 100644 --- a/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/HttpRequestBuilder.java +++ b/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/HttpRequestBuilder.java @@ -2,6 +2,10 @@ package org.xbib.net.http.server.netty; import io.netty.buffer.ByteBufUtil; import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.multipart.FileUpload; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; import org.xbib.net.Parameter; import org.xbib.net.URL; import org.xbib.net.http.HttpAddress; @@ -13,44 +17,47 @@ import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; +import org.xbib.net.http.server.Part; public class HttpRequestBuilder extends BaseHttpRequestBuilder { - protected FullHttpRequest fullHttpRequest; + private static final Logger logger = Logger.getLogger(HttpRequestBuilder.class.getName()); protected ByteBuffer byteBuffer; protected HttpRequestBuilder() { } + public HttpRequestBuilder setHttpRequest(io.netty.handler.codec.http.HttpRequest httpRequest) { + if (httpRequest != null) { + setVersion(HttpVersion.valueOf(httpRequest.protocolVersion().text())); + setMethod(HttpMethod.valueOf(httpRequest.method().name())); + setRequestURI(httpRequest.uri()); + httpRequest.headers().entries().forEach(e -> addHeader(e.getKey(), e.getValue())); + } + return this; + } + public HttpRequestBuilder setFullHttpRequest(FullHttpRequest fullHttpRequest) { - if (fullHttpRequest != null) { - // retain request, so we can read the body later without refCnt=0 error - this.fullHttpRequest = fullHttpRequest.retain(); - setVersion(HttpVersion.valueOf(fullHttpRequest.protocolVersion().text())); - setMethod(HttpMethod.valueOf(fullHttpRequest.method().name())); - setRequestURI(fullHttpRequest.uri()); - fullHttpRequest.headers().entries().forEach(e -> addHeader(e.getKey(), e.getValue())); + setVersion(HttpVersion.valueOf(fullHttpRequest.protocolVersion().text())); + setMethod(HttpMethod.valueOf(fullHttpRequest.method().name())); + setRequestURI(fullHttpRequest.uri()); + fullHttpRequest.headers().entries().forEach(e -> addHeader(e.getKey(), e.getValue())); + // read all bytes from request into a JDK ByteBuffer. This might be expensive. + if (fullHttpRequest.content() != null) { + byteBuffer = ByteBuffer.wrap(ByteBufUtil.getBytes(fullHttpRequest.content())); } return this; } @Override public ByteBuffer getBody() { - if (byteBuffer != null) { - return byteBuffer; - } - // read all bytes from request into a JDK ByteBuffer. This might be expensive. - if (fullHttpRequest.content() != null) { - byteBuffer = ByteBuffer.wrap(ByteBufUtil.getBytes(fullHttpRequest.content())); - } return byteBuffer; } @Override public CharBuffer getBodyAsChars(Charset charset) { - return fullHttpRequest.content() != null ? - CharBuffer.wrap(fullHttpRequest.content().toString(charset)) : null; + return byteBuffer != null ? charset.decode(byteBuffer) : null; } @Override @@ -101,6 +108,17 @@ public class HttpRequestBuilder extends BaseHttpRequestBuilder { return this; } + public HttpRequestBuilder addFileUpload(FileUpload fileUpload) throws IOException { + logger.log(Level.FINE, "add file upload = " + fileUpload); + Part part = new Part(fileUpload.getContentType(), + fileUpload.getContentTransferEncoding(), + fileUpload.getFilename(), + fileUpload.isInMemory() ? null : fileUpload.getFile().toPath(), + ByteBuffer.wrap(fileUpload.get())); + super.parts.add(part); + return this; + } + protected Parameter getParameter() { return super.parameter; } @@ -112,8 +130,6 @@ public class HttpRequestBuilder extends BaseHttpRequestBuilder { @Override public void release() { - if (fullHttpRequest != null) { - fullHttpRequest.release(); - } + } } diff --git a/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/NettyHttpServer.java b/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/NettyHttpServer.java index 663a2fa..de9590e 100644 --- a/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/NettyHttpServer.java +++ b/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/NettyHttpServer.java @@ -11,13 +11,6 @@ import io.netty.channel.socket.ServerSocketChannel; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; import io.netty.util.AttributeKey; -import org.xbib.net.NetworkClass; -import org.xbib.net.NetworkUtils; -import org.xbib.net.SocketConfig; -import org.xbib.net.http.HttpAddress; -import org.xbib.net.http.server.application.Application; -import org.xbib.net.http.server.HttpServer; - import java.io.IOException; import java.net.BindException; import java.net.InetSocketAddress; @@ -28,6 +21,18 @@ import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; +import org.xbib.net.NetworkClass; +import org.xbib.net.NetworkUtils; +import org.xbib.net.SocketConfig; +import org.xbib.net.http.HttpAddress; +import org.xbib.net.http.HttpResponseStatus; +import org.xbib.net.http.server.HttpRequestBuilder; +import org.xbib.net.http.server.HttpResponseBuilder; +import org.xbib.net.http.server.HttpServerContext; +import org.xbib.net.http.server.HttpServer; +import org.xbib.net.http.server.domain.HttpDomain; +import org.xbib.net.http.server.executor.CallableReleasable; +import org.xbib.net.http.server.route.HttpRouter; /** * Netty HTTP server. @@ -161,8 +166,50 @@ public class NettyHttpServer implements HttpServer { } @Override - public Application getApplication() { - return builder.application; + public void dispatch(HttpRequestBuilder requestBuilder, + HttpResponseBuilder responseBuilder) { + CallableReleasable callableReleasable = new CallableReleasable<>() { + @Override + public Object call() { + HttpRouter router = builder.application.getRouter(); + router.route(builder.application, requestBuilder, responseBuilder); + return true; + } + + @Override + public void release() { + requestBuilder.release(); + responseBuilder.release(); + } + }; + builder.application.getExecutor().execute(callableReleasable); + } + + @Override + public void dispatch(HttpRequestBuilder requestBuilder, + HttpResponseBuilder responseBuilder, + HttpResponseStatus responseStatus) { + CallableReleasable callableReleasable = new CallableReleasable<>() { + @Override + public Object call() { + HttpRouter router = builder.application.getRouter(); + HttpServerContext httpServerContext = builder.application.createContext(null, requestBuilder, responseBuilder); + router.routeStatus(responseStatus, httpServerContext); + return true; + } + + @Override + public void release() { + requestBuilder.release(); + responseBuilder.release(); + } + }; + builder.application.getExecutor().execute(callableReleasable); + } + + @Override + public Collection getDomains() { + return builder.application.getDomains(); } @Override diff --git a/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/NettyHttpServerConfig.java b/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/NettyHttpServerConfig.java index b4de21b..9223398 100644 --- a/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/NettyHttpServerConfig.java +++ b/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/NettyHttpServerConfig.java @@ -56,20 +56,37 @@ public class NettyHttpServerConfig extends HttpServerConfig { */ private int pipeliningCapacity = 1024; + /** + * HTTP object aggregation is enabled by default. + */ + private boolean isObjectAggregationEnabled = true; + /** * This is Netty's default. */ private int maxCompositeBufferComponents = 1024; /** - * Default for compression. + * Do not write chunks by default. */ - private boolean enableCompression = true; + private boolean isChunkedWriteEnabled = false; /** - * Default for decompression. + * Compression is enabled by default. */ - private boolean enableDecompression = true; + private boolean isCompressionEnabled = true; + + /** + * Decompression is enabled by default. + */ + private boolean isDecompressionEnabled = true; + + /** + * Disable file upload (POST) by default. + */ + private boolean isFileUploadEnabled = false; + + private int fileUploadDiskThreshold = 1 *1024 * 1024; public NettyHttpServerConfig() { } @@ -154,6 +171,15 @@ public class NettyHttpServerConfig extends HttpServerConfig { return pipeliningCapacity; } + public NettyHttpServerConfig setObjectAggregationEnabled(boolean isObjectAgregationEnabled) { + this.isObjectAggregationEnabled = isObjectAgregationEnabled; + return this; + } + + public boolean isObjectAggregationEnabled() { + return isObjectAggregationEnabled; + } + public NettyHttpServerConfig setMaxCompositeBufferComponents(int maxCompositeBufferComponents) { this.maxCompositeBufferComponents = maxCompositeBufferComponents; return this; @@ -163,22 +189,48 @@ public class NettyHttpServerConfig extends HttpServerConfig { return maxCompositeBufferComponents; } - public NettyHttpServerConfig setCompression(boolean enabled) { - this.enableCompression = enabled; + public NettyHttpServerConfig setCompression(boolean isCompressionEnabled) { + this.isCompressionEnabled = isCompressionEnabled; return this; } public boolean isCompressionEnabled() { - return enableCompression; + return isCompressionEnabled; } - public NettyHttpServerConfig setDecompression(boolean enabled) { - this.enableDecompression = enabled; + public NettyHttpServerConfig setDecompression(boolean isDecompressionEnabled) { + this.isDecompressionEnabled = isDecompressionEnabled; return this; } public boolean isDecompressionEnabled() { - return enableDecompression; + return isDecompressionEnabled; } + public NettyHttpServerConfig setChunkWriteEnabled(boolean isChunkedWriteEnabled) { + this.isChunkedWriteEnabled = isChunkedWriteEnabled; + return this; + } + + public boolean isChunkedWriteEnabled() { + return isChunkedWriteEnabled; + } + + public NettyHttpServerConfig setFileUploadEnabled(boolean isFileUploadEnabled) { + this.isFileUploadEnabled = isFileUploadEnabled; + return this; + } + + public boolean isFileUploadEnabled() { + return isFileUploadEnabled; + } + + public NettyHttpServerConfig setFileUploadDiskThreshold(int fileUploadDiskThreshold) { + this.fileUploadDiskThreshold = fileUploadDiskThreshold; + return this; + } + + public int getFileUploadDiskThreshold() { + return fileUploadDiskThreshold; + } } diff --git a/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/http1/Http1ChannelInitializer.java b/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/http1/Http1ChannelInitializer.java index 0ff5d55..4c923ee 100644 --- a/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/http1/Http1ChannelInitializer.java +++ b/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/http1/Http1ChannelInitializer.java @@ -41,7 +41,7 @@ public class Http1ChannelInitializer implements HttpChannelInitializer { if (nettyHttpServerConfig.isDebug()) { pipeline.addLast("server-logging", new TrafficLoggingHandler(LogLevel.DEBUG)); } - //pipeline.addLast("server-chunked-write", new ChunkedWriteHandler()); + // always use the server codec or we won't be able to handle HTTP pipeline.addLast("server-codec", new HttpServerCodec(nettyHttpServerConfig.getMaxInitialLineLength(), nettyHttpServerConfig.getMaxHeadersSize(), nettyHttpServerConfig.getMaxChunkSize())); if (nettyHttpServerConfig.isCompressionEnabled()) { @@ -50,9 +50,18 @@ public class Http1ChannelInitializer implements HttpChannelInitializer { if (nettyHttpServerConfig.isDecompressionEnabled()) { pipeline.addLast("server-decompressor", new HttpContentDecompressor()); } - HttpObjectAggregator httpObjectAggregator = new HttpObjectAggregator(nettyHttpServerConfig.getMaxContentLength()); - httpObjectAggregator.setMaxCumulationBufferComponents(nettyHttpServerConfig.getMaxCompositeBufferComponents()); - pipeline.addLast("server-aggregator", httpObjectAggregator); + if (nettyHttpServerConfig.isObjectAggregationEnabled()) { + HttpObjectAggregator httpObjectAggregator = new HttpObjectAggregator(nettyHttpServerConfig.getMaxContentLength()); + httpObjectAggregator.setMaxCumulationBufferComponents(nettyHttpServerConfig.getMaxCompositeBufferComponents()); + pipeline.addLast("server-aggregator", httpObjectAggregator); + } + if (nettyHttpServerConfig.isChunkedWriteEnabled()) { + pipeline.addLast("server-chunked-write", new ChunkedWriteHandler()); + } + if (nettyHttpServerConfig.isFileUploadEnabled()) { + HttpFileUploadHandler httpFileUploadHandler = new HttpFileUploadHandler(server); + pipeline.addLast("server-file-upload", httpFileUploadHandler); + } if (nettyHttpServerConfig.isPipeliningEnabled()) { pipeline.addLast("server-pipelining", new HttpPipeliningHandler(nettyHttpServerConfig.getPipeliningCapacity())); } diff --git a/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/http1/Http1Handler.java b/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/http1/Http1Handler.java index 6ba90ef..afd2281 100644 --- a/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/http1/Http1Handler.java +++ b/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/http1/Http1Handler.java @@ -34,11 +34,9 @@ class Http1Handler extends ChannelDuplexHandler { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { - if (msg instanceof HttpPipelinedRequest) { - HttpPipelinedRequest httpPipelinedRequest = (HttpPipelinedRequest) msg; + if (msg instanceof HttpPipelinedRequest httpPipelinedRequest) { try { - if (httpPipelinedRequest.getRequest() instanceof FullHttpRequest) { - FullHttpRequest fullHttpRequest = (FullHttpRequest) httpPipelinedRequest.getRequest(); + if (httpPipelinedRequest.getRequest() instanceof FullHttpRequest fullHttpRequest) { requestReceived(ctx, fullHttpRequest, httpPipelinedRequest.getSequenceId()); } } finally { @@ -73,7 +71,9 @@ class Http1Handler extends ChannelDuplexHandler { ctx.close(); } - protected void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest, Integer sequenceId) { + protected void requestReceived(ChannelHandlerContext ctx, + FullHttpRequest fullHttpRequest, + Integer sequenceId) { HttpAddress httpAddress = ctx.channel().attr(NettyHttpServerConfig.ATTRIBUTE_KEY_HTTP_ADDRESS).get(); try { HttpResponseBuilder serverResponseBuilder = HttpResponse.builder() @@ -82,7 +82,7 @@ class Http1Handler extends ChannelDuplexHandler { serverResponseBuilder.setSequenceId(sequenceId); } serverResponseBuilder.shouldClose("close".equalsIgnoreCase(fullHttpRequest.headers().get(HttpHeaderNames.CONNECTION))); - // the base URL construction may fail with exception. In hat case, we return a built-in 400 Bad Request. + // the base URL construction may fail with exception. In that case, we return a built-in 400 Bad Request. HttpRequestBuilder serverRequestBuilder = HttpRequest.builder() .setFullHttpRequest(fullHttpRequest) .setBaseURL(httpAddress, @@ -91,7 +91,7 @@ class Http1Handler extends ChannelDuplexHandler { .setLocalAddress((InetSocketAddress) ctx.channel().localAddress()) .setRemoteAddress((InetSocketAddress) ctx.channel().remoteAddress()) .setSequenceId(sequenceId); - nettyHttpServer.getApplication().dispatch(serverRequestBuilder, serverResponseBuilder); + nettyHttpServer.dispatch(serverRequestBuilder, serverResponseBuilder); } catch (Exception e) { logger.log(Level.SEVERE, "bad request: " + e.getMessage(), e); DefaultFullHttpResponse fullHttpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, diff --git a/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/http1/HttpFileUploadHandler.java b/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/http1/HttpFileUploadHandler.java new file mode 100644 index 0000000..d57d92f --- /dev/null +++ b/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/http1/HttpFileUploadHandler.java @@ -0,0 +1,120 @@ +package org.xbib.net.http.server.netty.http1; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.codec.http.DefaultFullHttpResponse; +import io.netty.handler.codec.http.HttpContent; +import io.netty.handler.codec.http.HttpHeaderNames; +import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpObject; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.HttpVersion; +import io.netty.handler.codec.http.LastHttpContent; +import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory; +import io.netty.handler.codec.http.multipart.FileUpload; +import io.netty.handler.codec.http.multipart.HttpDataFactory; +import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder; + +import io.netty.handler.codec.http.multipart.InterfaceHttpData; +import java.net.InetSocketAddress; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.xbib.net.http.HttpAddress; +import org.xbib.net.http.server.netty.HttpRequestBuilder; +import org.xbib.net.http.server.netty.HttpResponse; +import org.xbib.net.http.server.netty.HttpResponseBuilder; +import org.xbib.net.http.server.netty.NettyHttpServer; +import org.xbib.net.http.server.netty.NettyHttpServerConfig; + +public class HttpFileUploadHandler extends SimpleChannelInboundHandler { + + private static final Logger logger = Logger.getLogger(HttpFileUploadHandler.class.getName()); + + private final NettyHttpServer nettyHttpServer; + + public HttpFileUploadHandler(NettyHttpServer nettyHttpServer) { + this.nettyHttpServer = nettyHttpServer; + } + + @Override + protected void channelRead0(ChannelHandlerContext ctx, HttpObject httpObject) { + logger.log(Level.FINEST, "checking file upload"); + HttpRequest httpRequest = null; + HttpPostRequestDecoder httpDecoder = null; + if (httpObject instanceof HttpRequest) { + httpRequest = (HttpRequest) httpObject; + // peek into request if we have a POST request + if (httpRequest.method() == HttpMethod.POST) { + logger.log(Level.FINEST, "checking HTTP POST: success"); + HttpDataFactory factory = new DefaultHttpDataFactory(nettyHttpServer.getNettyHttpServerConfig().getFileUploadDiskThreshold()); + httpDecoder = new HttpPostRequestDecoder(factory, httpRequest); + } + } + if (httpDecoder != null) { + if (httpObject instanceof HttpContent chunk) { + logger.log(Level.FINEST, "got chunk"); + httpDecoder.offer(chunk); + try { + while (httpDecoder.hasNext()) { + InterfaceHttpData data = httpDecoder.next(); + logger.log(Level.FINEST, "got data"); + if (data != null) { + try { + if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.FileUpload) { + logger.log(Level.FINEST, "got file upload"); + FileUpload fileUpload = (FileUpload) data; + requestReceived(ctx, httpRequest, fileUpload); + } else { + logger.log(Level.FINEST, "got HTTP data type = " + data.getHttpDataType()); + } + } finally { + data.release(); + } + } + } + } catch (HttpPostRequestDecoder.EndOfDataDecoderException e) { + logger.log(Level.FINEST, "end of data decoder exception"); + } + if (chunk instanceof LastHttpContent) { + //httpDecoder.destroy(); + } + } else { + logger.log(Level.FINEST, "not a HttpContent: " ); + } + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + logger.log(Level.SEVERE, cause.getMessage(), cause); + ctx.close(); + } + + protected void requestReceived(ChannelHandlerContext ctx, + HttpRequest httpRequest, + FileUpload fileUpload) { + HttpAddress httpAddress = ctx.channel().attr(NettyHttpServerConfig.ATTRIBUTE_KEY_HTTP_ADDRESS).get(); + try { + HttpResponseBuilder serverResponseBuilder = HttpResponse.builder() + .setChannelHandlerContext(ctx); + serverResponseBuilder.shouldClose("close".equalsIgnoreCase(httpRequest.headers().get(HttpHeaderNames.CONNECTION))); + // the base URL construction may fail with exception. In that case, we return a built-in 400 Bad Request. + HttpRequestBuilder httpRequestBuilder = org.xbib.net.http.server.netty.HttpRequest.builder() + .setHttpRequest(httpRequest) + .addFileUpload(fileUpload) + .setBaseURL(httpAddress, + httpRequest.uri(), + httpRequest.headers().get(HttpHeaderNames.HOST)) + .setLocalAddress((InetSocketAddress) ctx.channel().localAddress()) + .setRemoteAddress((InetSocketAddress) ctx.channel().remoteAddress()); + nettyHttpServer.dispatch(httpRequestBuilder, serverResponseBuilder); + } catch (Exception e) { + logger.log(Level.SEVERE, "bad request: " + e.getMessage(), e); + DefaultFullHttpResponse fullHttpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, + HttpResponseStatus.BAD_REQUEST); + ctx.writeAndFlush(fullHttpResponse); + ctx.close(); + } + } +} diff --git a/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/http2/Http2ChannelInitializer.java b/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/http2/Http2ChannelInitializer.java index 50e8a52..13dfcce 100644 --- a/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/http2/Http2ChannelInitializer.java +++ b/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/http2/Http2ChannelInitializer.java @@ -28,6 +28,7 @@ import org.xbib.net.http.server.netty.TrafficLoggingHandler; import java.util.logging.Level; import java.util.logging.Logger; +import org.xbib.net.http.server.netty.http1.HttpFileUploadHandler; /** * Insecure HTTP/2 server channel initializer. @@ -63,9 +64,17 @@ public class Http2ChannelInitializer implements HttpChannelInitializer { if (nettyHttpServerConfig.isDecompressionEnabled()) { pipeline.addLast("server-decompressor", new HttpContentDecompressor()); } - pipeline.addLast("server-object-aggregator", - new HttpObjectAggregator(nettyHttpServerConfig.getMaxContentLength())); - pipeline.addLast("server-chunked-write", new ChunkedWriteHandler()); + if (nettyHttpServerConfig.isObjectAggregationEnabled()) { + pipeline.addLast("server-object-aggregator", + new HttpObjectAggregator(nettyHttpServerConfig.getMaxContentLength())); + } + if (nettyHttpServerConfig.isFileUploadEnabled()) { + HttpFileUploadHandler httpFileUploadHandler = new HttpFileUploadHandler(nettyHttpServer); + pipeline.addLast("server-file-upload", httpFileUploadHandler); + } + if (nettyHttpServerConfig.isChunkedWriteEnabled()) { + pipeline.addLast("server-chunked-write", new ChunkedWriteHandler()); + } pipeline.addLast("server-request", new Http2Handler(nettyHttpServer)); pipeline.addLast("server-messages", new Http2Messages()); pipeline.addLast("server-idle-timeout", new IdleTimeoutHandler(nettyHttpServerConfig.getTimeoutMillis())); diff --git a/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/http2/Http2Handler.java b/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/http2/Http2Handler.java index 87bc887..b4957c9 100644 --- a/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/http2/Http2Handler.java +++ b/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/http2/Http2Handler.java @@ -55,7 +55,7 @@ public class Http2Handler extends ChannelDuplexHandler { .setLocalAddress((InetSocketAddress) ctx.channel().localAddress()) .setRemoteAddress((InetSocketAddress) ctx.channel().remoteAddress()) .setStreamId(streamId); - nettyHttpServer.getApplication().dispatch(serverRequestBuilder, httpResponseBuilder); + nettyHttpServer.dispatch(serverRequestBuilder, httpResponseBuilder); } catch (Exception e) { logger.log(Level.SEVERE, "bad request:" + e.getMessage(), e); DefaultFullHttpResponse fullHttpResponse = diff --git a/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttp2ServerMultiRequestLoadTest.java b/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttp2ServerMultiRequestLoadTest.java index 27fdb6b..e764a79 100644 --- a/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttp2ServerMultiRequestLoadTest.java +++ b/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttp2ServerMultiRequestLoadTest.java @@ -19,6 +19,9 @@ import org.xbib.net.http.client.netty.NettyHttpClient; import org.xbib.net.http.client.netty.NettyHttpClientConfig; import org.xbib.net.http.server.application.BaseApplication; import org.xbib.net.http.server.domain.BaseHttpDomain; +import org.xbib.net.http.server.executor.BaseExecutor; +import org.xbib.net.http.server.executor.Executor; +import org.xbib.net.http.server.route.HttpRouter; import org.xbib.net.http.server.service.BaseHttpService; import org.xbib.net.http.server.netty.NettyHttpServer; import org.xbib.net.http.server.netty.NettyHttpServerConfig; @@ -37,29 +40,36 @@ public class NettyHttp2ServerMultiRequestLoadTest { NettyHttpServerConfig serverConfig = new NettyHttpServerConfig(); serverConfig.setServerName("NettyHttp2CleartextServer", Bootstrap.class.getPackage().getImplementationVersion()); serverConfig.setNetworkClass(NetworkClass.LOOPBACK); + + HttpRouter router = BaseHttpRouter.builder() + .addDomain(BaseHttpDomain.builder() + .setHttpAddress(httpAddress) + .addService(BaseHttpService.builder() + .setPath("/domain") + .setHandler(ctx -> { + ctx.response() + .setResponseStatus(HttpResponseStatus.OK) + .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) + .setCharset(StandardCharsets.UTF_8); + ctx.write("domain: " + + " base URL = " + ctx.httpRequest().getBaseURL() + + " parameter = " + ctx.httpRequest().getParameter().allToString() + + " attributes = " + ctx.getAttributes() + + " local address = " + ctx.httpRequest().getLocalAddress() + + " remote address = " + ctx.httpRequest().getRemoteAddress()); + }) + .build()) + .build()) + .build(); + + Executor executor = BaseExecutor.builder() + .build(); + try (NettyHttpServer server = NettyHttpServer.builder() .setHttpServerConfig(serverConfig) .setApplication(BaseApplication.builder() - .setRouter(BaseHttpRouter.builder() - .addDomain(BaseHttpDomain.builder() - .setHttpAddress(httpAddress) - .addService(BaseHttpService.builder() - .setPath("/domain") - .setHandler(ctx -> { - ctx.response() - .setResponseStatus(HttpResponseStatus.OK) - .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) - .setCharset(StandardCharsets.UTF_8); - ctx.write("domain: " + - " base URL = " + ctx.httpRequest().getBaseURL() + - " parameter = " + ctx.httpRequest().getParameter().allToString() + - " attributes = " + ctx.getAttributes() + - " local address = " + ctx.httpRequest().getLocalAddress() + - " remote address = " + ctx.httpRequest().getRemoteAddress()); - }) - .build()) - .build()) - .build()) + .setExecutor(executor) + .setRouter(router) .build()) .build()) { server.bind(); diff --git a/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttp2ServerTest.java b/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttp2ServerTest.java index 4c9d135..13da14a 100644 --- a/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttp2ServerTest.java +++ b/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttp2ServerTest.java @@ -18,6 +18,9 @@ import org.xbib.net.http.client.netty.NettyHttpClient; import org.xbib.net.http.client.netty.NettyHttpClientConfig; import org.xbib.net.http.server.application.BaseApplication; import org.xbib.net.http.server.domain.BaseHttpDomain; +import org.xbib.net.http.server.executor.BaseExecutor; +import org.xbib.net.http.server.executor.Executor; +import org.xbib.net.http.server.route.HttpRouter; import org.xbib.net.http.server.service.BaseHttpService; import org.xbib.net.http.server.netty.NettyHttpServer; import org.xbib.net.http.server.netty.NettyHttpServerConfig; @@ -38,27 +41,34 @@ public class NettyHttp2ServerTest { Bootstrap.class.getPackage().getImplementationVersion()); nettyHttpServerConfig.setNetworkClass(NetworkClass.ANY); nettyHttpServerConfig.setDebug(true); + + HttpRouter router = BaseHttpRouter.builder() + .addDomain(BaseHttpDomain.builder() + .setHttpAddress(httpAddress1) + .addService(BaseHttpService.builder() + .setPath("/domain") + .setHandler(ctx -> { + ctx.response() + .setResponseStatus(HttpResponseStatus.OK) + .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) + .setCharset(StandardCharsets.UTF_8); + ctx.write("domain " + + ctx.httpRequest().getParameter() + " " + + ctx.httpRequest().getLocalAddress() + " " + + ctx.httpRequest().getRemoteAddress()); + }) + .build()) + .build()) + .build(); + + Executor executor = BaseExecutor.builder() + .build(); + try (NettyHttpServer server = NettyHttpServer.builder() .setHttpServerConfig(nettyHttpServerConfig) .setApplication(BaseApplication.builder() - .setRouter(BaseHttpRouter.builder() - .addDomain(BaseHttpDomain.builder() - .setHttpAddress(httpAddress1) - .addService(BaseHttpService.builder() - .setPath("/domain") - .setHandler(ctx -> { - ctx.response() - .setResponseStatus(HttpResponseStatus.OK) - .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) - .setCharset(StandardCharsets.UTF_8); - ctx.write("domain " + - ctx.httpRequest().getParameter() + " " + - ctx.httpRequest().getLocalAddress() + " " + - ctx.httpRequest().getRemoteAddress()); - }) - .build()) - .build()) - .build()) + .setExecutor(executor) + .setRouter(router) .build()) .build()) { server.bind(); diff --git a/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttpServerBodyTest.java b/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttpServerBodyTest.java new file mode 100644 index 0000000..e347a1d --- /dev/null +++ b/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttpServerBodyTest.java @@ -0,0 +1,100 @@ +package org.xbib.net.http.netty.test; + +import io.netty.bootstrap.Bootstrap; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.junit.jupiter.api.Test; +import org.xbib.net.NetworkClass; +import org.xbib.net.URL; +import org.xbib.net.http.HttpAddress; +import org.xbib.net.http.HttpHeaderNames; +import org.xbib.net.http.HttpHeaderValues; +import org.xbib.net.http.HttpResponseStatus; +import org.xbib.net.http.client.netty.HttpRequest; +import org.xbib.net.http.client.netty.NettyHttpClient; +import org.xbib.net.http.client.netty.NettyHttpClientConfig; +import org.xbib.net.http.server.application.BaseApplication; +import org.xbib.net.http.server.domain.BaseHttpDomain; +import org.xbib.net.http.server.executor.BaseExecutor; +import org.xbib.net.http.server.executor.Executor; +import org.xbib.net.http.server.netty.NettyHttpServer; +import org.xbib.net.http.server.netty.NettyHttpServerConfig; +import org.xbib.net.http.server.route.BaseHttpRouter; +import org.xbib.net.http.server.route.HttpRouter; +import org.xbib.net.http.server.service.BaseHttpService; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class NettyHttpServerBodyTest { + + private static final Logger logger = Logger.getLogger(NettyHttpServerBodyTest.class.getName()); + + @Test + public void testHttp() throws Exception { + URL url = URL.from("http://localhost:8008/domain/"); + HttpAddress httpAddress1 = HttpAddress.http1(url); + NettyHttpServerConfig nettyHttpServerConfig = new NettyHttpServerConfig(); + nettyHttpServerConfig.setServerName("NettyHttpServer", + Bootstrap.class.getPackage().getImplementationVersion()); + nettyHttpServerConfig.setNetworkClass(NetworkClass.LOCAL); + nettyHttpServerConfig.setDebug(true); + + HttpRouter router = BaseHttpRouter.builder() + .addDomain(BaseHttpDomain.builder() + .setHttpAddress(httpAddress1) + .addService(BaseHttpService.builder() + .setPath("/domain") + .setHandler(ctx -> { + String body = ctx.request().getBodyAsChars(StandardCharsets.UTF_8).toString(); + ctx.response() + .setResponseStatus(HttpResponseStatus.OK) + .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) + .setCharset(StandardCharsets.UTF_8); + ctx.write("parameter = " + ctx.httpRequest().getParameter().allToString() + + " local address = " + ctx.httpRequest().getLocalAddress() + + " remote address = " + ctx.httpRequest().getRemoteAddress() + + " attributes = " + ctx.getAttributes() + + " body = " + body + ); + }) + .build()) + .build()) + .build(); + + Executor executor = BaseExecutor.builder() + .build(); + + try (NettyHttpServer server = NettyHttpServer.builder() + .setHttpServerConfig(nettyHttpServerConfig) + .setApplication(BaseApplication.builder() + .setExecutor(executor) + .setRouter(router) + .build()) + .build()) { + server.bind(); + NettyHttpClientConfig config = new NettyHttpClientConfig() + .setDebug(true); + AtomicBoolean received = new AtomicBoolean(); + try (NettyHttpClient client = NettyHttpClient.builder() + .setConfig(config) + .build()) { + HttpRequest request = HttpRequest.post() + .setURL(url) + .content("Hello, I'm a simple POST body for Jörg", + "text/plain", + StandardCharsets.UTF_8) + .setResponseListener(resp -> { + logger.log(Level.INFO, "got response:" + + " status = " + resp.getStatus() + + " header = " + resp.getHeaders() + + " body = " + resp.getBodyAsChars(StandardCharsets.UTF_8)); + received.set(true); + }) + .build(); + client.execute(request).get().close(); + } + assertTrue(received.get()); + } + } +} diff --git a/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttpServerFailureTest.java b/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttpServerFailureTest.java index 9022f72..2ac0f1a 100644 --- a/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttpServerFailureTest.java +++ b/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttpServerFailureTest.java @@ -23,6 +23,9 @@ import org.xbib.net.http.client.netty.NettyHttpClient; import org.xbib.net.http.client.netty.NettyHttpClientConfig; import org.xbib.net.http.server.application.BaseApplication; import org.xbib.net.http.server.domain.BaseHttpDomain; +import org.xbib.net.http.server.executor.BaseExecutor; +import org.xbib.net.http.server.executor.Executor; +import org.xbib.net.http.server.route.HttpRouter; import org.xbib.net.http.server.service.BaseHttpService; import org.xbib.net.http.server.netty.NettyHttpServer; import org.xbib.net.http.server.netty.NettyHttpServerConfig; @@ -42,30 +45,37 @@ public class NettyHttpServerFailureTest { nettyHttpServerConfig.setNetworkClass(NetworkClass.LOCAL); nettyHttpServerConfig.setDebug(true); nettyHttpServerConfig.setPipelining(false); + + HttpRouter router = BaseHttpRouter.builder() + .addDomain(BaseHttpDomain.builder() + .setHttpAddress(httpAddress1) + .addService(BaseHttpService.builder() + .setPath("/domain") + .setHandler(ctx -> { + ctx.response() + .setResponseStatus(HttpResponseStatus.OK) + .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) + .setCharset(StandardCharsets.UTF_8); + ctx.write("domain" + + " parameter = " + ctx.httpRequest().getParameter().allToString() + + " local address = " + ctx.httpRequest().getLocalAddress() + + " remote address = " + ctx.httpRequest().getRemoteAddress() + + " attributes = " + ctx.getAttributes() + ); + }) + .build()) + .build()) + .build(); + + Executor executor = BaseExecutor.builder() + .build(); + try (NettyHttpServer server = NettyHttpServer.builder() .setHttpServerConfig(nettyHttpServerConfig) .setApplication(BaseApplication.builder() .setHome(Paths.get(".")) - .setRouter(BaseHttpRouter.builder() - .addDomain(BaseHttpDomain.builder() - .setHttpAddress(httpAddress1) - .addService(BaseHttpService.builder() - .setPath("/domain") - .setHandler(ctx -> { - ctx.response() - .setResponseStatus(HttpResponseStatus.OK) - .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) - .setCharset(StandardCharsets.UTF_8); - ctx.write("domain" + - " parameter = " + ctx.httpRequest().getParameter().allToString() + - " local address = " + ctx.httpRequest().getLocalAddress() + - " remote address = " + ctx.httpRequest().getRemoteAddress() + - " attributes = " + ctx.getAttributes() - ); - }) - .build()) - .build()) - .build()) + .setExecutor(executor) + .setRouter(router) .build()) .build()) { server.bind(); diff --git a/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttpServerFileUploadTest.java b/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttpServerFileUploadTest.java new file mode 100644 index 0000000..9b948f1 --- /dev/null +++ b/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttpServerFileUploadTest.java @@ -0,0 +1,110 @@ +package org.xbib.net.http.netty.test; + +import io.netty.bootstrap.Bootstrap; +import java.nio.charset.StandardCharsets; +import java.nio.file.Paths; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.junit.jupiter.api.Test; +import org.xbib.net.NetworkClass; +import org.xbib.net.URL; +import org.xbib.net.http.HttpAddress; +import org.xbib.net.http.HttpHeaderNames; +import org.xbib.net.http.HttpHeaderValues; +import org.xbib.net.http.HttpMethod; +import org.xbib.net.http.HttpResponseStatus; +import org.xbib.net.http.client.Part; +import org.xbib.net.http.client.netty.HttpRequest; +import org.xbib.net.http.client.netty.NettyHttpClient; +import org.xbib.net.http.client.netty.NettyHttpClientConfig; +import org.xbib.net.http.server.application.BaseApplication; +import org.xbib.net.http.server.domain.BaseHttpDomain; +import org.xbib.net.http.server.executor.BaseExecutor; +import org.xbib.net.http.server.executor.Executor; +import org.xbib.net.http.server.netty.NettyHttpServer; +import org.xbib.net.http.server.netty.NettyHttpServerConfig; +import org.xbib.net.http.server.route.BaseHttpRouter; +import org.xbib.net.http.server.route.HttpRouter; +import org.xbib.net.http.server.service.BaseHttpService; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class NettyHttpServerFileUploadTest { + + private static final Logger logger = Logger.getLogger(NettyHttpServerFileUploadTest.class.getName()); + + @Test + public void testFileUpload() throws Exception { + URL url = URL.from("http://localhost:8008/domain/"); + HttpAddress httpAddress1 = HttpAddress.http1(url); + NettyHttpServerConfig nettyHttpServerConfig = new NettyHttpServerConfig(); + nettyHttpServerConfig.setServerName("NettyHttpServer", + Bootstrap.class.getPackage().getImplementationVersion()); + nettyHttpServerConfig.setNetworkClass(NetworkClass.LOCAL); + nettyHttpServerConfig.setDebug(true); + nettyHttpServerConfig.setChunkWriteEnabled(true); + nettyHttpServerConfig.setFileUploadEnabled(true); + + HttpRouter router = BaseHttpRouter.builder() + .addDomain(BaseHttpDomain.builder() + .setHttpAddress(httpAddress1) + .addService(BaseHttpService.builder() + .setPath("/domain") + .setMethod(HttpMethod.POST) + .setHandler(ctx -> { + logger.log(Level.FINEST, "handler starting"); + List parts = ctx.httpRequest().getParts(); + ctx.response() + .setResponseStatus(HttpResponseStatus.OK) + .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) + .setCharset(StandardCharsets.UTF_8); + ctx.write("parameter = " + ctx.httpRequest().getParameter().allToString() + + " local address = " + ctx.httpRequest().getLocalAddress() + + " remote address = " + ctx.httpRequest().getRemoteAddress() + + " attributes = " + ctx.getAttributes() + + " parts = " + parts + ); + }) + .build()) + .build()) + .build(); + + Executor executor = BaseExecutor.builder() + .build(); + + try (NettyHttpServer server = NettyHttpServer.builder() + .setHttpServerConfig(nettyHttpServerConfig) + .setApplication(BaseApplication.builder() + .setExecutor(executor) + .setRouter(router) + .build()) + .build()) { + server.bind(); + NettyHttpClientConfig config = new NettyHttpClientConfig() + .setGzipEnabled(false) + .setChunkWriteEnabled(true) + .setObjectAggregationEnabled(true) + .setDebug(true); + AtomicBoolean received = new AtomicBoolean(); + try (NettyHttpClient client = NettyHttpClient.builder() + .setConfig(config) + .build()) { + HttpRequest request = HttpRequest.post() + .setURL(url) + .addPart(new Part("text/plain", "base64", + "test", Paths.get("build.gradle"), StandardCharsets.UTF_8)) + .setResponseListener(resp -> { + logger.log(Level.INFO, "got response:" + + " status = " + resp.getStatus() + + " header = " + resp.getHeaders() + + " body = " + resp.getBodyAsChars(StandardCharsets.UTF_8)); + received.set(true); + }) + .build(); + client.execute(request).get().close(); + } + assertTrue(received.get()); + } + } +} diff --git a/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttpServerMultiRequestLoadTest.java b/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttpServerMultiRequestLoadTest.java index c5c9e44..01b5524 100644 --- a/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttpServerMultiRequestLoadTest.java +++ b/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttpServerMultiRequestLoadTest.java @@ -19,6 +19,9 @@ import org.xbib.net.http.client.netty.NettyHttpClient; import org.xbib.net.http.client.netty.NettyHttpClientConfig; import org.xbib.net.http.server.application.BaseApplication; import org.xbib.net.http.server.domain.BaseHttpDomain; +import org.xbib.net.http.server.executor.BaseExecutor; +import org.xbib.net.http.server.executor.Executor; +import org.xbib.net.http.server.route.HttpRouter; import org.xbib.net.http.server.service.BaseHttpService; import org.xbib.net.http.server.netty.NettyHttpServer; import org.xbib.net.http.server.netty.NettyHttpServerConfig; @@ -37,29 +40,36 @@ public class NettyHttpServerMultiRequestLoadTest { NettyHttpServerConfig serverConfig = new NettyHttpServerConfig(); serverConfig.setServerName("NettyHttpServer", Bootstrap.class.getPackage().getImplementationVersion()); serverConfig.setNetworkClass(NetworkClass.LOOPBACK); + + HttpRouter router = BaseHttpRouter.builder() + .addDomain(BaseHttpDomain.builder() + .setHttpAddress(httpAddress) + .addService(BaseHttpService.builder() + .setPath("/domain") + .setHandler(ctx -> { + ctx.response() + .setResponseStatus(HttpResponseStatus.OK) + .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) + .setCharset(StandardCharsets.UTF_8); + ctx.write("domain: " + + " base URL = " + ctx.httpRequest().getBaseURL() + + " parameter = " + ctx.httpRequest().getParameter().allToString() + + " attributes = " + ctx.getAttributes() + + " local address = " + ctx.httpRequest().getLocalAddress() + + " remote address = " + ctx.httpRequest().getRemoteAddress()); + }) + .build()) + .build()) + .build(); + + Executor executor = BaseExecutor.builder() + .build(); + try (NettyHttpServer server = NettyHttpServer.builder() .setHttpServerConfig(serverConfig) .setApplication(BaseApplication.builder() - .setRouter(BaseHttpRouter.builder() - .addDomain(BaseHttpDomain.builder() - .setHttpAddress(httpAddress) - .addService(BaseHttpService.builder() - .setPath("/domain") - .setHandler(ctx -> { - ctx.response() - .setResponseStatus(HttpResponseStatus.OK) - .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) - .setCharset(StandardCharsets.UTF_8); - ctx.write("domain: " + - " base URL = " + ctx.httpRequest().getBaseURL() + - " parameter = " + ctx.httpRequest().getParameter().allToString() + - " attributes = " + ctx.getAttributes() + - " local address = " + ctx.httpRequest().getLocalAddress() + - " remote address = " + ctx.httpRequest().getRemoteAddress()); - }) - .build()) - .build()) - .build()) + .setExecutor(executor) + .setRouter(router) .build()) .build()) { server.bind(); diff --git a/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttpServerTest.java b/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttpServerTest.java index fc910aa..236a9d9 100644 --- a/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttpServerTest.java +++ b/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttpServerTest.java @@ -15,7 +15,10 @@ import org.xbib.net.http.client.netty.NettyHttpClient; import org.xbib.net.http.client.netty.NettyHttpClientConfig; import org.xbib.net.http.server.application.BaseApplication; import org.xbib.net.http.server.domain.BaseHttpDomain; +import org.xbib.net.http.server.executor.BaseExecutor; +import org.xbib.net.http.server.executor.Executor; import org.xbib.net.http.server.route.BaseHttpRouter; +import org.xbib.net.http.server.route.HttpRouter; import org.xbib.net.http.server.service.BaseHttpService; import org.xbib.net.http.server.netty.NettyHttpServer; import org.xbib.net.http.server.netty.NettyHttpServerConfig; @@ -33,34 +36,42 @@ public class NettyHttpServerTest { URL url = URL.from("http://localhost:8008/domain/"); HttpAddress httpAddress1 = HttpAddress.http1(url); NettyHttpServerConfig nettyHttpServerConfig = new NettyHttpServerConfig(); - nettyHttpServerConfig.setServerName("NettyHttpServer", Bootstrap.class.getPackage().getImplementationVersion()); + nettyHttpServerConfig.setServerName("NettyHttpServer", + Bootstrap.class.getPackage().getImplementationVersion()); nettyHttpServerConfig.setNetworkClass(NetworkClass.LOCAL); nettyHttpServerConfig.setDebug(true); nettyHttpServerConfig.setPipelining(false); + + HttpRouter router = BaseHttpRouter.builder() + .addDomain(BaseHttpDomain.builder() + .setHttpAddress(httpAddress1) + .addService(BaseHttpService.builder() + .setPath("/domain") + .setHandler(ctx -> { + ctx.response() + .setResponseStatus(HttpResponseStatus.OK) + .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) + .setCharset(StandardCharsets.UTF_8); + ctx.write("domain" + + " parameter = " + ctx.httpRequest().getParameter().allToString() + + " local address = " + ctx.httpRequest().getLocalAddress() + + " remote address = " + ctx.httpRequest().getRemoteAddress() + + " attributes = " + ctx.getAttributes() + ); + }) + .build()) + .build()) + .build(); + + Executor executor = BaseExecutor.builder() + .build(); + try (NettyHttpServer server = NettyHttpServer.builder() .setHttpServerConfig(nettyHttpServerConfig) .setApplication(BaseApplication.builder() - .setRouter(BaseHttpRouter.builder() - .addDomain(BaseHttpDomain.builder() - .setHttpAddress(httpAddress1) - .addService(BaseHttpService.builder() - .setPath("/domain") - .setHandler(ctx -> { - ctx.response() - .setResponseStatus(HttpResponseStatus.OK) - .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) - .setCharset(StandardCharsets.UTF_8); - ctx.write("domain" + - " parameter = " + ctx.httpRequest().getParameter().allToString() + - " local address = " + ctx.httpRequest().getLocalAddress() + - " remote address = " + ctx.httpRequest().getRemoteAddress() + - " attributes = " + ctx.getAttributes() - ); - }) - .build()) - .build()) - .build()) - .build()) + .setExecutor(executor) + .setRouter(router) + .build()) .build()) { server.bind(); NettyHttpClientConfig config = new NettyHttpClientConfig() diff --git a/net-http-server-nio/src/main/java/org/xbib/net/http/server/nio/NioHttpServer.java b/net-http-server-nio/src/main/java/org/xbib/net/http/server/nio/NioHttpServer.java index f3f3abc..1640d23 100644 --- a/net-http-server-nio/src/main/java/org/xbib/net/http/server/nio/NioHttpServer.java +++ b/net-http-server-nio/src/main/java/org/xbib/net/http/server/nio/NioHttpServer.java @@ -1,11 +1,13 @@ package org.xbib.net.http.server.nio; +import java.util.Collection; import org.xbib.net.http.HttpAddress; import org.xbib.net.http.HttpHeaderNames; import org.xbib.net.http.HttpHeaders; import org.xbib.net.http.HttpMethod; +import org.xbib.net.http.HttpResponseStatus; import org.xbib.net.http.HttpVersion; -import org.xbib.net.http.server.application.Application; +import org.xbib.net.http.server.HttpServerContext; import org.xbib.net.http.server.HttpServer; import java.io.IOException; @@ -30,6 +32,9 @@ import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.xbib.net.http.server.domain.HttpDomain; +import org.xbib.net.http.server.executor.CallableReleasable; +import org.xbib.net.http.server.route.HttpRouter; public class NioHttpServer implements HttpServer { @@ -101,7 +106,7 @@ public class NioHttpServer implements HttpServer { httpAddress, (InetSocketAddress) socketChannel.getLocalAddress(), (InetSocketAddress) socketChannel.getRemoteAddress()); - builder.application.dispatch(requestBuilder, responseBuilder); + dispatch(requestBuilder, responseBuilder); socketChannel.close(); } catch (IOException e) { logger.log(Level.SEVERE, e.getMessage(), e); @@ -128,8 +133,50 @@ public class NioHttpServer implements HttpServer { } @Override - public Application getApplication() { - return builder.application; + public void dispatch(org.xbib.net.http.server.HttpRequestBuilder requestBuilder, + org.xbib.net.http.server.HttpResponseBuilder responseBuilder) { + CallableReleasable callableReleasable = new CallableReleasable<>() { + @Override + public Object call() { + HttpRouter router = builder.application.getRouter(); + router.route(builder.application, requestBuilder, responseBuilder); + return true; + } + + @Override + public void release() { + requestBuilder.release(); + responseBuilder.release(); + } + }; + builder.application.getExecutor().execute(callableReleasable); + } + + @Override + public void dispatch(org.xbib.net.http.server.HttpRequestBuilder requestBuilder, + org.xbib.net.http.server.HttpResponseBuilder responseBuilder, + HttpResponseStatus responseStatus) { + HttpServerContext httpServerContext = builder.application.createContext(null, requestBuilder, responseBuilder); + CallableReleasable callableReleasable = new CallableReleasable<>() { + @Override + public Object call() { + HttpRouter router = builder.application.getRouter(); + router.routeStatus(responseStatus, httpServerContext); + return true; + } + + @Override + public void release() { + requestBuilder.release(); + responseBuilder.release(); + } + }; + builder.application.getExecutor().execute(callableReleasable); + } + + @Override + public Collection getDomains() { + return builder.application.getDomains(); } @Override diff --git a/net-http-server-nio/src/test/java/org/xbib/net/http/nio/test/NioHttpServerTest.java b/net-http-server-nio/src/test/java/org/xbib/net/http/nio/test/NioHttpServerTest.java index 85e1f9a..a1cf0ff 100644 --- a/net-http-server-nio/src/test/java/org/xbib/net/http/nio/test/NioHttpServerTest.java +++ b/net-http-server-nio/src/test/java/org/xbib/net/http/nio/test/NioHttpServerTest.java @@ -9,6 +9,9 @@ import org.xbib.net.http.HttpHeaderValues; import org.xbib.net.http.HttpResponseStatus; import org.xbib.net.http.server.application.BaseApplication; import org.xbib.net.http.server.domain.BaseHttpDomain; +import org.xbib.net.http.server.executor.BaseExecutor; +import org.xbib.net.http.server.executor.Executor; +import org.xbib.net.http.server.route.HttpRouter; import org.xbib.net.http.server.service.BaseHttpService; import org.xbib.net.http.server.route.BaseHttpRouter; import org.xbib.net.http.server.HttpServerConfig; @@ -24,49 +27,54 @@ public class NioHttpServerTest { public void nioServerTest() throws Exception { HttpAddress httpAddress1 = HttpAddress.http1("localhost", 8008); HttpAddress httpAddress2 = HttpAddress.http1("localhost", 8009); - NioHttpServer server = NioHttpServer.builder() - .setHttpServerConfig(new HttpServerConfig() - .setServerName("NioHttpServer", NioHttpServer.class.getPackage().getImplementationVendor()) - .setNetworkClass(NetworkClass.SITE) - ) - .setApplication(BaseApplication.builder() - .setRouter(BaseHttpRouter.builder() - .addDomain(BaseHttpDomain.builder() - .setHttpAddress(httpAddress1) - .addService(BaseHttpService.builder() - .setPath("/domain1") - .setHandler(ctx -> { - ctx.response() - .setResponseStatus(HttpResponseStatus.OK) - .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) - .setCharset(StandardCharsets.UTF_8); - ctx.write("domain1 " + - ctx.httpRequest().getParameter().toString() + " " + - ctx.httpRequest().getLocalAddress() + " " + - ctx.httpRequest().getRemoteAddress()); - }) - .build()) - .build()) - .addDomain(BaseHttpDomain.builder() - .setHttpAddress(httpAddress2) - .addService(BaseHttpService.builder() - .setPath("/domain2") - .setHandler(ctx -> { - ctx.response() - .setResponseStatus(HttpResponseStatus.OK) - .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) - .setCharset(StandardCharsets.UTF_8); - ctx.write("domain2 " + - ctx.httpRequest().getParameter().toString() + " " + - ctx.httpRequest().getLocalAddress() + " " + - ctx.httpRequest().getRemoteAddress()); - }) - .build()) + + HttpRouter router = BaseHttpRouter.builder() + .addDomain(BaseHttpDomain.builder() + .setHttpAddress(httpAddress1) + .addService(BaseHttpService.builder() + .setPath("/domain1") + .setHandler(ctx -> { + ctx.response() + .setResponseStatus(HttpResponseStatus.OK) + .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) + .setCharset(StandardCharsets.UTF_8); + ctx.write("domain1 " + + ctx.httpRequest().getParameter().toString() + " " + + ctx.httpRequest().getLocalAddress() + " " + + ctx.httpRequest().getRemoteAddress()); + }) + .build()) + .build()) + .addDomain(BaseHttpDomain.builder() + .setHttpAddress(httpAddress2) + .addService(BaseHttpService.builder() + .setPath("/domain2") + .setHandler(ctx -> { + ctx.response() + .setResponseStatus(HttpResponseStatus.OK) + .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) + .setCharset(StandardCharsets.UTF_8); + ctx.write("domain2 " + + ctx.httpRequest().getParameter().toString() + " " + + ctx.httpRequest().getLocalAddress() + " " + + ctx.httpRequest().getRemoteAddress()); + }) .build()) .build()) - .build()) .build(); - try { + + Executor executor = BaseExecutor.builder() + .build(); + + try(NioHttpServer server = NioHttpServer.builder() + .setHttpServerConfig(new HttpServerConfig() + .setServerName("NioHttpServer", NioHttpServer.class.getPackage().getImplementationVendor()) + .setNetworkClass(NetworkClass.SITE)) + .setApplication(BaseApplication.builder() + .setExecutor(executor) + .setRouter(router) + .build()) + .build()) { server.bind(); } catch (BindException e) { throw new RuntimeException(e); diff --git a/net-http-server-simple-secure/src/test/java/org/xbib/net/http/server/simple/secure/SimpleHttpsServerTest.java b/net-http-server-simple-secure/src/test/java/org/xbib/net/http/server/simple/secure/SimpleHttpsServerTest.java index 5273d0c..0b810f3 100644 --- a/net-http-server-simple-secure/src/test/java/org/xbib/net/http/server/simple/secure/SimpleHttpsServerTest.java +++ b/net-http-server-simple-secure/src/test/java/org/xbib/net/http/server/simple/secure/SimpleHttpsServerTest.java @@ -9,7 +9,10 @@ import org.xbib.net.http.HttpHeaderValues; import org.xbib.net.http.HttpResponseStatus; import org.xbib.net.http.server.application.BaseApplication; import org.xbib.net.http.server.domain.BaseHttpDomain; +import org.xbib.net.http.server.executor.BaseExecutor; +import org.xbib.net.http.server.executor.Executor; import org.xbib.net.http.server.route.BaseHttpRouter; +import org.xbib.net.http.server.route.HttpRouter; import org.xbib.net.http.server.service.BaseHttpService; import org.xbib.net.http.server.HttpServerConfig; import org.xbib.net.http.server.simple.SimpleHttpServer; @@ -33,37 +36,44 @@ public class SimpleHttpsServerTest { serverConfig.setServerName("SimpleSecureHttpServer", SimpleHttpServer.class.getPackage().getImplementationVersion()); serverConfig.setNetworkClass(NetworkClass.LOOPBACK); serverConfig.setDebug(true); + + HttpRouter router = BaseHttpRouter.builder() + .addDomain(BaseHttpDomain.builder() + .setHttpAddress(httpsAddress) + .addService(BaseHttpService.builder() + .setPath("/favicon.ico") + .setHandler(ctx -> ctx.response() + .setResponseStatus(HttpResponseStatus.NOT_FOUND) + .build() + .flush()) + .build()) + .addService(BaseHttpService.builder() + .setPath("/secure") + .setHandler(ctx -> { + ctx.response() + .setResponseStatus(HttpResponseStatus.OK) + .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) + .setCharset(StandardCharsets.UTF_8); + ctx.write("secure domain: " + + " SNI host = " + ctx.httpRequest().as(HttpsRequest.class).getSNIHost() + + " SSL peer host = " + ctx.httpRequest().as(HttpsRequest.class).getSSLSession() + + " base URL = " + ctx.httpRequest().getBaseURL() + + " parameter = " + ctx.httpRequest().getParameter() + + " local address = " + ctx.httpRequest().getLocalAddress() + + " remote address = " + ctx.httpRequest().getRemoteAddress()); + }) + .build()) + .build()) + .build(); + + Executor executor = BaseExecutor.builder() + .build(); + try (SimpleHttpServer server = SimpleHttpsServer.builder() .setHttpServerConfig(serverConfig) .setApplication(BaseApplication.builder() - .setRouter(BaseHttpRouter.builder() - .addDomain(BaseHttpDomain.builder() - .setHttpAddress(httpsAddress) - .addService(BaseHttpService.builder() - .setPath("/favicon.ico") - .setHandler(ctx -> ctx.response() - .setResponseStatus(HttpResponseStatus.NOT_FOUND) - .build() - .flush()) - .build()) - .addService(BaseHttpService.builder() - .setPath("/secure") - .setHandler(ctx -> { - ctx.response() - .setResponseStatus(HttpResponseStatus.OK) - .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) - .setCharset(StandardCharsets.UTF_8); - ctx.write("secure domain: " + - " SNI host = " + ctx.httpRequest().as(HttpsRequest.class).getSNIHost() + - " SSL peer host = " + ctx.httpRequest().as(HttpsRequest.class).getSSLSession() + - " base URL = " + ctx.httpRequest().getBaseURL() + - " parameter = " + ctx.httpRequest().getParameter() + - " local address = " + ctx.httpRequest().getLocalAddress() + - " remote address = " + ctx.httpRequest().getRemoteAddress()); - }) - .build()) - .build()) - .build()) + .setExecutor(executor) + .setRouter(router) .build()) .build()) { server.bind(); diff --git a/net-http-server-simple/src/main/java/org/xbib/net/http/server/simple/SimpleHttpServer.java b/net-http-server-simple/src/main/java/org/xbib/net/http/server/simple/SimpleHttpServer.java index c03c598..44cf418 100644 --- a/net-http-server-simple/src/main/java/org/xbib/net/http/server/simple/SimpleHttpServer.java +++ b/net-http-server-simple/src/main/java/org/xbib/net/http/server/simple/SimpleHttpServer.java @@ -1,11 +1,13 @@ package org.xbib.net.http.server.simple; +import java.util.Collection; import org.xbib.net.NetworkClass; import org.xbib.net.NetworkUtils; import org.xbib.net.SocketConfig; import org.xbib.net.http.HttpAddress; import org.xbib.net.http.HttpHeaderNames; -import org.xbib.net.http.server.application.Application; +import org.xbib.net.http.HttpResponseStatus; +import org.xbib.net.http.server.HttpServerContext; import org.xbib.net.http.HttpHeaders; import org.xbib.net.http.HttpMethod; import org.xbib.net.http.HttpVersion; @@ -31,6 +33,9 @@ import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.xbib.net.http.server.domain.HttpDomain; +import org.xbib.net.http.server.executor.CallableReleasable; +import org.xbib.net.http.server.route.HttpRouter; public class SimpleHttpServer implements HttpServer { @@ -108,7 +113,7 @@ public class SimpleHttpServer implements HttpServer { httpAddress, (InetSocketAddress) socket.getLocalSocketAddress(), (InetSocketAddress) socket.getRemoteSocketAddress()); - builder.application.dispatch(httpRequestBuilder, httpResponseBuilder); + dispatch(httpRequestBuilder, httpResponseBuilder); } catch (Throwable t) { logger.log(Level.SEVERE, t.getMessage(), t); } finally { @@ -129,6 +134,53 @@ public class SimpleHttpServer implements HttpServer { } } + @Override + public void dispatch(org.xbib.net.http.server.HttpRequestBuilder requestBuilder, + org.xbib.net.http.server.HttpResponseBuilder responseBuilder) { + CallableReleasable callableReleasable = new CallableReleasable<>() { + @Override + public Object call() { + HttpRouter router = builder.application.getRouter(); + router.route(builder.application, requestBuilder, responseBuilder); + return true; + } + + @Override + public void release() { + requestBuilder.release(); + responseBuilder.release(); + } + }; + builder.application.getExecutor().execute(callableReleasable); + } + + @Override + public void dispatch(org.xbib.net.http.server.HttpRequestBuilder requestBuilder, + org.xbib.net.http.server.HttpResponseBuilder responseBuilder, + HttpResponseStatus responseStatus) { + HttpServerContext httpServerContext = builder.application.createContext(null, requestBuilder, responseBuilder); + CallableReleasable callableReleasable = new CallableReleasable<>() { + @Override + public Object call() { + HttpRouter router = builder.application.getRouter(); + router.routeStatus(responseStatus, httpServerContext); + return true; + } + + @Override + public void release() { + requestBuilder.release(); + responseBuilder.release(); + } + }; + builder.application.getExecutor().execute(callableReleasable); + } + + @Override + public Collection getDomains() { + return builder.application.getDomains(); + } + @Override public void loop() throws IOException { CountDownLatch latch = new CountDownLatch(1); @@ -139,11 +191,6 @@ public class SimpleHttpServer implements HttpServer { } } - @Override - public Application getApplication() { - return builder.application; - } - @Override public void close() throws IOException { logger.log(Level.INFO, "closing"); @@ -248,5 +295,4 @@ public class SimpleHttpServer implements HttpServer { } return result.toString(); } - } diff --git a/net-http-server-simple/src/test/java/org/xbib/net/http/server/simple/test/SimpleHttpServerTest.java b/net-http-server-simple/src/test/java/org/xbib/net/http/server/simple/test/SimpleHttpServerTest.java index 8116eda..13364f0 100644 --- a/net-http-server-simple/src/test/java/org/xbib/net/http/server/simple/test/SimpleHttpServerTest.java +++ b/net-http-server-simple/src/test/java/org/xbib/net/http/server/simple/test/SimpleHttpServerTest.java @@ -9,7 +9,10 @@ import org.xbib.net.http.HttpHeaderValues; import org.xbib.net.http.HttpResponseStatus; import org.xbib.net.http.server.application.BaseApplication; import org.xbib.net.http.server.domain.BaseHttpDomain; +import org.xbib.net.http.server.executor.BaseExecutor; +import org.xbib.net.http.server.executor.Executor; import org.xbib.net.http.server.route.BaseHttpRouter; +import org.xbib.net.http.server.route.HttpRouter; import org.xbib.net.http.server.service.BaseHttpService; import org.xbib.net.http.server.HttpServerConfig; import org.xbib.net.http.server.resource.FileResourceHandler; @@ -24,50 +27,56 @@ public class SimpleHttpServerTest { public void simpleServerTest() throws Exception { HttpAddress httpAddress1 = HttpAddress.http1("localhost", 8008); HttpAddress httpAddress2 = HttpAddress.http1("localhost", 8008); + + HttpRouter router = BaseHttpRouter.builder() + .addDomain(BaseHttpDomain.builder() + .setHttpAddress(httpAddress1) + .addService(BaseHttpService.builder() + .setPath("/domain1") + .setHandler(ctx -> { + ctx.response() + .setResponseStatus(HttpResponseStatus.OK) + .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) + .setCharset(StandardCharsets.UTF_8); + ctx.write("domain1 " + + ctx.httpRequest().getParameter() + " " + + ctx.httpRequest().getLocalAddress() + " " + + ctx.httpRequest().getRemoteAddress()); + }) + .build()) + .addService(BaseHttpService.builder() + .setPath("/file1/*") + .setHandler(new FileResourceHandler()) + .build()) + .build()) + .addDomain(BaseHttpDomain.builder() + .setHttpAddress(httpAddress2) + .addService(BaseHttpService.builder() + .setPath("/domain2") + .setHandler(ctx -> { + ctx.response() + .setResponseStatus(HttpResponseStatus.OK) + .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) + .setCharset(StandardCharsets.UTF_8); + ctx.write("domain2 " + + ctx.httpRequest().getParameter() + " " + + ctx.httpRequest().getLocalAddress() + " " + + ctx.httpRequest().getRemoteAddress()); + }) + .build()) + .build()) + .build(); + + Executor executor = BaseExecutor.builder() + .build(); + SimpleHttpServer server = SimpleHttpServer.builder() .setHttpServerConfig(new HttpServerConfig() .setServerName("SimpleHttpServer", SimpleHttpServer.class.getPackage().getImplementationVendor()) - .setNetworkClass(NetworkClass.SITE) - ) + .setNetworkClass(NetworkClass.SITE)) .setApplication(BaseApplication.builder() - .setRouter(BaseHttpRouter.builder() - .addDomain(BaseHttpDomain.builder() - .setHttpAddress(httpAddress1) - .addService(BaseHttpService.builder() - .setPath("/domain1") - .setHandler(ctx -> { - ctx.response() - .setResponseStatus(HttpResponseStatus.OK) - .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) - .setCharset(StandardCharsets.UTF_8); - ctx.write("domain1 " + - ctx.httpRequest().getParameter() + " " + - ctx.httpRequest().getLocalAddress() + " " + - ctx.httpRequest().getRemoteAddress()); - }) - .build()) - .addService(BaseHttpService.builder() - .setPath("/file1/*") - .setHandler(new FileResourceHandler()) - .build()) - .build()) - .addDomain(BaseHttpDomain.builder() - .setHttpAddress(httpAddress2) - .addService(BaseHttpService.builder() - .setPath("/domain2") - .setHandler(ctx -> { - ctx.response() - .setResponseStatus(HttpResponseStatus.OK) - .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) - .setCharset(StandardCharsets.UTF_8); - ctx.write("domain2 " + - ctx.httpRequest().getParameter() + " " + - ctx.httpRequest().getLocalAddress() + " " + - ctx.httpRequest().getRemoteAddress()); - }) - .build()) - .build()) - .build()) + .setExecutor(executor) + .setRouter(router) .build()) .build(); server.bind(); diff --git a/net-http-server/src/main/java/module-info.java b/net-http-server/src/main/java/module-info.java index 021e5fb..953dec8 100644 --- a/net-http-server/src/main/java/module-info.java +++ b/net-http-server/src/main/java/module-info.java @@ -23,6 +23,7 @@ module org.xbib.net.http.server { exports org.xbib.net.http.server.session.file; exports org.xbib.net.http.server.session.memory; exports org.xbib.net.http.server.validate; + exports org.xbib.net.http.server.executor; requires org.xbib.net; requires org.xbib.net.mime; requires org.xbib.net.http; diff --git a/net-http-server/src/main/java/org/xbib/net/http/server/BaseHttpRequest.java b/net-http-server/src/main/java/org/xbib/net/http/server/BaseHttpRequest.java index 0147708..91517e0 100644 --- a/net-http-server/src/main/java/org/xbib/net/http/server/BaseHttpRequest.java +++ b/net-http-server/src/main/java/org/xbib/net/http/server/BaseHttpRequest.java @@ -1,6 +1,7 @@ package org.xbib.net.http.server; import java.net.InetSocketAddress; +import java.util.List; import org.xbib.net.Attributes; import org.xbib.net.Parameter; import org.xbib.net.URL; @@ -85,6 +86,11 @@ public abstract class BaseHttpRequest implements HttpRequest { return builder.requestId; } + @Override + public List getParts() { + return builder.parts; + } + @Override public HttpServerContext getContext() { return builder.httpServerContext; diff --git a/net-http-server/src/main/java/org/xbib/net/http/server/BaseHttpRequestBuilder.java b/net-http-server/src/main/java/org/xbib/net/http/server/BaseHttpRequestBuilder.java index ec8a4fc..d8678af 100644 --- a/net-http-server/src/main/java/org/xbib/net/http/server/BaseHttpRequestBuilder.java +++ b/net-http-server/src/main/java/org/xbib/net/http/server/BaseHttpRequestBuilder.java @@ -4,6 +4,8 @@ import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; import org.xbib.net.Parameter; import org.xbib.net.URL; @@ -49,8 +51,11 @@ public abstract class BaseHttpRequestBuilder implements HttpRequestBuilder { protected boolean done; + protected List parts; + protected BaseHttpRequestBuilder() { this.httpHeaders = new HttpHeaders(); + this.parts = new ArrayList<>(); } @Override @@ -264,6 +269,14 @@ public abstract class BaseHttpRequestBuilder implements HttpRequestBuilder { return this; } + public BaseHttpRequestBuilder addPart(Part part) { + if (done) { + return this; + } + this.parts.add(part); + return this; + } + @Override public void done() { this.done = true; diff --git a/net-http-server/src/main/java/org/xbib/net/http/server/HttpRequest.java b/net-http-server/src/main/java/org/xbib/net/http/server/HttpRequest.java index c33033d..c16ac5e 100644 --- a/net-http-server/src/main/java/org/xbib/net/http/server/HttpRequest.java +++ b/net-http-server/src/main/java/org/xbib/net/http/server/HttpRequest.java @@ -2,6 +2,8 @@ package org.xbib.net.http.server; import java.io.InputStream; import java.nio.ByteBuffer; +import java.nio.file.Path; +import java.util.List; import org.xbib.net.Attributes; import org.xbib.net.Parameter; import org.xbib.net.Request; @@ -38,5 +40,7 @@ public interface HttpRequest extends Request { InputStream getInputStream(); + List getParts(); + Attributes getAttributes(); } diff --git a/net-http-server/src/main/java/org/xbib/net/http/server/HttpRequestBuilder.java b/net-http-server/src/main/java/org/xbib/net/http/server/HttpRequestBuilder.java index a223ff3..00fdcfe 100644 --- a/net-http-server/src/main/java/org/xbib/net/http/server/HttpRequestBuilder.java +++ b/net-http-server/src/main/java/org/xbib/net/http/server/HttpRequestBuilder.java @@ -31,6 +31,8 @@ public interface HttpRequestBuilder { HttpRequestBuilder addHeader(String name, String value); + HttpRequestBuilder addPart(Part part); + URL getBaseURL(); HttpMethod getMethod(); diff --git a/net-http-server/src/main/java/org/xbib/net/http/server/HttpServer.java b/net-http-server/src/main/java/org/xbib/net/http/server/HttpServer.java index 20b3d24..d6478c5 100644 --- a/net-http-server/src/main/java/org/xbib/net/http/server/HttpServer.java +++ b/net-http-server/src/main/java/org/xbib/net/http/server/HttpServer.java @@ -3,7 +3,9 @@ package org.xbib.net.http.server; import java.io.Closeable; import java.io.IOException; import java.net.BindException; -import org.xbib.net.http.server.application.Application; +import java.util.Collection; +import org.xbib.net.http.HttpResponseStatus; +import org.xbib.net.http.server.domain.HttpDomain; public interface HttpServer extends Closeable { @@ -11,5 +13,13 @@ public interface HttpServer extends Closeable { void loop() throws IOException; - Application getApplication(); + void dispatch(HttpRequestBuilder requestBuilder, + HttpResponseBuilder responseBuilder); + + void dispatch(HttpRequestBuilder requestBuilder, + HttpResponseBuilder responseBuilder, + HttpResponseStatus responseStatus); + + Collection getDomains(); + } diff --git a/net-http-server/src/main/java/org/xbib/net/http/server/Part.java b/net-http-server/src/main/java/org/xbib/net/http/server/Part.java new file mode 100644 index 0000000..fb6749d --- /dev/null +++ b/net-http-server/src/main/java/org/xbib/net/http/server/Part.java @@ -0,0 +1,55 @@ +package org.xbib.net.http.server; + +import java.nio.ByteBuffer; +import java.nio.file.Path; +import static java.nio.charset.StandardCharsets.UTF_8; + +public class Part { + + private final String contentType; + + private final String contentTransferEncoding; + + private final String name; + + private final Path path; + + private final ByteBuffer byteBuffer; + + public Part(String contentType, + String contentTransferEncoding, + String name, + Path path, + ByteBuffer byteBuffer) { + this.contentType = contentType; + this.contentTransferEncoding = contentTransferEncoding; + this.name = name; + this.path = path; + this.byteBuffer = byteBuffer; + } + + public String getContentType() { + return contentType; + } + + public String getContentTransferEncoding() { + return contentTransferEncoding; + } + + public String getName() { + return name; + } + + public Path getPath() { + return path; + } + + public ByteBuffer getByteBuffer() { + return byteBuffer; + } + + @Override + public String toString() { + return "Part[name=" + name + ",path=" + path + ",bytebuffer=" + (byteBuffer != null ? UTF_8.decode(byteBuffer) : "") + "]"; + } +} diff --git a/net-http-server/src/main/java/org/xbib/net/http/server/application/Application.java b/net-http-server/src/main/java/org/xbib/net/http/server/application/Application.java index 75e3302..6167e67 100644 --- a/net-http-server/src/main/java/org/xbib/net/http/server/application/Application.java +++ b/net-http-server/src/main/java/org/xbib/net/http/server/application/Application.java @@ -8,11 +8,12 @@ import java.util.Collection; import java.util.Locale; import java.util.Set; import org.xbib.net.http.HttpAddress; -import org.xbib.net.http.HttpResponseStatus; import org.xbib.net.http.server.HttpRequestBuilder; import org.xbib.net.http.server.HttpResponseBuilder; import org.xbib.net.http.server.HttpServerContext; import org.xbib.net.http.server.domain.HttpDomain; +import org.xbib.net.http.server.executor.Executor; +import org.xbib.net.http.server.route.HttpRouter; import org.xbib.net.http.server.session.SessionListener; import org.xbib.net.mime.MimeTypeService; import org.xbib.settings.Settings; @@ -35,26 +36,10 @@ public interface Application extends SessionListener, Resolver, Closeable Settings getSettings(); + void addModule(ApplicationModule applicationModule); + Collection getModules(); - /** - * Dispatch a request. - * @param requestBuilder the request - * @param responseBuilder the response - */ - void dispatch(HttpRequestBuilder requestBuilder, - HttpResponseBuilder responseBuilder); - - /** - * Dispatch a status. - * @param requestBuilder the request - * @param responseBuilder the response - * @param httpResponseStatus the status - */ - void dispatch(HttpRequestBuilder requestBuilder, - HttpResponseBuilder responseBuilder, - HttpResponseStatus httpResponseStatus); - HttpServerContext createContext(HttpDomain domain, HttpRequestBuilder httpRequestBuilder, HttpResponseBuilder httpResponseBuilder); @@ -63,5 +48,9 @@ public interface Application extends SessionListener, Resolver, Closeable void onClose(HttpServerContext httpServerContext); + Executor getExecutor(); + + HttpRouter getRouter(); + void close() throws IOException; } diff --git a/net-http-server/src/main/java/org/xbib/net/http/server/application/ApplicationBuilder.java b/net-http-server/src/main/java/org/xbib/net/http/server/application/ApplicationBuilder.java index f0afe23..0bd327f 100644 --- a/net-http-server/src/main/java/org/xbib/net/http/server/application/ApplicationBuilder.java +++ b/net-http-server/src/main/java/org/xbib/net/http/server/application/ApplicationBuilder.java @@ -3,23 +3,12 @@ package org.xbib.net.http.server.application; import java.nio.file.Path; import java.time.ZoneId; import java.util.Locale; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; +import org.xbib.net.http.server.executor.Executor; import org.xbib.net.http.server.route.HttpRouter; import org.xbib.net.mime.MimeTypeService; public interface ApplicationBuilder { - ApplicationBuilder setThreadCount(int blockingThreadCount); - - ApplicationBuilder setQueueCount(int blockingThreadQueueCount); - - ApplicationBuilder setKeepAliveTime(int keepAliveTime); - - ApplicationBuilder setKeepAliveTimeUnit(TimeUnit keepAliveTimeUnit); - - ApplicationBuilder setExecutor(ThreadPoolExecutor executor); - ApplicationBuilder setHome(Path home); ApplicationBuilder setContextPath(String contextPath); @@ -28,8 +17,6 @@ public interface ApplicationBuilder { ApplicationBuilder setSessionsEnabled(boolean sessionsEnabled); - ApplicationBuilder setRouter(HttpRouter router); - ApplicationBuilder setLocale(Locale locale); ApplicationBuilder setZoneId(ZoneId zoneId); @@ -38,7 +25,9 @@ public interface ApplicationBuilder { ApplicationBuilder setStaticSuffixes(String... suffixes); - ApplicationBuilder registerModule(ApplicationModule applicationModule); + ApplicationBuilder setExecutor(Executor executor); + + ApplicationBuilder setRouter(HttpRouter httpRouter); Application build(); } diff --git a/net-http-server/src/main/java/org/xbib/net/http/server/application/ApplicationCallable.java b/net-http-server/src/main/java/org/xbib/net/http/server/application/ApplicationCallable.java deleted file mode 100644 index 0b0c789..0000000 --- a/net-http-server/src/main/java/org/xbib/net/http/server/application/ApplicationCallable.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.xbib.net.http.server.application; - -import java.util.concurrent.Callable; -import org.xbib.net.buffer.Releasable; - -public interface ApplicationCallable extends Callable, Releasable { -} diff --git a/net-http-server/src/main/java/org/xbib/net/http/server/application/BaseApplication.java b/net-http-server/src/main/java/org/xbib/net/http/server/application/BaseApplication.java index d28b33f..336828f 100644 --- a/net-http-server/src/main/java/org/xbib/net/http/server/application/BaseApplication.java +++ b/net-http-server/src/main/java/org/xbib/net/http/server/application/BaseApplication.java @@ -6,16 +6,15 @@ import java.nio.file.Files; import java.nio.file.Path; import java.time.Duration; import java.time.ZoneId; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Set; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import org.xbib.net.http.HttpAddress; -import org.xbib.net.http.HttpResponseStatus; import org.xbib.net.http.cookie.SameSite; import org.xbib.net.http.server.BaseHttpServerContext; import org.xbib.net.http.server.HttpException; @@ -26,6 +25,7 @@ import org.xbib.net.http.server.HttpServerContext; import org.xbib.net.http.server.cookie.IncomingCookieHandler; import org.xbib.net.http.server.cookie.OutgoingCookieHandler; import org.xbib.net.http.server.domain.HttpDomain; +import org.xbib.net.http.server.executor.Executor; import org.xbib.net.http.server.persist.Codec; import org.xbib.net.http.server.render.HttpResponseRenderer; import org.xbib.net.http.server.route.HttpRouter; @@ -60,6 +60,8 @@ public class BaseApplication implements Application { private HttpHandler outgoingSessionHandler; + protected List applicationModuleList; + protected BaseApplication(BaseApplicationBuilder builder) { this.builder = builder; this.sessionName = getSettings().get("session.name", "SESS"); @@ -67,12 +69,38 @@ public class BaseApplication implements Application { this.incomingCookieHandler = newIncomingCookieHandler(); this.outgoingCookieHandler = newOutgoingCookieHandler(); this.httpResponseRenderer = newResponseRenderer(); + this.applicationModuleList = new ArrayList<>(); + for (Map.Entry entry : builder.settings.getGroups("module").entrySet()) { + String moduleName = entry.getKey(); + Settings moduleSettings = entry.getValue(); + if (moduleSettings.getAsBoolean("enabled", true)) { + try { + String className = moduleSettings.get("class"); + @SuppressWarnings("unchecked") + Class clazz = + (Class) Class.forName(className, true, builder.classLoader); + ApplicationModule applicationModule = clazz.getConstructor(Application.class, String.class, Settings.class) + .newInstance(this, moduleName, moduleSettings); + applicationModuleList.add(applicationModule); + } catch (Exception e) { + logger.log(Level.WARNING, e.getMessage(), e); + throw new IllegalArgumentException("class not found or not loadable: " + e.getMessage()); + } + } else { + logger.log(Level.WARNING, "disabled module: " + moduleName); + } + } } public static BaseApplicationBuilder builder() { return new BaseApplicationBuilder(); } + @Override + public void addModule(ApplicationModule applicationModule) { + applicationModuleList.add(applicationModule); + } + @Override public Locale getLocale() { return builder.locale; @@ -88,10 +116,12 @@ public class BaseApplication implements Application { return builder.mimeTypeService; } + @Override public Path getHome() { return builder.home; } + @Override public String getContextPath() { return builder.contextPath; } @@ -101,10 +131,6 @@ public class BaseApplication implements Application { return builder.settings; } - public HttpRouter getRouter() { - return builder.router; - } - public String getSecret() { return builder.secret; } @@ -115,60 +141,17 @@ public class BaseApplication implements Application { @Override public Collection getModules() { - return builder.applicationModuleList; + return applicationModuleList; } @Override public Collection getDomains() { - return getRouter().getDomains(); + return builder.httpRouter.getDomains(); } @Override public Set getAddresses() { - return getRouter().getDomainsByAddress().keySet(); - } - - @Override - public void dispatch(HttpRequestBuilder httpRequestBuilder, - HttpResponseBuilder httpResponseBuilder) { - final Application application = this; - ApplicationCallable applicationCallable = new ApplicationCallable<>() { - @Override - public Object call() { - getRouter().route(application, httpRequestBuilder, httpResponseBuilder); - return true; - } - - @Override - public void release() { - httpRequestBuilder.release(); - httpResponseBuilder.release(); - } - }; - Future future = builder.executor.submit(applicationCallable); - logger.log(Level.FINEST, "dispatched " + future); - } - - @Override - public void dispatch(HttpRequestBuilder httpRequestBuilder, - HttpResponseBuilder httpResponseBuilder, - HttpResponseStatus httpResponseStatus) { - HttpServerContext httpServerContext = createContext(null, httpRequestBuilder, httpResponseBuilder); - ApplicationCallable applicationCallable = new ApplicationCallable<>() { - @Override - public Object call() { - getRouter().routeStatus(httpResponseStatus, httpServerContext); - return true; - } - - @Override - public void release() { - httpRequestBuilder.release(); - httpResponseBuilder.release(); - } - }; - Future future = builder.executor.submit(applicationCallable); - logger.log(Level.FINEST, "dispatched status " + future); + return builder.httpRouter.getDomainsByAddress().keySet(); } @Override @@ -242,13 +225,13 @@ public class BaseApplication implements Application { @Override public void onCreated(Session session) { logger.log(Level.FINER, "session name = " + sessionName + " created = " + session); - builder.applicationModuleList.forEach(module -> module.onOpen(session)); + applicationModuleList.forEach(module -> module.onOpen(session)); } @Override public void onDestroy(Session session) { logger.log(Level.FINER, "session name = " + sessionName + " destroyed = " + session); - builder.applicationModuleList.forEach(module -> module.onClose(session)); + applicationModuleList.forEach(module -> module.onClose(session)); } @Override @@ -264,12 +247,12 @@ public class BaseApplication implements Application { incomingSessionHandler.handle(httpServerContext); } // call modules after request/cookie/session setup - builder.applicationModuleList.forEach(module -> module.onOpen(httpServerContext)); + applicationModuleList.forEach(module -> module.onOpen(httpServerContext)); } catch (HttpException e) { - getRouter().routeException(e); + builder.httpRouter.routeException(e); httpServerContext.fail(); } catch (Throwable t) { - getRouter().routeToErrorHandler(httpServerContext, t); + builder.httpRouter.routeToErrorHandler(httpServerContext, t); httpServerContext.fail(); } } @@ -278,7 +261,7 @@ public class BaseApplication implements Application { public void onClose(HttpServerContext httpServerContext) { try { // call modules before session/cookie - builder.applicationModuleList.forEach(module -> module.onClose(httpServerContext)); + applicationModuleList.forEach(module -> module.onClose(httpServerContext)); if (builder.sessionsEnabled && outgoingSessionHandler != null) { outgoingSessionHandler.handle(httpServerContext); } @@ -286,9 +269,9 @@ public class BaseApplication implements Application { outgoingCookieHandler.handle(httpServerContext); } } catch (HttpException e) { - getRouter().routeException(e); + builder.httpRouter.routeException(e); } catch (Throwable t) { - getRouter().routeToErrorHandler(httpServerContext, t); + builder.httpRouter.routeToErrorHandler(httpServerContext, t); } finally { try { if (httpResponseRenderer != null) { @@ -300,6 +283,16 @@ public class BaseApplication implements Application { } } + @Override + public Executor getExecutor() { + return builder.executor; + } + + @Override + public HttpRouter getRouter() { + return builder.httpRouter; + } + @Override public Path resolve(String string) { if (string == null) { @@ -321,18 +314,9 @@ public class BaseApplication implements Application { @Override public void close() throws IOException { logger.log(Level.INFO, "application closing"); - // stop dispatching and stop dispatched requests builder.executor.shutdown(); - try { - if (!builder.executor.awaitTermination(10, TimeUnit.SECONDS)) { - List list = builder.executor.shutdownNow(); - logger.log(Level.WARNING, "unable to stop runnables " + list); - } - } catch (InterruptedException e) { - List list = builder.executor.shutdownNow(); - logger.log(Level.WARNING, "unable to stop runnables " + list); - } - builder.applicationModuleList.forEach(module -> { + // stop dispatching and stop dispatched requests + applicationModuleList.forEach(module -> { logger.log(Level.FINE, "application closing module " + module); module.onClose(); }); diff --git a/net-http-server/src/main/java/org/xbib/net/http/server/application/BaseApplicationBuilder.java b/net-http-server/src/main/java/org/xbib/net/http/server/application/BaseApplicationBuilder.java index ae628cd..5072c3f 100644 --- a/net-http-server/src/main/java/org/xbib/net/http/server/application/BaseApplicationBuilder.java +++ b/net-http-server/src/main/java/org/xbib/net/http/server/application/BaseApplicationBuilder.java @@ -3,30 +3,23 @@ package org.xbib.net.http.server.application; import java.nio.file.Path; import java.nio.file.Paths; import java.time.ZoneId; -import java.util.ArrayList; -import java.util.List; import java.util.Locale; -import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.ServiceLoader; import java.util.Set; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import java.util.logging.Logger; import org.xbib.config.ConfigLoader; import org.xbib.config.ConfigLogger; import org.xbib.config.ConfigParams; import org.xbib.config.SystemConfigLogger; +import org.xbib.net.http.server.executor.BaseExecutor; +import org.xbib.net.http.server.executor.Executor; import org.xbib.net.http.server.route.HttpRouter; import org.xbib.net.mime.MimeTypeService; -import org.xbib.net.util.NamedThreadFactory; import org.xbib.settings.Settings; public class BaseApplicationBuilder implements ApplicationBuilder { - private static final Logger logger = Logger.getLogger(BaseApplicationBuilder.class.getName()); - private static final ConfigLogger bootLogger; static { @@ -36,18 +29,11 @@ public class BaseApplicationBuilder implements ApplicationBuilder { bootLogger = optionalBootLogger.orElse(new SystemConfigLogger()); } + private static final Set DEFAULT_SUFFIXES = + Set.of("css", "js", "ico", "png", "jpg", "jpeg", "gif", "woff2"); + protected ClassLoader classLoader; - protected int blockingThreadCount; - - protected int blockingThreadQueueCount; - - protected int blockingThreadKeepAliveTime; - - protected TimeUnit blockingThreadKeepAliveTimeUnit; - - protected ThreadPoolExecutor executor; - protected Path home; protected String contextPath; @@ -56,8 +42,6 @@ public class BaseApplicationBuilder implements ApplicationBuilder { protected boolean sessionsEnabled; - protected HttpRouter router; - protected Locale locale; protected ZoneId zoneId; @@ -72,14 +56,12 @@ public class BaseApplicationBuilder implements ApplicationBuilder { protected Settings settings; - protected List applicationModuleList; + protected Executor executor; + + protected HttpRouter httpRouter; protected BaseApplicationBuilder() { this.classLoader = getClass().getClassLoader(); - this.blockingThreadCount = Runtime.getRuntime().availableProcessors(); - this.blockingThreadQueueCount = 0; // use fair synchronous queue - this.blockingThreadKeepAliveTime = 60; - this.blockingThreadKeepAliveTimeUnit = TimeUnit.SECONDS; this.home = Paths.get(System.getProperties().containsKey("application.home") ? System.getProperty("application.home") : "."); this.contextPath = "/"; this.secret = "secret"; @@ -87,7 +69,26 @@ public class BaseApplicationBuilder implements ApplicationBuilder { this.locale = Locale.getDefault(); this.zoneId = ZoneId.systemDefault(); this.mimeTypeService = new MimeTypeService(); - this.applicationModuleList = new ArrayList<>(); + String name = System.getProperty("application.name"); + if (name == null) { + name = "application"; + } + String profile = System.getProperty("application.profile"); + if (profile == null) { + profile = "developer"; + } + this.configParams = new ConfigParams() + .withDirectoryName(name) + .withFileNamesWithoutSuffix(profile) + .withSystemEnvironment() + .withSystemProperties(); + this.configLoader = ConfigLoader.getInstance() + .withLogger(bootLogger); + this.settings = configLoader.load(configParams); + if (staticFileSuffixes == null) { + staticFileSuffixes = DEFAULT_SUFFIXES; + } + this.executor = BaseExecutor.builder().build(); } public BaseApplicationBuilder setSettings(Settings settings) { @@ -95,36 +96,6 @@ public class BaseApplicationBuilder implements ApplicationBuilder { return this; } - @Override - public BaseApplicationBuilder setThreadCount(int blockingThreadCount) { - this.blockingThreadCount = blockingThreadCount; - return this; - } - - @Override - public BaseApplicationBuilder setQueueCount(int blockingThreadQueueCount) { - this.blockingThreadQueueCount = blockingThreadQueueCount; - return this; - } - - @Override - public BaseApplicationBuilder setKeepAliveTime(int blockingThreadKeepAliveTime) { - this.blockingThreadKeepAliveTime = blockingThreadKeepAliveTime; - return this; - } - - @Override - public BaseApplicationBuilder setKeepAliveTimeUnit(TimeUnit blockingThreadKeepAliveTimeUnit) { - this.blockingThreadKeepAliveTimeUnit = blockingThreadKeepAliveTimeUnit; - return this; - } - - @Override - public BaseApplicationBuilder setExecutor(ThreadPoolExecutor executor) { - this.executor = executor; - return this; - } - @Override public BaseApplicationBuilder setHome(Path home) { this.home = home; @@ -149,12 +120,6 @@ public class BaseApplicationBuilder implements ApplicationBuilder { return this; } - @Override - public ApplicationBuilder setRouter(HttpRouter router) { - this.router = router; - return this; - } - @Override public ApplicationBuilder setLocale(Locale locale) { this.locale = locale; @@ -180,73 +145,20 @@ public class BaseApplicationBuilder implements ApplicationBuilder { } @Override - public ApplicationBuilder registerModule(ApplicationModule applicationModule) { - applicationModuleList.add(applicationModule); + public ApplicationBuilder setExecutor(Executor executor) { + this.executor = executor; + return this; + } + + @Override + public ApplicationBuilder setRouter(HttpRouter httpRouter) { + this.httpRouter = httpRouter; return this; } @Override public Application build() { - prepareApplication(); - Application application = new BaseApplication(this); - setupApplication(application); - return application; + Objects.requireNonNull(httpRouter); + return new BaseApplication(this); } - - protected void prepareApplication() { - String name = System.getProperty("application.name"); - if (name == null) { - name = "application"; - } - String profile = System.getProperty("application.profile"); - if (profile == null) { - profile = "developer"; - } - String[] args = profile.split(";"); - this.configParams = new ConfigParams() - .withArgs(args) - .withDirectoryName(name) - .withFileNamesWithoutSuffix(args[0]) - .withSystemEnvironment() - .withSystemProperties(); - this.configLoader = ConfigLoader.getInstance() - .withLogger(bootLogger); - this.settings = configLoader.load(configParams); - if (staticFileSuffixes == null) { - staticFileSuffixes = DEFAULT_SUFFIXES; - } - if (this.executor == null) { - this.executor = new ApplicationThreadPoolExecutor(blockingThreadCount, blockingThreadQueueCount, - blockingThreadKeepAliveTime, blockingThreadKeepAliveTimeUnit, - new NamedThreadFactory("org-xbib-net-http-server-application")); - this.executor.setRejectedExecutionHandler((runnable, threadPoolExecutor) -> - logger.log(Level.SEVERE, "rejected " + runnable + " for thread pool executor = " + threadPoolExecutor)); - } - } - - protected void setupApplication(Application application) { - for (Map.Entry entry : settings.getGroups("module").entrySet()) { - String moduleName = entry.getKey(); - Settings moduleSettings = entry.getValue(); - if (moduleSettings.getAsBoolean("enabled", true)) { - try { - String className = moduleSettings.get("class"); - @SuppressWarnings("unchecked") - Class clazz = - (Class) Class.forName(className, true, classLoader); - ApplicationModule applicationModule = clazz.getConstructor(Application.class, String.class, Settings.class) - .newInstance(application, moduleName, moduleSettings); - applicationModuleList.add(applicationModule); - } catch (Exception e) { - logger.log(Level.WARNING, e.getMessage(), e); - throw new IllegalArgumentException("class not found or not loadable: " + e.getMessage()); - } - } else { - logger.log(Level.WARNING, "disabled module: " + moduleName); - } - } - } - - private static final Set DEFAULT_SUFFIXES = - Set.of("css", "js", "ico", "png", "jpg", "jpeg", "gif", "woff2"); } diff --git a/net-http-server/src/main/java/org/xbib/net/http/server/executor/BaseExecutor.java b/net-http-server/src/main/java/org/xbib/net/http/server/executor/BaseExecutor.java new file mode 100644 index 0000000..986c844 --- /dev/null +++ b/net-http-server/src/main/java/org/xbib/net/http/server/executor/BaseExecutor.java @@ -0,0 +1,41 @@ +package org.xbib.net.http.server.executor; + +import java.io.IOException; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class BaseExecutor implements Executor { + + private static final Logger logger = Logger.getLogger(BaseExecutor.class.getName()); + + private final BaseExecutorBuilder builder; + + protected BaseExecutor(BaseExecutorBuilder builder) { + this.builder = builder; + } + + public static BaseExecutorBuilder builder() { + return new BaseExecutorBuilder(); + } + + @Override + public void execute(CallableReleasable callableReleasable) { + builder.executor.submit(callableReleasable); + } + + @Override + public void shutdown() throws IOException { + builder.executor.shutdown(); + try { + if (!builder.executor.awaitTermination(builder.threadKeepAliveTime, builder.threadKeepAliveTimeUnit)) { + List list = builder.executor.shutdownNow(); + logger.log(Level.WARNING, "unable to stop runnables " + list); + } + } catch (InterruptedException e) { + List list = builder.executor.shutdownNow(); + logger.log(Level.WARNING, "unable to stop runnables " + list); + throw new IOException(e); + } + } +} diff --git a/net-http-server/src/main/java/org/xbib/net/http/server/executor/BaseExecutorBuilder.java b/net-http-server/src/main/java/org/xbib/net/http/server/executor/BaseExecutorBuilder.java new file mode 100644 index 0000000..f63d5bd --- /dev/null +++ b/net-http-server/src/main/java/org/xbib/net/http/server/executor/BaseExecutorBuilder.java @@ -0,0 +1,80 @@ +package org.xbib.net.http.server.executor; + +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.xbib.net.util.NamedThreadFactory; + +public class BaseExecutorBuilder implements ExecutorBuilder { + + private static final Logger logger = Logger.getLogger(BaseExecutorBuilder.class.getName()); + + protected String threadPrefix; + + protected int threadCount; + + protected int threadQueueCount; + + protected int threadKeepAliveTime; + + protected TimeUnit threadKeepAliveTimeUnit; + + protected ThreadPoolExecutor executor; + + protected BaseExecutorBuilder() { + this.threadPrefix = "org-xbib-net-server-executor"; + this.threadCount = Runtime.getRuntime().availableProcessors(); + this.threadQueueCount = 0; // use fair synchronous queue + this.threadKeepAliveTime = 10; + this.threadKeepAliveTimeUnit = TimeUnit.SECONDS; + } + + @Override + public ExecutorBuilder setThreadPrefix(String threadPrefix) { + this.threadPrefix = threadPrefix; + return this; + } + + @Override + public ExecutorBuilder setThreadCount(int threadCount) { + this.threadCount = threadCount; + return this; + } + + @Override + public ExecutorBuilder setQueueCount(int threadQueueCount) { + this.threadQueueCount = threadQueueCount; + return this; + } + + @Override + public ExecutorBuilder setKeepAliveTime(int keepAliveTime) { + this.threadKeepAliveTime = keepAliveTime; + return this; + } + + @Override + public ExecutorBuilder setKeepAliveTimeUnit(TimeUnit keepAliveTimeUnit) { + this.threadKeepAliveTimeUnit = keepAliveTimeUnit; + return this; + } + + @Override + public ExecutorBuilder setExecutor(ThreadPoolExecutor executor) { + this.executor = executor; + return this; + } + + @Override + public Executor build() { + if (executor == null) { + this.executor = new BaseThreadPoolExecutor(threadCount, threadQueueCount, + threadKeepAliveTime, threadKeepAliveTimeUnit, + new NamedThreadFactory(threadPrefix)); + this.executor.setRejectedExecutionHandler((runnable, threadPoolExecutor) -> + logger.log(Level.SEVERE, "rejected " + runnable + " for thread pool executor = " + threadPoolExecutor)); + } + return new BaseExecutor(this); + } +} diff --git a/net-http-server/src/main/java/org/xbib/net/http/server/application/ApplicationThreadPoolExecutor.java b/net-http-server/src/main/java/org/xbib/net/http/server/executor/BaseThreadPoolExecutor.java similarity index 63% rename from net-http-server/src/main/java/org/xbib/net/http/server/application/ApplicationThreadPoolExecutor.java rename to net-http-server/src/main/java/org/xbib/net/http/server/executor/BaseThreadPoolExecutor.java index 76e543c..2b72513 100644 --- a/net-http-server/src/main/java/org/xbib/net/http/server/application/ApplicationThreadPoolExecutor.java +++ b/net-http-server/src/main/java/org/xbib/net/http/server/executor/BaseThreadPoolExecutor.java @@ -1,4 +1,4 @@ -package org.xbib.net.http.server.application; +package org.xbib.net.http.server.executor; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; @@ -11,15 +11,15 @@ import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; -public class ApplicationThreadPoolExecutor extends ThreadPoolExecutor { +public class BaseThreadPoolExecutor extends ThreadPoolExecutor { - private final Logger logger = Logger.getLogger(ApplicationThreadPoolExecutor.class.getName()); + private final Logger logger = Logger.getLogger(BaseThreadPoolExecutor.class.getName()); - public ApplicationThreadPoolExecutor(int nThreads, - int maxQueue, - long keepAliveTime, - TimeUnit timeUnit, - ThreadFactory threadFactory) { + public BaseThreadPoolExecutor(int nThreads, + int maxQueue, + long keepAliveTime, + TimeUnit timeUnit, + ThreadFactory threadFactory) { super(nThreads, nThreads, keepAliveTime, timeUnit, createBlockingQueue(maxQueue), threadFactory); logger.log(Level.FINE, () -> "threadpool executor up with nThreads = " + nThreads + " keepAliveTime = " + keepAliveTime + @@ -34,7 +34,7 @@ public class ApplicationThreadPoolExecutor extends ThreadPoolExecutor { @Override protected RunnableFuture newTaskFor(Callable callable) { - return new ApplicationTask<>(callable); + return new Task<>(callable); } @Override @@ -45,10 +45,10 @@ public class ApplicationThreadPoolExecutor extends ThreadPoolExecutor { logger.log(Level.SEVERE, terminationCause.getMessage(), terminationCause); return; } - if (runnable instanceof ApplicationTask applicationTask) { - ApplicationCallable applicationCallable = (ApplicationCallable) applicationTask.getCallable(); - logger.log(Level.FINEST, () -> "releasing " + applicationCallable); - applicationCallable.release(); + if (runnable instanceof Task task) { + CallableReleasable callableReleasable = (CallableReleasable) task.getCallable(); + logger.log(Level.FINEST, () -> "releasing " + callableReleasable); + callableReleasable.release(); } } } diff --git a/net-http-server/src/main/java/org/xbib/net/http/server/executor/CallableReleasable.java b/net-http-server/src/main/java/org/xbib/net/http/server/executor/CallableReleasable.java new file mode 100644 index 0000000..07cb84e --- /dev/null +++ b/net-http-server/src/main/java/org/xbib/net/http/server/executor/CallableReleasable.java @@ -0,0 +1,7 @@ +package org.xbib.net.http.server.executor; + +import java.util.concurrent.Callable; +import org.xbib.net.buffer.Releasable; + +public interface CallableReleasable extends Callable, Releasable { +} diff --git a/net-http-server/src/main/java/org/xbib/net/http/server/executor/Executor.java b/net-http-server/src/main/java/org/xbib/net/http/server/executor/Executor.java new file mode 100644 index 0000000..459ce78 --- /dev/null +++ b/net-http-server/src/main/java/org/xbib/net/http/server/executor/Executor.java @@ -0,0 +1,13 @@ +package org.xbib.net.http.server.executor; + +import java.io.IOException; + +public interface Executor { + + /** + * Execute a task that must be released after execution. + */ + void execute(CallableReleasable callableReleasable); + + void shutdown() throws IOException; +} diff --git a/net-http-server/src/main/java/org/xbib/net/http/server/executor/ExecutorBuilder.java b/net-http-server/src/main/java/org/xbib/net/http/server/executor/ExecutorBuilder.java new file mode 100644 index 0000000..c2b3d1f --- /dev/null +++ b/net-http-server/src/main/java/org/xbib/net/http/server/executor/ExecutorBuilder.java @@ -0,0 +1,21 @@ +package org.xbib.net.http.server.executor; + +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +public interface ExecutorBuilder { + + ExecutorBuilder setThreadPrefix(String prefix); + + ExecutorBuilder setThreadCount(int threadCount); + + ExecutorBuilder setQueueCount(int threadQueueCount); + + ExecutorBuilder setKeepAliveTime(int keepAliveTime); + + ExecutorBuilder setKeepAliveTimeUnit(TimeUnit keepAliveTimeUnit); + + ExecutorBuilder setExecutor(ThreadPoolExecutor executor); + + Executor build(); +} diff --git a/net-http-server/src/main/java/org/xbib/net/http/server/application/ApplicationTask.java b/net-http-server/src/main/java/org/xbib/net/http/server/executor/Task.java similarity index 62% rename from net-http-server/src/main/java/org/xbib/net/http/server/application/ApplicationTask.java rename to net-http-server/src/main/java/org/xbib/net/http/server/executor/Task.java index 00c6659..0b6c46f 100644 --- a/net-http-server/src/main/java/org/xbib/net/http/server/application/ApplicationTask.java +++ b/net-http-server/src/main/java/org/xbib/net/http/server/executor/Task.java @@ -1,13 +1,13 @@ -package org.xbib.net.http.server.application; +package org.xbib.net.http.server.executor; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; -public class ApplicationTask extends FutureTask { +public class Task extends FutureTask { private final Callable callable; - public ApplicationTask(Callable callable) { + public Task(Callable callable) { super(callable); this.callable = callable; } diff --git a/net-http-server/src/main/java/org/xbib/net/http/server/route/BaseHttpRouter.java b/net-http-server/src/main/java/org/xbib/net/http/server/route/BaseHttpRouter.java index ad75d82..659f3b3 100644 --- a/net-http-server/src/main/java/org/xbib/net/http/server/route/BaseHttpRouter.java +++ b/net-http-server/src/main/java/org/xbib/net/http/server/route/BaseHttpRouter.java @@ -37,24 +37,8 @@ public class BaseHttpRouter implements HttpRouter { private final DomainsByAddress domainsByAddress; - private final HttpRouteResolver httpRouteResolver; - protected BaseHttpRouter(BaseHttpRouterBuilder builder) { this.builder = builder; - HttpRouteResolver.Builder httpRouteResolverBuilder = newHttpRouteResolverBuilder(); - for (HttpDomain domain : builder.domains) { - for (HttpService httpService : domain.getServices()) { - logger.log(Level.FINER, "adding " + domain.getAddress() + " " + httpService.getMethods() + - " prefix = " + httpService.getPrefix() + - " path = " + httpService.getPathSpecification() + " " + httpService); - HttpRoute httpRoute = new BaseHttpRoute(domain.getAddress(), - httpService.getMethods(), - httpService.getPrefix(), - httpService.getPathSpecification(), false); - httpRouteResolverBuilder.add(httpRoute, httpService); - } - } - this.httpRouteResolver = httpRouteResolverBuilder.build(); this.domains = createDomains(builder.domains); this.domainsByAddress = createAddresses(builder.domains); } @@ -63,10 +47,6 @@ public class BaseHttpRouter implements HttpRouter { return new BaseHttpRouterBuilder(); } - public HttpRouteResolver.Builder newHttpRouteResolverBuilder() { - return BaseHttpRouteResolver.builder(); - } - @Override public Collection getDomains() { return builder.domains; @@ -95,7 +75,7 @@ public class BaseHttpRouter implements HttpRouter { builder.prefix, requestBuilder.getRequestPath(), true); - httpRouteResolver.resolve(httpRoute, httpRouteResolverResults::add); + builder.httpRouteResolver.resolve(httpRoute, httpRouteResolverResults::add); HttpServerContext httpServerContext = application.createContext(httpDomain, requestBuilder, responseBuilder); application.onOpen(httpServerContext); try { diff --git a/net-http-server/src/main/java/org/xbib/net/http/server/route/BaseHttpRouterBuilder.java b/net-http-server/src/main/java/org/xbib/net/http/server/route/BaseHttpRouterBuilder.java index 6b13751..4b892f6 100644 --- a/net-http-server/src/main/java/org/xbib/net/http/server/route/BaseHttpRouterBuilder.java +++ b/net-http-server/src/main/java/org/xbib/net/http/server/route/BaseHttpRouterBuilder.java @@ -5,6 +5,8 @@ import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Objects; +import java.util.logging.Level; +import java.util.logging.Logger; import org.xbib.net.http.server.HttpHandler; import org.xbib.net.http.server.domain.HttpDomain; import org.xbib.net.http.server.handler.BadRequestHandler; @@ -14,15 +16,20 @@ import org.xbib.net.http.server.handler.NotFoundHandler; import org.xbib.net.http.server.handler.NotImplementedHandler; import org.xbib.net.http.server.handler.UnauthorizedHandler; import org.xbib.net.http.server.handler.VersionNotSupportedHandler; +import org.xbib.net.http.server.service.HttpService; public class BaseHttpRouterBuilder implements HttpRouterBuilder { + private static final Logger logger = Logger.getLogger(BaseHttpRouterBuilder.class.getName()); + protected String prefix; protected final Collection domains; protected final Map handlers; + protected HttpRouteResolver httpRouteResolver; + protected BaseHttpRouterBuilder() { prefix = ""; domains = new ArrayList<>(); @@ -58,11 +65,34 @@ public class BaseHttpRouterBuilder implements HttpRouterBuilder { return this; } + @Override + public BaseHttpRouterBuilder setRouteResolver(HttpRouteResolver httpRouteResolver) { + this.httpRouteResolver = httpRouteResolver; + return this; + } + @Override public BaseHttpRouter build() { if (domains.isEmpty()) { throw new IllegalArgumentException("no domain configured, unable to continue"); } + if (httpRouteResolver == null) { + HttpRouteResolver.Builder httpRouteResolverBuilder = BaseHttpRouteResolver.builder(); + for (HttpDomain domain : domains) { + for (HttpService httpService : domain.getServices()) { + logger.log(Level.FINER, "adding " + domain.getAddress() + " " + httpService.getMethods() + + " prefix = " + httpService.getPrefix() + + " path = " + httpService.getPathSpecification() + " " + httpService); + HttpRoute httpRoute = new BaseHttpRoute(domain.getAddress(), + httpService.getMethods(), + httpService.getPrefix(), + httpService.getPathSpecification(), + false); + httpRouteResolverBuilder.add(httpRoute, httpService); + } + } + this.httpRouteResolver = httpRouteResolverBuilder.build(); + } return new BaseHttpRouter(this); } } diff --git a/net-http-server/src/main/java/org/xbib/net/http/server/route/HttpRouterBuilder.java b/net-http-server/src/main/java/org/xbib/net/http/server/route/HttpRouterBuilder.java index 8dc8070..cce96f1 100644 --- a/net-http-server/src/main/java/org/xbib/net/http/server/route/HttpRouterBuilder.java +++ b/net-http-server/src/main/java/org/xbib/net/http/server/route/HttpRouterBuilder.java @@ -2,6 +2,7 @@ package org.xbib.net.http.server.route; import org.xbib.net.http.server.HttpHandler; import org.xbib.net.http.server.domain.HttpDomain; +import org.xbib.net.http.server.service.HttpService; public interface HttpRouterBuilder { @@ -11,5 +12,7 @@ public interface HttpRouterBuilder { HttpRouterBuilder addDomain(HttpDomain domain); + HttpRouterBuilder setRouteResolver(HttpRouteResolver httpRouteResolver); + HttpRouter build(); } diff --git a/settings.gradle b/settings.gradle index 651cc71..bbf1db3 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,7 +4,7 @@ dependencyResolutionManagement { version('gradle', '8.0.2') version('junit', '5.9.2') version('groovy', '4.0.11') - version('netty', '4.1.90.Final') + version('netty', '4.1.91.Final') version('netty-tcnative', '2.0.59.Final') version('datastructures', '2.0.0') version('config', '5.0.2')