From ad460a5111b393974f6257e5bb2720ab4c25f9ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=CC=88rg=20Prante?= Date: Mon, 22 Apr 2019 23:09:47 +0200 Subject: [PATCH] add static file server capability --- build.gradle | 8 +- gradle.properties | 7 +- gradle/wrapper/gradle-wrapper.jar | Bin 55190 -> 55616 bytes gradle/wrapper/gradle-wrapper.properties | 4 +- gradlew | 18 +++- gradlew.bat | 18 +++- .../http/client/transport/HttpTransport.java | 4 + .../src/test/resources/logging.properties | 5 ++ .../org/xbib/netty/http/server/Server.java | 2 +- .../xbib/netty/http/server/ServerBuilder.java | 2 +- .../context/ClasspathContextHandler.java | 53 +++++++++++ .../http/server/context/ContextInfo.java | 23 ++--- .../context/DirectoryContextHandler.java | 4 +- .../server/context/NioContextHandler.java | 42 +++++++++ .../http/server/context/VirtualServer.java | 82 ++++++++++++------ .../server/transport/BaseServerTransport.java | 27 +++--- .../server/transport/Http2ServerResponse.java | 22 ++--- .../transport/Http2ServerTransport.java | 3 + .../server/transport/HttpServerResponse.java | 29 +++---- .../server/transport/HttpServerTransport.java | 3 + .../http/server/transport/ServerRequest.java | 14 +++ .../http/server/transport/ServerResponse.java | 13 +-- .../netty/http/server/util/MimeTypeUtils.java | 81 +++++++++++++++++ .../http/server/test/CleartextHttp1Test.java | 8 +- .../http/server/test/CleartextHttp2Test.java | 6 +- .../http/server/test/SecureHttp1Test.java | 9 +- .../http/server/test/SecureHttp2Test.java | 9 +- .../server/test/StaticFileServerTest.java | 55 ++++++++++++ .../http/server/test}/TestBase.java | 2 +- .../http/server/test/ThreadLeakTest.java | 1 - .../test}/hacks/CleartextHttp2Test.java | 4 +- .../hacks/HttpPipeliningHandlerTest.java | 4 +- .../MultiplexCodecCleartextHttp2Test.java | 4 +- .../MultithreadedCleartextHttp2Test.java | 4 +- ...eadedMultiplexCodecCleartextHttp2Test.java | 4 +- .../{ => server/test}/hacks/package-info.java | 2 +- .../src/test/resources/logging.properties | 5 ++ 37 files changed, 452 insertions(+), 129 deletions(-) create mode 100644 netty-http-client/src/test/resources/logging.properties create mode 100644 netty-http-server/src/main/java/org/xbib/netty/http/server/context/ClasspathContextHandler.java create mode 100644 netty-http-server/src/main/java/org/xbib/netty/http/server/context/NioContextHandler.java create mode 100644 netty-http-server/src/main/java/org/xbib/netty/http/server/util/MimeTypeUtils.java create mode 100644 netty-http-server/src/test/java/org/xbib/netty/http/server/test/StaticFileServerTest.java rename netty-http-server/src/test/java/org/xbib/{ => netty/http/server/test}/TestBase.java (96%) rename netty-http-server/src/test/java/org/xbib/netty/http/{ => server/test}/hacks/CleartextHttp2Test.java (99%) rename netty-http-server/src/test/java/org/xbib/netty/http/{ => server/test}/hacks/HttpPipeliningHandlerTest.java (98%) rename netty-http-server/src/test/java/org/xbib/netty/http/{ => server/test}/hacks/MultiplexCodecCleartextHttp2Test.java (99%) rename netty-http-server/src/test/java/org/xbib/netty/http/{ => server/test}/hacks/MultithreadedCleartextHttp2Test.java (99%) rename netty-http-server/src/test/java/org/xbib/netty/http/{ => server/test}/hacks/MultithreadedMultiplexCodecCleartextHttp2Test.java (99%) rename netty-http-server/src/test/java/org/xbib/netty/http/{ => server/test}/hacks/package-info.java (53%) create mode 100644 netty-http-server/src/test/resources/logging.properties diff --git a/build.gradle b/build.gradle index 4305628..1573f59 100644 --- a/build.gradle +++ b/build.gradle @@ -64,14 +64,14 @@ subprojects { } test { - if (JavaVersion.current() == JavaVersion.VERSION_1_8) { - jvmArgs "-javaagent:" + configurations.alpnagent.asPath - } + systemProperty 'java.util.logging.config.file', 'src/test/resources/logging.properties' testLogging { - // set this to 'true' to show lot of debug output showStandardStreams = false exceptionFormat = 'full' } + if (JavaVersion.current() == JavaVersion.VERSION_1_8) { + jvmArgs "-javaagent:" + configurations.alpnagent.asPath + } } clean { diff --git a/gradle.properties b/gradle.properties index 6373179..efe7b45 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,17 +1,16 @@ group = org.xbib name = netty-http -version = 4.1.34.0 +version = 4.1.35.0 # main packages -netty.version = 4.1.34.Final +netty.version = 4.1.35.Final tcnative.version = 2.0.22.Final bouncycastle.version = 1.61 alpnagent.version = 2.0.9 -xbib-net-url.version = 1.2.1 +xbib-net-url.version = 1.2.2 # test packages junit.version = 4.12 -# 1.0.1 conscrypt.version = 2.0.0 jackson.version = 2.8.11.1 wagon.version = 3.0.0 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 87b738cbd051603d91cc39de6cb000dd98fe6b02..5c2d1cf016b3885f6930543d57b744ea8c220a1a 100644 GIT binary patch delta 3320 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 mappingBuilder = new DomainNameMappingBuilder<>(sslContext); for (VirtualServer virtualServer : serverConfig.getVirtualServers()) { String name = virtualServer.getName(); - mappingBuilder.add( name == null ? "*" : name, sslContext); + mappingBuilder.add(name == null ? "*" : name, sslContext); } domainNameMapping = mappingBuilder.build(); } diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/ServerBuilder.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/ServerBuilder.java index e3c704f..a107e46 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/ServerBuilder.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/ServerBuilder.java @@ -224,7 +224,7 @@ public class ServerBuilder { return this; } - public ServerBuilder addVirtualHost(VirtualServer virtualServer) { + public ServerBuilder addVirtualServer(VirtualServer virtualServer) { this.serverConfig.addVirtualServer(virtualServer); return this; } diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/context/ClasspathContextHandler.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/context/ClasspathContextHandler.java new file mode 100644 index 0000000..dd58212 --- /dev/null +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/context/ClasspathContextHandler.java @@ -0,0 +1,53 @@ +package org.xbib.netty.http.server.context; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.handler.codec.http.HttpResponseStatus; +import org.xbib.netty.http.server.transport.ServerRequest; +import org.xbib.netty.http.server.transport.ServerResponse; +import org.xbib.netty.http.server.util.MimeTypeUtils; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class ClasspathContextHandler implements ContextHandler { + + private final ClassLoader classLoader; + + private final String prefix; + + public ClasspathContextHandler(ClassLoader classLoader, String prefix) { + this.classLoader = classLoader; + this.prefix = prefix; + } + + @Override + public void serve(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException { + String contextPath = serverRequest.getContextPath(); + URL url = classLoader.getResource(prefix + contextPath); + if (url != null) { + try { + Path path = Paths.get(url.toURI()); + FileChannel fileChannel = (FileChannel) Files.newByteChannel(path); + MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileChannel.size()); + ByteBuf byteBuf = Unpooled.wrappedBuffer(mappedByteBuffer); + try { + String contentType = MimeTypeUtils.guessFromPath(contextPath, false); + serverResponse.write(HttpResponseStatus.OK, contentType, byteBuf); + } finally { + byteBuf.release(); + } + } catch (URISyntaxException e) { + serverResponse.write(HttpResponseStatus.INTERNAL_SERVER_ERROR); + } + } else { + serverResponse.write(HttpResponseStatus.NOT_FOUND); + } + } +} diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/context/ContextInfo.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/context/ContextInfo.java index 470ff38..483cfc8 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/context/ContextInfo.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/context/ContextInfo.java @@ -1,6 +1,6 @@ package org.xbib.netty.http.server.context; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; /** @@ -8,12 +8,13 @@ import java.util.Map; */ public class ContextInfo { - private final Map handlers = new HashMap<>(2); - private final VirtualServer virtualServer; + private final Map methodHandlerMap; + public ContextInfo(VirtualServer virtualServer) { this.virtualServer = virtualServer; + this.methodHandlerMap = new LinkedHashMap<>(); } /** @@ -21,8 +22,8 @@ public class ContextInfo { * * @return the map of supported HTTP methods and their corresponding handlers */ - public Map getHandlers() { - return handlers; + public Map getMethodHandlerMap() { + return methodHandlerMap; } /** @@ -33,11 +34,13 @@ public class ContextInfo { */ public void addHandler(ContextHandler handler, String... methods) { if (methods.length == 0) { - methods = new String[]{"GET"}; - } - for (String method : methods) { - handlers.put(method, handler); - virtualServer.getMethods().add(method); + methodHandlerMap.put("GET", handler); + virtualServer.getMethods().add("GET"); + } else { + for (String method : methods) { + methodHandlerMap.put(method, handler); + virtualServer.getMethods().add(method); + } } } } diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/context/DirectoryContextHandler.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/context/DirectoryContextHandler.java index 9f032fb..02b6e4d 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/context/DirectoryContextHandler.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/context/DirectoryContextHandler.java @@ -2,6 +2,7 @@ package org.xbib.netty.http.server.context; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; +import io.netty.handler.codec.http.HttpResponseStatus; import org.xbib.netty.http.server.transport.ServerRequest; import org.xbib.netty.http.server.transport.ServerResponse; @@ -28,11 +29,10 @@ public class DirectoryContextHandler implements ContextHandler { String uri = serverRequest.getRequest().uri(); Path p = path.resolve(uri); ByteBuf byteBuf = read(allocator, p); - serverResponse.write(200, "application/octet-stream", byteBuf); + serverResponse.write(HttpResponseStatus.OK, "application/octet-stream", byteBuf); byteBuf.release(); } - public static ByteBuf read(ByteBufAllocator allocator, Path path) throws IOException { try (SeekableByteChannel sbc = Files.newByteChannel(path); diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/context/NioContextHandler.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/context/NioContextHandler.java new file mode 100644 index 0000000..3af4205 --- /dev/null +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/context/NioContextHandler.java @@ -0,0 +1,42 @@ +package org.xbib.netty.http.server.context; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.handler.codec.http.HttpResponseStatus; +import org.xbib.netty.http.server.transport.ServerRequest; +import org.xbib.netty.http.server.transport.ServerResponse; +import org.xbib.netty.http.server.util.MimeTypeUtils; + +import java.io.IOException; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.file.Files; +import java.nio.file.Path; + +public class NioContextHandler implements ContextHandler { + + private final Path prefix; + + public NioContextHandler(Path prefix) { + this.prefix = prefix; + if (!Files.exists(prefix) || !Files.isDirectory(prefix)) { + throw new IllegalArgumentException("prefix: " + prefix + " (not a directory"); + } + } + + @Override + public void serve(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException { + String requestPath = serverRequest.getRequestPath(); + Path path = prefix.resolve(requestPath.substring(1)); // starts always with '/' + if (Files.exists(path) && Files.isReadable(path)) { + try (FileChannel fileChannel = (FileChannel) Files.newByteChannel(path)) { + MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileChannel.size()); + ByteBuf byteBuf = Unpooled.wrappedBuffer(mappedByteBuffer); + String contentType = MimeTypeUtils.guessFromPath(requestPath, false); + serverResponse.write(HttpResponseStatus.OK, contentType, byteBuf); + } + } else { + serverResponse.write(HttpResponseStatus.NOT_FOUND); + } + } +} diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/context/VirtualServer.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/context/VirtualServer.java index 04d24b8..9fb39ec 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/context/VirtualServer.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/context/VirtualServer.java @@ -24,6 +24,10 @@ public class VirtualServer { private volatile boolean allowGeneratedIndex; + public VirtualServer() { + this(null); + } + /** * Constructs a VirtualServer with the given name. * @@ -94,25 +98,6 @@ public class VirtualServer { return methods; } - /** - * Returns the context handler for the given path. - * If a context is not found for the given path, the search is repeated for - * its parent path, and so on until a base context is found. If neither the - * given path nor any of its parents has a context, an empty context is returned. - * - * @param path the context's path - * @return the context info for the given path, or an empty context if none exists - */ - public ContextInfo getContext(String path) { - path = trimRight(path, '/'); // remove trailing slash - ContextInfo info = null; - while (info == null && path != null) { - info = contexts.get(path); - path = getParentPath(path); - } - return info != null ? info : emptyContext; - } - /** * Adds a context and its corresponding context handler to this server. * Paths are normalized by removing trailing slashes (except the root). @@ -122,15 +107,16 @@ public class VirtualServer { * @param methods the HTTP methods supported by the context handler (default is "GET") * @throws IllegalArgumentException if path is malformed */ - public void addContext(String path, ContextHandler handler, String... methods) { + public VirtualServer addContext(String path, ContextHandler handler, String... methods) { if (path == null || !path.startsWith("/") && !path.equals("*")) { throw new IllegalArgumentException("invalid path: " + path); } - path = trimRight(path, '/'); + String s = trimRight(path, '/'); ContextInfo info = new ContextInfo(this); - ContextInfo existing = contexts.putIfAbsent(path, info); + ContextInfo existing = contexts.putIfAbsent(s, info); info = existing != null ? existing : info; info.addHandler(handler, methods); + return this; } /** @@ -141,17 +127,37 @@ public class VirtualServer { * @throws IllegalArgumentException if a Context-annotated * method has an {@link Context invalid signature} */ - public void addContexts(Object o) throws IllegalArgumentException { + public VirtualServer addContexts(Object o) throws IllegalArgumentException { for (Class c = o.getClass(); c != null; c = c.getSuperclass()) { - // add to contexts those with @Context annotation for (Method m : c.getDeclaredMethods()) { Context context = m.getAnnotation(Context.class); if (context != null) { - //m.setAccessible(true); // allow access to private method addContext(context.value(), new MethodContextHandler(m, o), context.methods()); } } } + return this; + } + + /** + * Returns the context handler for the given path. + * If a context is not found for the given path, the search is repeated for + * its parent path, and so on until a base context is found. If neither the + * given path nor any of its parents has a context, an empty context is returned. + * + * @param path the context's path + * @return the context info for the given path, or an empty context if none exists + */ + public ContextPath getContextPath(String path) { + String s = trimRight(path, '/'); + ContextInfo info = null; + String hook = null; + while (info == null && s != null) { + hook = s; + info = contexts.get(s); + s = getParentPath(s); + } + return new ContextPath(hook, info != null ? info : emptyContext); } /** @@ -179,9 +185,29 @@ public class VirtualServer { * or null if given path is the root path */ private static String getParentPath(String path) { - path = trimRight(path, '/'); // remove trailing slash - int slash = path.lastIndexOf('/'); - return slash == -1 ? null : path.substring(0, slash); + String s = trimRight(path, '/'); // remove trailing slash + int slash = s.lastIndexOf('/'); + return slash == -1 ? null : s.substring(0, slash); + } + + public class ContextPath { + + private final String hook; + + private final ContextInfo contextInfo; + + ContextPath(String hook, ContextInfo contextInfo) { + this.hook = hook; + this.contextInfo = contextInfo; + } + + public String getHook() { + return hook; + } + + public ContextInfo getContextInfo() { + return contextInfo; + } } } diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/BaseServerTransport.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/BaseServerTransport.java index 251e4a3..cd17bc4 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/BaseServerTransport.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/BaseServerTransport.java @@ -3,6 +3,7 @@ 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.context.ContextHandler; @@ -33,7 +34,7 @@ abstract class BaseServerTransport implements ServerTransport { } @Override - public void exceptionReceived(ChannelHandlerContext ctx, Throwable throwable) throws IOException { + public void exceptionReceived(ChannelHandlerContext ctx, Throwable throwable) { logger.log(Level.WARNING, throwable.getMessage(), throwable); } @@ -54,7 +55,7 @@ abstract class BaseServerTransport implements ServerTransport { case 2: if (!reqHeaders.contains(HttpHeaderNames.HOST)) { // RFC2616#14.23: missing Host header gets 400 - serverResponse.writeError(400, "missing 'Host' header"); + serverResponse.writeError(HttpResponseStatus.BAD_REQUEST, "missing 'Host' header"); return false; } // return a continue response before reading body @@ -65,13 +66,13 @@ abstract class BaseServerTransport implements ServerTransport { //tempResp.sendHeaders(100); } else { // RFC2616#14.20: if unknown expect, send 417 - serverResponse.writeError(417); + serverResponse.writeError(HttpResponseStatus.EXPECTATION_FAILED); return false; } } break; default: - serverResponse.writeError(400, "Unknown version: " + version); + serverResponse.writeError(HttpResponseStatus.BAD_REQUEST, "Unknown version: " + version); return false; } return true; @@ -88,12 +89,14 @@ abstract class BaseServerTransport implements ServerTransport { String method = serverRequest.getRequest().method().name(); String path = serverRequest.getRequest().uri(); VirtualServer virtualServer = serverRequest.getVirtualServer(); - Map handlers = virtualServer.getContext(path).getHandlers(); + VirtualServer.ContextPath contextPath = virtualServer.getContextPath(path); + serverRequest.setContextPath(contextPath.getHook()); + Map methodHandlerMap = contextPath.getContextInfo().getMethodHandlerMap(); // RFC 2616#5.1.1 - GET and HEAD must be supported - if (method.equals("GET") || method.equals("HEAD") || handlers.containsKey(method)) { - ContextHandler handler = virtualServer.getContext(path).getHandlers().get(method); + if (method.equals("GET") || method.equals("HEAD") || methodHandlerMap.containsKey(method)) { + ContextHandler handler = methodHandlerMap.get(method); if (handler == null) { - serverResponse.writeError(404); + serverResponse.writeError(HttpResponseStatus.NOT_FOUND); } else { handler.serve(serverRequest, serverResponse); } @@ -101,15 +104,15 @@ abstract class BaseServerTransport implements ServerTransport { Set methods = new LinkedHashSet<>(METHODS); // "*" is a special server-wide (no-context) request supported by OPTIONS boolean isServerOptions = path.equals("*") && method.equals("OPTIONS"); - methods.addAll(isServerOptions ? virtualServer.getMethods() : handlers.keySet()); + methods.addAll(isServerOptions ? virtualServer.getMethods() : methodHandlerMap.keySet()); serverResponse.setHeader(HttpHeaderNames.ALLOW, String.join(", ", methods)); if (method.equals("OPTIONS")) { // default OPTIONS handler serverResponse.setHeader(HttpHeaderNames.CONTENT_LENGTH, "0"); // RFC2616#9.2 - serverResponse.write(200); + serverResponse.write(HttpResponseStatus.OK); } else if (virtualServer.getMethods().contains(method)) { - serverResponse.write(405); // supported by server, but not this context (nor built-in) + serverResponse.write(HttpResponseStatus.METHOD_NOT_ALLOWED); // supported by server, but not this context (nor built-in) } else { - serverResponse.writeError(501); // unsupported method + serverResponse.writeError(HttpResponseStatus.NOT_IMPLEMENTED); // unsupported method } } } 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 index e495544..182f98b 100644 --- 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 @@ -41,7 +41,7 @@ public class Http2ServerResponse implements ServerResponse { @Override public void write(String text) { - write(200, "text/plain; charset=utf-8", text); + write(HttpResponseStatus.OK, "text/plain; charset=utf-8", text); } /** @@ -50,8 +50,8 @@ public class Http2ServerResponse implements ServerResponse { * @param status the response status */ @Override - public void writeError(int status) { - writeError(status, status < 400 ? ":)" : "sorry it didn't work out :("); + public void writeError(HttpResponseStatus status) { + writeError(status, status.code() < 400 ? ":)" : "sorry it didn't work out :("); } /** @@ -64,32 +64,32 @@ public class Http2ServerResponse implements ServerResponse { * @param text the text body (sent as text/html) */ @Override - public void writeError(int status, String text) { + public void writeError(HttpResponseStatus status, String text) { write(status, "text/html; charset=utf-8", String.format("%n%n%d %s%n" + "

%d %s

%n

%s

%n", - status, HttpResponseStatus.valueOf(status).reasonPhrase(), - status, HttpResponseStatus.valueOf(status).reasonPhrase(), + status.code(), status.reasonPhrase(), + status.code(), status.reasonPhrase(), escapeHTML(text))); } @Override - public void write(int status) { + public void write(HttpResponseStatus status) { write(status, null, (ByteBuf) null); } @Override - public void write(int status, String contentType, String text) { + public void write(HttpResponseStatus status, String contentType, String text) { write(status, contentType, ByteBufUtil.writeUtf8(ctx.alloc(), text)); } @Override - public void write(int status, String contentType, String text, Charset charset) { + public void write(HttpResponseStatus status, String contentType, String text, Charset charset) { write(status, contentType, ByteBufUtil.encodeString(ctx.alloc(), CharBuffer.allocate(text.length()).append(text), charset)); } @Override - public void write(int status, String contentType, ByteBuf byteBuf) { + public void write(HttpResponseStatus status, String contentType, ByteBuf byteBuf) { if (byteBuf != null) { CharSequence s = headers.get(HttpHeaderNames.CONTENT_TYPE); if (s == null) { @@ -120,7 +120,7 @@ public class Http2ServerResponse implements ServerResponse { } } Http2Headers http2Headers = new DefaultHttp2Headers() - .status(HttpResponseStatus.valueOf(status).codeAsText()) + .status(status.codeAsText()) .add(headers); ctx.channel().write(new DefaultHttp2HeadersFrame(http2Headers,byteBuf == null)); if (byteBuf != null) { diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/Http2ServerTransport.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/Http2ServerTransport.java index 69e29b6..3280dfb 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/Http2ServerTransport.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/Http2ServerTransport.java @@ -3,6 +3,7 @@ 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.HttpHeaderNames; +import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http2.Http2Settings; import io.netty.handler.codec.http2.HttpConversionUtil; import org.xbib.netty.http.common.HttpAddress; @@ -36,6 +37,8 @@ public class Http2ServerTransport extends BaseServerTransport { ServerResponse serverResponse = new Http2ServerResponse(serverRequest, ctx); if (acceptRequest(serverRequest, serverResponse)) { handle(serverRequest, serverResponse); + } else { + serverResponse.write(HttpResponseStatus.NOT_ACCEPTABLE); } } 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/transport/HttpServerResponse.java index e381b69..df9821a 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/transport/HttpServerResponse.java @@ -21,12 +21,9 @@ import java.nio.charset.Charset; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; -import java.util.logging.Logger; public class HttpServerResponse implements ServerResponse { - private static final Logger logger = Logger.getLogger(HttpServerResponse.class.getName()); - private final ServerRequest serverRequest; private final ChannelHandlerContext ctx; @@ -49,7 +46,7 @@ public class HttpServerResponse implements ServerResponse { @Override public void write(String text) { - write(200, "text/plain; charset=utf-8", text); + write(HttpResponseStatus.OK, "text/plain; charset=utf-8", text); } /** @@ -58,8 +55,8 @@ public class HttpServerResponse implements ServerResponse { * @param status the response status */ @Override - public void writeError(int status) { - writeError(status, status < 400 ? ":)" : "sorry it didn't work out :("); + public void writeError(HttpResponseStatus status) { + writeError(status, status.code() < 400 ? ":)" : "sorry it didn't work out :("); } /** @@ -72,32 +69,32 @@ public class HttpServerResponse implements ServerResponse { * @param text the text body (sent as text/html) */ @Override - public void writeError(int status, String text) { + public void writeError(HttpResponseStatus status, String text) { write(status, "text/html; charset=utf-8", String.format("%n%n%d %s%n" + "

%d %s

%n

%s

%n", - status, HttpResponseStatus.valueOf(status).reasonPhrase(), - status, HttpResponseStatus.valueOf(status).reasonPhrase(), + status.code(), status.reasonPhrase(), + status.code(), status.reasonPhrase(), escapeHTML(text))); } @Override - public void write(int status) { + public void write(HttpResponseStatus status) { write(status, null, (ByteBuf) null); } @Override - public void write(int status, String contentType, String text) { + public void write(HttpResponseStatus status, String contentType, String text) { write(status, contentType, ByteBufUtil.writeUtf8(ctx.alloc(), text)); } @Override - public void write(int status, String contentType, String text, Charset charset) { + public void write(HttpResponseStatus status, String contentType, String text, Charset charset) { write(status, contentType, ByteBufUtil.encodeString(ctx.alloc(), CharBuffer.allocate(text.length()).append(text), charset)); } @Override - public void write(int status, String contentType, ByteBuf byteBuf) { + public void write(HttpResponseStatus status, String contentType, ByteBuf byteBuf) { if (byteBuf != null) { CharSequence s = headers.get(HttpHeaderNames.CONTENT_TYPE); if (s == null) { @@ -123,9 +120,9 @@ public class HttpServerResponse implements ServerResponse { } FullHttpResponse fullHttpResponse = byteBuf != null ? new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, - HttpResponseStatus.valueOf(status), byteBuf, headers, trailingHeaders) : + status, byteBuf, headers, trailingHeaders) : new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, - HttpResponseStatus.valueOf(status), Unpooled.EMPTY_BUFFER, headers, trailingHeaders); + status, Unpooled.EMPTY_BUFFER, headers, trailingHeaders); if (serverRequest != null && serverRequest.getSequenceId() != null) { HttpPipelinedResponse httpPipelinedResponse = new HttpPipelinedResponse(fullHttpResponse, ctx.channel().newPromise(), serverRequest.getSequenceId()); @@ -176,7 +173,7 @@ public class HttpServerResponse implements ServerResponse { break; } if (ref != null) { - es.append(s.substring(start, i)).append(ref); + es.append(s, start, i).append(ref); start = i + 1; } } diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/HttpServerTransport.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/HttpServerTransport.java index c6962dc..cfa2cba 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/HttpServerTransport.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/HttpServerTransport.java @@ -3,6 +3,7 @@ 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.HttpHeaderNames; +import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http2.Http2Settings; import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.server.Server; @@ -35,6 +36,8 @@ public class HttpServerTransport extends BaseServerTransport { ServerResponse serverResponse = new HttpServerResponse(serverRequest, ctx); if (acceptRequest(serverRequest, serverResponse)) { handle(serverRequest, serverResponse); + } else { + serverResponse.write(HttpResponseStatus.NOT_ACCEPTABLE); } } diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/ServerRequest.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/ServerRequest.java index 3bd4559..9b766cd 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/ServerRequest.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/ServerRequest.java @@ -21,6 +21,8 @@ public class ServerRequest { private final Integer requestId; + private String contextPath; + public ServerRequest(VirtualServer virtualServer, HttpAddress httpAddress, FullHttpRequest httpRequest, Integer sequenceId, Integer streamId, Integer requestId) { this.virtualServer = virtualServer; @@ -35,6 +37,18 @@ public class ServerRequest { return virtualServer; } + public void setContextPath(String contextPath) { + this.contextPath = contextPath; + } + + public String getContextPath() { + return contextPath; + } + + public String getRequestPath() { + return contextPath != null ? httpRequest.uri().substring(contextPath.length()) : httpRequest.uri(); + } + public HttpAddress getHttpAddress() { return httpAddress; } diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/ServerResponse.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/ServerResponse.java index 297e813..c4dd0ee 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/ServerResponse.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/ServerResponse.java @@ -1,6 +1,7 @@ package org.xbib.netty.http.server.transport; import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.util.AsciiString; import java.nio.charset.Charset; @@ -14,16 +15,16 @@ public interface ServerResponse { void write(String text); - void writeError(int status); + void writeError(HttpResponseStatus status); - void writeError(int status, String text); + void writeError(HttpResponseStatus status, String text); - void write(int status); + void write(HttpResponseStatus status); - void write(int status, String contentType, String text); + void write(HttpResponseStatus status, String contentType, String text); - void write(int status, String contentType, String text, Charset charset); + void write(HttpResponseStatus status, String contentType, String text, Charset charset); - void write(int status, String contentType, ByteBuf byteBuf); + void write(HttpResponseStatus status, String contentType, ByteBuf byteBuf); } diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/util/MimeTypeUtils.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/util/MimeTypeUtils.java new file mode 100644 index 0000000..ed14c18 --- /dev/null +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/util/MimeTypeUtils.java @@ -0,0 +1,81 @@ +package org.xbib.netty.http.server.util; + +import java.net.URLConnection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import static java.util.Objects.requireNonNull; + +public class MimeTypeUtils { + + /** + * A map from extension to MIME types, which is queried before + * {@link URLConnection#guessContentTypeFromName(String)}, so that + * important extensions are always mapped to the right MIME types. + */ + private static final Map EXTENSION_TO_MEDIA_TYPE; + + static { + Map map = new HashMap<>(); + // Text files + add(map, "text/css", "css"); + add(map, "text/html", "html", "htm"); + add(map, "text/plain", "txt"); + + // Image files + add(map, "image/gif", "gif"); + add(map, "image/jpeg", "jpeg", "jpg"); + add(map, "image/png", "png"); + add(map, "image/svg+xml", "svg", "svgz"); + add(map, "image/x-icon", "ico"); + + // Font files + add(map, "application/x-font-ttf", "ttc", "ttf"); + add(map, "application/font-woff", "woff"); + add(map, "application/font-woff2", "woff2"); + add(map, "application/vnd.ms-fontobject", "eot"); + add(map, "font/opentype", "otf"); + + // JavaScript, XML, etc + add(map, "application/javascript", "js", "map"); + add(map, "application/json", "json"); + add(map, "application/pdf", "pdf"); + add(map, "application/xhtml+xml", "xhtml", "xhtm"); + add(map, "application/xml", "xml", "xsd"); + add(map, "application/xml-dtd", "dtd"); + + EXTENSION_TO_MEDIA_TYPE = Collections.unmodifiableMap(map); + } + + private static void add(Map extensionToMediaType, + String mediaType, String... extensions) { + for (String s : extensions) { + extensionToMediaType.put(s, mediaType); + } + } + + public static String guessFromPath(String path, boolean preCompressed) { + requireNonNull(path, "path"); + String s = path; + // If the path is for a precompressed file, it will have an additional extension indicating the + // encoding, which we don't want to use when determining content type. + if (preCompressed) { + s = s.substring(0, s.lastIndexOf('.')); + } + int dotIdx = s.lastIndexOf('.'); + int slashIdx = s.lastIndexOf('/'); + if (dotIdx < 0 || slashIdx > dotIdx) { + // No extension + return null; + } + String extension = s.substring(dotIdx + 1).toLowerCase(Locale.ROOT); + String mediaType = EXTENSION_TO_MEDIA_TYPE.get(extension); + if (mediaType != null) { + return mediaType; + } + String guessedContentType = URLConnection.guessContentTypeFromName(path); + return guessedContentType != null ? guessedContentType : "application/octet-stream"; + } +} diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/CleartextHttp1Test.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/CleartextHttp1Test.java index 9381ef6..b332bbb 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/CleartextHttp1Test.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/CleartextHttp1Test.java @@ -1,8 +1,8 @@ package org.xbib.netty.http.server.test; +import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpVersion; import org.junit.Test; -import org.xbib.TestBase; import org.xbib.netty.http.client.Client; import org.xbib.netty.http.client.Request; import org.xbib.netty.http.client.listener.ResponseListener; @@ -30,7 +30,7 @@ public class CleartextHttp1Test extends TestBase { Server server = Server.builder() .bind(httpAddress).build(); server.getDefaultVirtualServer().addContext("/", (request, response) -> - response.write(200, "text/plain", request.getRequest().content().retain())); + response.write(HttpResponseStatus.OK, "text/plain", request.getRequest().content().retain())); server.accept(); Client client = Client.builder() .build(); @@ -63,7 +63,7 @@ public class CleartextHttp1Test extends TestBase { //.enableDebug() .bind(httpAddress).build(); server.getDefaultVirtualServer().addContext("/", (request, response) -> { - response.write(200, "text/plain", request.getRequest().content().retain()); + response.write(HttpResponseStatus.OK, "text/plain", request.getRequest().content().retain()); }); server.accept(); Client client = Client.builder() @@ -109,7 +109,7 @@ public class CleartextHttp1Test extends TestBase { //.enableDebug() .bind(httpAddress).build(); server.getDefaultVirtualServer().addContext("/", (request, response) -> { - response.write(200, "text/plain", request.getRequest().content().retain()); + response.write(HttpResponseStatus.OK, "text/plain", request.getRequest().content().retain()); }); server.accept(); Client client = Client.builder() diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/CleartextHttp2Test.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/CleartextHttp2Test.java index 8d58d24..ef4011d 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/CleartextHttp2Test.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/CleartextHttp2Test.java @@ -1,7 +1,7 @@ package org.xbib.netty.http.server.test; +import io.netty.handler.codec.http.HttpResponseStatus; import org.junit.Test; -import org.xbib.TestBase; import org.xbib.netty.http.client.Client; import org.xbib.netty.http.client.Request; import org.xbib.netty.http.client.listener.ResponseListener; @@ -31,7 +31,7 @@ public class CleartextHttp2Test extends TestBase { .bind(httpAddress) .build(); server.getDefaultVirtualServer().addContext("/", (request, response) -> - response.write(200, "text/plain", request.getRequest().content().retain())); + response.write(HttpResponseStatus.OK, "text/plain", request.getRequest().content().retain())); server.accept(); Client client = Client.builder() .build(); @@ -71,7 +71,7 @@ public class CleartextHttp2Test extends TestBase { Server server = Server.builder() .bind(httpAddress).build(); server.getDefaultVirtualServer().addContext("/", (request, response) -> - response.write(200, "text/plain", request.getRequest().content().retain())); + response.write(HttpResponseStatus.OK, "text/plain", request.getRequest().content().retain())); //server.getDefaultVirtualServer().addContext("/", (request, response) -> // response.write(request.getRequest().content().toString(StandardCharsets.UTF_8))); server.accept(); diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/SecureHttp1Test.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/SecureHttp1Test.java index cd3400b..322eaa2 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/SecureHttp1Test.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/SecureHttp1Test.java @@ -1,10 +1,9 @@ package org.xbib.netty.http.server.test; +import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpVersion; import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.junit.Ignore; import org.junit.Test; -import org.xbib.TestBase; import org.xbib.netty.http.client.Client; import org.xbib.netty.http.client.Request; import org.xbib.netty.http.client.listener.ResponseListener; @@ -53,7 +52,7 @@ public class SecureHttp1Test extends TestBase { }; try { server.getDefaultVirtualServer().addContext("/", (request, response) -> - response.write(200, "text/plain", request.getRequest().content().retain())); + response.write(HttpResponseStatus.OK, "text/plain", request.getRequest().content().retain())); server.accept(); Request request = Request.get().setVersion(HttpVersion.HTTP_1_1) .url(server.getServerConfig().getAddress().base()) @@ -77,7 +76,7 @@ public class SecureHttp1Test extends TestBase { .setSelfCert() .bind(httpAddress).build(); server.getDefaultVirtualServer().addContext("/", (request, response) -> - response.write(200, "text/plain", request.getRequest().content().retain())); + response.write(HttpResponseStatus.OK, "text/plain", request.getRequest().content().retain())); server.accept(); Client client = Client.builder() .setJdkSslProvider() @@ -125,7 +124,7 @@ public class SecureHttp1Test extends TestBase { .bind(httpAddress) .build(); server.getDefaultVirtualServer().addContext("/", (request, response) -> - response.write(200, "text/plain", request.getRequest().content().retain()) + response.write(HttpResponseStatus.OK, "text/plain", request.getRequest().content().retain()) ); server.accept(); Client client = Client.builder() diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/SecureHttp2Test.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/SecureHttp2Test.java index 60a26a3..805bd31 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/SecureHttp2Test.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/SecureHttp2Test.java @@ -1,9 +1,8 @@ package org.xbib.netty.http.server.test; +import io.netty.handler.codec.http.HttpResponseStatus; import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.junit.Ignore; import org.junit.Test; -import org.xbib.TestBase; import org.xbib.netty.http.client.Client; import org.xbib.netty.http.client.Request; import org.xbib.netty.http.client.listener.ResponseListener; @@ -42,7 +41,7 @@ public class SecureHttp2Test extends TestBase { .bind(httpAddress) .build(); server.getDefaultVirtualServer().addContext("/", (request, response) -> - response.write(200, "text/plain", request.getRequest().content().retain())); + response.write(HttpResponseStatus.OK, "text/plain", request.getRequest().content().retain())); server.accept(); Client client = Client.builder() .setJdkSslProvider() @@ -86,7 +85,7 @@ public class SecureHttp2Test extends TestBase { .bind(httpAddress) .build(); server.getDefaultVirtualServer().addContext("/", (request, response) -> - response.write(200, "text/plain", request.getRequest().content().retain())); + response.write(HttpResponseStatus.OK, "text/plain", request.getRequest().content().retain())); server.accept(); Client client = Client.builder() .setJdkSslProvider() @@ -138,7 +137,7 @@ public class SecureHttp2Test extends TestBase { .bind(httpAddress) .build(); server.getDefaultVirtualServer().addContext("/", (request, response) -> - response.write(200, "text/plain", request.getRequest().content().retain()) + response.write(HttpResponseStatus.OK, "text/plain", request.getRequest().content().retain()) ); server.accept(); Client client = Client.builder() diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/StaticFileServerTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/StaticFileServerTest.java new file mode 100644 index 0000000..346dec1 --- /dev/null +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/StaticFileServerTest.java @@ -0,0 +1,55 @@ +package org.xbib.netty.http.server.test; + +import io.netty.handler.codec.http.HttpVersion; +import org.junit.Test; +import org.xbib.netty.http.client.Client; +import org.xbib.netty.http.client.Request; +import org.xbib.netty.http.server.Server; +import org.xbib.netty.http.server.context.NioContextHandler; +import org.xbib.netty.http.server.context.VirtualServer; + +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class StaticFileServerTest { + + private static final Logger logger = Logger.getLogger(StaticFileServerTest.class.getName()); + + @Test + public void testStaticFileServer() throws Exception { + Path vartmp = Paths.get("/var/tmp/"); + Server server = Server.builder() + .addVirtualServer(new VirtualServer().addContext("/static", new NioContextHandler(vartmp))) + .build(); + Client client = Client.builder() + .build(); + final AtomicBoolean success = new AtomicBoolean(false); + try { + Files.write(vartmp.resolve("test.txt"), "Hello Jörg".getBytes(StandardCharsets.UTF_8)); + server.accept(); + Request request = Request.get().setVersion(HttpVersion.HTTP_1_1) + .url(server.getServerConfig().getAddress().base().resolve("/static/test.txt")) + .build() + .setResponseListener(r -> { + assertEquals("Hello Jörg", r.content().toString(StandardCharsets.UTF_8)); + success.set(true); + }); + logger.log(Level.INFO, request.toString()); + client.execute(request).get(); + logger.log(Level.INFO, "request complete"); + } finally { + server.shutdownGracefully(); + client.shutdownGracefully(); + Files.delete(vartmp.resolve("test.txt")); + } + assertTrue(success.get()); + } +} diff --git a/netty-http-server/src/test/java/org/xbib/TestBase.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/TestBase.java similarity index 96% rename from netty-http-server/src/test/java/org/xbib/TestBase.java rename to netty-http-server/src/test/java/org/xbib/netty/http/server/test/TestBase.java index 7b46847..126e379 100644 --- a/netty-http-server/src/test/java/org/xbib/TestBase.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/TestBase.java @@ -1,4 +1,4 @@ -package org.xbib; +package org.xbib.netty.http.server.test; import java.util.logging.ConsoleHandler; import java.util.logging.Handler; 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 bbe63be..ecbe611 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 @@ -3,7 +3,6 @@ package org.xbib.netty.http.server.test; import io.netty.buffer.UnpooledByteBufAllocator; import org.junit.After; import org.junit.Test; -import org.xbib.TestBase; import org.xbib.netty.http.server.Server; import java.io.IOException; diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/hacks/CleartextHttp2Test.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/hacks/CleartextHttp2Test.java similarity index 99% rename from netty-http-server/src/test/java/org/xbib/netty/http/hacks/CleartextHttp2Test.java rename to netty-http-server/src/test/java/org/xbib/netty/http/server/test/hacks/CleartextHttp2Test.java index a4ffe5d..44b1fe3 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/hacks/CleartextHttp2Test.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/hacks/CleartextHttp2Test.java @@ -1,4 +1,4 @@ -package org.xbib.netty.http.hacks; +package org.xbib.netty.http.server.test.hacks; import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.ServerBootstrap; @@ -31,7 +31,7 @@ import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; import org.junit.Ignore; import org.junit.Test; -import org.xbib.TestBase; +import org.xbib.netty.http.server.test.TestBase; import java.net.InetSocketAddress; import java.util.concurrent.CompletableFuture; diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/hacks/HttpPipeliningHandlerTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/hacks/HttpPipeliningHandlerTest.java similarity index 98% rename from netty-http-server/src/test/java/org/xbib/netty/http/hacks/HttpPipeliningHandlerTest.java rename to netty-http-server/src/test/java/org/xbib/netty/http/server/test/hacks/HttpPipeliningHandlerTest.java index 5f82f00..3005170 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/hacks/HttpPipeliningHandlerTest.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/hacks/HttpPipeliningHandlerTest.java @@ -1,4 +1,4 @@ -package org.xbib.netty.http.hacks; +package org.xbib.netty.http.server.test.hacks; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; @@ -24,7 +24,7 @@ import org.junit.Test; 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.TestBase; +import org.xbib.netty.http.server.test.TestBase; import java.nio.channels.ClosedChannelException; import java.nio.charset.StandardCharsets; diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/hacks/MultiplexCodecCleartextHttp2Test.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/hacks/MultiplexCodecCleartextHttp2Test.java similarity index 99% rename from netty-http-server/src/test/java/org/xbib/netty/http/hacks/MultiplexCodecCleartextHttp2Test.java rename to netty-http-server/src/test/java/org/xbib/netty/http/server/test/hacks/MultiplexCodecCleartextHttp2Test.java index 10cb1b8..eb0a8e8 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/hacks/MultiplexCodecCleartextHttp2Test.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/hacks/MultiplexCodecCleartextHttp2Test.java @@ -1,4 +1,4 @@ -package org.xbib.netty.http.hacks; +package org.xbib.netty.http.server.test.hacks; import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.ServerBootstrap; @@ -43,7 +43,7 @@ import io.netty.handler.logging.LoggingHandler; import io.netty.util.AsciiString; import org.junit.Ignore; import org.junit.Test; -import org.xbib.TestBase; +import org.xbib.netty.http.server.test.TestBase; import java.net.InetSocketAddress; import java.util.concurrent.CompletableFuture; diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/hacks/MultithreadedCleartextHttp2Test.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/hacks/MultithreadedCleartextHttp2Test.java similarity index 99% rename from netty-http-server/src/test/java/org/xbib/netty/http/hacks/MultithreadedCleartextHttp2Test.java rename to netty-http-server/src/test/java/org/xbib/netty/http/server/test/hacks/MultithreadedCleartextHttp2Test.java index 47a45fe..a527983 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/hacks/MultithreadedCleartextHttp2Test.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/hacks/MultithreadedCleartextHttp2Test.java @@ -1,4 +1,4 @@ -package org.xbib.netty.http.hacks; +package org.xbib.netty.http.server.test.hacks; import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.ServerBootstrap; @@ -29,7 +29,7 @@ import io.netty.handler.codec.http2.HttpToHttp2ConnectionHandlerBuilder; import io.netty.handler.codec.http2.InboundHttp2ToHttpAdapterBuilder; import org.junit.Ignore; import org.junit.Test; -import org.xbib.TestBase; +import org.xbib.netty.http.server.test.TestBase; import java.net.InetSocketAddress; import java.util.concurrent.CompletableFuture; diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/hacks/MultithreadedMultiplexCodecCleartextHttp2Test.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/hacks/MultithreadedMultiplexCodecCleartextHttp2Test.java similarity index 99% rename from netty-http-server/src/test/java/org/xbib/netty/http/hacks/MultithreadedMultiplexCodecCleartextHttp2Test.java rename to netty-http-server/src/test/java/org/xbib/netty/http/server/test/hacks/MultithreadedMultiplexCodecCleartextHttp2Test.java index 32e9a44..94b61cc 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/hacks/MultithreadedMultiplexCodecCleartextHttp2Test.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/hacks/MultithreadedMultiplexCodecCleartextHttp2Test.java @@ -1,4 +1,4 @@ -package org.xbib.netty.http.hacks; +package org.xbib.netty.http.server.test.hacks; import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.ServerBootstrap; @@ -37,7 +37,7 @@ import io.netty.handler.codec.http2.Http2StreamFrameToHttpObjectCodec; import io.netty.util.AsciiString; import org.junit.Ignore; import org.junit.Test; -import org.xbib.TestBase; +import org.xbib.netty.http.server.test.TestBase; import java.net.InetSocketAddress; import java.util.concurrent.CompletableFuture; diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/hacks/package-info.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/hacks/package-info.java similarity index 53% rename from netty-http-server/src/test/java/org/xbib/netty/http/hacks/package-info.java rename to netty-http-server/src/test/java/org/xbib/netty/http/server/test/hacks/package-info.java index 61b2924..d316592 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/hacks/package-info.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/hacks/package-info.java @@ -1,4 +1,4 @@ /** * Hacking Netty for showing server functions. */ -package org.xbib.netty.http.hacks; +package org.xbib.netty.http.server.test.hacks; diff --git a/netty-http-server/src/test/resources/logging.properties b/netty-http-server/src/test/resources/logging.properties new file mode 100644 index 0000000..b955428 --- /dev/null +++ b/netty-http-server/src/test/resources/logging.properties @@ -0,0 +1,5 @@ +handlers = java.util.logging.ConsoleHandler +.level = FINE +java.util.logging.ConsoleHandler.level = FINE +java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter +java.util.logging.SimpleFormatter.format = %1$tFT%1$tT.%1$tL%1$tz [%4$-11s] [%3$s] %5$s %6$s%n