From 849a77aeec01f22c9d6f1df488bec37773e4dd26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=CC=88rg=20Prante?= Date: Mon, 27 Jul 2020 10:43:57 +0200 Subject: [PATCH] introduce builders for server requests und responses, add context info on server requests --- gradle.properties | 2 +- .../netty/http/common}/util/HtmlUtils.java | 2 +- .../xbib/netty/http/server/api/Domain.java | 4 +- .../xbib/netty/http/server/api/Endpoint.java | 4 +- .../http/server/api/EndpointDispatcher.java | 9 - .../http/server/api/EndpointResolver.java | 10 +- .../netty/http/server/api/ServerRequest.java | 49 +- .../netty/http/server/api/ServerResponse.java | 90 ++-- .../src/main/java/module-info.java | 10 +- .../xbib/netty/http/server/AcceptState.java | 23 + .../xbib/netty/http/server/BaseTransport.java | 57 +++ .../http/server/DefaultServerConfig.java | 10 +- .../netty/http/server/HttpServerDomain.java | 97 ++-- .../netty/http/server/HttpServerRequest.java | 419 ++++++++++++++++++ .../org/xbib/netty/http/server/Server.java | 113 ++--- .../http/server/endpoint/HttpEndpoint.java | 23 +- .../endpoint/HttpEndpointDescriptor.java | 20 +- .../server/endpoint/HttpEndpointResolver.java | 62 +-- .../endpoint/service/ResourceService.java | 180 +++++--- .../stream/SeekableChunkedNioStream.java | 46 -- .../server/{ => protocol/http1}/Http1.java | 4 +- .../http1}/Http1ChannelInitializer.java | 3 +- .../server/protocol/http1/Http1Transport.java | 75 ++++ .../http1}/HttpPipelinedRequest.java | 2 +- .../http1}/HttpPipelinedResponse.java | 2 +- .../http1}/HttpPipeliningHandler.java | 2 +- .../http1}/HttpServerResponse.java | 234 +++++++--- .../server/{ => protocol/http2}/Http2.java | 4 +- .../http2/Http2ChannelInitializer.java | 2 +- .../protocol/http2/Http2ServerResponse.java | 303 +++++++++++++ .../server/protocol/http2/Http2Transport.java | 84 ++++ .../http/server/transport/BaseTransport.java | 70 --- .../http/server/transport/Http1Transport.java | 43 -- .../server/transport/Http2ServerResponse.java | 201 --------- .../http/server/transport/Http2Transport.java | 50 --- .../server/transport/HttpServerRequest.java | 247 ----------- ...tty.http.server.api.ServerProtocolProvider | 4 +- .../http/server/test/BindExceptionTest.java | 3 +- .../http/server/test/ContextURLTest.java | 31 +- .../test/MultiDomainSecureServerTest.java | 7 +- .../server/test/MultiDomainServerTest.java | 7 +- .../http/server/test/ThreadLeakTest.java | 4 +- .../TransportLayerSecurityServerTest.java | 10 +- .../server/test/endpoint/EndpointTest.java | 35 +- .../test/hacks/HttpPipeliningHandlerTest.java | 6 +- .../http/server/test/http1/CleartextTest.java | 15 +- .../http/server/test/http1/EncryptedTest.java | 17 +- .../http/server/test/http1/FlushTest.java | 2 +- .../server/test/http1/MimeUploadTest.java | 3 +- .../http/server/test/http1/PostTest.java | 10 +- .../netty/http/server/test/http1/PutTest.java | 4 +- .../http/server/test/http1/StreamTest.java | 3 +- .../http/server/test/http2/CleartextTest.java | 29 +- .../http/server/test/http2/EncryptedTest.java | 17 +- .../http/server/test/http2/FlushTest.java | 2 +- .../server/test/http2/MixedProtocolTest.java | 16 +- .../http/server/test/http2/PostTest.java | 5 +- .../netty/http/server/test/http2/PutTest.java | 5 +- .../http/server/test/http2/StreamTest.java | 5 +- 59 files changed, 1573 insertions(+), 1223 deletions(-) rename {netty-http-server/src/main/java/org/xbib/netty/http/server => netty-http-common/src/main/java/org/xbib/netty/http/common}/util/HtmlUtils.java (97%) delete mode 100644 netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/EndpointDispatcher.java create mode 100644 netty-http-server/src/main/java/org/xbib/netty/http/server/AcceptState.java create mode 100644 netty-http-server/src/main/java/org/xbib/netty/http/server/BaseTransport.java create mode 100644 netty-http-server/src/main/java/org/xbib/netty/http/server/HttpServerRequest.java delete mode 100644 netty-http-server/src/main/java/org/xbib/netty/http/server/handler/stream/SeekableChunkedNioStream.java rename netty-http-server/src/main/java/org/xbib/netty/http/server/{ => protocol/http1}/Http1.java (75%) rename netty-http-server/src/main/java/org/xbib/netty/http/server/{handler/http => protocol/http1}/Http1ChannelInitializer.java (99%) create mode 100644 netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/http1/Http1Transport.java rename netty-http-server/src/main/java/org/xbib/netty/http/server/{handler/http => protocol/http1}/HttpPipelinedRequest.java (90%) rename netty-http-server/src/main/java/org/xbib/netty/http/server/{handler/http => protocol/http1}/HttpPipelinedResponse.java (94%) rename netty-http-server/src/main/java/org/xbib/netty/http/server/{handler/http => protocol/http1}/HttpPipeliningHandler.java (98%) rename netty-http-server/src/main/java/org/xbib/netty/http/server/{transport => protocol/http1}/HttpServerResponse.java (50%) rename netty-http-server/src/main/java/org/xbib/netty/http/server/{ => protocol/http2}/Http2.java (75%) rename netty-http-server/src/main/java/org/xbib/netty/http/server/{handler => protocol}/http2/Http2ChannelInitializer.java (99%) create mode 100644 netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/http2/Http2ServerResponse.java create mode 100644 netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/http2/Http2Transport.java delete mode 100644 netty-http-server/src/main/java/org/xbib/netty/http/server/transport/BaseTransport.java delete mode 100644 netty-http-server/src/main/java/org/xbib/netty/http/server/transport/Http1Transport.java delete mode 100644 netty-http-server/src/main/java/org/xbib/netty/http/server/transport/Http2ServerResponse.java delete mode 100644 netty-http-server/src/main/java/org/xbib/netty/http/server/transport/Http2Transport.java delete mode 100644 netty-http-server/src/main/java/org/xbib/netty/http/server/transport/HttpServerRequest.java diff --git a/gradle.properties b/gradle.properties index 317d526..7b04382 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ group = org.xbib name = netty-http -version = 4.1.51.2 +version = 4.1.51.3 gradle.wrapper.version = 6.4.1 netty.version = 4.1.51.Final diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/util/HtmlUtils.java b/netty-http-common/src/main/java/org/xbib/netty/http/common/util/HtmlUtils.java similarity index 97% rename from netty-http-server/src/main/java/org/xbib/netty/http/server/util/HtmlUtils.java rename to netty-http-common/src/main/java/org/xbib/netty/http/common/util/HtmlUtils.java index 8938e2a..0a666a9 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/util/HtmlUtils.java +++ b/netty-http-common/src/main/java/org/xbib/netty/http/common/util/HtmlUtils.java @@ -1,4 +1,4 @@ -package org.xbib.netty.http.server.util; +package org.xbib.netty.http.common.util; public class HtmlUtils { diff --git a/netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/Domain.java b/netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/Domain.java index f9c3c73..1d83b23 100644 --- a/netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/Domain.java +++ b/netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/Domain.java @@ -18,7 +18,5 @@ public interface Domain> { Collection getCertificateChain(); - String findContextPathOf(ServerRequest serverRequest) throws IOException; - - void handle(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException; + void handle(ServerRequest.Builder serverRequestBuilder, ServerResponse.Builder serverResponseBuilder) throws IOException; } diff --git a/netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/Endpoint.java b/netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/Endpoint.java index 1248bf2..69bd7a6 100644 --- a/netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/Endpoint.java +++ b/netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/Endpoint.java @@ -10,7 +10,9 @@ public interface Endpoint { boolean matches(D descriptor); - void resolveUriTemplate(ServerRequest serverRequest) throws IOException; + ServerRequest resolveRequest(ServerRequest.Builder serverRequestBuilder, + Domain>> domain, + EndpointResolver> endpointResolver); void before(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException; diff --git a/netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/EndpointDispatcher.java b/netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/EndpointDispatcher.java deleted file mode 100644 index d7a6571..0000000 --- a/netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/EndpointDispatcher.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.xbib.netty.http.server.api; - -import java.io.IOException; - -@FunctionalInterface -public interface EndpointDispatcher> { - - void dispatch(E endpoint, ServerRequest serverRequest, ServerResponse serverResponse) throws IOException; -} diff --git a/netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/EndpointResolver.java b/netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/EndpointResolver.java index 418bf86..1adbc33 100644 --- a/netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/EndpointResolver.java +++ b/netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/EndpointResolver.java @@ -1,16 +1,14 @@ package org.xbib.netty.http.server.api; +import org.xbib.netty.http.common.HttpMethod; import java.io.IOException; import java.util.List; -public interface EndpointResolver> { +public interface EndpointResolver> { - List matchingEndpointsFor(ServerRequest serverRequest); + List matchingEndpointsFor(String path, HttpMethod method, String contentType); - void resolve(List matchingEndpoints, - ServerRequest serverRequest) throws IOException; - - void handle(List matchingEndpoints, + void handle(E matchingEndpoint, ServerRequest serverRequest, ServerResponse serverResponse) throws IOException; } diff --git a/netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/ServerRequest.java b/netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/ServerRequest.java index 1e6bb23..2b08959 100644 --- a/netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/ServerRequest.java +++ b/netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/ServerRequest.java @@ -6,23 +6,23 @@ import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpMethod; import org.xbib.net.URL; import org.xbib.netty.http.common.HttpParameters; - import javax.net.ssl.SSLSession; -import java.io.IOException; import java.net.InetSocketAddress; import java.util.List; import java.util.Map; public interface ServerRequest { + Builder getBuilder(); + + InetSocketAddress getLocalAddress(); + + InetSocketAddress getRemoteAddress(); + URL getURL(); - void setContext(List context); - List getContext(); - void addPathParameter(String key, String value) throws IOException; - Map getPathParameters(); String getRequestURI(); @@ -49,7 +49,40 @@ public interface ServerRequest { SSLSession getSession(); - InetSocketAddress getLocalAddress(); + URL getBaseURL(); - InetSocketAddress getRemoteAddress(); + URL getContextURL(); + + Domain>> getDomain(); + + EndpointResolver> getEndpointResolver(); + + Endpoint getEndpoint(); + + interface Builder { + + String getRequestURI(); + + HttpMethod getMethod(); + + HttpHeaders getHeaders(); + + String getEffectiveRequestPath(); + + Builder setBaseURL(URL baseURL); + + Builder setDomain(Domain>> domain); + + Builder setEndpointResolver(EndpointResolver> endpointResolver); + + Builder setEndpoint(Endpoint endpoint); + + Builder setContext(List context); + + Builder addPathParameter(String key, String value); + + ServerRequest build(); + + void release(); + } } diff --git a/netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/ServerResponse.java b/netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/ServerResponse.java index 47e0b4c..fe32b1f 100644 --- a/netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/ServerResponse.java +++ b/netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/ServerResponse.java @@ -2,38 +2,34 @@ package org.xbib.netty.http.server.api; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufOutputStream; -import io.netty.buffer.ByteBufUtil; -import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.stream.ChunkedInput; import org.xbib.netty.http.common.cookie.Cookie; - +import java.io.Flushable; +import java.io.IOException; import java.nio.CharBuffer; import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; /** * HTTP server response. */ -public interface ServerResponse { +public interface ServerResponse extends Flushable { - ChannelHandlerContext getChannelHandlerContext(); + Builder getBuilder(); - HttpResponseStatus getStatus(); + Integer getStreamId(); - ServerResponse withStatus(HttpResponseStatus httpResponseStatus); + Integer getSequenceId(); - ServerResponse withHeader(CharSequence name, String value); - - ServerResponse withContentType(String contentType); - - ServerResponse withCharset(Charset charset); - - ServerResponse withCookie(Cookie cookie); + Long getResponseId(); ByteBufOutputStream getOutputStream(); - void flush(); + void flush() throws IOException; + + void write(String content); + + void write(CharBuffer charBuffer, Charset charset); void write(byte[] bytes); @@ -43,56 +39,30 @@ public interface ServerResponse { void write(ChunkedInput chunkedInput); - static void write(ServerResponse serverResponse, int status) { - write(serverResponse, HttpResponseStatus.valueOf(status)); - } + interface Builder { - static void write(ServerResponse serverResponse, HttpResponseStatus status) { - write(serverResponse, status, "application/octet-stream", EMPTY_STRING); - } + Builder setStatus(HttpResponseStatus httpResponseStatus); - /** - * Responses to a HEAD request. - * @param serverResponse server response - * @param status status - * @param contentType content-type as if it were for a GET request (RFC 2616) - */ - static void write(ServerResponse serverResponse, HttpResponseStatus status, String contentType) { - write(serverResponse, status, contentType, EMPTY_STRING); - } + Builder setContentType(CharSequence contentType); - static void write(ServerResponse serverResponse, String text) { - write(serverResponse, HttpResponseStatus.OK, "text/plain", text); - } + Builder setCharset(Charset charset); - static void write(ServerResponse serverResponse, HttpResponseStatus status, String contentType, String text) { - ByteBuf byteBuf = ByteBufUtil.writeUtf8(serverResponse.getChannelHandlerContext().alloc(), text); - serverResponse.withStatus(status) - .withContentType(contentType) - .withCharset(StandardCharsets.UTF_8) - .write(byteBuf); - } + Builder setHeader(CharSequence name, String value); - static void write(ServerResponse serverResponse, HttpResponseStatus status, String contentType, ByteBuf byteBuf) { - serverResponse.withStatus(status) - .withContentType(contentType) - .withCharset(StandardCharsets.UTF_8) - .write(byteBuf); - } + Builder setTrailingHeader(CharSequence name, String value); - static void write(ServerResponse serverResponse, - HttpResponseStatus status, String contentType, String text, Charset charset) { - write(serverResponse, status, contentType, CharBuffer.allocate(text.length()).append(text), charset); - } + Builder addCookie(Cookie cookie); - static void write(ServerResponse serverResponse, HttpResponseStatus status, String contentType, - CharBuffer charBuffer, Charset charset) { - ByteBuf byteBuf = ByteBufUtil.encodeString(serverResponse.getChannelHandlerContext().alloc(), charBuffer, charset); - serverResponse.withStatus(status) - .withContentType(contentType) - .withCharset(charset) - .write(byteBuf); - } + Builder shouldClose(boolean shouldClose); - String EMPTY_STRING = ""; + Builder shouldAddServerName(boolean shouldAddServerName); + + Builder setSequenceId(Integer sequenceId); + + Builder setStreamId(Integer streamId); + + Builder setResponseId(Long responseId); + + ServerResponse build(); + } } diff --git a/netty-http-server/src/main/java/module-info.java b/netty-http-server/src/main/java/module-info.java index 80ab1f1..540ec10 100644 --- a/netty-http-server/src/main/java/module-info.java +++ b/netty-http-server/src/main/java/module-info.java @@ -1,5 +1,5 @@ -import org.xbib.netty.http.server.Http1; -import org.xbib.netty.http.server.Http2; +import org.xbib.netty.http.server.protocol.http1.Http1; +import org.xbib.netty.http.server.protocol.http2.Http2; module org.xbib.netty.http.server { uses org.xbib.netty.http.server.api.security.ServerCertificateProvider; @@ -10,10 +10,8 @@ module org.xbib.netty.http.server { exports org.xbib.netty.http.server.endpoint; exports org.xbib.netty.http.server.endpoint.service; exports org.xbib.netty.http.server.handler; - exports org.xbib.netty.http.server.handler.http; - exports org.xbib.netty.http.server.handler.http2; - exports org.xbib.netty.http.server.handler.stream; - exports org.xbib.netty.http.server.transport; + exports org.xbib.netty.http.server.protocol.http1; + exports org.xbib.netty.http.server.protocol.http2; exports org.xbib.netty.http.server.util; requires transitive org.xbib.netty.http.server.api; requires java.logging; diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/AcceptState.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/AcceptState.java new file mode 100644 index 0000000..c03948d --- /dev/null +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/AcceptState.java @@ -0,0 +1,23 @@ +package org.xbib.netty.http.server; + +import io.netty.handler.codec.http.HttpResponseStatus; + +public enum AcceptState { + + OK(HttpResponseStatus.OK, null, null), + MISSING_HOST_HEADER(HttpResponseStatus.BAD_REQUEST, "application/octet-stream", "missing 'Host' header"), + EXPECTATION_FAILED(HttpResponseStatus.EXPECTATION_FAILED, null, null), + UNSUPPORTED_HTTP_VERSION( HttpResponseStatus.BAD_REQUEST, "application/octet-stream", "unsupported HTTP version"); + + HttpResponseStatus status; + + String contentType; + + String content; + + AcceptState(HttpResponseStatus status, String contentType, String content) { + this.status = status; + this.contentType = contentType; + this.content = content; + } +} diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/BaseTransport.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/BaseTransport.java new file mode 100644 index 0000000..860fb99 --- /dev/null +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/BaseTransport.java @@ -0,0 +1,57 @@ +package org.xbib.netty.http.server; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.HttpHeaderNames; +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.codec.http.HttpVersion; +import org.xbib.netty.http.server.api.ServerTransport; + +import java.util.concurrent.atomic.AtomicLong; +import java.util.logging.Level; +import java.util.logging.Logger; + +public abstract class BaseTransport implements ServerTransport { + + private static final Logger logger = Logger.getLogger(BaseTransport.class.getName()); + + protected final Server server; + + protected BaseTransport(Server server) { + this.server = server; + } + + @Override + public void exceptionReceived(ChannelHandlerContext ctx, Throwable throwable) { + logger.log(Level.WARNING, throwable.getMessage(), throwable); + } + + /** + * Accepts a request, performing various validation checks + * and required special header handling, possibly returning an + * appropriate response. + * + * @param httpVersion the server HTTP version + * @param reqHeaders the request headers + * @return whether further processing should be performed + */ + protected static AcceptState acceptRequest(HttpVersion httpVersion, + HttpHeaders reqHeaders) { + if (httpVersion.majorVersion() == 1 || httpVersion.majorVersion() == 2) { + if (!reqHeaders.contains(HttpHeaderNames.HOST)) { + // RFC2616#14.23: missing Host header gets 400 + return AcceptState.MISSING_HOST_HEADER; + } + // return a continue response before reading body + String expect = reqHeaders.get(HttpHeaderNames.EXPECT); + if (expect != null) { + if (!"100-continue".equalsIgnoreCase(expect)) { + // RFC2616#14.20: if unknown expect, send 417 + return AcceptState.EXPECTATION_FAILED; + } + } + return AcceptState.OK; + } else { + return AcceptState.UNSUPPORTED_HTTP_VERSION; + } + } +} diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/DefaultServerConfig.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/DefaultServerConfig.java index 7098558..876d83c 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/DefaultServerConfig.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/DefaultServerConfig.java @@ -45,19 +45,19 @@ public class DefaultServerConfig implements ServerConfig { int PARENT_THREAD_COUNT = 0; /** - * Child thread count. Let Netty decide. + * Let Netty decide about child thread count. */ int CHILD_THREAD_COUNT = 0; /** - * Blocking thread pool count. + * Blocking thread pool count. Disabled by default, use Netty threads. */ - int BLOCKING_THREAD_COUNT = Runtime.getRuntime().availableProcessors(); + int BLOCKING_THREAD_COUNT = 0; /** - * Blocking thread pool queue count. + * Blocking thread pool queue count. Disabled by default, use Netty threads. */ - int BLOCKING_QUEUE_COUNT = 1024; + int BLOCKING_QUEUE_COUNT = 0; /** * Default for SO_REUSEADDR. diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/HttpServerDomain.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/HttpServerDomain.java index 6c47d71..720b05b 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/HttpServerDomain.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/HttpServerDomain.java @@ -7,7 +7,9 @@ import io.netty.handler.ssl.CipherSuiteFilter; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.SslProvider; +import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; import org.xbib.netty.http.common.HttpAddress; +import org.xbib.netty.http.common.HttpMethod; import org.xbib.netty.http.server.api.Domain; import org.xbib.netty.http.server.api.EndpointResolver; import org.xbib.netty.http.server.api.security.ServerCertificateProvider; @@ -36,21 +38,14 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.ServiceLoader; -import java.util.logging.Level; -import java.util.logging.Logger; /** * The {@code HttpServerDomain} class represents a virtual server with a name. */ public class HttpServerDomain implements Domain { - private static final Logger logger = Logger.getLogger(HttpServerDomain.class.getName()); - - private static final String EMPTY = ""; - private final String name; private final HttpAddress httpAddress; @@ -137,40 +132,41 @@ public class HttpServerDomain implements Domain { } /** - * Evaluate the context path of a given request. - * The request is not dispatched. - * URI request parameters are evaluated. - * @param serverRequest the server request - * @return the context path - * @throws IOException if handling fails - */ - @Override - public String findContextPathOf(ServerRequest serverRequest) throws IOException { - if (serverRequest == null) { - return EMPTY; - } - Map.Entry> resolved = resolve(serverRequest); - if (resolved != null) { - resolved.getKey().resolve(resolved.getValue(), serverRequest); - return serverRequest.getContextPath(); - } - return null; - } - - /** - * Handle server requests by resolving and handling a server request. - * @param serverRequest the server request - * @param serverResponse the server response + * Handle server requests by resolving and handling. + * @param serverRequestBuilder the server request + * @param serverResponseBuilder the server response * @throws IOException if handling server request fails */ @Override - public void handle(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException { - Map.Entry> resolved = resolve(serverRequest); - if (resolved != null) { - resolved.getKey().handle(resolved.getValue(), serverRequest, serverResponse); + public void handle(ServerRequest.Builder serverRequestBuilder, + ServerResponse.Builder serverResponseBuilder) throws IOException { + String path = extractPath(serverRequestBuilder.getRequestURI()); + HttpMethod method = Enum.valueOf(HttpMethod.class, serverRequestBuilder.getMethod().name()); + String contentType = serverRequestBuilder.getHeaders().get(CONTENT_TYPE); + HttpEndpointResolver httpEndpointResolver = null; + List endpoints = null; + for (HttpEndpointResolver endpointResolver : httpEndpointResolvers) { + List matchingEndpoints = endpointResolver.matchingEndpointsFor(path, method, contentType); + if (!matchingEndpoints.isEmpty()) { + httpEndpointResolver = endpointResolver; + endpoints = matchingEndpoints; + break; + } + } + if (endpoints != null) { + for (HttpEndpoint httpEndpoint : endpoints) { + ServerRequest resolvedServerRequest = httpEndpoint.resolveRequest(serverRequestBuilder, this, httpEndpointResolver); + if (serverResponseBuilder != null) { + httpEndpointResolver.handle(httpEndpoint, resolvedServerRequest, serverResponseBuilder.build()); + } + break; + } } else { - ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND, - "text/plain", "No endpoint found to match request"); + if (serverResponseBuilder != null) { + serverResponseBuilder.setStatus(HttpResponseStatus.NOT_FOUND) + .setContentType("text/plain") + .build().write("no endpoint found to match request"); + } } } @@ -179,19 +175,13 @@ public class HttpServerDomain implements Domain { return name + " (" + httpAddress + ")"; } - /** - * Just resolve a server request to a matching endpoint resolver with endpoints matched. - * @param serverRequest the server request - * @return the endpoint resolver together with the matching endpoints - */ - private Map.Entry> resolve(ServerRequest serverRequest) { - for (HttpEndpointResolver httpEndpointResolver : httpEndpointResolvers) { - List matchingEndpoints = httpEndpointResolver.matchingEndpointsFor(serverRequest); - if (!matchingEndpoints.isEmpty()) { - return Map.entry(httpEndpointResolver, matchingEndpoints); - } - } - return null; + private static String extractPath(String uri) { + String path = uri; + int pos = uri.lastIndexOf('#'); + path = pos >= 0 ? path.substring(0, pos) : path; + pos = uri.lastIndexOf('?'); + path = pos >= 0 ? path.substring(0, pos) : path; + return path; } public static class Builder { @@ -323,7 +313,6 @@ public class HttpServerDomain implements Domain { serverCertificateProvider.prepare(serverName); setKeyCertChain(serverCertificateProvider.getCertificateChain()); setKey(serverCertificateProvider.getPrivateKey(), serverCertificateProvider.getKeyPassword()); - logger.log(Level.INFO, "self signed certificate installed"); } } if (keyCertChain == null) { @@ -346,7 +335,7 @@ public class HttpServerDomain implements Domain { .addEndpoint(HttpEndpoint.builder() .setPath(path) .build()) - .setDispatcher((endpoint, req, resp) -> filter.handle(req, resp)) + .setDispatcher(filter::handle) .build()); return this; } @@ -360,7 +349,7 @@ public class HttpServerDomain implements Domain { .setPrefix(prefix) .setPath(path) .build()) - .setDispatcher((endpoint, req, resp) -> filter.handle(req, resp)) + .setDispatcher(filter::handle) .build()); return this; } @@ -376,7 +365,7 @@ public class HttpServerDomain implements Domain { .setPath(path) .setMethods(Arrays.asList(methods)) .build()) - .setDispatcher((endpoint, req, resp) -> filter.handle(req, resp)) + .setDispatcher(filter::handle) .build()); return this; } diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/HttpServerRequest.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/HttpServerRequest.java new file mode 100644 index 0000000..d0099a3 --- /dev/null +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/HttpServerRequest.java @@ -0,0 +1,419 @@ +package org.xbib.netty.http.server; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufInputStream; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpHeaderValues; +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpUtil; +import org.xbib.net.Pair; +import org.xbib.net.PercentDecoder; +import org.xbib.net.QueryParameters; +import org.xbib.net.URL; +import org.xbib.netty.http.common.HttpParameters; +import org.xbib.netty.http.server.api.Domain; +import org.xbib.netty.http.server.api.Endpoint; +import org.xbib.netty.http.server.api.EndpointResolver; +import org.xbib.netty.http.server.api.ServerRequest; +import javax.net.ssl.SSLSession; +import java.net.InetSocketAddress; +import java.nio.charset.Charset; +import java.nio.charset.CodingErrorAction; +import java.nio.charset.StandardCharsets; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * The {@code HttpServerRequest} class encapsulates a single request. + */ +public class HttpServerRequest implements ServerRequest { + + private static final Logger logger = Logger.getLogger(HttpServerRequest.class.getName()); + + private static final String PATH_SEPARATOR = "/"; + + private final Builder builder; + + private final InetSocketAddress localAddress; + + private final InetSocketAddress remoteAddress; + + private final FullHttpRequest httpRequest; + + private final URL baseURL; + + private final URL contextURL; + + private final URL url; + + private final List context; + + private final String contextPath; + + private final HttpParameters parameters; + + private final Map pathParameters; + + private final String effectiveRequestPath; + + private final Integer sequenceId; + + private final Integer streamId; + + private final Long requestId; + + private final SSLSession sslSession; + + private final Domain>> domain; + + private final EndpointResolver> endpointResolver; + + private final Endpoint endpoint; + + private HttpServerRequest(Builder builder) { + this.builder = builder; + this.localAddress = builder.localAddress; + this.remoteAddress = builder.remoteAddress; + this.httpRequest = builder.fullHttpRequest; + this.baseURL = builder.baseURL; + this.contextURL = builder.contextURL; + this.url = builder.url; + this.context = builder.context; + this.contextPath = builder.contextPath; + this.parameters = builder.parameters; + this.pathParameters = builder.pathParameters; + this.effectiveRequestPath = builder.effectiveRequestPath; + this.sequenceId = builder.sequenceId; + this.streamId = builder.streamId; + this.requestId = builder.requestId; + this.sslSession = builder.sslSession; + this.domain = builder.domain; + this.endpointResolver = builder.endpointResolver; + this.endpoint = builder.endpoint; + } + + public Builder getBuilder() { + return builder; + } + + public static Builder builder() { + return new Builder(); + } + + @Override + public InetSocketAddress getLocalAddress() { + return localAddress; + } + + @Override + public InetSocketAddress getRemoteAddress() { + return remoteAddress; + } + + @Override + public HttpMethod getMethod() { + return httpRequest.method(); + } + + @Override + public String getRequestURI() { + return httpRequest.uri(); + } + + @Override + public HttpHeaders getHeaders() { + return httpRequest.headers(); + } + + @Override + public ByteBuf getContent() { + return httpRequest.content(); + } + + @Override + public ByteBufInputStream getInputStream() { + return new ByteBufInputStream(httpRequest.content(), true); + } + + @Override + public URL getBaseURL() { + return baseURL; + } + + @Override + public List getContext() { + return context; + } + + @Override + public String getContextPath() { + return contextPath; + } + + @Override + public URL getContextURL() { + return contextURL; + } + + @Override + public String getEffectiveRequestPath() { + return effectiveRequestPath; + } + + @Override + public Map getPathParameters() { + return pathParameters; + } + + @Override + public Domain>> getDomain() { + return domain; + } + + @Override + public EndpointResolver> getEndpointResolver() { + return endpointResolver; + } + + @Override + public Endpoint getEndpoint() { + return endpoint; + } + + @Override + public URL getURL() { + return url; + } + + @Override + public HttpParameters getParameters() { + return parameters; + } + + @Override + public Integer getSequenceId() { + return sequenceId; + } + + @Override + public Integer getStreamId() { + return streamId; + } + + @Override + public Long getRequestId() { + return requestId; + } + + @Override + public SSLSession getSession() { + return sslSession; + } + + @Override + public String toString() { + return "ServerRequest[request=" + httpRequest + "]"; + } + + public static class Builder implements ServerRequest.Builder { + + private final Map pathParameters; + + private InetSocketAddress localAddress; + + private InetSocketAddress remoteAddress; + + private FullHttpRequest fullHttpRequest; + + private URL baseURL; + + private URL contextURL; + + private URL url; + + private List context; + + private String contextPath; + + private String effectiveRequestPath; + + private HttpParameters parameters; + + private Domain>> domain; + + private EndpointResolver> endpointResolver; + + private Endpoint endpoint; + + private Integer sequenceId; + + private Integer streamId; + + private Long requestId; + + private SSLSession sslSession; + + private Builder() { + this.pathParameters = new LinkedHashMap<>(); + } + + public Builder setLocalAddress(InetSocketAddress localAddress) { + this.localAddress = localAddress; + return this; + } + + public Builder setRemoteAddress(InetSocketAddress remoteAddress) { + this.remoteAddress = remoteAddress; + return this; + } + + public Builder setHttpRequest(FullHttpRequest fullHttpRequest) { + this.fullHttpRequest = fullHttpRequest; + return this; + } + + public String getRequestURI() { + return fullHttpRequest.uri(); + } + + public HttpMethod getMethod() { + return fullHttpRequest.method(); + } + + public HttpHeaders getHeaders() { + return fullHttpRequest.headers(); + } + + public Builder setBaseURL(URL baseURL) { + this.baseURL = baseURL; + return this; + } + + public Builder setContext(List context) { + this.context = context; + this.contextPath = context != null ? PATH_SEPARATOR + String.join(PATH_SEPARATOR, context) : null; + this.contextURL = baseURL.resolve(contextPath != null ? contextPath + "/" : ""); + String path = extractPath(fullHttpRequest.uri()); + String effective = contextPath != null && !PATH_SEPARATOR.equals(contextPath) && path.startsWith(contextPath) ? + path.substring(contextPath.length()) : path; + this.effectiveRequestPath = effective.isEmpty() ? PATH_SEPARATOR : effective; + return this; + } + + public String getEffectiveRequestPath() { + return effectiveRequestPath; + } + + public Builder addPathParameter(String key, String value) { + pathParameters.put(key, value); + //parameters.addRaw(key, value); + return this; + } + + public Builder setSequenceId(Integer sequenceId) { + this.sequenceId = sequenceId; + return this; + } + + public Builder setStreamId(Integer streamId) { + this.streamId = streamId; + return this; + } + + public Builder setRequestId(Long requestId) { + this.requestId = requestId; + return this; + } + + public Builder setSession(SSLSession sslSession) { + this.sslSession = sslSession; + return this; + } + + public Builder setDomain(Domain>> domain) { + this.domain = domain; + return this; + } + + public Builder setEndpointResolver(EndpointResolver> endpointResolver) { + this.endpointResolver = endpointResolver; + return this; + } + + public Builder setEndpoint(Endpoint endpoint) { + this.endpoint = endpoint; + return this; + } + + public ServerRequest build() { + // build URL and parameters + Charset charset = HttpUtil.getCharset(fullHttpRequest, StandardCharsets.UTF_8); + // creates path, query params, fragment + this.url = URL.builder() + .charset(charset, CodingErrorAction.REPLACE) + .path(fullHttpRequest.uri()) // creates path, query params, fragment + .build(); + QueryParameters queryParameters = url.getQueryParams(); + CharSequence mimeType = HttpUtil.getMimeType(fullHttpRequest); + ByteBuf byteBuf = fullHttpRequest.content(); + if (byteBuf != null) { + if (fullHttpRequest.method().equals(HttpMethod.POST)) { + String params; + // https://www.w3.org/TR/html4/interact/forms.html#h-17.13.4 + if (mimeType != null && HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED.toString().equals(mimeType.toString())) { + Charset htmlCharset = HttpUtil.getCharset(fullHttpRequest, StandardCharsets.ISO_8859_1); + params = byteBuf.toString(htmlCharset).replace('+', ' '); + if (logger.isLoggable(Level.FINER)) { + logger.log(Level.FINER, "html form, charset = " + htmlCharset + " param body = " + params); + } + queryParameters.addPercentEncodedBody(params); + } + } + } + // copy to HTTP parameters but percent-decoded (looks very clumsy) + PercentDecoder percentDecoder = new PercentDecoder(charset.newDecoder() + .onMalformedInput(CodingErrorAction.REPLACE) + .onUnmappableCharacter(CodingErrorAction.REPLACE)); + this.parameters = new HttpParameters(mimeType, charset); + for (Pair pair : queryParameters) { + try { + parameters.addRaw(percentDecoder.decode(pair.getFirst()), percentDecoder.decode(pair.getSecond())); + } catch (Exception e) { + // does not happen + throw new IllegalArgumentException(pair.toString()); + } + } + return new HttpServerRequest(this); + } + + @Override + public void release() { + fullHttpRequest.release(); + } + + public ServerRequest applyTo(Server server) { + URL baseURL = server.getBaseURL(fullHttpRequest.headers()); + setBaseURL(baseURL); + Domain> domain = server.getDomain(baseURL); + try { + domain.handle(this, null); + } catch (Throwable t) { + logger.log(Level.SEVERE, t.getMessage(), t); + } + return build(); + } + + private String extractPath(String uri) { + String path = uri; + int pos = uri.lastIndexOf('#'); + path = pos >= 0 ? path.substring(0, pos) : path; + pos = uri.lastIndexOf('?'); + path = pos >= 0 ? path.substring(0, pos) : path; + return path; + } + } +} diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/Server.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/Server.java index 5d3ab02..2a52871 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/Server.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/Server.java @@ -9,6 +9,7 @@ import io.netty.channel.WriteBufferWaterMark; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.ServerSocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.logging.LoggingHandler; import io.netty.handler.ssl.SslContext; @@ -27,7 +28,6 @@ import org.xbib.netty.http.server.api.ServerResponse; import org.xbib.netty.http.server.api.ServerTransport; import org.xbib.netty.http.server.endpoint.HttpEndpointResolver; import org.xbib.netty.http.server.security.CertificateUtils; -import org.xbib.netty.http.server.transport.HttpServerRequest; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.security.cert.CertificateExpiredException; @@ -70,10 +70,6 @@ public final class Server implements AutoCloseable { } } - private static final AtomicLong requestCounter = new AtomicLong(0); - - private static final AtomicLong responseCounter = new AtomicLong(0); - private final DefaultServerConfig serverConfig; private final EventLoopGroup parentEventLoopGroup; @@ -81,7 +77,8 @@ public final class Server implements AutoCloseable { private final EventLoopGroup childEventLoopGroup; /** - * A thread pool for executing blocking tasks. + * An extra thread pool for handling requests. May be null + * for executing request on the Netty event pool threads. */ private final BlockingThreadPoolExecutor executor; @@ -91,6 +88,10 @@ public final class Server implements AutoCloseable { private final List> protocolProviders; + private static final AtomicLong requestCounter = new AtomicLong(); + + private static final AtomicLong responseCounter = new AtomicLong(); + /** * Create a new HTTP server. * @@ -196,52 +197,23 @@ public final class Server implements AutoCloseable { return channelFuture; } - /** - * Returns the domain for the given server request. - * - * @param serverRequest the server request - * @return the domain - */ - public Domain> getDomain(ServerRequest serverRequest) { - return getDomain(getBaseURL(serverRequest)); + public AtomicLong getRequestCounter() { + return requestCounter; } - /** - * Returns the domain of the given URL. - * @param url the URL - * @return the domain - */ - public Domain> getDomain(URL url) { - return getDomain(hostAndPort(url)); + public AtomicLong getResponseCounter() { + return responseCounter; } - /** - * Returns the domain for the given host name. - * - * @param name the name of the virtual host with optional port, or null for the - * default domain - * @return the virtual host with the given name or the default domain - */ - public Domain> getDomain(String name) { - return serverConfig.getDomain(name); - } - - /** - * Return the base URL regarding to a server request. - * The base URL depends on the host and port defined in a reqeust, - * if no request is defined, the bind URL is taken. - * @param serverRequest the server request - * @return the URL - */ - public URL getBaseURL(ServerRequest serverRequest) { + public URL getBaseURL(HttpHeaders headers) { URL bindURL = serverConfig.getDefaultDomain().getHttpAddress().base(); - String scheme = serverRequest != null ? serverRequest.getHeaders().get("x-forwarded-proto") : null; + String scheme = headers != null ? headers.get("x-forwarded-proto") : null; if (scheme == null) { scheme = bindURL.getScheme(); } - String host = serverRequest != null ? serverRequest.getHeaders().get("x-forwarded-host") : null; + String host = headers != null ? headers.get("x-forwarded-host") : null; if (host == null) { - host = serverRequest != null ? serverRequest.getHeaders().get("host") : null; + host = headers != null ? headers.get("host") : null; if (host == null) { host = bindURL.getHost(); } @@ -262,62 +234,49 @@ public final class Server implements AutoCloseable { } /** - * Return the context URL of this server. This is equivalent to the bindURL - * @return the context URL - * @throws IOException should not happen + * Returns the domain of the given URL. + * @param url the URL + * @return the domain */ - public URL getContextURL() throws IOException { - return getContextURL(null); + public Domain> getDomain(URL url) { + return getDomain(hostAndPort(url)); } /** - * Get context URL of this server regarding to a given request. - * The context URL is the base URL with the path given in the matching - * domain prefix setting of the endpoint resolver. - * @param serverRequest the server request - * @return the context URL - * @throws IOException if context path finding fails + * Returns the domain for the given host name. + * + * @param name the name of the virtual host with optional port, or null for the + * default domain + * @return the virtual host with the given name or the default domain */ - public URL getContextURL(ServerRequest serverRequest) throws IOException { - URL baseURL = getBaseURL(serverRequest); - Domain> domain = getDomain(baseURL); - String context = domain.findContextPathOf(serverRequest); - if (!context.endsWith("/")) { - context = context + "/"; - } - return baseURL.resolve(context); + public Domain> getDomain(String name) { + return serverConfig.getDomain(name); } - public void handle(HttpServerRequest serverRequest, ServerResponse serverResponse) throws IOException { - Domain> domain = getDomain(serverRequest); - logger.log(Level.FINEST, () -> "found domain " + domain + " for " + serverRequest); + public void handle(ServerRequest.Builder serverRequestBuilder, + ServerResponse.Builder serverResponseBuilder) throws IOException { + URL baseURL = getBaseURL(serverRequestBuilder.getHeaders()); + serverRequestBuilder.setBaseURL(baseURL); + Domain> domain = getDomain(baseURL); if (executor != null) { executor.submit(() -> { try { - domain.handle(serverRequest, serverResponse); + domain.handle(serverRequestBuilder, serverResponseBuilder); } catch (IOException e) { executor.afterExecute(null, e); } finally { - serverRequest.release(); + serverRequestBuilder.release(); } }); } else { try { - domain.handle(serverRequest, serverResponse); + domain.handle(serverRequestBuilder, serverResponseBuilder); } finally { - serverRequest.release(); + serverRequestBuilder.release(); } } } - public AtomicLong getRequestCounter() { - return requestCounter; - } - - public AtomicLong getResponseCounter() { - return responseCounter; - } - public ServerTransport newTransport(HttpVersion httpVersion) { for (ServerProtocolProvider protocolProvider : protocolProviders) { if (protocolProvider.supportsMajorVersion(httpVersion.majorVersion())) { diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/HttpEndpoint.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/HttpEndpoint.java index fdfc1fa..c0dc56b 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/HttpEndpoint.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/HttpEndpoint.java @@ -5,7 +5,9 @@ import org.xbib.net.QueryParameters; import org.xbib.net.path.PathMatcher; import org.xbib.net.path.PathNormalizer; import org.xbib.netty.http.common.HttpMethod; +import org.xbib.netty.http.server.api.Domain; import org.xbib.netty.http.server.api.Endpoint; +import org.xbib.netty.http.server.api.EndpointResolver; import org.xbib.netty.http.server.api.ServerRequest; import org.xbib.netty.http.server.api.ServerResponse; import org.xbib.netty.http.server.api.Filter; @@ -82,19 +84,27 @@ public class HttpEndpoint implements Endpoint { } @Override - public void resolveUriTemplate(ServerRequest serverRequest) throws IOException { - if (pathMatcher.match(prefix + path, serverRequest.getEffectiveRequestPath())) { - QueryParameters queryParameters = pathMatcher.extractUriTemplateVariables(prefix + path, - serverRequest.getEffectiveRequestPath()); + public ServerRequest resolveRequest(ServerRequest.Builder serverRequestBuilder, + Domain>> domain, + EndpointResolver> endpointResolver) { + List context = pathMatcher.tokenizePath(getPrefix()); + serverRequestBuilder.setDomain(domain) + .setEndpointResolver(endpointResolver) + .setEndpoint((this)) + .setContext(context); + String pattern = prefix + path; + String effectiveRequestPath = serverRequestBuilder.getEffectiveRequestPath(); + if (pathMatcher.match(pattern, effectiveRequestPath)) { + QueryParameters queryParameters = pathMatcher.extractUriTemplateVariables(pattern, effectiveRequestPath); for (Pair pair : queryParameters) { - serverRequest.addPathParameter(pair.getFirst(), pair.getSecond()); + serverRequestBuilder.addPathParameter(pair.getFirst(), pair.getSecond()); } } + return serverRequestBuilder.build(); } @Override public void before(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException { - serverRequest.setContext(pathMatcher.tokenizePath(getPrefix())); if (serverResponse != null) { for (Filter filter : beforeFilters) { filter.handle(serverRequest, serverResponse); @@ -104,7 +114,6 @@ public class HttpEndpoint implements Endpoint { @Override public void after(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException { - serverRequest.setContext(pathMatcher.tokenizePath(getPrefix())); if (serverResponse != null) { for (Filter filter : afterFilters) { filter.handle(serverRequest, serverResponse); diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/HttpEndpointDescriptor.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/HttpEndpointDescriptor.java index cf4db02..df6dd45 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/HttpEndpointDescriptor.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/HttpEndpointDescriptor.java @@ -2,9 +2,6 @@ package org.xbib.netty.http.server.endpoint; import org.xbib.netty.http.common.HttpMethod; import org.xbib.netty.http.server.api.EndpointDescriptor; -import org.xbib.netty.http.server.api.ServerRequest; - -import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; public class HttpEndpointDescriptor implements EndpointDescriptor, Comparable { @@ -14,10 +11,10 @@ public class HttpEndpointDescriptor implements EndpointDescriptor, Comparable= 0 ? path.substring(0, pos) : path; - pos = uri.lastIndexOf('?'); - path = pos >= 0 ? path.substring(0, pos) : path; - return path; - } } diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/HttpEndpointResolver.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/HttpEndpointResolver.java index bcb562b..5bfbea5 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/HttpEndpointResolver.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/HttpEndpointResolver.java @@ -1,8 +1,9 @@ package org.xbib.netty.http.server.endpoint; +import org.xbib.netty.http.common.HttpMethod; import org.xbib.netty.http.common.util.LimitedConcurrentHashMap; -import org.xbib.netty.http.server.api.EndpointDispatcher; import org.xbib.netty.http.server.api.EndpointResolver; +import org.xbib.netty.http.server.api.Filter; import org.xbib.netty.http.server.api.ServerRequest; import org.xbib.netty.http.server.api.ServerResponse; import org.xbib.netty.http.server.api.annotation.Endpoint; @@ -23,27 +24,25 @@ public class HttpEndpointResolver implements EndpointResolver { private final List endpoints; - private final EndpointDispatcher endpointDispatcher; + private final Filter dispatcher; private final Map> endpointDescriptors; private HttpEndpointResolver(List endpoints, - EndpointDispatcher endpointDispatcher, - int limit) { - Objects.requireNonNull(endpointDispatcher); + Filter dispatcher, + Integer limit) { this.endpoints = endpoints; - this.endpointDispatcher = endpointDispatcher; - this.endpointDescriptors = new LimitedConcurrentHashMap<>(limit); + this.dispatcher = dispatcher; + this.endpointDescriptors = new LimitedConcurrentHashMap<>(limit != null ? limit : DEFAULT_LIMIT); } /** * Find matching endpoints for a server request. - * @param serverRequest the server request * @return a */ @Override - public List matchingEndpointsFor(ServerRequest serverRequest) { - HttpEndpointDescriptor httpEndpointDescriptor = new HttpEndpointDescriptor(serverRequest); + public List matchingEndpointsFor(String path, HttpMethod method, String contentType) { + HttpEndpointDescriptor httpEndpointDescriptor = new HttpEndpointDescriptor(path, method, contentType); endpointDescriptors.putIfAbsent(httpEndpointDescriptor, endpoints.stream() .filter(endpoint -> endpoint.matches(httpEndpointDescriptor)) .sorted(new HttpEndpoint.EndpointPathComparator(httpEndpointDescriptor.getSortKey())) @@ -52,30 +51,12 @@ public class HttpEndpointResolver implements EndpointResolver { } @Override - public void resolve(List matchingEndpoints, - ServerRequest serverRequest) throws IOException { - Objects.requireNonNull(matchingEndpoints); - for (HttpEndpoint endpoint : matchingEndpoints) { - endpoint.resolveUriTemplate(serverRequest); - endpoint.before(serverRequest, null); - break; - } - } - - @Override - public void handle(List matchingEndpoints, + public void handle(HttpEndpoint endpoint, ServerRequest serverRequest, ServerResponse serverResponse) throws IOException { - Objects.requireNonNull(matchingEndpoints); - for (HttpEndpoint endpoint : matchingEndpoints) { - endpoint.resolveUriTemplate(serverRequest); - endpoint.before(serverRequest, serverResponse); - endpointDispatcher.dispatch(endpoint, serverRequest, serverResponse); - endpoint.after(serverRequest, serverResponse); - if (serverResponse != null && serverResponse.getStatus() != null) { - break; - } - } + endpoint.before(serverRequest, serverResponse); + dispatcher.handle(serverRequest, serverResponse); + endpoint.after(serverRequest, serverResponse); } public static Builder builder() { @@ -86,11 +67,11 @@ public class HttpEndpointResolver implements EndpointResolver { private final List endpoints; - private int limit; + private Integer limit; private String prefix; - private EndpointDispatcher endpointDispatcher; + private Filter dispatcher; Builder() { this.limit = DEFAULT_LIMIT; @@ -98,7 +79,7 @@ public class HttpEndpointResolver implements EndpointResolver { } public Builder setLimit(int limit) { - this.limit = limit > 0 ? limit < 1024 * DEFAULT_LIMIT ? limit : DEFAULT_LIMIT : DEFAULT_LIMIT; + this.limit = limit; return this; } @@ -153,17 +134,20 @@ public class HttpEndpointResolver implements EndpointResolver { return this; } - public Builder setDispatcher(EndpointDispatcher endpointDispatcher) { - Objects.requireNonNull(endpointDispatcher); - this.endpointDispatcher = endpointDispatcher; + public Builder setDispatcher(Filter dispatcher) { + Objects.requireNonNull(dispatcher); + this.dispatcher = dispatcher; return this; } public HttpEndpointResolver build() { + Objects.requireNonNull(endpoints); + Objects.requireNonNull(dispatcher); + Objects.requireNonNull(limit); if (endpoints.isEmpty()) { throw new IllegalArgumentException("no endpoints configured"); } - return new HttpEndpointResolver(endpoints, endpointDispatcher, limit); + return new HttpEndpointResolver(endpoints, dispatcher, limit); } } } diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/service/ResourceService.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/service/ResourceService.java index 8bfbf8a..e92a4e6 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/service/ResourceService.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/service/ResourceService.java @@ -6,6 +6,7 @@ import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.stream.ChunkedNioStream; +import io.netty.util.AsciiString; import org.xbib.netty.http.common.util.DateTimeUtil; import org.xbib.netty.http.server.api.Filter; import org.xbib.netty.http.server.api.FilterConfig; @@ -58,24 +59,32 @@ public abstract class ResourceService implements Filter { protected abstract int getMaxAgeSeconds(); - private void handleCachedResource(ServerRequest serverRequest, ServerResponse serverResponse, Resource resource) { + private void handleCachedResource(ServerRequest serverRequest, ServerResponse serverResponse, Resource resource) throws IOException { if (logger.isLoggable(Level.FINER)) { logger.log(Level.FINER, "resource = " + resource); } if (resource.isDirectory()) { if (!resource.getResourcePath().isEmpty() && !resource.getResourcePath().endsWith("/")) { // external redirect to - serverResponse.withHeader(HttpHeaderNames.LOCATION, resource.getResourcePath() + "/"); - ServerResponse.write(serverResponse, HttpResponseStatus.MOVED_PERMANENTLY); + serverResponse.getBuilder() + .setHeader(HttpHeaderNames.LOCATION, resource.getResourcePath() + "/") + .setStatus( HttpResponseStatus.MOVED_PERMANENTLY) + .build() + .flush(); return; } else if (resource.indexFileName() != null) { // external redirect to default index file in this directory - serverResponse.withHeader(HttpHeaderNames.LOCATION, resource.indexFileName()); - ServerResponse.write(serverResponse, HttpResponseStatus.MOVED_PERMANENTLY); + serverResponse.getBuilder() + .setHeader(HttpHeaderNames.LOCATION, resource.indexFileName()) + .setStatus( HttpResponseStatus.MOVED_PERMANENTLY) + .build() + .flush(); return; } else { // send forbidden, we do not allow directory access - ServerResponse.write(serverResponse, HttpResponseStatus.FORBIDDEN); + serverResponse.getBuilder() + .setStatus(HttpResponseStatus.FORBIDDEN) + .build().flush(); return; } } @@ -88,8 +97,9 @@ public abstract class ResourceService implements Filter { String contentType = MimeTypeUtils.guessFromPath(resource.getResourcePath(), false); long expirationMillis = System.currentTimeMillis() + 1000 * getMaxAgeSeconds(); if (isCacheResponseEnabled()) { - serverResponse.withHeader(HttpHeaderNames.EXPIRES, DateTimeUtil.formatRfc1123(expirationMillis)) - .withHeader(HttpHeaderNames.CACHE_CONTROL, "public, max-age=" + getMaxAgeSeconds()); + serverResponse.getBuilder() + .setHeader(HttpHeaderNames.EXPIRES, DateTimeUtil.formatRfc1123(expirationMillis)) + .setHeader(HttpHeaderNames.CACHE_CONTROL, "public, max-age=" + getMaxAgeSeconds()); } boolean sent = false; if (isETagResponseEnabled()) { @@ -98,38 +108,48 @@ public abstract class ResourceService implements Filter { Instant ifUnmodifiedSinceInstant = DateTimeUtil.parseDate(headers.get(HttpHeaderNames.IF_UNMODIFIED_SINCE)); if (ifUnmodifiedSinceInstant != null && ifUnmodifiedSinceInstant.plusMillis(1000L).isAfter(lastModifiedInstant)) { - ServerResponse.write(serverResponse, HttpResponseStatus.PRECONDITION_FAILED); + serverResponse.getBuilder() + .setStatus(HttpResponseStatus.PRECONDITION_FAILED) + .build().flush(); return; } String ifMatch = headers.get(HttpHeaderNames.IF_MATCH); if (ifMatch != null && !matches(ifMatch, eTag)) { - ServerResponse.write(serverResponse, HttpResponseStatus.PRECONDITION_FAILED); + serverResponse.getBuilder() + .setStatus(HttpResponseStatus.PRECONDITION_FAILED) + .build().flush(); return; } String ifNoneMatch = headers.get(HttpHeaderNames.IF_NONE_MATCH); if (ifNoneMatch != null && matches(ifNoneMatch, eTag)) { - serverResponse.withHeader(HttpHeaderNames.ETAG, eTag) - .withHeader(HttpHeaderNames.EXPIRES, DateTimeUtil.formatRfc1123(expirationMillis)); - ServerResponse.write(serverResponse, HttpResponseStatus.NOT_MODIFIED); + serverResponse.getBuilder() + .setHeader(HttpHeaderNames.ETAG, eTag) + .setHeader(HttpHeaderNames.EXPIRES, DateTimeUtil.formatRfc1123(expirationMillis)) + .setStatus(HttpResponseStatus.NOT_MODIFIED) + .build().flush(); return; } Instant ifModifiedSinceInstant = DateTimeUtil.parseDate(headers.get(HttpHeaderNames.IF_MODIFIED_SINCE)); if (ifModifiedSinceInstant != null && ifModifiedSinceInstant.plusMillis(1000L).isAfter(lastModifiedInstant)) { - serverResponse.withHeader(HttpHeaderNames.ETAG, eTag) - .withHeader(HttpHeaderNames.EXPIRES, DateTimeUtil.formatRfc1123(expirationMillis)); - ServerResponse.write(serverResponse, HttpResponseStatus.NOT_MODIFIED); + serverResponse.getBuilder() + .setHeader(HttpHeaderNames.ETAG, eTag) + .setHeader(HttpHeaderNames.EXPIRES, DateTimeUtil.formatRfc1123(expirationMillis)) + .setStatus(HttpResponseStatus.NOT_MODIFIED) + .build().flush(); return; } - serverResponse.withHeader(HttpHeaderNames.ETAG, eTag) - .withHeader(HttpHeaderNames.LAST_MODIFIED, DateTimeUtil.formatRfc1123(lastModifiedInstant)); + serverResponse.getBuilder() + .setHeader(HttpHeaderNames.ETAG, eTag) + .setHeader(HttpHeaderNames.LAST_MODIFIED, DateTimeUtil.formatRfc1123(lastModifiedInstant)); if (isRangeResponseEnabled()) { performRangeResponse(serverRequest, serverResponse, resource, contentType, eTag, headers); sent = true; } } if (!sent) { - serverResponse.withHeader(HttpHeaderNames.CONTENT_LENGTH, Long.toString(resource.getLength())); + serverResponse.getBuilder() + .setHeader(HttpHeaderNames.CONTENT_LENGTH, Long.toString(resource.getLength())); send(resource.getURL(), contentType, serverRequest, serverResponse); } } @@ -137,16 +157,18 @@ public abstract class ResourceService implements Filter { private void performRangeResponse(ServerRequest serverRequest, ServerResponse serverResponse, Resource resource, String contentType, String eTag, - HttpHeaders headers) { + HttpHeaders headers) throws IOException { long length = resource.getLength(); - serverResponse.withHeader(HttpHeaderNames.ACCEPT_RANGES, "bytes"); + serverResponse.getBuilder().setHeader(HttpHeaderNames.ACCEPT_RANGES, "bytes"); Range full = new Range(0, length - 1, length); List ranges = new ArrayList<>(); String range = headers.get(HttpHeaderNames.RANGE); if (range != null) { if (!range.matches("^bytes=\\d*-\\d*(,\\d*-\\d*)*$")) { - serverResponse.withHeader(HttpHeaderNames.CONTENT_RANGE, "bytes */" + length); - ServerResponse.write(serverResponse, HttpResponseStatus.REQUESTED_RANGE_NOT_SATISFIABLE); + serverResponse.getBuilder() + .setHeader(HttpHeaderNames.CONTENT_RANGE, "bytes */" + length) + .setStatus(HttpResponseStatus.REQUESTED_RANGE_NOT_SATISFIABLE) + .build().flush(); return; } String ifRange = headers.get(HttpHeaderNames.IF_RANGE); @@ -171,8 +193,10 @@ public abstract class ResourceService implements Filter { end = length - 1; } if (start > end) { - serverResponse.withHeader(HttpHeaderNames.CONTENT_RANGE, "bytes */" + length); - ServerResponse.write(serverResponse, HttpResponseStatus.REQUESTED_RANGE_NOT_SATISFIABLE); + serverResponse.getBuilder() + .setHeader(HttpHeaderNames.CONTENT_RANGE, "bytes */" + length) + .setStatus(HttpResponseStatus.REQUESTED_RANGE_NOT_SATISFIABLE) + .build().flush(); return; } ranges.add(new Range(start, end, length)); @@ -180,16 +204,19 @@ public abstract class ResourceService implements Filter { } } if (ranges.isEmpty() || ranges.get(0) == full) { - serverResponse.withHeader(HttpHeaderNames.CONTENT_RANGE, "bytes " + full.start + '-' + full.end + '/' + full.total) - .withHeader(HttpHeaderNames.CONTENT_LENGTH, Long.toString(full.length)); + serverResponse.getBuilder() + .setHeader(HttpHeaderNames.CONTENT_RANGE, "bytes " + full.start + '-' + full.end + '/' + full.total) + .setHeader(HttpHeaderNames.CONTENT_LENGTH, Long.toString(full.length)); send(resource.getURL(), HttpResponseStatus.OK, contentType, serverRequest, serverResponse, full.start, full.length); } else if (ranges.size() == 1) { Range r = ranges.get(0); - serverResponse.withHeader(HttpHeaderNames.CONTENT_RANGE, "bytes " + r.start + '-' + r.end + '/' + r.total) - .withHeader(HttpHeaderNames.CONTENT_LENGTH, Long.toString(r.length)); + serverResponse.getBuilder() + .setHeader(HttpHeaderNames.CONTENT_RANGE, "bytes " + r.start + '-' + r.end + '/' + r.total) + .setHeader(HttpHeaderNames.CONTENT_LENGTH, Long.toString(r.length)); send(resource.getURL(), HttpResponseStatus.PARTIAL_CONTENT, contentType, serverRequest, serverResponse, r.start, r.length); } else { - serverResponse.withHeader(HttpHeaderNames.CONTENT_TYPE, "multipart/byteranges; boundary=MULTIPART_BOUNDARY"); + serverResponse.getBuilder() + .setHeader(HttpHeaderNames.CONTENT_TYPE, "multipart/byteranges; boundary=MULTIPART_BOUNDARY"); StringBuilder sb = new StringBuilder(); for (Range r : ranges) { try { @@ -203,7 +230,11 @@ public abstract class ResourceService implements Filter { logger.log(Level.FINEST, e.getMessage(), e); } } - ServerResponse.write(serverResponse, HttpResponseStatus.OK, contentType, CharBuffer.wrap(sb), StandardCharsets.ISO_8859_1); + serverResponse.getBuilder() + .setStatus(HttpResponseStatus.OK) + .setContentType(contentType) + .build() + .write(CharBuffer.wrap(sb), StandardCharsets.ISO_8859_1); } } @@ -219,37 +250,50 @@ public abstract class ResourceService implements Filter { } private void send(URL url, String contentType, - ServerRequest serverRequest, ServerResponse serverResponse) { + ServerRequest serverRequest, ServerResponse serverResponse) throws IOException { if (url == null) { - ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND); + serverResponse.getBuilder() + .setStatus( HttpResponseStatus.NOT_FOUND) + .build().flush(); } else if (serverRequest.getMethod() == HttpMethod.HEAD) { - ServerResponse.write(serverResponse, HttpResponseStatus.OK, contentType); + serverResponse.getBuilder() + .setStatus( HttpResponseStatus.OK) + .setContentType(contentType) + .build().flush(); } else { if ("file".equals(url.getProtocol())) { try { - send((FileChannel) Files.newByteChannel(Paths.get(url.toURI())), - HttpResponseStatus.OK, contentType, serverResponse); + send((FileChannel) Files.newByteChannel(Paths.get(url.toURI())), contentType, serverResponse); } catch (URISyntaxException | IOException e) { logger.log(Level.SEVERE, e.getMessage(), e); - ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND); + serverResponse.getBuilder() + .setStatus( HttpResponseStatus.NOT_FOUND) + .build().flush(); } } else { try (InputStream inputStream = url.openStream()) { - send(inputStream, HttpResponseStatus.OK, contentType, serverResponse); + send(inputStream, contentType, serverResponse); } catch (IOException e) { logger.log(Level.SEVERE, e.getMessage(), e); - ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND); + serverResponse.getBuilder() + .setStatus( HttpResponseStatus.NOT_FOUND) + .build().flush(); } } } } private void send(URL url, HttpResponseStatus httpResponseStatus, String contentType, - ServerRequest serverRequest, ServerResponse serverResponse, long offset, long size) { + ServerRequest serverRequest, ServerResponse serverResponse, long offset, long size) throws IOException { if (url == null) { - ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND); + serverResponse.getBuilder() + .setStatus( HttpResponseStatus.NOT_FOUND) + .build().flush(); } else if (serverRequest.getMethod() == HttpMethod.HEAD) { - ServerResponse.write(serverResponse, HttpResponseStatus.OK, contentType); + serverResponse.getBuilder() + .setStatus( HttpResponseStatus.OK) + .setContentType(contentType) + .build().flush(); } else { if ("file".equals(url.getProtocol())) { Path path = null; @@ -259,52 +303,66 @@ public abstract class ResourceService implements Filter { contentType, serverResponse, offset, size); } catch (URISyntaxException | IOException e) { logger.log(Level.SEVERE, e.getMessage() + " path=" + path, e); - ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND); + serverResponse.getBuilder() + .setStatus( HttpResponseStatus.NOT_FOUND) + .build().flush(); } } else { try (InputStream inputStream = url.openStream()) { send(inputStream, httpResponseStatus, contentType, serverResponse, offset, size); } catch (IOException e) { logger.log(Level.SEVERE, e.getMessage(), e); - ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND); + serverResponse.getBuilder() + .setStatus( HttpResponseStatus.NOT_FOUND) + .build().flush(); } } } } - private void send(FileChannel fileChannel, HttpResponseStatus httpResponseStatus, String contentType, + private void send(FileChannel fileChannel, String contentType, ServerResponse serverResponse) throws IOException { - send(fileChannel, httpResponseStatus, contentType, serverResponse, 0L, fileChannel.size()); + send(fileChannel, HttpResponseStatus.OK, contentType, serverResponse, 0L, fileChannel.size()); } private void send(FileChannel fileChannel, HttpResponseStatus httpResponseStatus, String contentType, - ServerResponse serverResponse, long offset, long size) { + ServerResponse serverResponse, long offset, long size) throws IOException { if (fileChannel == null) { - ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND); + serverResponse.getBuilder() + .setStatus( HttpResponseStatus.NOT_FOUND) + .build().flush(); } else { MappedByteBuffer mappedByteBuffer = null; try { mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, offset, size); } catch (IOException e) { // resource is not a file that can be mapped - ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND); + serverResponse.getBuilder() + .setStatus(HttpResponseStatus.NOT_FOUND) + .build().flush(); } if (mappedByteBuffer != null) { - serverResponse.withStatus(httpResponseStatus) - .withContentType(contentType) + serverResponse.getBuilder() + .setStatus(httpResponseStatus) + .setContentType(contentType) + .build() .write(Unpooled.wrappedBuffer(mappedByteBuffer)); } } } - private void send(InputStream inputStream, HttpResponseStatus httpResponseStatus, String contentType, + private void send(InputStream inputStream, String contentType, ServerResponse serverResponse) throws IOException { if (inputStream == null) { - ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND); + serverResponse.getBuilder() + .setStatus(HttpResponseStatus.NOT_FOUND) + .build().flush(); } else { try (ReadableByteChannel channel = Channels.newChannel(inputStream)) { - serverResponse.withStatus(httpResponseStatus) - .withContentType(contentType) + serverResponse.getBuilder() + .setStatus(HttpResponseStatus.OK) + .setContentType(contentType) + .build() .write(new ChunkedNioStream(channel)); } } @@ -313,10 +371,14 @@ public abstract class ResourceService implements Filter { private void send(InputStream inputStream, HttpResponseStatus httpResponseStatus, String contentType, ServerResponse serverResponse, long offset, long size) throws IOException { if (inputStream == null) { - ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND); + serverResponse.getBuilder() + .setStatus(HttpResponseStatus.NOT_FOUND) + .build().flush(); } else { - serverResponse.withStatus(httpResponseStatus) - .withContentType(contentType) + serverResponse.getBuilder() + .setStatus(httpResponseStatus) + .setContentType(contentType) + .build() .write(Unpooled.wrappedBuffer(readBuffer(inputStream, offset, size))); } } @@ -351,7 +413,7 @@ public abstract class ResourceService implements Filter { return buf; } - class Range { + static class Range { long start; long end; long length; diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/handler/stream/SeekableChunkedNioStream.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/handler/stream/SeekableChunkedNioStream.java deleted file mode 100644 index f6bb390..0000000 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/handler/stream/SeekableChunkedNioStream.java +++ /dev/null @@ -1,46 +0,0 @@ - -package org.xbib.netty.http.server.handler.stream; - -import io.netty.handler.stream.ChunkedNioStream; - -import java.io.IOException; -import java.nio.channels.SeekableByteChannel; - -/** - * A {@link ChunkedNioStream} that fetches data from a {@link SeekableByteChannel} - * chunk by chunk. Please note that the {@link SeekableByteChannel} must - * operate in blocking mode. Non-blocking mode channels are not supported. - */ -public class SeekableChunkedNioStream extends ChunkedNioStream { - - /** - * Creates a new instance that fetches data from the specified channel. - * @param in input - */ - public SeekableChunkedNioStream(SeekableByteChannel in) { - super(in); - } - - /** - * Creates a new instance that fetches data from the specified channel. - * - * @param in channel - * @param chunkSize the number of bytes to fetch on each call - */ - public SeekableChunkedNioStream(SeekableByteChannel in, int chunkSize) { - super(in, chunkSize); - } - - /** - * Creates a new instance that fetches data from the specified channel. - * - * @param in channel - * @param position the position in the byte channel - * @param chunkSize the number of bytes to fetch on each call - * @throws IOException if creation fails - */ - public SeekableChunkedNioStream(SeekableByteChannel in, long position, int chunkSize) throws IOException { - super(in, chunkSize); - in.position(position); - } -} diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/Http1.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/http1/Http1.java similarity index 75% rename from netty-http-server/src/main/java/org/xbib/netty/http/server/Http1.java rename to netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/http1/Http1.java index 3735850..736f995 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/Http1.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/http1/Http1.java @@ -1,8 +1,6 @@ -package org.xbib.netty.http.server; +package org.xbib.netty.http.server.protocol.http1; import org.xbib.netty.http.server.api.ServerProtocolProvider; -import org.xbib.netty.http.server.handler.http.Http1ChannelInitializer; -import org.xbib.netty.http.server.transport.Http1Transport; public class Http1 implements ServerProtocolProvider { diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/handler/http/Http1ChannelInitializer.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/http1/Http1ChannelInitializer.java similarity index 99% rename from netty-http-server/src/main/java/org/xbib/netty/http/server/handler/http/Http1ChannelInitializer.java rename to netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/http1/Http1ChannelInitializer.java index 9bc4045..a4bcb12 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/handler/http/Http1ChannelInitializer.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/http1/Http1ChannelInitializer.java @@ -1,4 +1,4 @@ -package org.xbib.netty.http.server.handler.http; +package org.xbib.netty.http.server.protocol.http1; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; @@ -28,7 +28,6 @@ import org.xbib.netty.http.server.handler.ExtendedSNIHandler; import org.xbib.netty.http.server.handler.IdleTimeoutHandler; import org.xbib.netty.http.server.handler.TrafficLoggingHandler; import org.xbib.netty.http.server.api.ServerTransport; - import java.nio.charset.StandardCharsets; import java.util.logging.Level; import java.util.logging.Logger; diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/http1/Http1Transport.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/http1/Http1Transport.java new file mode 100644 index 0000000..2a7d353 --- /dev/null +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/http1/Http1Transport.java @@ -0,0 +1,75 @@ +package org.xbib.netty.http.server.protocol.http1; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpHeaderNames; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http2.Http2Settings; +import io.netty.handler.ssl.SslHandler; +import org.xbib.netty.http.server.Server; +import org.xbib.netty.http.server.api.ServerResponse; +import org.xbib.netty.http.server.AcceptState; +import org.xbib.netty.http.server.BaseTransport; +import org.xbib.netty.http.server.HttpServerRequest; +import java.io.IOException; +import java.net.InetSocketAddress; + +public class Http1Transport extends BaseTransport { + + public Http1Transport(Server server) { + super(server); + } + + @Override + public void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest, Integer sequenceId) throws IOException { + AcceptState acceptState = acceptRequest(server.getServerConfig().getAddress().getVersion(), + fullHttpRequest.headers()); + ServerResponse.Builder serverResponseBuilder = HttpServerResponse.builder(ctx) + .setResponseId(server.getResponseCounter().incrementAndGet()); + switch (acceptState) { + case OK: { + HttpServerRequest.Builder serverRequestBuilder = HttpServerRequest.builder() + .setLocalAddress((InetSocketAddress) ctx.channel().localAddress()) + .setRemoteAddress((InetSocketAddress) ctx.channel().remoteAddress()) + .setHttpRequest(fullHttpRequest.retainedDuplicate()) + .setSequenceId(sequenceId) + .setRequestId(server.getRequestCounter().incrementAndGet()); + SslHandler sslHandler = ctx.channel().pipeline().get(SslHandler.class); + if (sslHandler != null) { + serverRequestBuilder.setSession(sslHandler.engine().getSession()); + } + boolean shouldClose = "close".equalsIgnoreCase(fullHttpRequest.headers().get(HttpHeaderNames.CONNECTION)); + serverResponseBuilder.shouldClose(shouldClose); + server.handle(serverRequestBuilder, serverResponseBuilder); + break; + } + case MISSING_HOST_HEADER: { + HttpServerResponse.builder(ctx) + .setStatus(HttpResponseStatus.BAD_REQUEST) + .setContentType("text/plain") + .build() + .write("missing 'Host' header"); + } + case EXPECTATION_FAILED: { + HttpServerResponse.builder(ctx) + .setStatus(HttpResponseStatus.EXPECTATION_FAILED) + .build() + .flush(); + break; + } + case UNSUPPORTED_HTTP_VERSION: { + HttpServerResponse.builder(ctx) + .setStatus(HttpResponseStatus.BAD_REQUEST) + .setContentType("text/plain") + .build() + .write("unsupported HTTP version"); + break; + } + } + } + + @Override + public void settingsReceived(ChannelHandlerContext ctx, Http2Settings http2Settings) { + // there are no settings in HTTP 1 + } +} diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/handler/http/HttpPipelinedRequest.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/http1/HttpPipelinedRequest.java similarity index 90% rename from netty-http-server/src/main/java/org/xbib/netty/http/server/handler/http/HttpPipelinedRequest.java rename to netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/http1/HttpPipelinedRequest.java index 990eccf..2d2b179 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/handler/http/HttpPipelinedRequest.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/http1/HttpPipelinedRequest.java @@ -1,4 +1,4 @@ -package org.xbib.netty.http.server.handler.http; +package org.xbib.netty.http.server.protocol.http1; import io.netty.handler.codec.http.LastHttpContent; diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/handler/http/HttpPipelinedResponse.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/http1/HttpPipelinedResponse.java similarity index 94% rename from netty-http-server/src/main/java/org/xbib/netty/http/server/handler/http/HttpPipelinedResponse.java rename to netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/http1/HttpPipelinedResponse.java index 575ac05..af3aab2 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/handler/http/HttpPipelinedResponse.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/http1/HttpPipelinedResponse.java @@ -1,4 +1,4 @@ -package org.xbib.netty.http.server.handler.http; +package org.xbib.netty.http.server.protocol.http1; import io.netty.channel.ChannelPromise; import io.netty.handler.codec.http.HttpResponse; diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/handler/http/HttpPipeliningHandler.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/http1/HttpPipeliningHandler.java similarity index 98% rename from netty-http-server/src/main/java/org/xbib/netty/http/server/handler/http/HttpPipeliningHandler.java rename to netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/http1/HttpPipeliningHandler.java index e14f25c..beb6c25 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/handler/http/HttpPipeliningHandler.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/http1/HttpPipeliningHandler.java @@ -1,4 +1,4 @@ -package org.xbib.netty.http.server.handler.http; +package org.xbib.netty.http.server.protocol.http1; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelHandlerContext; diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/HttpServerResponse.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/http1/HttpServerResponse.java similarity index 50% rename from netty-http-server/src/main/java/org/xbib/netty/http/server/transport/HttpServerResponse.java rename to netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/http1/HttpServerResponse.java index f78d7a7..0e36987 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/HttpServerResponse.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/http1/HttpServerResponse.java @@ -1,7 +1,8 @@ -package org.xbib.netty.http.server.transport; +package org.xbib.netty.http.server.protocol.http1; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufOutputStream; +import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; @@ -18,14 +19,12 @@ import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.stream.ChunkedInput; +import io.netty.util.AsciiString; import org.xbib.netty.http.common.cookie.Cookie; -import org.xbib.netty.http.server.Server; import org.xbib.netty.http.server.ServerName; -import org.xbib.netty.http.server.api.ServerRequest; import org.xbib.netty.http.server.api.ServerResponse; import org.xbib.netty.http.server.cookie.ServerCookieEncoder; -import org.xbib.netty.http.server.handler.http.HttpPipelinedResponse; - +import java.nio.CharBuffer; import java.nio.charset.Charset; import java.time.ZoneOffset; import java.time.ZonedDateTime; @@ -38,70 +37,64 @@ public class HttpServerResponse implements ServerResponse { private static final Logger logger = Logger.getLogger(HttpServerResponse.class.getName()); - private final Server server; - - private final ServerRequest serverRequest; + private final Builder builder; private final ChannelHandlerContext ctx; - private HttpHeaders headers; + private final HttpHeaders headers; - private HttpHeaders trailingHeaders; + private final HttpHeaders trailingHeaders; - private HttpResponseStatus httpResponseStatus; + private final HttpResponseStatus httpResponseStatus; - HttpServerResponse(Server server, HttpServerRequest serverRequest, ChannelHandlerContext ctx) { - this.server = server; - this.serverRequest = serverRequest; - this.ctx = ctx; - this.headers = new DefaultHttpHeaders(); - this.trailingHeaders = new DefaultHttpHeaders(); + private final boolean shouldClose; + + private final boolean shouldAddServerName; + + private final Integer sequenceId; + + private final Integer streamId; + + private final Long responseId; + + private final CharSequence contentType; + + private HttpServerResponse(Builder builder) { + this.builder = builder; + this.ctx = builder.ctx; + this.headers = builder.headers; + this.trailingHeaders = builder.trailingHeaders; + this.httpResponseStatus = builder.httpResponseStatus; + this.shouldClose = builder.shouldClose; + this.shouldAddServerName = builder.shouldAddServerName; + this.sequenceId = builder.sequenceId; + this.streamId = builder.streamId; + this.responseId = builder.responseId; + this.contentType = builder.contentType; } @Override - public ServerResponse withHeader(CharSequence name, String value) { - headers.set(name, value); - return this; + public Builder getBuilder() { + return builder; } @Override - public ChannelHandlerContext getChannelHandlerContext() { - return ctx; + public Integer getStreamId() { + return streamId; } @Override - public HttpResponseStatus getStatus() { - return httpResponseStatus; + public Integer getSequenceId() { + return sequenceId; } @Override - public ServerResponse withStatus(HttpResponseStatus httpResponseStatus) { - this.httpResponseStatus = httpResponseStatus; - return this; + public Long getResponseId() { + return responseId; } - @Override - public ServerResponse withContentType(String contentType) { - headers.remove(HttpHeaderNames.CONTENT_TYPE); - headers.add(HttpHeaderNames.CONTENT_TYPE, contentType); - return this; - } - - @Override - public ServerResponse withCharset(Charset charset) { - CharSequence contentType = headers.get(HttpHeaderNames.CONTENT_TYPE); - if (contentType != null) { - headers.remove(HttpHeaderNames.CONTENT_TYPE); - headers.add(HttpHeaderNames.CONTENT_TYPE, contentType + "; charset=" + charset.name()); - } - return this; - } - - @Override - public ServerResponse withCookie(Cookie cookie) { - Objects.requireNonNull(cookie); - headers.add(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.STRICT.encode(cookie)); - return this; + public static Builder builder(ChannelHandlerContext ctx) { + return new Builder(ctx); } @Override @@ -114,6 +107,16 @@ public class HttpServerResponse implements ServerResponse { write(Unpooled.buffer(0)); } + @Override + public void write(String string) { + write(ByteBufUtil.writeUtf8(ctx.alloc(), string)); + } + + @Override + public void write(CharBuffer charBuffer, Charset charset) { + write(ByteBufUtil.encodeString(ctx.alloc(), charBuffer, charset)); + } + @Override public void write(byte[] bytes) { ByteBuf byteBuf = ctx.alloc().buffer(bytes.length); @@ -129,37 +132,30 @@ public class HttpServerResponse implements ServerResponse { @Override public void write(ByteBuf byteBuf) { Objects.requireNonNull(byteBuf); - if (httpResponseStatus == null) { - httpResponseStatus = HttpResponseStatus.OK; - } - CharSequence contentType = headers.get(HttpHeaderNames.CONTENT_TYPE); - if (contentType == null) { - headers.add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_OCTET_STREAM); - } + headers.add(HttpHeaderNames.CONTENT_TYPE, contentType); if (httpResponseStatus.code() >= 200 && httpResponseStatus.code() != 204) { if (!headers.contains(HttpHeaderNames.CONTENT_LENGTH) && !headers.contains(HttpHeaderNames.TRANSFER_ENCODING)) { headers.add(HttpHeaderNames.CONTENT_LENGTH, Long.toString(byteBuf.readableBytes())); } } - if (serverRequest != null && "close".equalsIgnoreCase(serverRequest.getHeaders().get(HttpHeaderNames.CONNECTION)) && - !headers.contains(HttpHeaderNames.CONNECTION)) { + if (shouldClose) { headers.add(HttpHeaderNames.CONNECTION, "close"); } if (!headers.contains(HttpHeaderNames.DATE)) { headers.add(HttpHeaderNames.DATE, DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now(ZoneOffset.UTC))); } - headers.add(HttpHeaderNames.SERVER, ServerName.getServerName()); + if (shouldAddServerName) { + headers.add(HttpHeaderNames.SERVER, ServerName.getServerName()); + } if (ctx.channel().isWritable()) { FullHttpResponse fullHttpResponse; fullHttpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, httpResponseStatus, byteBuf, headers, trailingHeaders); - if (serverRequest != null && serverRequest.getSequenceId() != null) { + if (sequenceId != null) { HttpPipelinedResponse httpPipelinedResponse = new HttpPipelinedResponse(fullHttpResponse, - ctx.channel().newPromise(), serverRequest.getSequenceId()); + ctx.channel().newPromise(), sequenceId); ctx.channel().writeAndFlush(httpPipelinedResponse); - server.getResponseCounter().incrementAndGet(); } else { ctx.channel().writeAndFlush(fullHttpResponse); - server.getResponseCounter().incrementAndGet(); } } else { logger.log(Level.WARNING, "channel not writeable: " + ctx.channel()); @@ -174,9 +170,6 @@ public class HttpServerResponse implements ServerResponse { @Override public void write(ChunkedInput chunkedInput) { Objects.requireNonNull(chunkedInput); - if (httpResponseStatus == null) { - httpResponseStatus = HttpResponseStatus.OK; - } CharSequence contentType = headers.get(HttpHeaderNames.CONTENT_TYPE); if (contentType == null) { headers.add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_OCTET_STREAM); @@ -192,12 +185,117 @@ public class HttpServerResponse implements ServerResponse { logger.log(Level.FINEST, httpResponse.headers()::toString); ctx.channel().write(httpResponse); ChannelFuture channelFuture = ctx.channel().writeAndFlush(new HttpChunkedInput(chunkedInput)); - if ("close".equalsIgnoreCase(serverRequest.getHeaders().get(HttpHeaderNames.CONNECTION)) && - !headers.contains(HttpHeaderNames.CONNECTION)) { + if (shouldClose) { channelFuture.addListener(ChannelFutureListener.CLOSE); } } else { logger.log(Level.WARNING, "channel not writeable"); } } + + public static class Builder implements ServerResponse.Builder { + + private final ChannelHandlerContext ctx; + + private final HttpHeaders headers; + + private final HttpHeaders trailingHeaders; + + private HttpResponseStatus httpResponseStatus; + + private boolean shouldClose; + + private boolean shouldAddServerName; + + private Integer sequenceId; + + private Integer streamId; + + private Long responseId; + + private CharSequence contentType; + + private Builder(ChannelHandlerContext ctx) { + this.ctx = ctx; + this.httpResponseStatus = HttpResponseStatus.OK; + this.headers = new DefaultHttpHeaders(); + this.trailingHeaders = new DefaultHttpHeaders(); + this.contentType = HttpHeaderValues.APPLICATION_OCTET_STREAM; + } + + @Override + public Builder setStatus(HttpResponseStatus httpResponseStatus) { + this.httpResponseStatus = httpResponseStatus; + return this; + } + + @Override + public ServerResponse.Builder setContentType(CharSequence contentType) { + this.contentType = contentType; + return this; + } + + @Override + public Builder setHeader(CharSequence name, String value) { + headers.set(name, value); + return this; + } + + @Override + public Builder setTrailingHeader(CharSequence name, String value) { + trailingHeaders.set(name, value); + return this; + } + + @Override + public Builder setCharset(Charset charset) { + if (contentType != null) { + this.contentType = AsciiString.of(contentType.toString() + "; charset=" + charset.name()); + } + return this; + } + + @Override + public Builder addCookie(Cookie cookie) { + Objects.requireNonNull(cookie); + headers.add(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.STRICT.encode(cookie)); + return this; + } + + @Override + public Builder shouldClose(boolean shouldClose) { + this.shouldClose = shouldClose; + return this; + } + + @Override + public Builder shouldAddServerName(boolean shouldAddServerName) { + this.shouldAddServerName = shouldAddServerName; + return this; + } + + @Override + public Builder setSequenceId(Integer sequenceId) { + this.sequenceId = sequenceId; + return this; + } + + @Override + public Builder setStreamId(Integer streamId) { + this.streamId = streamId; + return this; + } + + @Override + public Builder setResponseId(Long responseId) { + this.responseId = responseId; + return this; + } + + @Override + public ServerResponse build() { + return new HttpServerResponse(this); + } + + } } diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/Http2.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/http2/Http2.java similarity index 75% rename from netty-http-server/src/main/java/org/xbib/netty/http/server/Http2.java rename to netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/http2/Http2.java index 75821f7..a7e4087 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/Http2.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/http2/Http2.java @@ -1,8 +1,6 @@ -package org.xbib.netty.http.server; +package org.xbib.netty.http.server.protocol.http2; import org.xbib.netty.http.server.api.ServerProtocolProvider; -import org.xbib.netty.http.server.handler.http2.Http2ChannelInitializer; -import org.xbib.netty.http.server.transport.Http2Transport; public class Http2 implements ServerProtocolProvider { diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/handler/http2/Http2ChannelInitializer.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/http2/Http2ChannelInitializer.java similarity index 99% rename from netty-http-server/src/main/java/org/xbib/netty/http/server/handler/http2/Http2ChannelInitializer.java rename to netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/http2/Http2ChannelInitializer.java index 4db873b..17afcda 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/handler/http2/Http2ChannelInitializer.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/http2/Http2ChannelInitializer.java @@ -1,4 +1,4 @@ -package org.xbib.netty.http.server.handler.http2; +package org.xbib.netty.http.server.protocol.http2; import io.netty.channel.Channel; import io.netty.channel.ChannelHandler; diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/http2/Http2ServerResponse.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/http2/Http2ServerResponse.java new file mode 100644 index 0000000..96bc7fe --- /dev/null +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/http2/Http2ServerResponse.java @@ -0,0 +1,303 @@ +package org.xbib.netty.http.server.protocol.http2; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufOutputStream; +import io.netty.buffer.ByteBufUtil; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.HttpChunkedInput; +import io.netty.handler.codec.http.HttpHeaderNames; +import io.netty.handler.codec.http.HttpHeaderValues; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http2.DefaultHttp2DataFrame; +import io.netty.handler.codec.http2.DefaultHttp2Headers; +import io.netty.handler.codec.http2.DefaultHttp2HeadersFrame; +import io.netty.handler.codec.http2.Http2DataFrame; +import io.netty.handler.codec.http2.Http2Headers; +import io.netty.handler.codec.http2.Http2HeadersFrame; +import io.netty.handler.codec.http2.HttpConversionUtil; +import io.netty.handler.stream.ChunkedInput; +import org.xbib.netty.http.common.cookie.Cookie; +import org.xbib.netty.http.server.ServerName; +import org.xbib.netty.http.server.api.ServerResponse; +import org.xbib.netty.http.server.cookie.ServerCookieEncoder; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Objects; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class Http2ServerResponse implements ServerResponse { + + private static final Logger logger = Logger.getLogger(Http2ServerResponse.class.getName()); + + private final Builder builder; + + private final ChannelHandlerContext ctx; + + private final Http2Headers headers; + + private final Http2Headers trailingHeaders; + + private final HttpResponseStatus httpResponseStatus; + + private final boolean shouldClose; + + private final boolean shouldAddServerName; + + private final Integer sequenceId; + + private final Integer streamId; + + private final Long responseId; + + private final CharSequence contentType; + + private Http2ServerResponse(Builder builder) { + this.builder = builder; + this.ctx = builder.ctx; + this.headers = builder.headers; + this.trailingHeaders = builder.trailingHeaders; + this.httpResponseStatus = builder.httpResponseStatus; + this.shouldClose = builder.shouldClose; + this.shouldAddServerName = builder.shouldAddServerName; + this.sequenceId = builder.sequenceId; + this.streamId = builder.streamId; + this.responseId = builder.responseId; + this.contentType = builder.contentType; + } + + @Override + public Builder getBuilder() { + return builder; + } + + @Override + public Integer getStreamId() { + return streamId; + } + + @Override + public Integer getSequenceId() { + return sequenceId; + } + + @Override + public Long getResponseId() { + return responseId; + } + + public static Builder builder(ChannelHandlerContext ctx) { + return new Builder(ctx); + } + + @Override + public ByteBufOutputStream getOutputStream() { + return new ByteBufOutputStream(ctx.alloc().buffer()); + } + + @Override + public void flush() { + write((ByteBuf) null); + } + + @Override + public void write(String string) { + write(ByteBufUtil.writeUtf8(ctx.alloc(), string)); + } + + @Override + public void write(CharBuffer charBuffer, Charset charset) { + write(ByteBufUtil.encodeString(ctx.alloc(), charBuffer, charset)); + } + + @Override + public void write(byte[] bytes) { + ByteBuf byteBuf = ctx.alloc().buffer(bytes.length); + byteBuf.writeBytes(bytes); + write(byteBuf); + } + + @Override + public void write(ByteBufOutputStream byteBufOutputStream) { + write(byteBufOutputStream.buffer()); + } + + @Override + public void write(ByteBuf byteBuf) { + headers.add(HttpHeaderNames.CONTENT_TYPE, contentType); + if (!headers.contains(HttpHeaderNames.CONTENT_LENGTH) && !headers.contains(HttpHeaderNames.TRANSFER_ENCODING)) { + if (byteBuf != null) { + headers.add(HttpHeaderNames.CONTENT_LENGTH, Long.toString(byteBuf.readableBytes())); + } + } + if (shouldClose) { + headers.add(HttpHeaderNames.CONNECTION, "close"); + } + if (!headers.contains(HttpHeaderNames.DATE)) { + headers.add(HttpHeaderNames.DATE, DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now(ZoneOffset.UTC))); + } + if (shouldAddServerName) { + headers.add(HttpHeaderNames.SERVER, ServerName.getServerName()); + } + if (streamId != null) { + headers.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), streamId); + } + if (ctx.channel().isWritable()) { + Http2Headers http2Headers = new DefaultHttp2Headers().status(httpResponseStatus.codeAsText()).add(headers); + Http2HeadersFrame http2HeadersFrame = new DefaultHttp2HeadersFrame(http2Headers, byteBuf == null); + ctx.channel().write(http2HeadersFrame); + if (byteBuf != null) { + Http2DataFrame http2DataFrame = new DefaultHttp2DataFrame(byteBuf, true); + ctx.channel().write(http2DataFrame); + } + /*if (trailingHeaders != null) { + Http2Headers trailingHttp2Headers = new DefaultHttp2Headers().add(trailingHeaders); + Http2HeadersFrame trailingHttp2HeadersFrame = new DefaultHttp2HeadersFrame(trailingHttp2Headers, byteBuf == null); + ctx.channel().write(trailingHttp2HeadersFrame); + }*/ + ctx.channel().flush(); + } else { + logger.log(Level.WARNING, "channel is not writeable: " + ctx.channel()); + } + } + + /** + * Chunked response from a readable byte channel. + * + * @param chunkedInput chunked input + */ + @Override + public void write(ChunkedInput chunkedInput) { + Objects.requireNonNull(chunkedInput); + CharSequence contentType = headers.get(HttpHeaderNames.CONTENT_TYPE); + if (contentType == null) { + headers.add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_OCTET_STREAM); + } + headers.add(HttpHeaderNames.TRANSFER_ENCODING, "chunked"); + if (!headers.contains(HttpHeaderNames.DATE)) { + headers.add(HttpHeaderNames.DATE, DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now(ZoneOffset.UTC))); + } + headers.add(HttpHeaderNames.SERVER, ServerName.getServerName()); + if (ctx.channel().isWritable()) { + Http2Headers http2Headers = new DefaultHttp2Headers().status(httpResponseStatus.codeAsText()).add(headers); + Http2HeadersFrame http2HeadersFrame = new DefaultHttp2HeadersFrame(http2Headers,false); + ctx.channel().write(http2HeadersFrame); + ChannelFuture channelFuture = ctx.channel().writeAndFlush(new HttpChunkedInput(chunkedInput)); + if (shouldClose) { + channelFuture.addListener(ChannelFutureListener.CLOSE); + } + } else { + logger.log(Level.WARNING, "channel is not writeable: " + ctx.channel()); + } + } + + public static class Builder implements ServerResponse.Builder { + + private final ChannelHandlerContext ctx; + + private final Http2Headers headers; + + private final Http2Headers trailingHeaders; + + private HttpResponseStatus httpResponseStatus; + + private boolean shouldClose; + + private boolean shouldAddServerName; + + private Integer sequenceId; + + private Integer streamId; + + private Long responseId; + + private CharSequence contentType; + + private Builder(ChannelHandlerContext ctx) { + this.ctx = ctx; + this.httpResponseStatus = HttpResponseStatus.OK; + this.headers = new DefaultHttp2Headers(); + this.trailingHeaders = new DefaultHttp2Headers(); + this.contentType = HttpHeaderValues.APPLICATION_OCTET_STREAM; + } + + @Override + public Builder setStatus(HttpResponseStatus httpResponseStatus) { + this.httpResponseStatus = httpResponseStatus; + return this; + } + + @Override + public Builder setContentType(CharSequence contentType) { + this.contentType = contentType; + return this; + } + + @Override + public Builder setHeader(CharSequence name, String value) { + headers.set(name, value); + return this; + } + + @Override + public Builder setTrailingHeader(CharSequence name, String value) { + trailingHeaders.set(name, value); + return this; + } + + @Override + public Builder setCharset(Charset charset) { + if (contentType != null) { + this.contentType = contentType + "; charset=" + charset.name(); + } + return this; + } + + @Override + public Builder addCookie(Cookie cookie) { + Objects.requireNonNull(cookie); + headers.add(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.STRICT.encode(cookie)); + return this; + } + + @Override + public Builder shouldClose(boolean shouldClose) { + this.shouldClose = shouldClose; + return this; + } + + @Override + public Builder shouldAddServerName(boolean shouldAddServerName) { + this.shouldAddServerName = shouldAddServerName; + return this; + } + + @Override + public Builder setSequenceId(Integer sequenceId) { + this.sequenceId = sequenceId; + return this; + } + + @Override + public Builder setStreamId(Integer streamId) { + this.streamId = streamId; + return this; + } + + @Override + public Builder setResponseId(Long responseId) { + this.responseId = responseId; + return this; + } + + @Override + public ServerResponse build() { + return new Http2ServerResponse(this); + } + } +} diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/http2/Http2Transport.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/http2/Http2Transport.java new file mode 100644 index 0000000..49089c0 --- /dev/null +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/http2/Http2Transport.java @@ -0,0 +1,84 @@ +package org.xbib.netty.http.server.protocol.http2; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpHeaderNames; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http2.Http2Settings; +import io.netty.handler.codec.http2.HttpConversionUtil; +import io.netty.handler.ssl.SslHandler; +import org.xbib.netty.http.server.AcceptState; +import org.xbib.netty.http.server.BaseTransport; +import org.xbib.netty.http.server.Server; +import org.xbib.netty.http.server.api.ServerResponse; +import org.xbib.netty.http.server.HttpServerRequest; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class Http2Transport extends BaseTransport { + + private static final Logger logger = Logger.getLogger(Http2Transport.class.getName()); + + public Http2Transport(Server server) { + super(server); + } + + @Override + public void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest, Integer sequenceId) throws IOException { + AcceptState acceptState = acceptRequest(server.getServerConfig().getAddress().getVersion(), + fullHttpRequest.headers()); + Integer streamId = fullHttpRequest.headers().getInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text()); + ServerResponse.Builder serverResponseBuilder = Http2ServerResponse.builder(ctx) + .setResponseId(server.getResponseCounter().incrementAndGet()) + .setStreamId(streamId) + .setSequenceId(sequenceId); + switch (acceptState) { + case OK: { + HttpServerRequest.Builder serverRequestBuilder = HttpServerRequest.builder() + .setHttpRequest(fullHttpRequest.retainedDuplicate()) + .setLocalAddress((InetSocketAddress) ctx.channel().localAddress()) + .setRemoteAddress((InetSocketAddress) ctx.channel().remoteAddress()) + .setStreamId(streamId) + .setSequenceId(sequenceId) + .setRequestId(server.getRequestCounter().incrementAndGet()); + SslHandler sslHandler = ctx.channel().pipeline().get(SslHandler.class); + if (sslHandler != null) { + serverRequestBuilder.setSession(sslHandler.engine().getSession()); + } + boolean shouldClose = "close".equalsIgnoreCase(fullHttpRequest.headers().get(HttpHeaderNames.CONNECTION)); + serverResponseBuilder.shouldClose(shouldClose); + server.handle(serverRequestBuilder, serverResponseBuilder); + break; + } + case MISSING_HOST_HEADER: { + serverResponseBuilder + .setStatus(HttpResponseStatus.BAD_REQUEST) + .setContentType("text/plain") + .build() + .write("missing 'Host' header"); + } + case EXPECTATION_FAILED: { + serverResponseBuilder + .setStatus(HttpResponseStatus.EXPECTATION_FAILED) + .build() + .flush(); + break; + } + case UNSUPPORTED_HTTP_VERSION: { + serverResponseBuilder + .setStatus(HttpResponseStatus.BAD_REQUEST) + .setContentType("text/plain") + .build() + .write("unsupported HTTP version"); + break; + } + } + } + + @Override + public void settingsReceived(ChannelHandlerContext ctx, Http2Settings http2Settings) { + logger.log(Level.FINER, "settings received, ignoring"); + } +} diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/BaseTransport.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/BaseTransport.java deleted file mode 100644 index 06e4fac..0000000 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/BaseTransport.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.xbib.netty.http.server.transport; - -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.http.HttpHeaderNames; -import io.netty.handler.codec.http.HttpHeaders; -import io.netty.handler.codec.http.HttpResponseStatus; -import io.netty.handler.codec.http.HttpVersion; -import org.xbib.netty.http.server.Server; -import org.xbib.netty.http.server.DefaultServerConfig; -import org.xbib.netty.http.server.api.ServerRequest; -import org.xbib.netty.http.server.api.ServerResponse; -import org.xbib.netty.http.server.api.ServerTransport; - -import java.util.logging.Level; -import java.util.logging.Logger; - -abstract class BaseTransport implements ServerTransport { - - private static final Logger logger = Logger.getLogger(BaseTransport.class.getName()); - - protected final Server server; - - BaseTransport(Server server) { - this.server = server; - } - - @Override - public void exceptionReceived(ChannelHandlerContext ctx, Throwable throwable) { - logger.log(Level.WARNING, throwable.getMessage(), throwable); - } - - /** - * Accepts a request, performing various validation checks - * and required special header handling, possibly returning an - * appropriate response. - * - * @param serverConfig the server config - * @param serverRequest the request - * @param serverResponse the response - * @return whether further processing should be performed - */ - static boolean acceptRequest(DefaultServerConfig serverConfig, - ServerRequest serverRequest, - ServerResponse serverResponse) { - HttpVersion version = serverConfig.getAddress().getVersion(); - HttpHeaders reqHeaders = serverRequest.getHeaders(); - if (version.majorVersion() == 1 || version.majorVersion() == 2) { - if (!reqHeaders.contains(HttpHeaderNames.HOST)) { - // RFC2616#14.23: missing Host header gets 400 - ServerResponse.write(serverResponse, - HttpResponseStatus.BAD_REQUEST, "application/octet-stream", "missing 'Host' header"); - return false; - } - // return a continue response before reading body - String expect = reqHeaders.get(HttpHeaderNames.EXPECT); - if (expect != null) { - if (!"100-continue".equalsIgnoreCase(expect)) { - // RFC2616#14.20: if unknown expect, send 417 - ServerResponse.write(serverResponse, HttpResponseStatus.EXPECTATION_FAILED); - return false; - } - } - } else { - ServerResponse.write(serverResponse, HttpResponseStatus.BAD_REQUEST, - "application/octet-stream", "unsupported HTTP version: " + version); - return false; - } - return true; - } -} diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/Http1Transport.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/Http1Transport.java deleted file mode 100644 index 8467e3a..0000000 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/Http1Transport.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.xbib.netty.http.server.transport; - -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.http.FullHttpRequest; -import io.netty.handler.codec.http.HttpResponseStatus; -import io.netty.handler.codec.http2.Http2Settings; -import io.netty.handler.ssl.SslHandler; -import org.xbib.netty.http.server.Server; -import org.xbib.netty.http.server.api.ServerResponse; -import java.io.IOException; -import java.net.InetSocketAddress; - -public class Http1Transport extends BaseTransport { - - public Http1Transport(Server server) { - super(server); - } - - @Override - public void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest, Integer sequenceId) throws IOException { - HttpServerRequest serverRequest = new HttpServerRequest(fullHttpRequest, - (InetSocketAddress) ctx.channel().localAddress(), - (InetSocketAddress) ctx.channel().remoteAddress()); - serverRequest.setSequenceId(sequenceId); - serverRequest.setRequestId(server.getRequestCounter().incrementAndGet()); - SslHandler sslHandler = ctx.channel().pipeline().get(SslHandler.class); - if (sslHandler != null) { - serverRequest.setSession(sslHandler.engine().getSession()); - } - HttpServerResponse serverResponse = new HttpServerResponse(server, serverRequest, ctx); - if (acceptRequest(server.getServerConfig(), serverRequest, serverResponse)) { - serverRequest.handleParameters(); - server.handle(serverRequest, serverResponse); - } else { - ServerResponse.write(serverResponse, HttpResponseStatus.NOT_ACCEPTABLE); - } - } - - @Override - public void settingsReceived(ChannelHandlerContext ctx, Http2Settings http2Settings) { - // there are no settings in HTTP 1 - } -} diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/Http2ServerResponse.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/Http2ServerResponse.java deleted file mode 100644 index 2d6c041..0000000 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/Http2ServerResponse.java +++ /dev/null @@ -1,201 +0,0 @@ -package org.xbib.netty.http.server.transport; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufOutputStream; -import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelFutureListener; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.http.HttpChunkedInput; -import io.netty.handler.codec.http.HttpHeaderNames; -import io.netty.handler.codec.http.HttpHeaderValues; -import io.netty.handler.codec.http.HttpResponseStatus; -import io.netty.handler.codec.http2.DefaultHttp2DataFrame; -import io.netty.handler.codec.http2.DefaultHttp2Headers; -import io.netty.handler.codec.http2.DefaultHttp2HeadersFrame; -import io.netty.handler.codec.http2.Http2DataFrame; -import io.netty.handler.codec.http2.Http2Headers; -import io.netty.handler.codec.http2.Http2HeadersFrame; -import io.netty.handler.codec.http2.HttpConversionUtil; -import io.netty.handler.stream.ChunkedInput; -import org.xbib.netty.http.common.cookie.Cookie; -import org.xbib.netty.http.server.Server; -import org.xbib.netty.http.server.ServerName; -import org.xbib.netty.http.server.api.ServerRequest; -import org.xbib.netty.http.server.api.ServerResponse; -import org.xbib.netty.http.server.cookie.ServerCookieEncoder; - -import java.nio.charset.Charset; -import java.time.ZoneOffset; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.util.Objects; -import java.util.logging.Level; -import java.util.logging.Logger; - -public class Http2ServerResponse implements ServerResponse { - - private static final Logger logger = Logger.getLogger(Http2ServerResponse.class.getName()); - - private final Server server; - - private final ServerRequest serverRequest; - - private final ChannelHandlerContext ctx; - - private Http2Headers headers; - - private HttpResponseStatus httpResponseStatus; - - Http2ServerResponse(Server server, HttpServerRequest serverRequest, ChannelHandlerContext ctx) { - this.server = server; - this.serverRequest = serverRequest; - this.ctx = ctx; - this.headers = new DefaultHttp2Headers(); - } - - @Override - public ServerResponse withHeader(CharSequence name, String value) { - headers.set(name, value); - return this; - } - - @Override - public ServerResponse withCookie(Cookie cookie) { - Objects.requireNonNull(cookie); - headers.add(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.STRICT.encode(cookie)); - return this; - } - - @Override - public ChannelHandlerContext getChannelHandlerContext() { - return ctx; - } - - @Override - public HttpResponseStatus getStatus() { - return httpResponseStatus; - } - - @Override - public ServerResponse withStatus(HttpResponseStatus httpResponseStatus) { - this.httpResponseStatus = httpResponseStatus; - return this; - } - - @Override - public ServerResponse withContentType(String contentType) { - headers.remove(HttpHeaderNames.CONTENT_TYPE); - headers.add(HttpHeaderNames.CONTENT_TYPE, contentType); - return this; - } - - @Override - public ServerResponse withCharset(Charset charset) { - CharSequence contentType = headers.get(HttpHeaderNames.CONTENT_TYPE); - if (contentType != null) { - headers.remove(HttpHeaderNames.CONTENT_TYPE); - headers.add(HttpHeaderNames.CONTENT_TYPE, contentType + "; charset=" + charset.name()); - } - return this; - } - - @Override - public ByteBufOutputStream getOutputStream() { - return new ByteBufOutputStream(ctx.alloc().buffer()); - } - - @Override - public void flush() { - write((ByteBuf) null); - } - - @Override - public void write(byte[] bytes) { - ByteBuf byteBuf = ctx.alloc().buffer(bytes.length); - byteBuf.writeBytes(bytes); - write(byteBuf); - } - - @Override - public void write(ByteBufOutputStream byteBufOutputStream) { - write(byteBufOutputStream.buffer()); - } - - @Override - public void write(ByteBuf byteBuf) { - if (httpResponseStatus == null) { - httpResponseStatus = HttpResponseStatus.OK; - } - CharSequence contentType = headers.get(HttpHeaderNames.CONTENT_TYPE); - if (contentType == null) { - headers.add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_OCTET_STREAM); - } - if (!headers.contains(HttpHeaderNames.CONTENT_LENGTH) && !headers.contains(HttpHeaderNames.TRANSFER_ENCODING)) { - if (byteBuf != null) { - headers.add(HttpHeaderNames.CONTENT_LENGTH, Long.toString(byteBuf.readableBytes())); - } - } - if (serverRequest != null && "close".equalsIgnoreCase(serverRequest.getHeaders().get(HttpHeaderNames.CONNECTION)) && - !headers.contains(HttpHeaderNames.CONNECTION)) { - headers.add(HttpHeaderNames.CONNECTION, "close"); - } - if (!headers.contains(HttpHeaderNames.DATE)) { - headers.add(HttpHeaderNames.DATE, DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now(ZoneOffset.UTC))); - } - headers.add(HttpHeaderNames.SERVER, ServerName.getServerName()); - if (serverRequest != null) { - Integer streamId = serverRequest.getStreamId(); - if (streamId != null) { - headers.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), streamId); - } - } - if (ctx.channel().isWritable()) { - Http2Headers http2Headers = new DefaultHttp2Headers().status(httpResponseStatus.codeAsText()).add(headers); - Http2HeadersFrame http2HeadersFrame = new DefaultHttp2HeadersFrame(http2Headers, byteBuf == null); - ctx.channel().write(http2HeadersFrame); - if (byteBuf != null) { - Http2DataFrame http2DataFrame = new DefaultHttp2DataFrame(byteBuf, true); - ctx.channel().write(http2DataFrame); - } - ctx.channel().flush(); - server.getResponseCounter().incrementAndGet(); - } else { - logger.log(Level.WARNING, "channel is not writeable: " + ctx.channel()); - } - } - - /** - * Chunked response from a readable byte channel. - * - * @param chunkedInput chunked input - */ - @Override - public void write(ChunkedInput chunkedInput) { - Objects.requireNonNull(chunkedInput); - if (httpResponseStatus == null) { - httpResponseStatus = HttpResponseStatus.OK; - } - CharSequence contentType = headers.get(HttpHeaderNames.CONTENT_TYPE); - if (contentType == null) { - headers.add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_OCTET_STREAM); - } - headers.add(HttpHeaderNames.TRANSFER_ENCODING, "chunked"); - if (!headers.contains(HttpHeaderNames.DATE)) { - headers.add(HttpHeaderNames.DATE, DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now(ZoneOffset.UTC))); - } - headers.add(HttpHeaderNames.SERVER, ServerName.getServerName()); - if (ctx.channel().isWritable()) { - Http2Headers http2Headers = new DefaultHttp2Headers().status(httpResponseStatus.codeAsText()).add(headers); - Http2HeadersFrame http2HeadersFrame = new DefaultHttp2HeadersFrame(http2Headers,false); - ctx.channel().write(http2HeadersFrame); - ChannelFuture channelFuture = ctx.channel().writeAndFlush(new HttpChunkedInput(chunkedInput)); - if ("close".equalsIgnoreCase(serverRequest.getHeaders().get(HttpHeaderNames.CONNECTION)) && - !headers.contains(HttpHeaderNames.CONNECTION)) { - channelFuture.addListener(ChannelFutureListener.CLOSE); - } - server.getResponseCounter().incrementAndGet(); - } else { - logger.log(Level.WARNING, "channel is not writeable: " + ctx.channel()); - } - } -} diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/Http2Transport.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/Http2Transport.java deleted file mode 100644 index 369cd38..0000000 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/Http2Transport.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.xbib.netty.http.server.transport; - -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.http.FullHttpRequest; -import io.netty.handler.codec.http.HttpResponseStatus; -import io.netty.handler.codec.http2.Http2Settings; -import io.netty.handler.codec.http2.HttpConversionUtil; -import io.netty.handler.ssl.SslHandler; -import org.xbib.netty.http.server.Server; -import org.xbib.netty.http.server.api.ServerResponse; - -import java.io.IOException; -import java.net.InetSocketAddress; -import java.util.logging.Level; -import java.util.logging.Logger; - -public class Http2Transport extends BaseTransport { - - private static final Logger logger = Logger.getLogger(Http2Transport.class.getName()); - - public Http2Transport(Server server) { - super(server); - } - - @Override - public void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest, Integer sequenceId) throws IOException { - HttpServerRequest serverRequest = new HttpServerRequest(fullHttpRequest, - (InetSocketAddress) ctx.channel().localAddress(), - (InetSocketAddress) ctx.channel().remoteAddress()); - serverRequest.setSequenceId(sequenceId); - serverRequest.setRequestId(server.getRequestCounter().incrementAndGet()); - serverRequest.setStreamId(fullHttpRequest.headers().getInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text())); - SslHandler sslHandler = ctx.channel().pipeline().get(SslHandler.class); - if (sslHandler != null) { - serverRequest.setSession(sslHandler.engine().getSession()); - } - ServerResponse serverResponse = new Http2ServerResponse(server, serverRequest, ctx); - if (acceptRequest(server.getServerConfig(), serverRequest, serverResponse)) { - serverRequest.handleParameters(); - server.handle(serverRequest, serverResponse); - } else { - ServerResponse.write(serverResponse, HttpResponseStatus.NOT_ACCEPTABLE); - } - } - - @Override - public void settingsReceived(ChannelHandlerContext ctx, Http2Settings http2Settings) { - logger.log(Level.FINER, "settings received, ignoring"); - } -} diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/HttpServerRequest.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/HttpServerRequest.java deleted file mode 100644 index 3c2f30c..0000000 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/HttpServerRequest.java +++ /dev/null @@ -1,247 +0,0 @@ -package org.xbib.netty.http.server.transport; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufInputStream; -import io.netty.handler.codec.http.FullHttpRequest; -import io.netty.handler.codec.http.HttpHeaderValues; -import io.netty.handler.codec.http.HttpHeaders; -import io.netty.handler.codec.http.HttpMethod; -import io.netty.handler.codec.http.HttpUtil; -import org.xbib.net.Pair; -import org.xbib.net.PercentDecoder; -import org.xbib.net.QueryParameters; -import org.xbib.net.URL; -import org.xbib.netty.http.common.HttpParameters; -import org.xbib.netty.http.server.api.ServerRequest; - -import javax.net.ssl.SSLSession; -import java.io.IOException; -import java.net.InetSocketAddress; -import java.nio.charset.Charset; -import java.nio.charset.CodingErrorAction; -import java.nio.charset.StandardCharsets; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * The {@code HttpServerRequest} class encapsulates a single request. - * There must be a default constructor. - */ -public class HttpServerRequest implements ServerRequest { - - private static final Logger logger = Logger.getLogger(HttpServerRequest.class.getName()); - - private static final String PATH_SEPARATOR = "/"; - - private final FullHttpRequest httpRequest; - - private final InetSocketAddress localAddress; - - private final InetSocketAddress remoteAddress; - - private final Map pathParameters; - - private List context; - - private String contextPath; - - private HttpParameters parameters; - - private URL url; - - private Integer sequenceId; - - private Integer streamId; - - private Long requestId; - - private SSLSession sslSession; - - public HttpServerRequest(FullHttpRequest fullHttpRequest) { - this( fullHttpRequest ,null, null); - } - - public HttpServerRequest(FullHttpRequest fullHttpRequest, - InetSocketAddress localAddress, - InetSocketAddress remoteAddress) { - this.httpRequest = fullHttpRequest != null ? fullHttpRequest.retainedDuplicate() : null; - this.localAddress = localAddress; - this.remoteAddress = remoteAddress; - this.pathParameters = new LinkedHashMap<>(); - } - - void handleParameters() { - Charset charset = HttpUtil.getCharset(httpRequest, StandardCharsets.UTF_8); - this.url = URL.builder() - .charset(charset, CodingErrorAction.REPLACE) - .path(httpRequest.uri()) // creates path, query params, fragment - .build(); - QueryParameters queryParameters = url.getQueryParams(); - CharSequence mimeType = HttpUtil.getMimeType(httpRequest); - ByteBuf byteBuf = httpRequest.content(); - if (byteBuf != null) { - if (httpRequest.method().equals(HttpMethod.POST)) { - String params; - // https://www.w3.org/TR/html4/interact/forms.html#h-17.13.4 - if (mimeType != null && HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED.toString().equals(mimeType.toString())) { - Charset htmlCharset = HttpUtil.getCharset(httpRequest, StandardCharsets.ISO_8859_1); - params = byteBuf.toString(htmlCharset).replace('+', ' '); - if (logger.isLoggable(Level.FINER)) { - logger.log(Level.FINER, "html form, charset = " + htmlCharset + " param body = " + params); - } - queryParameters.addPercentEncodedBody(params); - } - } - } - // copy to HTTP parameters but percent-decoded (looks very clumsy) - PercentDecoder percentDecoder = new PercentDecoder(charset.newDecoder() - .onMalformedInput(CodingErrorAction.REPLACE) - .onUnmappableCharacter(CodingErrorAction.REPLACE)); - HttpParameters httpParameters = new HttpParameters(mimeType, charset); - for (Pair pair : queryParameters) { - try { - httpParameters.addRaw(percentDecoder.decode(pair.getFirst()), percentDecoder.decode(pair.getSecond())); - } catch (Exception e) { - // does not happen - throw new IllegalArgumentException(pair.toString()); - } - } - this.parameters = httpParameters; - } - - @Override - public URL getURL() { - return url; - } - - @Override - public void setContext(List context) { - this.context = context; - this.contextPath = context != null ? PATH_SEPARATOR + String.join(PATH_SEPARATOR, context) : null; - } - - @Override - public List getContext() { - return context; - } - - @Override - public String getContextPath() { - return contextPath; - } - - @Override - public String getEffectiveRequestPath() { - String path = extractPath(getRequestURI()); - String effective = contextPath != null && !PATH_SEPARATOR.equals(contextPath) && path.startsWith(contextPath) ? - path.substring(contextPath.length()) : path; - return effective.isEmpty() ? PATH_SEPARATOR : effective; - } - - @Override - public void addPathParameter(String key, String value) throws IOException { - pathParameters.put(key, value); - parameters.addRaw(key, value); - } - - @Override - public Map getPathParameters() { - return pathParameters; - } - - @Override - public HttpMethod getMethod() { - return httpRequest.method(); - } - - @Override - public HttpHeaders getHeaders() { - return httpRequest.headers(); - } - - @Override - public HttpParameters getParameters() { - return parameters; - } - - @Override - public String getRequestURI() { - return httpRequest.uri(); - } - - public void setSequenceId(Integer sequenceId) { - this.sequenceId = sequenceId; - } - - @Override - public Integer getSequenceId() { - return sequenceId; - } - - public void setStreamId(Integer streamId) { - this.streamId = streamId; - } - - @Override - public Integer getStreamId() { - return streamId; - } - - public void setRequestId(Long requestId) { - this.requestId = requestId; - } - - @Override - public Long getRequestId() { - return requestId; - } - - public void setSession(SSLSession sslSession) { - this.sslSession = sslSession; - } - - @Override - public SSLSession getSession() { - return sslSession; - } - - @Override - public InetSocketAddress getLocalAddress() { - return localAddress; - } - - @Override - public InetSocketAddress getRemoteAddress() { - return remoteAddress; - } - - @Override - public ByteBuf getContent() { - return httpRequest.content(); - } - - @Override - public ByteBufInputStream getInputStream() { - return new ByteBufInputStream(getContent(), true); - } - - public void release() { - httpRequest.release(); - } - - public String toString() { - return "ServerRequest[request=" + httpRequest + "]"; - } - - private static String extractPath(String uri) { - String path = uri; - int pos = uri.lastIndexOf('#'); - path = pos >= 0 ? path.substring(0, pos) : path; - pos = uri.lastIndexOf('?'); - path = pos >= 0 ? path.substring(0, pos) : path; - return path; - } -} diff --git a/netty-http-server/src/main/resources/META-INF/services/org.xbib.netty.http.server.api.ServerProtocolProvider b/netty-http-server/src/main/resources/META-INF/services/org.xbib.netty.http.server.api.ServerProtocolProvider index 6e83868..6bb4cda 100644 --- a/netty-http-server/src/main/resources/META-INF/services/org.xbib.netty.http.server.api.ServerProtocolProvider +++ b/netty-http-server/src/main/resources/META-INF/services/org.xbib.netty.http.server.api.ServerProtocolProvider @@ -1,2 +1,2 @@ -org.xbib.netty.http.server.Http1 -org.xbib.netty.http.server.Http2 +org.xbib.netty.http.server.protocol.http1.Http1 +org.xbib.netty.http.server.protocol.http2.Http2 diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/BindExceptionTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/BindExceptionTest.java index 77fc1b7..96eb847 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/BindExceptionTest.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/BindExceptionTest.java @@ -5,7 +5,6 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.server.Server; -import org.xbib.netty.http.server.api.ServerResponse; import org.xbib.netty.http.server.HttpServerDomain; import java.io.IOException; @@ -19,7 +18,7 @@ class BindExceptionTest { @Test void testDoubleServer() throws IOException { HttpServerDomain domain = HttpServerDomain.builder(HttpAddress.http1("localhost", 8008)) - .singleEndpoint("/", (request, response) -> ServerResponse.write(response, "Hello World")) + .singleEndpoint("/", (request, response) -> response.write("Hello World")) .build(); Server server1 = Server.builder(domain).build(); Server server2 = Server.builder(domain).build(); diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/ContextURLTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/ContextURLTest.java index cb71cd9..8099f5a 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/ContextURLTest.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/ContextURLTest.java @@ -13,26 +13,26 @@ import org.xbib.netty.http.server.Server; import org.xbib.netty.http.server.api.ServerRequest; import org.xbib.netty.http.server.endpoint.HttpEndpoint; import org.xbib.netty.http.server.endpoint.HttpEndpointResolver; -import org.xbib.netty.http.server.transport.HttpServerRequest; +import org.xbib.netty.http.server.HttpServerRequest; @ExtendWith(NettyHttpTestExtension.class) public class ContextURLTest { @Test - void testServerPublishURL() throws Exception { + void testServerPublishURL() { HttpAddress httpAddress = HttpAddress.http1("localhost", 8008); HttpEndpointResolver endpointResolver1 = HttpEndpointResolver.builder() .addEndpoint(HttpEndpoint.builder().setPrefix("/one").setPath("/**").build()) - .setDispatcher((endpoint, serverRequest, serverResponse) -> {}) + .setDispatcher((serverRequest, serverResponse) -> {}) .build(); HttpEndpointResolver endpointResolver2 = HttpEndpointResolver.builder() .addEndpoint(HttpEndpoint.builder().setPrefix("/two").setPath("/**").build()) - .setDispatcher((endpoint, serverRequest, serverResponse) -> {}) + .setDispatcher((serverRequest, serverResponse) -> {}) .build(); HttpEndpointResolver endpointResolver3 = HttpEndpointResolver.builder() .addEndpoint(HttpEndpoint.builder().setPrefix("/three").setPath("/**").build()) - .setDispatcher((endpoint, serverRequest, serverResponse) -> {}) + .setDispatcher((serverRequest, serverResponse) -> {}) .build(); HttpServerDomain one = HttpServerDomain.builder(httpAddress, "domain.one:8008") @@ -48,27 +48,30 @@ public class ContextURLTest { .addDomain(two) .build(); - URL url0 = server.getContextURL(); - assertEquals("http://localhost:8008/", url0.toString()); - DefaultFullHttpRequest fullHttpRequest1 = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/one"); fullHttpRequest1.headers().add("host", "domain.one:8008"); - ServerRequest serverRequest1 = new HttpServerRequest(fullHttpRequest1); - URL url1 = server.getContextURL(serverRequest1); + ServerRequest serverRequest1 = HttpServerRequest.builder() + .setHttpRequest(fullHttpRequest1) + .applyTo(server); + String contextPath1 = serverRequest1.getContextPath(); + assertEquals("/one", contextPath1); + URL url1 = serverRequest1.getContextURL(); assertEquals("domain.one", url1.getHost()); assertEquals("/one/", url1.getPath()); DefaultFullHttpRequest fullHttpRequest2 = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/two"); fullHttpRequest2.headers().add("host", "domain.two:8008"); - ServerRequest serverRequest2 = new HttpServerRequest(fullHttpRequest2); - URL url2 = server.getContextURL(serverRequest2); + ServerRequest serverRequest2 = HttpServerRequest.builder().setHttpRequest(fullHttpRequest2).applyTo(server); + String contextPath2 = serverRequest2.getContextPath(); + assertEquals("/two", contextPath2); + URL url2 = serverRequest2.getContextURL(); assertEquals("domain.two", url2.getHost()); assertEquals("/two/", url2.getPath()); DefaultFullHttpRequest fullHttpRequest3 = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/three"); fullHttpRequest3.headers().add("host", "domain.two:8008"); - ServerRequest serverRequest3 = new HttpServerRequest(fullHttpRequest3); - URL url3 = server.getContextURL(serverRequest3); + ServerRequest serverRequest3 = HttpServerRequest.builder().setHttpRequest(fullHttpRequest3).applyTo(server); + URL url3 = serverRequest3.getContextURL(); assertEquals("domain.two", url3.getHost()); assertEquals("/three/", url3.getPath()); } diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/MultiDomainSecureServerTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/MultiDomainSecureServerTest.java index 85dbf98..2be8589 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/MultiDomainSecureServerTest.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/MultiDomainSecureServerTest.java @@ -9,7 +9,6 @@ import org.xbib.netty.http.client.api.Request; import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.server.HttpServerDomain; import org.xbib.netty.http.server.Server; -import org.xbib.netty.http.server.api.ServerResponse; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.logging.Level; @@ -35,11 +34,13 @@ class MultiDomainSecureServerTest { HttpServerDomain fl = HttpServerDomain.builder(httpAddress, "fl.hbz-nrw.de") .setKeyCertChain(certInputStream) .setKey(keyInputStream, null) - .singleEndpoint("/", (request, response) -> ServerResponse.write(response, "Hello fl.hbz-nrw.de")) + .singleEndpoint("/", (request, response) -> + response.write("Hello fl.hbz-nrw.de")) .build(); HttpServerDomain zfl2 = HttpServerDomain.builder(fl) .setServerName("zfl2.hbz-nrw.de") - .singleEndpoint("/", (request, response) -> ServerResponse.write(response, "Hello zfl2.hbz-nrw.de")) + .singleEndpoint("/", (request, response) -> + response.write( "Hello zfl2.hbz-nrw.de")) .build(); Server server = Server.builder(fl) .addDomain(zfl2) diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/MultiDomainServerTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/MultiDomainServerTest.java index 23d3725..74258f1 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/MultiDomainServerTest.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/MultiDomainServerTest.java @@ -8,7 +8,6 @@ import org.xbib.netty.http.client.Client; import org.xbib.netty.http.client.api.Request; import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.server.Server; -import org.xbib.netty.http.server.api.ServerResponse; import org.xbib.netty.http.server.HttpServerDomain; import java.nio.charset.StandardCharsets; import java.util.logging.Level; @@ -24,11 +23,13 @@ class MultiDomainServerTest { void testServer() throws Exception { HttpAddress httpAddress = HttpAddress.http1("localhost", 8008); HttpServerDomain fl = HttpServerDomain.builder(httpAddress, "fl.hbz-nrw.de") - .singleEndpoint("/", (request, response) -> ServerResponse.write(response, "Hello fl.hbz-nrw.de")) + .singleEndpoint("/", (request, response) -> + response.write( "Hello fl.hbz-nrw.de")) .build(); HttpServerDomain zfl2 = HttpServerDomain.builder(fl) .setServerName("zfl2.hbz-nrw.de") - .singleEndpoint("/", (request, response) -> ServerResponse.write(response, "Hello zfl2.hbz-nrw.de")) + .singleEndpoint("/", (request, response) -> + response.write("Hello zfl2.hbz-nrw.de")) .build(); Server server = Server.builder(fl) .addDomain(zfl2) diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/ThreadLeakTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/ThreadLeakTest.java index 317a237..7de1535 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/ThreadLeakTest.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/ThreadLeakTest.java @@ -7,7 +7,6 @@ import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.extension.ExtendWith; import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.server.Server; -import org.xbib.netty.http.server.api.ServerResponse; import org.xbib.netty.http.server.HttpServerDomain; import java.io.IOException; @@ -24,7 +23,8 @@ class ThreadLeakTest { @Test void testForLeaks() throws IOException { HttpServerDomain domain = HttpServerDomain.builder(HttpAddress.http1("localhost", 8008)) - .singleEndpoint("/", (request, response) -> ServerResponse.write(response, "Hello World")) + .singleEndpoint("/", (request, response) -> + response.write("Hello World")) .build(); Server server = Server.builder(domain) .setByteBufAllocator(UnpooledByteBufAllocator.DEFAULT) diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/TransportLayerSecurityServerTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/TransportLayerSecurityServerTest.java index 763e35a..8284d3a 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/TransportLayerSecurityServerTest.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/TransportLayerSecurityServerTest.java @@ -29,9 +29,8 @@ class TransportLayerSecurityServerTest { Server server = Server.builder(HttpServerDomain.builder(httpAddress) .setSelfCert() .singleEndpoint("/", (request, response) -> - response.withStatus(HttpResponseStatus.OK) - .withContentType("text/plain") - .write(request.getContent().retain())) + response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build() + .write(request.getContent().toString(StandardCharsets.UTF_8))) .build()) .setTransportLayerSecurityProtocols("TLSv1.2") .build(); @@ -67,9 +66,8 @@ class TransportLayerSecurityServerTest { Server server = Server.builder(HttpServerDomain.builder(httpAddress) .setSelfCert() .singleEndpoint("/", (request, response) -> - response.withStatus(HttpResponseStatus.OK) - .withContentType("text/plain") - .write(request.getContent().retain())) + response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build() + .write(request.getContent().toString(StandardCharsets.UTF_8))) .build()) .setTransportLayerSecurityProtocols("TLSv1.3") .build(); diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/endpoint/EndpointTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/endpoint/EndpointTest.java index d7118c6..84785ff 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/endpoint/EndpointTest.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/endpoint/EndpointTest.java @@ -8,13 +8,11 @@ import org.xbib.netty.http.client.Client; import org.xbib.netty.http.client.api.Request; import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.server.Server; -import org.xbib.netty.http.server.api.ServerResponse; import org.xbib.netty.http.server.endpoint.HttpEndpoint; import org.xbib.netty.http.server.endpoint.HttpEndpointResolver; import org.xbib.netty.http.server.HttpServerDomain; import org.xbib.netty.http.server.endpoint.service.FileService; import org.xbib.netty.http.server.test.NettyHttpTestExtension; - import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -24,7 +22,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -40,8 +37,8 @@ class EndpointTest { HttpAddress httpAddress = HttpAddress.http1("localhost", 8008); HttpEndpointResolver httpEndpointResolver = HttpEndpointResolver.builder() .addEndpoint(HttpEndpoint.builder().setPath("/**").build()) - .setDispatcher((endpoint, req, resp) -> { - logger.log(Level.FINE, "dispatching endpoint = " + endpoint + + .setDispatcher((req, resp) -> { + logger.log(Level.FINE, "dispatching endpoint = " + req.getEndpoint() + " req = " + req + " req context path = " + req.getContextPath()); fileService.handle(req, resp); }) @@ -81,8 +78,8 @@ class EndpointTest { HttpAddress httpAddress = HttpAddress.http1("localhost", 8008); HttpEndpointResolver httpEndpointResolver = HttpEndpointResolver.builder() .addEndpoint(HttpEndpoint.builder().setPrefix("/").setPath("/**").build()) - .setDispatcher((endpoint, req, resp) -> { - logger.log(Level.FINE, "dispatching endpoint = " + endpoint + + .setDispatcher((req, resp) -> { + logger.log(Level.FINE, "dispatching endpoint = " + req.getEndpoint() + " req = " + req + " req context path = " + req.getContextPath()); fileService.handle(req, resp); }) @@ -124,8 +121,8 @@ class EndpointTest { .addEndpoint(HttpEndpoint.builder().setPrefix("/static1").setPath("/**").build()) .addEndpoint(HttpEndpoint.builder().setPrefix("/static2").setPath("/**").build()) .addEndpoint(HttpEndpoint.builder().setPrefix("/static3").setPath("/**").build()) - .setDispatcher((endpoint, req, resp) -> { - logger.log(Level.FINE, "dispatching endpoint = " + endpoint + + .setDispatcher(( req, resp) -> { + logger.log(Level.FINE, "dispatching endpoint = " + req.getEndpoint() + " req = " + req + " req context path = " + req.getContextPath()); fileService.handle(req, resp); }) @@ -191,8 +188,8 @@ class EndpointTest { .addEndpoint(HttpEndpoint.builder().setPrefix("/static1").setPath("/**").build()) .addEndpoint(HttpEndpoint.builder().setPrefix("/static2").setPath("/**").build()) .addEndpoint(HttpEndpoint.builder().setPrefix("/static3").setPath("/**").build()) - .setDispatcher((endpoint, req, resp) -> { - logger.log(Level.FINE, "dispatching endpoint = " + endpoint + " req = " + req); + .setDispatcher((req, resp) -> { + logger.log(Level.FINE, "dispatching endpoint = " + req.getEndpoint() + " req = " + req); fileService.handle(req, resp); }) .build(); @@ -274,22 +271,22 @@ class EndpointTest { HttpAddress httpAddress = HttpAddress.http1("localhost", 8008); HttpEndpointResolver httpEndpointResolver1 = HttpEndpointResolver.builder() .addEndpoint(HttpEndpoint.builder().setPrefix("/static1").setPath("/**").build()) - .setDispatcher((endpoint, req, resp) -> { - logger.log(Level.FINE, "dispatching endpoint = " + endpoint + " context path = " + req.getContextPath()); + .setDispatcher((req, resp) -> { + logger.log(Level.FINE, "dispatching endpoint = " + req.getEndpoint() + " context path = " + req.getContextPath()); fileService1.handle(req, resp); }) .build(); HttpEndpointResolver httpEndpointResolver2 = HttpEndpointResolver.builder() .addEndpoint(HttpEndpoint.builder().setPrefix("/static2").setPath("/**").build()) - .setDispatcher((endpoint, req, resp) -> { - logger.log(Level.FINE, "dispatching endpoint = " + endpoint + " context path = " + req.getContextPath()); + .setDispatcher((req, resp) -> { + logger.log(Level.FINE, "dispatching endpoint = " + req.getEndpoint() + " context path = " + req.getContextPath()); fileService2.handle(req, resp); }) .build(); HttpEndpointResolver httpEndpointResolver3 = HttpEndpointResolver.builder() .addEndpoint(HttpEndpoint.builder().setPrefix("/static3").setPath("/**").build()) - .setDispatcher((endpoint, req, resp) -> { - logger.log(Level.FINE, "dispatching endpoint = " + endpoint + " context path = " + req.getContextPath()); + .setDispatcher((req, resp) -> { + logger.log(Level.FINE, "dispatching endpoint = " + req.getEndpoint() + " context path = " + req.getContextPath()); fileService3.handle(req, resp); }) .build(); @@ -378,7 +375,7 @@ class EndpointTest { } HttpServerDomain domain = HttpServerDomain.builder(httpAddress) .addEndpointResolver(endpointResolverBuilder - .setDispatcher((endpoint,req, resp) -> ServerResponse.write(resp, HttpResponseStatus.OK)) + .setDispatcher((req, resp) -> resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush()) .build()) .build(); Server server = Server.builder(domain) @@ -419,7 +416,7 @@ class EndpointTest { for (int i = 0; i < max; i++) { domainBuilder.addEndpointResolver(endpointResolverBuilder.addEndpoint(HttpEndpoint.builder() .setPath("/" + i + "/**").build()) - .setDispatcher((endpoint,req, resp) -> ServerResponse.write(resp, HttpResponseStatus.OK)) + .setDispatcher((req, resp) -> resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush()) .build()); } Server server = Server.builder(domainBuilder.build()) diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/hacks/HttpPipeliningHandlerTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/hacks/HttpPipeliningHandlerTest.java index a065904..cb1bbdd 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/hacks/HttpPipeliningHandlerTest.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/hacks/HttpPipeliningHandlerTest.java @@ -23,9 +23,9 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.extension.ExtendWith; -import org.xbib.netty.http.server.handler.http.HttpPipelinedRequest; -import org.xbib.netty.http.server.handler.http.HttpPipelinedResponse; -import org.xbib.netty.http.server.handler.http.HttpPipeliningHandler; +import org.xbib.netty.http.server.protocol.http1.HttpPipelinedRequest; +import org.xbib.netty.http.server.protocol.http1.HttpPipelinedResponse; +import org.xbib.netty.http.server.protocol.http1.HttpPipeliningHandler; import org.xbib.netty.http.server.test.NettyHttpTestExtension; import java.nio.channels.ClosedChannelException; diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http1/CleartextTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http1/CleartextTest.java index 2ab876c..416908f 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http1/CleartextTest.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http1/CleartextTest.java @@ -12,7 +12,6 @@ import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.common.HttpResponse; import org.xbib.netty.http.server.Server; import org.xbib.netty.http.server.HttpServerDomain; -import org.xbib.netty.http.server.api.ServerResponse; import org.xbib.netty.http.server.test.NettyHttpTestExtension; import java.nio.charset.StandardCharsets; @@ -35,8 +34,8 @@ class CleartextTest { HttpAddress httpAddress = HttpAddress.http1("localhost", 8008); HttpServerDomain domain = HttpServerDomain.builder(httpAddress) .singleEndpoint("/**", (request, response) -> - ServerResponse.write(response, HttpResponseStatus.OK, "text/plain", - request.getContent().toString(StandardCharsets.UTF_8))) + response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build() + .write(request.getContent().toString(StandardCharsets.UTF_8))) .build(); Server server = Server.builder(domain).build(); server.accept(); @@ -69,8 +68,8 @@ class CleartextTest { HttpAddress httpAddress = HttpAddress.http1("localhost", 8008); HttpServerDomain domain = HttpServerDomain.builder(httpAddress) .singleEndpoint("/**", (request, response) -> - ServerResponse.write(response, HttpResponseStatus.OK, "text/plain", - request.getContent().toString(StandardCharsets.UTF_8))) + response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build() + .write(request.getContent().toString(StandardCharsets.UTF_8))) .build(); Server server = Server.builder(domain).build(); server.accept(); @@ -114,8 +113,8 @@ class CleartextTest { HttpAddress httpAddress = HttpAddress.http1("localhost", 8008); HttpServerDomain domain = HttpServerDomain.builder(httpAddress) .singleEndpoint("/**", (request, response) -> - ServerResponse.write(response, HttpResponseStatus.OK, "text/plain", - request.getContent().toString(StandardCharsets.UTF_8))) + response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build() + .write(request.getContent().toString(StandardCharsets.UTF_8))) .build(); Server server = Server.builder(domain).build(); server.accept(); @@ -167,8 +166,6 @@ class CleartextTest { server.shutdownGracefully(20L, TimeUnit.SECONDS); client.shutdownGracefully(20L, TimeUnit.SECONDS); } - logger.log(Level.INFO, "server requests = " + server.getRequestCounter() + - " server responses = " + server.getResponseCounter()); logger.log(Level.INFO, "client requests = " + client.getRequestCounter() + " client responses = " + client.getResponseCounter()); logger.log(Level.INFO, "expected=" + (threads * loop) + " counter=" + counter.get()); diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http1/EncryptedTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http1/EncryptedTest.java index 2542af4..fb9f05d 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http1/EncryptedTest.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http1/EncryptedTest.java @@ -13,8 +13,8 @@ import org.xbib.netty.http.common.HttpResponse; import org.xbib.netty.http.server.Server; import org.xbib.netty.http.server.HttpServerDomain; import org.xbib.netty.http.server.test.NettyHttpTestExtension; - import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; @@ -35,9 +35,8 @@ class EncryptedTest { Server server = Server.builder(HttpServerDomain.builder(httpAddress) .setSelfCert() .singleEndpoint("/", (request, response) -> - response.withStatus(HttpResponseStatus.OK) - .withContentType("text/plain") - .write(request.getContent().retain())) + response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build() + .write(request.getContent().retain())) .build()) .build(); Client client = Client.builder() @@ -66,9 +65,8 @@ class EncryptedTest { Server server = Server.builder(HttpServerDomain.builder(httpAddress) .setSelfCert() .singleEndpoint("/", (request, response) -> - response.withStatus(HttpResponseStatus.OK) - .withContentType("text/plain") - .write(request.getContent().retain())) + response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build() + .write(request.getContent().toString(StandardCharsets.UTF_8))) .build()) .build(); server.accept(); @@ -110,9 +108,8 @@ class EncryptedTest { Server server = Server.builder(HttpServerDomain.builder(httpAddress) .setSelfCert() .singleEndpoint("/", (request, response) -> - response.withStatus(HttpResponseStatus.OK) - .withContentType("text/plain") - .write(request.getContent().retain())) + response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build() + .write(request.getContent().toString(StandardCharsets.UTF_8))) .build()) .build(); server.accept(); diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http1/FlushTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http1/FlushTest.java index fb8c4db..2e50663 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http1/FlushTest.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http1/FlushTest.java @@ -37,7 +37,7 @@ class FlushTest { .singleEndpoint("/flush", "/**", (req, resp) -> { HttpParameters parameters = req.getParameters(); logger.log(Level.INFO, "got request " + parameters.toString() + ", sending 302 Found"); - resp.withStatus(HttpResponseStatus.FOUND).flush(); + resp.getBuilder().setStatus(HttpResponseStatus.FOUND).build().flush(); }) .build(); Server server = Server.builder(domain) diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http1/MimeUploadTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http1/MimeUploadTest.java index ce6dbeb..9367c27 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http1/MimeUploadTest.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http1/MimeUploadTest.java @@ -17,7 +17,6 @@ import org.xbib.netty.http.common.HttpParameters; import org.xbib.netty.http.common.HttpResponse; import org.xbib.netty.http.server.HttpServerDomain; import org.xbib.netty.http.server.Server; -import org.xbib.netty.http.server.api.ServerResponse; import org.xbib.netty.http.server.test.NettyHttpTestExtension; import java.nio.charset.StandardCharsets; import java.util.concurrent.atomic.AtomicBoolean; @@ -39,7 +38,7 @@ class MimeUploadTest { logger.log(Level.INFO, "got request, headers = " + req.getHeaders() + " params = " + parameters.toString() + " body = " + req.getContent().toString(StandardCharsets.UTF_8)); - ServerResponse.write(resp, HttpResponseStatus.OK); + resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush(); }, "POST") .build(); Server server = Server.builder(domain) diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http1/PostTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http1/PostTest.java index 00b3182..7c7f3fc 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http1/PostTest.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http1/PostTest.java @@ -42,7 +42,7 @@ class PostTest { if ("Jörg".equals(parameters.getFirst("name"))) { success3.set(true); } - ServerResponse.write(resp, HttpResponseStatus.OK); + resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush(); }, "POST") .build(); Server server = Server.builder(domain) @@ -92,7 +92,7 @@ class PostTest { if ("Jörg".equals(parameters.getFirst("name"))) { success3.set(true); } - ServerResponse.write(resp, HttpResponseStatus.OK); + resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush(); }, "POST") .build(); Server server = Server.builder(domain) @@ -146,7 +146,7 @@ class PostTest { if ("my value".equals(parameters.getFirst("my param"))) { success4.set(true); } - ServerResponse.write(resp, HttpResponseStatus.OK); + resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush(); }, "POST") .build(); Server server = Server.builder(domain) @@ -203,7 +203,7 @@ class PostTest { if ("my value".equals(parameters.getFirst("my param"))) { success4.set(true); } - ServerResponse.write(resp, HttpResponseStatus.OK); + resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush(); }, "POST") .build(); Server server = Server.builder(domain) @@ -257,7 +257,7 @@ class PostTest { if ("bÿc".equals(parameters.getFirst("a"))) { success2.set(true); } - ServerResponse.write(resp, HttpResponseStatus.OK); + resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush(); }, "POST") .build(); Server server = Server.builder(domain) diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http1/PutTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http1/PutTest.java index be025b3..3b99f6d 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http1/PutTest.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http1/PutTest.java @@ -36,7 +36,7 @@ class PutTest { .singleEndpoint("/put", "/**", (req, resp) -> { logger.log(Level.INFO, "got request " + req.getContent().toString(StandardCharsets.UTF_8)); - ServerResponse.write(resp, HttpResponseStatus.OK); + resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush(); success1.set(true); }, "PUT") .build(); @@ -84,7 +84,7 @@ class PutTest { .singleEndpoint("/put", "/**", (req, resp) -> { logger.log(Level.INFO, "got request, length = " + req.getContent().readableBytes()); - ServerResponse.write(resp, HttpResponseStatus.OK); + resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush(); success1.set(true); }, "PUT") .build(); diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http1/StreamTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http1/StreamTest.java index 0b75e1e..2f689d7 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http1/StreamTest.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http1/StreamTest.java @@ -31,8 +31,7 @@ class StreamTest { assertEquals("my body parameter", content); ByteBufOutputStream outputStream = response.getOutputStream(); outputStream.writeBytes("Hello World"); - response.withStatus(HttpResponseStatus.OK) - .withContentType("text/plain") + response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build() .write(outputStream); }) .build(); diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/CleartextTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/CleartextTest.java index 3f9b528..ea62752 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/CleartextTest.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/CleartextTest.java @@ -10,10 +10,8 @@ import org.xbib.netty.http.client.api.ClientTransport; import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.common.HttpResponse; import org.xbib.netty.http.server.Server; -import org.xbib.netty.http.server.api.ServerResponse; import org.xbib.netty.http.server.HttpServerDomain; import org.xbib.netty.http.server.test.NettyHttpTestExtension; - import java.nio.charset.StandardCharsets; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -34,8 +32,8 @@ class CleartextTest { HttpAddress httpAddress = HttpAddress.http2("localhost", 8008); HttpServerDomain domain = HttpServerDomain.builder(httpAddress) .singleEndpoint("/", (request, response) -> - ServerResponse.write(response, HttpResponseStatus.OK, "text.plain", - request.getContent().toString(StandardCharsets.UTF_8))) + response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build() + .write(request.getContent().toString(StandardCharsets.UTF_8))) .build(); Server server = Server.builder(domain) .build(); @@ -79,9 +77,8 @@ class CleartextTest { HttpAddress httpAddress = HttpAddress.http2("localhost", 8008); HttpServerDomain domain = HttpServerDomain.builder(httpAddress) .singleEndpoint("/", (request, response) -> - response.withStatus(HttpResponseStatus.OK) - .withContentType("text/plain") - .write(request.getContent().retain())) + response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build() + .write(request.getContent().toString(StandardCharsets.UTF_8))) .build(); Server server = Server.builder(domain) .build(); @@ -131,8 +128,8 @@ class CleartextTest { HttpAddress httpAddress = HttpAddress.http2("localhost", 8008); HttpServerDomain domain = HttpServerDomain.builder(httpAddress) .singleEndpoint("/**", (request, response) -> - ServerResponse.write(response, HttpResponseStatus.OK, "text/plain", - request.getContent().toString(StandardCharsets.UTF_8))) + response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build() + .write(request.getContent().toString(StandardCharsets.UTF_8))) .build(); Server server = Server.builder(domain).build(); server.accept(); @@ -186,8 +183,6 @@ class CleartextTest { } logger.log(Level.INFO, "client requests = " + client.getRequestCounter() + " client responses = " + client.getResponseCounter()); - logger.log(Level.INFO, "server requests = " + server.getRequestCounter() + - " server responses = " + server.getResponseCounter()); logger.log(Level.INFO, "expected=" + (threads * loop) + " counter=" + counter.get()); assertEquals(threads * loop , counter.get()); } @@ -200,8 +195,8 @@ class CleartextTest { AtomicInteger counter1 = new AtomicInteger(); HttpServerDomain domain1 = HttpServerDomain.builder(httpAddress1) .singleEndpoint("/", (request, response) -> { - ServerResponse.write(response, HttpResponseStatus.OK, "text.plain", - request.getContent().toString(StandardCharsets.UTF_8)); + response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build() + .write(request.getContent().toString(StandardCharsets.UTF_8)); counter1.incrementAndGet(); }) .build(); @@ -212,8 +207,8 @@ class CleartextTest { AtomicInteger counter2 = new AtomicInteger(); HttpServerDomain domain2 = HttpServerDomain.builder(httpAddress2) .singleEndpoint("/", (request, response) -> { - ServerResponse.write(response, HttpResponseStatus.OK, "text/plain", - request.getContent().toString(StandardCharsets.UTF_8)); + response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build() + .write(request.getContent().toString(StandardCharsets.UTF_8)); counter2.incrementAndGet(); }) .build(); @@ -273,10 +268,6 @@ class CleartextTest { server2.shutdownGracefully(); client.shutdownGracefully(); } - logger.log(Level.INFO, "server1 requests = " + server1.getRequestCounter() + - " server1 responses = " + server1.getResponseCounter()); - logger.log(Level.INFO, "server2 requests = " + server1.getRequestCounter() + - " server2 responses = " + server1.getResponseCounter()); logger.log(Level.INFO, "client requests = " + client.getRequestCounter() + " client responses = " + client.getResponseCounter()); logger.log(Level.INFO, "counter1=" + counter1.get() + " counter2=" + counter2.get()); diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/EncryptedTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/EncryptedTest.java index a05384c..96f0634 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/EncryptedTest.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/EncryptedTest.java @@ -12,7 +12,6 @@ import org.xbib.netty.http.common.HttpResponse; import org.xbib.netty.http.server.Server; import org.xbib.netty.http.server.HttpServerDomain; import org.xbib.netty.http.server.test.NettyHttpTestExtension; - import java.nio.charset.StandardCharsets; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -34,9 +33,8 @@ class EncryptedTest { Server server = Server.builder(HttpServerDomain.builder(httpAddress) .setSelfCert() .singleEndpoint("/", (request, response) -> - response.withStatus(HttpResponseStatus.OK) - .withContentType("text/plain") - .write(request.getContent().retain())) + response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build() + .write(request.getContent().toString(StandardCharsets.UTF_8))) .build()) .build(); server.accept(); @@ -75,9 +73,8 @@ class EncryptedTest { HttpServerDomain domain = HttpServerDomain.builder(httpAddress) .setSelfCert() .singleEndpoint("/", (request, response) -> - response.withStatus(HttpResponseStatus.OK) - .withContentType("text/plain") - .write(request.getContent().retain())) + response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build() + .write(request.getContent().toString(StandardCharsets.UTF_8))) .build(); Server server = Server.builder(domain) .build(); @@ -129,10 +126,8 @@ class EncryptedTest { Server server = Server.builder(HttpServerDomain.builder(httpAddress) .setSelfCert() .singleEndpoint("/", (request, response) -> - response.withStatus(HttpResponseStatus.OK) - .withContentType("text/plain") - .write(request.getContent().retain()) - ) + response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build() + .write(request.getContent().toString(StandardCharsets.UTF_8))) .build()) .build(); server.accept(); diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/FlushTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/FlushTest.java index b71932e..cf05dee 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/FlushTest.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/FlushTest.java @@ -34,7 +34,7 @@ class FlushTest { .singleEndpoint("/flush", "/**", (req, resp) -> { HttpParameters parameters = req.getParameters(); logger.log(Level.INFO, "got request " + parameters.toString() + ", sending 302 Found"); - resp.withStatus(HttpResponseStatus.FOUND).flush(); + resp.getBuilder().setStatus(HttpResponseStatus.FOUND).build().flush(); }) .build(); Server server = Server.builder(domain) diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/MixedProtocolTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/MixedProtocolTest.java index ac16520..d9cb4aa 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/MixedProtocolTest.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/MixedProtocolTest.java @@ -10,7 +10,6 @@ import org.xbib.netty.http.client.api.ClientTransport; import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.server.HttpServerDomain; import org.xbib.netty.http.server.Server; -import org.xbib.netty.http.server.api.ServerResponse; import org.xbib.netty.http.server.test.NettyHttpTestExtension; import java.util.concurrent.atomic.AtomicInteger; import io.netty.handler.codec.http.HttpResponseStatus; @@ -22,9 +21,7 @@ class MixedProtocolTest { void testHttp1ClientHttp2Server() throws Exception { HttpAddress httpAddress = HttpAddress.http2("localhost", 8008); HttpServerDomain domain = HttpServerDomain.builder(httpAddress) - .singleEndpoint("/", (request, response) -> { - ServerResponse.write(response, HttpResponseStatus.OK); - }) + .singleEndpoint("/", (request, response) -> response.getBuilder().setStatus(HttpResponseStatus.OK).build().flush()) .build(); Server server = Server.builder(domain) .build(); @@ -56,9 +53,8 @@ class MixedProtocolTest { void testHttp2ClientHttp1Server() throws Exception { HttpAddress httpAddress = HttpAddress.http1("localhost", 8008); HttpServerDomain domain = HttpServerDomain.builder(httpAddress) - .singleEndpoint("/", (request, response) -> { - ServerResponse.write(response, HttpResponseStatus.OK); - }) + .singleEndpoint("/", (request, response) -> + response.getBuilder().setStatus(HttpResponseStatus.OK).build().flush()) .build(); Server server = Server.builder(domain) .build(); @@ -94,9 +90,9 @@ class MixedProtocolTest { HttpAddress httpAddress = HttpAddress.secureHttp2("localhost", 8143); HttpServerDomain domain = HttpServerDomain.builder(httpAddress) .setSelfCert() - .singleEndpoint("/", (request, response) -> { - ServerResponse.write(response, HttpResponseStatus.OK); - }) + .singleEndpoint("/", (request, response) -> + response.getBuilder().setStatus(HttpResponseStatus.OK).build().flush() + ) .build(); Server server = Server.builder(domain) //.enableDebug() diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/PostTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/PostTest.java index f67a038..b32d338 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/PostTest.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/PostTest.java @@ -11,7 +11,6 @@ import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.common.HttpParameters; import org.xbib.netty.http.common.HttpResponse; import org.xbib.netty.http.server.Server; -import org.xbib.netty.http.server.api.ServerResponse; import org.xbib.netty.http.server.HttpServerDomain; import org.xbib.netty.http.server.test.NettyHttpTestExtension; @@ -43,7 +42,7 @@ class PostTest { if ("Jörg".equals(parameters.getFirst("name"))) { success3.set(true); } - ServerResponse.write(resp, HttpResponseStatus.OK); + resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush(); }, "POST") .build(); Server server = Server.builder(domain) @@ -93,7 +92,7 @@ class PostTest { if ("Jörg".equals(parameters.getFirst("name"))) { success3.set(true); } - ServerResponse.write(resp, HttpResponseStatus.OK); + resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush(); }, "POST") .build(); Server server = Server.builder(domain) diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/PutTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/PutTest.java index 9aeee3f..e9fe4a8 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/PutTest.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/PutTest.java @@ -10,7 +10,6 @@ import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.common.HttpResponse; import org.xbib.netty.http.server.HttpServerDomain; import org.xbib.netty.http.server.Server; -import org.xbib.netty.http.server.api.ServerResponse; import org.xbib.netty.http.server.test.NettyHttpTestExtension; import java.nio.charset.StandardCharsets; import java.util.concurrent.atomic.AtomicBoolean; @@ -35,7 +34,7 @@ class PutTest { .singleEndpoint("/put", "/**", (req, resp) -> { logger.log(Level.INFO, "got request " + req.getContent().toString(StandardCharsets.UTF_8)); - ServerResponse.write(resp, HttpResponseStatus.OK); + resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush(); success1.set(true); }, "PUT") .build(); @@ -84,7 +83,7 @@ class PutTest { .singleEndpoint("/put", "/**", (req, resp) -> { logger.log(Level.INFO, "got request, length = " + req.getContent().readableBytes()); - ServerResponse.write(resp, HttpResponseStatus.OK); + resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush(); success1.set(true); }, "PUT") .build(); diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/StreamTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/StreamTest.java index 856a3c0..ea1d9bd 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/StreamTest.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/StreamTest.java @@ -28,8 +28,9 @@ class StreamTest { assertEquals("my body parameter", content); ByteBufOutputStream outputStream = response.getOutputStream(); outputStream.writeBytes("Hello World"); - response.withStatus(HttpResponseStatus.OK) - .withContentType("text/plain") + response.getBuilder().setStatus(HttpResponseStatus.OK) + .setContentType("text/plain") + .build() .write(outputStream); }) .build();