diff --git a/gradle.properties b/gradle.properties index 710d53f..a90966a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,13 +1,13 @@ group = org.xbib name = netty-http -version = 4.1.39.2 +version = 4.1.41.0 # netty -netty.version = 4.1.39.Final +netty.version = 4.1.41.Final tcnative.version = 2.0.25.Final # for netty-http-common -xbib-net-url.version = 2.0.0 +xbib-net-url.version = 2.0.1 # for netty-http-server bouncycastle.version = 1.62 diff --git a/netty-http-client/src/main/java/org/xbib/netty/http/client/Request.java b/netty-http-client/src/main/java/org/xbib/netty/http/client/Request.java index 61b5b6a..fe98ba1 100644 --- a/netty-http-client/src/main/java/org/xbib/netty/http/client/Request.java +++ b/netty-http-client/src/main/java/org/xbib/netty/http/client/Request.java @@ -172,8 +172,8 @@ public class Request { @Override public String toString() { - return "Request[url='" + url + - "',version=" + httpVersion + + return "Request[url=" + url + + ",version=" + httpVersion + ",method=" + httpMethod + ",headers=" + headers.entries() + ",content=" + (content != null && content.readableBytes() >= 16 ? diff --git a/netty-http-client/src/main/java/org/xbib/netty/http/client/transport/BaseTransport.java b/netty-http-client/src/main/java/org/xbib/netty/http/client/transport/BaseTransport.java index 7f15c11..19731ff 100644 --- a/netty-http-client/src/main/java/org/xbib/netty/http/client/transport/BaseTransport.java +++ b/netty-http-client/src/main/java/org/xbib/netty/http/client/transport/BaseTransport.java @@ -102,7 +102,7 @@ abstract class BaseTransport implements Transport { flow.close(); } channels.clear(); - requests.clear(); + // do not clear requests } @Override diff --git a/netty-http-client/src/test/java/org/xbib/netty/http/client/test/htt2push/Http2PushTest.java b/netty-http-client/src/test/java/org/xbib/netty/http/client/test/htt2push/Http2PushTest.java index 08ebcd0..b26d21e 100644 --- a/netty-http-client/src/test/java/org/xbib/netty/http/client/test/htt2push/Http2PushTest.java +++ b/netty-http-client/src/test/java/org/xbib/netty/http/client/test/htt2push/Http2PushTest.java @@ -1,6 +1,7 @@ package org.xbib.netty.http.client.test.htt2push; import io.netty.handler.codec.http.HttpMethod; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.xbib.netty.http.client.Client; @@ -11,6 +12,7 @@ import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; +@Disabled // /http2-push.io "connection refused" @ExtendWith(NettyHttpTestExtension.class) class Http2PushTest { diff --git a/netty-http-common/src/main/java/org/xbib/netty/http/common/HttpMethod.java b/netty-http-common/src/main/java/org/xbib/netty/http/common/HttpMethod.java new file mode 100644 index 0000000..3b95663 --- /dev/null +++ b/netty-http-common/src/main/java/org/xbib/netty/http/common/HttpMethod.java @@ -0,0 +1,6 @@ +package org.xbib.netty.http.common; + +public enum HttpMethod { + + GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH +} diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/util/NetworkClass.java b/netty-http-common/src/main/java/org/xbib/netty/http/common/net/NetworkClass.java similarity index 70% rename from netty-http-server/src/main/java/org/xbib/netty/http/server/util/NetworkClass.java rename to netty-http-common/src/main/java/org/xbib/netty/http/common/net/NetworkClass.java index 16d9912..f8e3786 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/util/NetworkClass.java +++ b/netty-http-common/src/main/java/org/xbib/netty/http/common/net/NetworkClass.java @@ -1,4 +1,4 @@ -package org.xbib.netty.http.server.util; +package org.xbib.netty.http.common.net; /** * The network classes. diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/util/NetworkProtocolVersion.java b/netty-http-common/src/main/java/org/xbib/netty/http/common/net/NetworkProtocolVersion.java similarity index 74% rename from netty-http-server/src/main/java/org/xbib/netty/http/server/util/NetworkProtocolVersion.java rename to netty-http-common/src/main/java/org/xbib/netty/http/common/net/NetworkProtocolVersion.java index b4f997a..b3cdc25 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/util/NetworkProtocolVersion.java +++ b/netty-http-common/src/main/java/org/xbib/netty/http/common/net/NetworkProtocolVersion.java @@ -1,4 +1,4 @@ -package org.xbib.netty.http.server.util; +package org.xbib.netty.http.common.net; /** * The TCP/IP network protocol versions. diff --git a/netty-http-common/src/main/java/org/xbib/netty/http/common/util/LimitedConcurrentHashMap.java b/netty-http-common/src/main/java/org/xbib/netty/http/common/util/LimitedConcurrentHashMap.java new file mode 100644 index 0000000..d648fb6 --- /dev/null +++ b/netty-http-common/src/main/java/org/xbib/netty/http/common/util/LimitedConcurrentHashMap.java @@ -0,0 +1,39 @@ +package org.xbib.netty.http.common.util; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +@SuppressWarnings("serial") +public class LimitedConcurrentHashMap extends ConcurrentHashMap { + + private final Semaphore semaphore; + + public LimitedConcurrentHashMap(int limit) { + super(16, 0.75f); + this.semaphore = new Semaphore(limit); + } + + @Override + public V put(K key, V value) { + try { + if (semaphore.tryAcquire(1L, TimeUnit.SECONDS)) { + return super.put(key, value); + } + } catch (InterruptedException e) { + throw new IllegalArgumentException("size limit exceeded"); + } + throw new IllegalArgumentException("size limit exceeded"); + } + + @Override + public V remove(Object key) { + V v; + try { + v = super.remove(key); + } finally { + semaphore.release(); + } + return v; + } +} diff --git a/netty-http-common/src/main/java/org/xbib/netty/http/common/util/LimitedMap.java b/netty-http-common/src/main/java/org/xbib/netty/http/common/util/LimitedLinkedHashMap.java similarity index 78% rename from netty-http-common/src/main/java/org/xbib/netty/http/common/util/LimitedMap.java rename to netty-http-common/src/main/java/org/xbib/netty/http/common/util/LimitedLinkedHashMap.java index d6b0af1..93ebb66 100644 --- a/netty-http-common/src/main/java/org/xbib/netty/http/common/util/LimitedMap.java +++ b/netty-http-common/src/main/java/org/xbib/netty/http/common/util/LimitedLinkedHashMap.java @@ -3,11 +3,11 @@ package org.xbib.netty.http.common.util; import java.util.LinkedHashMap; @SuppressWarnings("serial") -public class LimitedMap extends LinkedHashMap { +public class LimitedLinkedHashMap extends LinkedHashMap { private final int limit; - public LimitedMap(int limit) { + public LimitedLinkedHashMap(int limit) { super(16, 0.75f, true); this.limit = limit; } diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/Domain.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/Domain.java index bd955ed..88b800c 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/Domain.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/Domain.java @@ -24,6 +24,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; +import java.util.Objects; import java.util.Set; /** @@ -99,10 +100,25 @@ public class Domain { return aliases; } + /** + * Handle server requests. + * @param serverRequest the server request + * @param serverResponse the server response + * @throws IOException if handling server request fails + */ public void handle(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException { - if (httpEndpointResolvers != null && !httpEndpointResolvers.isEmpty()) { + if (httpEndpointResolvers != null) { + boolean found = false; for (HttpEndpointResolver httpEndpointResolver : httpEndpointResolvers) { - httpEndpointResolver.handle(serverRequest, serverResponse); + List matchingEndpoints = httpEndpointResolver.matchingEndpointsFor(serverRequest); + if (matchingEndpoints != null && !matchingEndpoints.isEmpty()) { + httpEndpointResolver.handle(matchingEndpoints, serverRequest, serverResponse); + found = true; + break; + } + } + if (!found) { + ServerResponse.write(serverResponse, HttpResponseStatus.NOT_IMPLEMENTED); } } else { ServerResponse.write(serverResponse, HttpResponseStatus.NOT_IMPLEMENTED); @@ -143,6 +159,8 @@ public class Domain { private String keyPassword; Builder(HttpAddress httpAddress, String serverName) { + Objects.requireNonNull(httpAddress); + Objects.requireNonNull(serverName); this.httpAddress = httpAddress; this.serverName = serverName; this.aliases = new LinkedHashSet<>(); @@ -154,31 +172,37 @@ public class Domain { } public Builder setTrustManagerFactory(TrustManagerFactory trustManagerFactory) { + Objects.requireNonNull(trustManagerFactory); this.trustManagerFactory = trustManagerFactory; return this; } public Builder setTrustManagerKeyStore(KeyStore trustManagerKeyStore) { + Objects.requireNonNull(trustManagerKeyStore); this.trustManagerKeyStore = trustManagerKeyStore; return this; } public Builder setSslContextProvider(Provider sslContextProvider) { + Objects.requireNonNull(sslContextProvider); this.sslContextProvider = sslContextProvider; return this; } public Builder setSslProvider(SslProvider sslProvider) { + Objects.requireNonNull(sslProvider); this.sslProvider = sslProvider; return this; } public Builder setCiphers(Iterable ciphers) { + Objects.requireNonNull(ciphers); this.ciphers = ciphers; return this; } public Builder setCipherSuiteFilter(CipherSuiteFilter cipherSuiteFilter) { + Objects.requireNonNull(cipherSuiteFilter); this.cipherSuiteFilter = cipherSuiteFilter; return this; } @@ -196,21 +220,26 @@ public class Domain { } public Builder setKeyCertChainInputStream(InputStream keyCertChainInputStream) { + Objects.requireNonNull(keyCertChainInputStream); this.keyCertChainInputStream = keyCertChainInputStream; return this; } public Builder setKeyInputStream(InputStream keyInputStream) { + Objects.requireNonNull(keyInputStream); this.keyInputStream = keyInputStream; return this; } public Builder setKeyPassword(String keyPassword) { + // null in keyPassword allowed, it means no password this.keyPassword = keyPassword; return this; } public Builder setKeyCert(InputStream keyCertChainInputStream, InputStream keyInputStream) { + Objects.requireNonNull(keyCertChainInputStream); + Objects.requireNonNull(keyInputStream); setKeyCertChainInputStream(keyCertChainInputStream); setKeyInputStream(keyInputStream); return this; @@ -218,6 +247,9 @@ public class Domain { public Builder setKeyCert(InputStream keyCertChainInputStream, InputStream keyInputStream, String keyPassword) { + Objects.requireNonNull(keyCertChainInputStream); + Objects.requireNonNull(keyInputStream); + Objects.requireNonNull(keyPassword); setKeyCertChainInputStream(keyCertChainInputStream); setKeyInputStream(keyInputStream); setKeyPassword(keyPassword); @@ -239,31 +271,56 @@ public class Domain { * @return this builder */ public Builder addAlias(String alias) { + Objects.requireNonNull(alias); aliases.add(alias); return this; } public Builder addEndpointResolver(HttpEndpointResolver httpEndpointResolver) { + Objects.requireNonNull(httpEndpointResolver); this.httpEndpointResolvers.add(httpEndpointResolver); return this; } public Builder singleEndpoint(String path, Service service) { + Objects.requireNonNull(path); + Objects.requireNonNull(service); addEndpointResolver(HttpEndpointResolver.builder() - .addEndpoint(HttpEndpoint.builder().setPath(path).addFilter(service).build()).build()); + .addEndpoint(HttpEndpoint.builder() + .setPath(path) + .build()) + .setDispatcher((endpoint, req, resp) -> service.handle(req, resp)) + .build()); return this; } public Builder singleEndpoint(String prefix, String path, Service service) { + Objects.requireNonNull(prefix); + Objects.requireNonNull(path); + Objects.requireNonNull(service); addEndpointResolver(HttpEndpointResolver.builder() - .addEndpoint(HttpEndpoint.builder().setPrefix(prefix).setPath(path).addFilter(service).build()).build()); + .addEndpoint(HttpEndpoint.builder() + .setPrefix(prefix) + .setPath(path) + .build()) + .setDispatcher((endpoint, req, resp) -> service.handle(req, resp)) + .build()); return this; } - public Builder singleEndpoint(String prefix, String path, Service service, String... methods) { + public Builder singleEndpoint(String prefix, String path, Service service, + String... methods) { + Objects.requireNonNull(prefix); + Objects.requireNonNull(path); + Objects.requireNonNull(service); addEndpointResolver(HttpEndpointResolver.builder() - .addEndpoint(HttpEndpoint.builder().setPrefix(prefix).setPath(path).addFilter(service) - .setMethods(Arrays.asList(methods)).build()).build()); + .addEndpoint(HttpEndpoint.builder() + .setPrefix(prefix) + .setPath(path) + .setMethods(Arrays.asList(methods)) + .build()) + .setDispatcher((endpoint, req, resp) -> service.handle(req, resp)) + .build()); return this; } diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/ServerRequest.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/ServerRequest.java index 3e4d76f..72553ab 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/ServerRequest.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/ServerRequest.java @@ -55,5 +55,4 @@ public interface ServerRequest { InetSocketAddress getLocalAddress(); InetSocketAddress getRemoteAddress(); - } 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 c9f6842..4d80321 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 @@ -4,33 +4,40 @@ import org.xbib.net.Pair; 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.ServerRequest; import org.xbib.netty.http.server.ServerResponse; import org.xbib.netty.http.server.endpoint.service.Service; import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; import java.util.Comparator; +import java.util.EnumSet; import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; public class HttpEndpoint implements Endpoint { - private static final PathMatcher pathMatcher = new PathMatcher(); + public static final EnumSet DEFAULT_METHODS = + EnumSet.of(HttpMethod.GET, HttpMethod.HEAD); - private static final List DEFAULT_METHODS = Arrays.asList("GET", "HEAD"); + private static final PathMatcher pathMatcher = new PathMatcher(); private final String prefix; private final String path; - private final List methods; + private final EnumSet methods; private final List contentTypes; private final List filters; - private HttpEndpoint(String prefix, String path, List methods, List contentTypes, List filters) { + private HttpEndpoint(String prefix, String path, + EnumSet methods, + List contentTypes, + List filters) { this.prefix = PathNormalizer.normalize(prefix); this.path = PathNormalizer.normalize(path); this.methods = methods; @@ -80,6 +87,7 @@ public class HttpEndpoint implements Endpoint { } } + @Override public void handle(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException { serverRequest.setContext(pathMatcher.tokenizePath(getPrefix())); for (Service service : filters) { @@ -92,7 +100,7 @@ public class HttpEndpoint implements Endpoint { @Override public String toString() { - return "Endpoint[prefix=" + prefix + ",path=" + path + ",methods=" + methods + ",contentTypes=" + contentTypes + " --> " + filters +"]"; + return "Endpoint[prefix=" + prefix + ",path=" + path + ",methods=" + methods + ",contentTypes=" + contentTypes + ",filters=" + filters +"]"; } static class EndpointPathComparator implements Comparator { @@ -115,7 +123,7 @@ public class HttpEndpoint implements Endpoint { private String path; - private List methods; + private EnumSet methods; private List contentTypes; @@ -124,55 +132,62 @@ public class HttpEndpoint implements Endpoint { Builder() { this.prefix = "/"; this.path = "/**"; - this.methods = new ArrayList<>(); + this.methods = DEFAULT_METHODS; this.contentTypes = new ArrayList<>(); this.filters = new ArrayList<>(); } public Builder setPrefix(String prefix) { + Objects.requireNonNull(prefix); this.prefix = prefix; return this; } public Builder setPath(String path) { + Objects.requireNonNull(path); this.path = path; return this; } - public Builder setMethods(List methods) { + public Builder setMethods(EnumSet methods) { + Objects.requireNonNull(methods); this.methods = methods; return this; } - public Builder addMethod(String method) { - methods.add(method); + public Builder setMethods(List methods) { + Objects.requireNonNull(methods); + this.methods = methods.stream() + .map(HttpMethod::valueOf) + .collect(Collectors.toCollection(() -> EnumSet.noneOf(HttpMethod.class))); return this; } public Builder setContentTypes(List contentTypes) { + Objects.requireNonNull(contentTypes); this.contentTypes = contentTypes; return this; } public Builder addContentType(String contentType) { + Objects.requireNonNull(contentType); this.contentTypes.add(contentType); return this; } public Builder setFilters(List filters) { + Objects.requireNonNull(filters); this.filters = filters; return this; } public Builder addFilter(Service filter) { + Objects.requireNonNull(filter); this.filters.add(filter); return this; } public HttpEndpoint build() { - if (methods.isEmpty()) { - methods = DEFAULT_METHODS; - } return new HttpEndpoint(prefix, path, methods, contentTypes, filters); } } 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 d5a7474..d173f93 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 @@ -1,5 +1,6 @@ package org.xbib.netty.http.server.endpoint; +import org.xbib.netty.http.common.HttpMethod; import org.xbib.netty.http.server.transport.HttpServerRequest; import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; @@ -8,13 +9,13 @@ public class HttpEndpointDescriptor implements EndpointDescriptor, Comparable endpoints; private final EndpointDispatcher endpointDispatcher; - private final LimitedMap> endpointDescriptors; + private final Map> endpointDescriptors; - private HttpEndpointResolver(HttpEndpoint defaultEndpoint, - List endpoints, + private HttpEndpointResolver(List endpoints, EndpointDispatcher endpointDispatcher, int limit) { - this.defaultEndpoint = defaultEndpoint == null ? createDefaultEndpoint() : defaultEndpoint; + Objects.requireNonNull(endpointDispatcher); this.endpoints = endpoints; this.endpointDispatcher = endpointDispatcher; - this.endpointDescriptors = new LimitedMap<>(limit); + this.endpointDescriptors = new LimitedConcurrentHashMap<>(limit); } - public void handle(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException { + /** + * Find matching endpoints for a server request. + * @param serverRequest the server request + * @return a + */ + public List matchingEndpointsFor(ServerRequest serverRequest) { HttpEndpointDescriptor httpEndpointDescriptor = serverRequest.getEndpointDescriptor(); endpointDescriptors.putIfAbsent(httpEndpointDescriptor, endpoints.stream() .filter(endpoint -> endpoint.matches(httpEndpointDescriptor)) .sorted(new HttpEndpoint.EndpointPathComparator(httpEndpointDescriptor.getPath())) .collect(Collectors.toList())); - List matchingEndpoints = endpointDescriptors.get(httpEndpointDescriptor); + return endpointDescriptors.get(httpEndpointDescriptor); + } + + public void handle(List matchingEndpoints, + ServerRequest serverRequest, ServerResponse serverResponse) throws IOException { + Objects.requireNonNull(matchingEndpoints); if (logger.isLoggable(Level.FINE)) { - logger.log(Level.FINE, () -> "endpoint = " + httpEndpointDescriptor + - " matching endpoints = " + matchingEndpoints); + logger.log(Level.FINE, () -> + "endpoint = " + serverRequest.getEndpointDescriptor() + + " matching endpoints = " + matchingEndpoints.size() + " --> " + matchingEndpoints); } - if (matchingEndpoints.isEmpty()) { - if (defaultEndpoint != null) { - defaultEndpoint.resolveUriTemplate(serverRequest); - defaultEndpoint.handle(serverRequest, serverResponse); - if (endpointDispatcher != null) { - endpointDispatcher.dispatch(defaultEndpoint, serverRequest, serverResponse); - } - } else { - ServerResponse.write(serverResponse, HttpResponseStatus.NOT_IMPLEMENTED); - } - } else { - for (HttpEndpoint endpoint : matchingEndpoints) { - endpoint.resolveUriTemplate(serverRequest); - endpoint.handle(serverRequest, serverResponse); - if (serverResponse.getStatus() != null) { - break; - } - } - if (endpointDispatcher != null) { - for (HttpEndpoint endpoint : matchingEndpoints) { - endpointDispatcher.dispatch(endpoint, serverRequest, serverResponse); - if (serverResponse.getStatus() != null) { - break; - } - } + for (HttpEndpoint endpoint : matchingEndpoints) { + endpoint.resolveUriTemplate(serverRequest); + endpoint.handle(serverRequest, serverResponse); + endpointDispatcher.dispatch(endpoint, serverRequest, serverResponse); + if (serverResponse.getStatus() != null) { + logger.log(Level.FINEST, () -> "endpoint " + endpoint + " status = " + serverResponse.getStatus()); + break; } } } @@ -83,17 +75,6 @@ public class HttpEndpointResolver { return endpointDescriptors; } - private HttpEndpoint createDefaultEndpoint() { - return HttpEndpoint.builder() - .setPath("/**") - .addMethod("GET") - .addMethod("HEAD") - .addFilter((req, resp) -> { - ServerResponse.write(resp, HttpResponseStatus.NOT_FOUND, - "application/octet-stream","no endpoint configured"); - }).build(); - } - public static Builder builder() { return new Builder(); } @@ -104,32 +85,26 @@ public class HttpEndpointResolver { private String prefix; - private HttpEndpoint defaultEndpoint; - private List endpoints; private EndpointDispatcher endpointDispatcher; Builder() { - this.limit = 1024; + this.limit = DEFAULT_LIMIT; this.endpoints = new ArrayList<>(); } public Builder setLimit(int limit) { - this.limit = limit; + this.limit = limit > 0 ? limit < 1024 * DEFAULT_LIMIT ? limit : DEFAULT_LIMIT : DEFAULT_LIMIT; return this; } public Builder setPrefix(String prefix) { + Objects.requireNonNull(prefix); this.prefix = prefix; return this; } - public Builder setDefaultEndpoint(HttpEndpoint endpoint) { - this.defaultEndpoint = endpoint; - return this; - } - /** * Add endpoint. * @@ -137,12 +112,19 @@ public class HttpEndpointResolver { * @return this builder */ public Builder addEndpoint(HttpEndpoint endpoint) { - if (endpoint.getPrefix().equals("/") && prefix != null && !prefix.isEmpty()) { - HttpEndpoint thisEndpoint = HttpEndpoint.builder(endpoint).setPrefix(prefix).build(); - logger.log(Level.FINE, "adding endpoint = " + thisEndpoint); - endpoints.add(thisEndpoint); + Objects.requireNonNull(endpoint); + if (prefix != null && !prefix.isEmpty()) { + HttpEndpoint prefixedEndpoint = HttpEndpoint.builder(endpoint) + .setPrefix(prefix + endpoint.getPrefix()) + .build(); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, () -> "prefix " + prefix + ": adding endpoint = " + prefixedEndpoint); + } + endpoints.add(prefixedEndpoint); } else { - logger.log(Level.FINE, "adding endpoint = " + endpoint); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, () -> "adding endpoint = " + endpoint); + } endpoints.add(endpoint); } return this; @@ -155,6 +137,7 @@ public class HttpEndpointResolver { * @return this builder */ public Builder addEndpoint(Object classWithAnnotatedMethods) { + Objects.requireNonNull(classWithAnnotatedMethods); for (Class clazz = classWithAnnotatedMethods.getClass(); clazz != null; clazz = clazz.getSuperclass()) { for (Method method : clazz.getDeclaredMethods()) { Endpoint endpoint = method.getAnnotation(Endpoint.class); @@ -174,12 +157,13 @@ public class HttpEndpointResolver { } public Builder setDispatcher(EndpointDispatcher endpointDispatcher) { + Objects.requireNonNull(endpointDispatcher); this.endpointDispatcher = endpointDispatcher; return this; } public HttpEndpointResolver build() { - return new HttpEndpointResolver(defaultEndpoint, endpoints, endpointDispatcher, limit); + return new HttpEndpointResolver(endpoints, endpointDispatcher, limit); } } } diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/service/ClassLoaderService.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/service/ClassLoaderService.java index 2ec141e..7fe04d6 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/service/ClassLoaderService.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/service/ClassLoaderService.java @@ -7,16 +7,27 @@ import java.io.IOException; import java.net.URL; import java.net.URLConnection; import java.time.Instant; +import java.util.logging.Level; +import java.util.logging.Logger; public class ClassLoaderService extends ResourceService { + private static final Logger logger = Logger.getLogger(ClassLoaderService.class.getName()); + private final Class clazz; private final String prefix; + private final String indexFileName; + public ClassLoaderService(Class clazz, String prefix) { + this(clazz, prefix, "index.html"); + } + + public ClassLoaderService(Class clazz, String prefix, String indexFileName) { this.clazz = clazz; this.prefix = prefix; + this.indexFileName = indexFileName; } @Override @@ -50,11 +61,24 @@ public class ClassLoaderService extends ResourceService { private final long length; ClassLoaderResource(ServerRequest serverRequest) throws IOException { - this.resourcePath = serverRequest.getEffectiveRequestPath().substring(1); - this.url = clazz.getResource(prefix + "/" + resourcePath); - URLConnection urlConnection = url.openConnection(); - this.lastModified = Instant.ofEpochMilli(urlConnection.getLastModified()); - this.length = urlConnection.getContentLength(); + String effectivePath = serverRequest.getEffectiveRequestPath(); + this.resourcePath = effectivePath.startsWith("/") ? effectivePath.substring(1) : effectivePath; + String path = prefix.endsWith("/") ? prefix : prefix + "/"; + path = resourcePath.startsWith("/") ? path + resourcePath.substring(1) : path + resourcePath; + this.url = clazz.getResource(path); + if (url != null) { + URLConnection urlConnection = url.openConnection(); + this.lastModified = Instant.ofEpochMilli(urlConnection.getLastModified()); + this.length = urlConnection.getContentLength(); + if (logger.isLoggable(Level.FINER)) { + logger.log(Level.FINER, "success: path=[" + path + + "] -> url=" + url + " lastModified=" + lastModified + "length=" + length); + } + } else { + this.lastModified = Instant.now(); + this.length = 0; + logger.log(Level.FINER, "fail: resource not found, url=" + url); + } } @Override @@ -76,5 +100,15 @@ public class ClassLoaderService extends ResourceService { public long getLength() { return length; } + + @Override + public boolean isDirectory() { + return resourcePath.endsWith("/"); + } + + @Override + public String indexFileName() { + return indexFileName; + } } } diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/service/FileService.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/service/FileService.java index 2460559..345179a 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/service/FileService.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/service/FileService.java @@ -13,14 +13,15 @@ public class FileService extends ResourceService { private final Path prefix; + private final String indexFileName; + public FileService(Path prefix) { + this(prefix, "index.html"); + } + + public FileService(Path prefix, String indexFileName) { this.prefix = prefix; - if (!Files.exists(prefix)) { - throw new IllegalArgumentException("prefix: " + prefix + " (does not exist)"); - } - if (!Files.isDirectory(prefix)) { - throw new IllegalArgumentException("prefix: " + prefix + " (not a directory)"); - } + this.indexFileName = indexFileName; } @Override @@ -49,16 +50,26 @@ public class FileService extends ResourceService { private final URL url; + private final boolean isDirectory; + private final Instant lastModified; private final long length; ChunkedFileResource(ServerRequest serverRequest) throws IOException { - this.resourcePath = serverRequest.getEffectiveRequestPath().substring(1); + String effectivePath = serverRequest.getEffectiveRequestPath(); + this.resourcePath = effectivePath.startsWith("/") ? effectivePath.substring(1) : effectivePath; Path path = prefix.resolve(resourcePath); this.url = path.toUri().toURL(); - this.lastModified = Files.getLastModifiedTime(path).toInstant(); - this.length = Files.size(path); + boolean isExists = Files.exists(path); + this.isDirectory = Files.isDirectory(path); + if (isExists) { + this.lastModified = Files.getLastModifiedTime(path).toInstant(); + this.length = Files.size(path); + } else { + this.lastModified = Instant.now(); + this.length = 0; + } } @Override @@ -71,6 +82,16 @@ public class FileService extends ResourceService { return url; } + @Override + public boolean isDirectory() { + return isDirectory; + } + + @Override + public String indexFileName() { + return indexFileName; + } + @Override public Instant getLastModified() { return lastModified; diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/service/Resource.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/service/Resource.java index c2c102e..254700a 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/service/Resource.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/service/Resource.java @@ -12,4 +12,8 @@ public interface Resource { Instant getLastModified(); long getLength(); + + boolean isDirectory(); + + String indexFileName(); } 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 bb2a792..8f9c334 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 @@ -24,6 +24,7 @@ import java.nio.channels.ReadableByteChannel; import java.nio.channels.SeekableByteChannel; import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; import java.time.Instant; import java.util.ArrayList; @@ -50,6 +51,23 @@ public abstract class ResourceService implements Service { protected abstract boolean isRangeResponseEnabled(); private void handleResource(ServerRequest serverRequest, ServerResponse serverResponse, Resource resource) { + if (resource.isDirectory()) { + if (!resource.getResourcePath().endsWith("/")) { + // external redirect to + serverResponse.withHeader(HttpHeaderNames.LOCATION, resource.getResourcePath() + "/"); + ServerResponse.write(serverResponse, HttpResponseStatus.MOVED_PERMANENTLY); + 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); + return; + } else { + // send forbidden, we do not allow directory access + ServerResponse.write(serverResponse, HttpResponseStatus.FORBIDDEN); + return; + } + } HttpHeaders headers = serverRequest.getHeaders(); String contentType = MimeTypeUtils.guessFromPath(resource.getResourcePath(), false); long maxAgeSeconds = 24 * 3600; @@ -187,7 +205,9 @@ public abstract class ResourceService implements Service { private void send(URL url, String contentType, ServerRequest serverRequest, ServerResponse serverResponse) { - if (serverRequest.getMethod() == HttpMethod.HEAD) { + if (url == null) { + ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND); + } else if (serverRequest.getMethod() == HttpMethod.HEAD) { ServerResponse.write(serverResponse, HttpResponseStatus.OK, contentType); } else { if ("file".equals(url.getProtocol())) { @@ -211,15 +231,19 @@ public abstract class ResourceService implements Service { private void send(URL url, HttpResponseStatus httpResponseStatus, String contentType, ServerRequest serverRequest, ServerResponse serverResponse, long offset, long size) { - if (serverRequest.getMethod() == HttpMethod.HEAD) { + if (url == null) { + ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND); + } else if (serverRequest.getMethod() == HttpMethod.HEAD) { ServerResponse.write(serverResponse, HttpResponseStatus.OK, contentType); } else { if ("file".equals(url.getProtocol())) { + Path path = null; try { - send((FileChannel) Files.newByteChannel(Paths.get(url.toURI())), httpResponseStatus, + path = Paths.get(url.toURI()); + send((FileChannel) Files.newByteChannel(path), httpResponseStatus, contentType, serverResponse, offset, size); } catch (URISyntaxException | IOException e) { - logger.log(Level.SEVERE, e.getMessage(), e); + logger.log(Level.SEVERE, e.getMessage() + " path=" + path, e); ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND); } } else { @@ -240,26 +264,46 @@ public abstract class ResourceService implements Service { private void send(FileChannel fileChannel, HttpResponseStatus httpResponseStatus, String contentType, ServerResponse serverResponse, long offset, long size) throws IOException { - MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, offset, size); - serverResponse.withStatus(httpResponseStatus) - .withContentType(contentType) - .write(Unpooled.wrappedBuffer(mappedByteBuffer)); + if (fileChannel == null) { + ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND); + } 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); + } + if (mappedByteBuffer != null) { + serverResponse.withStatus(httpResponseStatus) + .withContentType(contentType) + .write(Unpooled.wrappedBuffer(mappedByteBuffer)); + } + } } private void send(InputStream inputStream, HttpResponseStatus httpResponseStatus, String contentType, ServerResponse serverResponse) throws IOException { - try (ReadableByteChannel channel = Channels.newChannel(inputStream)) { - serverResponse.withStatus(httpResponseStatus) - .withContentType(contentType) - .write(new ChunkedNioStream(channel)); + if (inputStream == null) { + ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND); + } else { + try (ReadableByteChannel channel = Channels.newChannel(inputStream)) { + serverResponse.withStatus(httpResponseStatus) + .withContentType(contentType) + .write(new ChunkedNioStream(channel)); + } } } private void send(InputStream inputStream, HttpResponseStatus httpResponseStatus, String contentType, ServerResponse serverResponse, long offset, long size) throws IOException { - serverResponse.withStatus(httpResponseStatus) - .withContentType(contentType) - .write(Unpooled.wrappedBuffer(readBuffer(inputStream, offset, size))); + if (inputStream == null) { + ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND); + } else { + serverResponse.withStatus(httpResponseStatus) + .withContentType(contentType) + .write(Unpooled.wrappedBuffer(readBuffer(inputStream, offset, size))); + } } private static ByteBuffer readBuffer(URL url, long offset, long size) throws IOException, URISyntaxException { 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 index fb2e9eb..11ad738 100644 --- 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 @@ -61,7 +61,6 @@ public class HttpServerRequest implements ServerRequest { HttpServerRequest(Server server, FullHttpRequest fullHttpRequest, ChannelHandlerContext ctx) { - // server not required yet this.httpRequest = fullHttpRequest.retainedDuplicate(); this.ctx = ctx; this.httpEndpointDescriptor = new HttpEndpointDescriptor(this); @@ -70,7 +69,9 @@ public class HttpServerRequest implements ServerRequest { void handleParameters() throws IOException { try { HttpParameters httpParameters = new HttpParameters(); - this.url = URL.builder().path(httpRequest.uri()).build(); + this.url = URL.builder() + .path(httpRequest.uri()) // creates path, query params, fragment + .build(); QueryParameters queryParameters = url.getQueryParams(); ByteBuf byteBuf = httpRequest.content(); if (APPLICATION_FORM_URL_ENCODED.equals(HttpUtil.getMimeType(httpRequest)) && byteBuf != null) { 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 ec5d34e..225916e 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 @@ -38,8 +38,6 @@ public class HttpServerResponse implements ServerResponse { private static final Logger logger = Logger.getLogger(HttpServerResponse.class.getName()); - private static final ByteBuf EMPTY = Unpooled.buffer(0); - private final Server server; private final ServerRequest serverRequest; @@ -113,7 +111,7 @@ public class HttpServerResponse implements ServerResponse { @Override public void flush() { - write((ByteBuf) null); + write(Unpooled.buffer(0)); } @Override @@ -130,6 +128,7 @@ public class HttpServerResponse implements ServerResponse { @Override public void write(ByteBuf byteBuf) { + Objects.requireNonNull(byteBuf); if (httpResponseStatus == null) { httpResponseStatus = HttpResponseStatus.OK; } @@ -139,9 +138,7 @@ public class HttpServerResponse implements ServerResponse { } if (httpResponseStatus.code() >= 200 && httpResponseStatus.code() != 204) { if (!headers.contains(HttpHeaderNames.CONTENT_LENGTH) && !headers.contains(HttpHeaderNames.TRANSFER_ENCODING)) { - if (byteBuf != null) { - headers.add(HttpHeaderNames.CONTENT_LENGTH, Long.toString(byteBuf.readableBytes())); - } + headers.add(HttpHeaderNames.CONTENT_LENGTH, Long.toString(byteBuf.readableBytes())); } } if (serverRequest != null && "close".equalsIgnoreCase(serverRequest.getHeaders().get(HttpHeaderNames.CONNECTION)) && @@ -154,11 +151,7 @@ public class HttpServerResponse implements ServerResponse { headers.add(HttpHeaderNames.SERVER, ServerName.getServerName()); if (ctx.channel().isWritable()) { FullHttpResponse fullHttpResponse; - if (byteBuf != null) { - fullHttpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, httpResponseStatus, byteBuf, headers, trailingHeaders); - } else { - fullHttpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, httpResponseStatus, EMPTY, headers, trailingHeaders); - } + fullHttpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, httpResponseStatus, byteBuf, headers, trailingHeaders); if (serverRequest != null && serverRequest.getSequenceId() != null) { HttpPipelinedResponse httpPipelinedResponse = new HttpPipelinedResponse(fullHttpResponse, ctx.channel().newPromise(), serverRequest.getSequenceId()); diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/FileServiceTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/FileServiceTest.java deleted file mode 100644 index 65e3ec5..0000000 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/FileServiceTest.java +++ /dev/null @@ -1,97 +0,0 @@ -package org.xbib.netty.http.server.test; - -import io.netty.handler.codec.http.HttpVersion; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.xbib.netty.http.client.Client; -import org.xbib.netty.http.client.Request; -import org.xbib.netty.http.common.HttpAddress; -import org.xbib.netty.http.server.Server; -import org.xbib.netty.http.server.Domain; -import org.xbib.netty.http.server.endpoint.service.FileService; - -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.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -@ExtendWith(NettyHttpTestExtension.class) -class FileServiceTest { - - private static final Logger logger = Logger.getLogger(FileServiceTest.class.getName()); - - @Test - void testFileServiceHttp1() throws Exception { - Path vartmp = Paths.get("/var/tmp/"); - HttpAddress httpAddress = HttpAddress.http1("localhost", 8008); - Domain domain = Domain.builder(httpAddress) - .singleEndpoint("/static", "/**", new FileService(vartmp)) - .build(); - Server server = Server.builder(domain) - .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(resp -> { - assertEquals("Hello Jörg", resp.getBodyAsString(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")); - logger.log(Level.INFO, "server and client shut down"); - } - assertTrue(success.get()); - } - - @Test - void testFileServiceHttp2() throws Exception { - Path vartmp = Paths.get("/var/tmp/"); - HttpAddress httpAddress = HttpAddress.http2("localhost", 8008); - Domain domain = Domain.builder(httpAddress) - .singleEndpoint("/static", "/**", new FileService(vartmp)) - .build(); - Server server = Server.builder(domain) - .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.valueOf("HTTP/2.0")) - .url(server.getServerConfig().getAddress().base().resolve("/static/test.txt")) - .build() - .setResponseListener(resp -> { - assertEquals("Hello Jörg", resp.getBodyAsString(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")); - logger.log(Level.INFO, "server and client shut down"); - } - assertTrue(success.get()); - } -} diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/FlushTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/FlushTest.java new file mode 100644 index 0000000..cfb5f7e --- /dev/null +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/FlushTest.java @@ -0,0 +1,89 @@ +package org.xbib.netty.http.server.test; + +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.HttpVersion; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.xbib.netty.http.client.Client; +import org.xbib.netty.http.client.Request; +import org.xbib.netty.http.client.listener.ResponseListener; +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.Domain; +import org.xbib.netty.http.server.Server; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; + +@ExtendWith(NettyHttpTestExtension.class) +class FlushTest { + + private static final Logger logger = Logger.getLogger(FlushTest.class.getName()); + + /** + * This test checks the flush() operation of the server response. + * Flush uses Unpooled.buffer(0), and requires a fresh instance each call. + * If a static Unpooled.buffer(0) is used, a retry will fail - + * and the second "302 Found" will never be sent because of a server hang. + * @throws Exception exception + */ + @Test + void testFlush() throws Exception { + HttpAddress httpAddress = HttpAddress.http1("localhost", 8008); + Domain domain = Domain.builder(httpAddress) + .singleEndpoint("/flush", "/**", (req, resp) -> { + HttpParameters parameters = req.getParameters(); + logger.log(Level.INFO, "got request " + parameters.toString() + ", sending 302 Found"); + resp.withStatus(HttpResponseStatus.FOUND).flush(); + }) + .build(); + Server server = Server.builder(domain) + .enableDebug() + .build(); + Client client = Client.builder() + .build(); + final AtomicBoolean success1 = new AtomicBoolean(false); + final AtomicBoolean success2 = new AtomicBoolean(false); + try { + server.accept(); + + // first request to trigger flush() + + ResponseListener responseListener1 = (resp) -> { + logger.log(Level.INFO, "got response = " + resp.getStatus()); + if (resp.getStatus().getCode() == HttpResponseStatus.FOUND.code()) { + success1.set(true); + } + }; + Request getRequest = Request.get().setVersion(HttpVersion.HTTP_1_1) + .url(server.getServerConfig().getAddress().base().resolve("/flush")) + .addParameter("a", "b") + .build() + .setResponseListener(responseListener1); + client.execute(getRequest).get(); + + // second request to trigger flush() + + ResponseListener responseListener2 = (resp) -> { + logger.log(Level.INFO, "got response = " + resp.getStatus()); + if (resp.getStatus().getCode() == HttpResponseStatus.FOUND.code()) { + success2.set(true); + } + }; + getRequest = Request.get().setVersion(HttpVersion.HTTP_1_1) + .url(server.getServerConfig().getAddress().base().resolve("/flush")) + .addParameter("a", "b") + .build() + .setResponseListener(responseListener2); + client.execute(getRequest).get(); + } finally { + server.shutdownGracefully(); + client.shutdownGracefully(); + logger.log(Level.INFO, "server and client shut down"); + } + assertTrue(success1.get()); + assertTrue(success2.get()); + } +} 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 6c2e2d6..c9f8f63 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 @@ -105,7 +105,7 @@ class SecureHttp2Test { break; } } - transport.get(); + transport.get(60, TimeUnit.SECONDS); } finally { client.shutdownGracefully(); server.shutdownGracefully(); diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/ClassloaderServiceTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/endpoint/ClassloaderServiceTest.java similarity index 54% rename from netty-http-server/src/test/java/org/xbib/netty/http/server/test/ClassloaderServiceTest.java rename to netty-http-server/src/test/java/org/xbib/netty/http/server/test/endpoint/ClassloaderServiceTest.java index 7607022..14a281d 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/ClassloaderServiceTest.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/endpoint/ClassloaderServiceTest.java @@ -1,4 +1,4 @@ -package org.xbib.netty.http.server.test; +package org.xbib.netty.http.server.test.endpoint; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpVersion; @@ -10,6 +10,7 @@ import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.server.Server; import org.xbib.netty.http.server.Domain; import org.xbib.netty.http.server.endpoint.service.ClassLoaderService; +import org.xbib.netty.http.server.test.NettyHttpTestExtension; import java.nio.charset.StandardCharsets; import java.util.concurrent.atomic.AtomicInteger; @@ -24,16 +25,14 @@ class ClassloaderServiceTest { private static final Logger logger = Logger.getLogger(ClassloaderServiceTest.class.getName()); @Test - void testSimpleClassloader() throws Exception { + void testClassloaderFileResource() throws Exception { HttpAddress httpAddress = HttpAddress.http1("localhost", 8008); Domain domain = Domain.builder(httpAddress) .singleEndpoint("/classloader", "/**", new ClassLoaderService(ClassloaderServiceTest.class, "/cl")) .build(); Server server = Server.builder(domain) - .enableDebug() .build(); - server.logDiagnostics(Level.INFO); Client client = Client.builder() .build(); int max = 1; @@ -41,12 +40,52 @@ class ClassloaderServiceTest { try { server.accept(); Request request = Request.get().setVersion(HttpVersion.HTTP_1_1) - .url(server.getServerConfig().getAddress().base().resolve("/classloader/test.txt")) + .url(server.getServerConfig().getAddress().base() + .resolve("/classloader/test.txt")) .build() .setResponseListener(resp -> { if (resp.getStatus().getCode() == HttpResponseStatus.OK.code()) { assertEquals("Hello Jörg", resp.getBodyAsString(StandardCharsets.UTF_8)); count.incrementAndGet(); + } else { + logger.log(Level.WARNING, resp.getStatus().getMessage()); + } + }); + for (int i = 0; i < max; i++) { + client.execute(request).get(); + } + } finally { + server.shutdownGracefully(); + client.shutdownGracefully(); + logger.log(Level.INFO, "server and client shut down"); + } + assertEquals(max, count.get()); + } + + @Test + void testClassloaderDirectoryResource() throws Exception { + HttpAddress httpAddress = HttpAddress.http1("localhost", 8008); + Domain domain = Domain.builder(httpAddress) + .singleEndpoint("/classloader", "/**", + new ClassLoaderService(ClassloaderServiceTest.class, "/cl")) + .build(); + Server server = Server.builder(domain) + .build(); + Client client = Client.builder() + .build(); + int max = 1; + final AtomicInteger count = new AtomicInteger(0); + try { + server.accept(); + Request request = Request.get().setVersion(HttpVersion.HTTP_1_1) + .url(server.getServerConfig().getAddress().base() + .resolve("/classloader")) + .build() + .setResponseListener(resp -> { + if (resp.getStatus().getCode() == HttpResponseStatus.NOT_FOUND.code()) { + count.incrementAndGet(); + } else { + logger.log(Level.WARNING, resp.getStatus().getMessage()); } }); for (int i = 0; i < max; i++) { diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/EndpointTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/endpoint/EndpointTest.java similarity index 58% rename from netty-http-server/src/test/java/org/xbib/netty/http/server/test/EndpointTest.java rename to netty-http-server/src/test/java/org/xbib/netty/http/server/test/endpoint/EndpointTest.java index 77b6a05..051d380 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/EndpointTest.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/endpoint/EndpointTest.java @@ -1,4 +1,4 @@ -package org.xbib.netty.http.server.test; +package org.xbib.netty.http.server.test.endpoint; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpVersion; @@ -14,6 +14,7 @@ import org.xbib.netty.http.server.endpoint.HttpEndpointResolver; import org.xbib.netty.http.server.Domain; import org.xbib.netty.http.server.endpoint.service.FileService; import org.xbib.netty.http.server.endpoint.service.Service; +import org.xbib.netty.http.server.test.NettyHttpTestExtension; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -41,7 +42,8 @@ class EndpointTest { HttpEndpointResolver httpEndpointResolver = HttpEndpointResolver.builder() .addEndpoint(HttpEndpoint.builder().setPath("/**").build()) .setDispatcher((endpoint, req, resp) -> { - logger.log(Level.FINE, "dispatching endpoint = " + endpoint + " req = " + req); + logger.log(Level.FINE, "dispatching endpoint = " + endpoint + + " req = " + req + " req context path = " + req.getContextPath()); service.handle(req, resp); }) .build(); @@ -81,7 +83,8 @@ class EndpointTest { HttpEndpointResolver httpEndpointResolver = HttpEndpointResolver.builder() .addEndpoint(HttpEndpoint.builder().setPrefix("/").setPath("/**").build()) .setDispatcher((endpoint, req, resp) -> { - logger.log(Level.FINE, "dispatching endpoint = " + endpoint + " req = " + req); + logger.log(Level.FINE, "dispatching endpoint = " + endpoint + + " req = " + req + " req context path = " + req.getContextPath()); service.handle(req, resp); }) .build(); @@ -113,16 +116,82 @@ class EndpointTest { assertTrue(success.get()); } - @Test void testSimplePathEndpoints() throws Exception { Path vartmp = Paths.get("/var/tmp/"); Service service = new FileService(vartmp); HttpAddress httpAddress = HttpAddress.http1("localhost", 8008); HttpEndpointResolver httpEndpointResolver = HttpEndpointResolver.builder() - .addEndpoint(HttpEndpoint.builder().setPrefix("/static").setPath("/**").build()) .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 + " req context path = " + req.getContextPath()); + service.handle(req, resp); + }) + .build(); + Domain domain = Domain.builder(httpAddress) + .addEndpointResolver(httpEndpointResolver) + .build(); + Server server = Server.builder(domain) + .build(); + Client client = Client.builder() + .build(); + final AtomicBoolean success1 = new AtomicBoolean(false); + final AtomicBoolean success2 = new AtomicBoolean(false); + final AtomicBoolean success3 = new AtomicBoolean(false); + try { + Files.write(vartmp.resolve("test1.txt"), "Hello Jörg 1".getBytes(StandardCharsets.UTF_8)); + Files.write(vartmp.resolve("test2.txt"), "Hello Jörg 2".getBytes(StandardCharsets.UTF_8)); + Files.write(vartmp.resolve("test3.txt"), "Hello Jörg 3".getBytes(StandardCharsets.UTF_8)); + server.accept(); + Request request = Request.get().setVersion(HttpVersion.HTTP_1_1) + .url(server.getServerConfig().getAddress().base().resolve("/static1/test1.txt")) + .build() + .setResponseListener(resp -> { + assertEquals("Hello Jörg 1", resp.getBodyAsString(StandardCharsets.UTF_8)); + success1.set(true); + }); + client.execute(request).get(); + Request request1 = Request.get().setVersion(HttpVersion.HTTP_1_1) + .url(server.getServerConfig().getAddress().base().resolve("/static2/test2.txt")) + .build() + .setResponseListener(resp -> { + assertEquals("Hello Jörg 2",resp.getBodyAsString(StandardCharsets.UTF_8)); + success2.set(true); + }); + client.execute(request1).get(); + Request request2 = Request.get().setVersion(HttpVersion.HTTP_1_1) + .url(server.getServerConfig().getAddress().base().resolve("/static3/test3.txt")) + .build() + .setResponseListener(resp -> { + assertEquals("Hello Jörg 3", resp.getBodyAsString(StandardCharsets.UTF_8)); + success3.set(true); + }); + client.execute(request2).get(); + } finally { + server.shutdownGracefully(); + client.shutdownGracefully(); + Files.delete(vartmp.resolve("test1.txt")); + Files.delete(vartmp.resolve("test2.txt")); + Files.delete(vartmp.resolve("test3.txt")); + logger.log(Level.INFO, "server and client shut down"); + } + assertTrue(success1.get()); + assertTrue(success2.get()); + assertTrue(success3.get()); + } + + @Test + void testQueryEndpoints() throws Exception { + Path vartmp = Paths.get("/var/tmp/"); + Service service = new FileService(vartmp); + HttpAddress httpAddress = HttpAddress.http1("localhost", 8008); + HttpEndpointResolver httpEndpointResolver = HttpEndpointResolver.builder() + .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); service.handle(req, resp); @@ -135,100 +204,21 @@ class EndpointTest { .build(); Client client = Client.builder() .build(); - final AtomicBoolean success = new AtomicBoolean(false); final AtomicBoolean success1 = new AtomicBoolean(false); final AtomicBoolean success2 = new AtomicBoolean(false); + final AtomicBoolean success3 = new AtomicBoolean(false); try { - Files.write(vartmp.resolve("test.txt"), "Hello Jörg".getBytes(StandardCharsets.UTF_8)); Files.write(vartmp.resolve("test1.txt"), "Hello Jörg 1".getBytes(StandardCharsets.UTF_8)); Files.write(vartmp.resolve("test2.txt"), "Hello Jörg 2".getBytes(StandardCharsets.UTF_8)); + Files.write(vartmp.resolve("test3.txt"), "Hello Jörg 3".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(resp -> { - assertEquals("Hello Jörg", resp.getBodyAsString(StandardCharsets.UTF_8)); - success.set(true); - }); - client.execute(request).get(); Request request1 = Request.get().setVersion(HttpVersion.HTTP_1_1) - .url(server.getServerConfig().getAddress().base().resolve("/static1/test1.txt")) - .build() - .setResponseListener(resp -> { - assertEquals("Hello Jörg 1",resp.getBodyAsString(StandardCharsets.UTF_8)); - success1.set(true); - }); - client.execute(request1).get(); - Request request2 = Request.get().setVersion(HttpVersion.HTTP_1_1) - .url(server.getServerConfig().getAddress().base().resolve("/static2/test2.txt")) - .build() - .setResponseListener(resp -> { - assertEquals("Hello Jörg 2", resp.getBodyAsString(StandardCharsets.UTF_8)); - success2.set(true); - }); - client.execute(request2).get(); - } finally { - server.shutdownGracefully(); - client.shutdownGracefully(); - Files.delete(vartmp.resolve("test.txt")); - Files.delete(vartmp.resolve("test1.txt")); - Files.delete(vartmp.resolve("test2.txt")); - logger.log(Level.INFO, "server and client shut down"); - } - assertTrue(success.get()); - assertTrue(success1.get()); - assertTrue(success2.get()); - } - - @Test - void testQueryAndFragmentEndpoints() throws Exception { - Path vartmp = Paths.get("/var/tmp/"); - Service service = new FileService(vartmp); - HttpAddress httpAddress = HttpAddress.http1("localhost", 8008); - HttpEndpointResolver httpEndpointResolver = HttpEndpointResolver.builder() - .addEndpoint(HttpEndpoint.builder().setPrefix("/static").setPath("/**").build()) - .addEndpoint(HttpEndpoint.builder().setPrefix("/static1").setPath("/**").build()) - .addEndpoint(HttpEndpoint.builder().setPrefix("/static2").setPath("/**").build()) - .setDispatcher((endpoint, req, resp) -> { - logger.log(Level.FINE, "dispatching endpoint = " + endpoint + " req = " + req + - " fragment=" + req.getURL().getFragment()); - service.handle(req, resp); - }) - .build(); - Domain domain = Domain.builder(httpAddress) - .addEndpointResolver(httpEndpointResolver) - .build(); - Server server = Server.builder(domain) - .build(); - Client client = Client.builder() - .build(); - final AtomicBoolean success = new AtomicBoolean(false); - final AtomicBoolean success1 = new AtomicBoolean(false); - final AtomicBoolean success2 = new AtomicBoolean(false); - try { - Files.write(vartmp.resolve("test.txt"), "Hello Jörg".getBytes(StandardCharsets.UTF_8)); - Files.write(vartmp.resolve("test1.txt"), "Hello Jörg 1".getBytes(StandardCharsets.UTF_8)); - Files.write(vartmp.resolve("test2.txt"), "Hello Jörg 2".getBytes(StandardCharsets.UTF_8)); - server.accept(); - Request request = Request.get().setVersion(HttpVersion.HTTP_1_1) - .url(server.getServerConfig().getAddress().base().resolve("/static/test.txt")) + .url(server.getServerConfig().getAddress().base() + .resolve("/static1/test1.txt")) .addParameter("a", "b") .build() .setResponseListener(resp -> { if (resp.getStatus().getCode() == HttpResponseStatus.OK.code()) { - assertEquals("Hello Jörg", resp.getBodyAsString(StandardCharsets.UTF_8)); - success.set(true); - } else { - logger.log(Level.WARNING, resp.getStatus().getReasonPhrase()); - } - }); - client.execute(request).get(); - Request request1 = Request.get().setVersion(HttpVersion.HTTP_1_1) - .url(server.getServerConfig().getAddress().base() - .resolve("/static1/test1.txt").mutator().fragment("frag").build()) - .build() - .setResponseListener(resp -> { - if (resp.getStatus().getCode() == HttpResponseStatus.OK.code()) { assertEquals("Hello Jörg 1", resp.getBodyAsString(StandardCharsets.UTF_8)); success1.set(true); } else { @@ -239,44 +229,157 @@ class EndpointTest { Request request2 = Request.get().setVersion(HttpVersion.HTTP_1_1) .url(server.getServerConfig().getAddress().base() .resolve("/static2/test2.txt")) - .content("{\"a\":\"b\"}","application/json") .build() .setResponseListener(resp -> { - if (resp.getStatus().getCode() == HttpResponseStatus.OK.code()) { - assertEquals("Hello Jörg 2",resp.getBodyAsString(StandardCharsets.UTF_8)); + if (resp.getStatus().getCode() == HttpResponseStatus.OK.code()) { + assertEquals("Hello Jörg 2", resp.getBodyAsString(StandardCharsets.UTF_8)); success2.set(true); } else { logger.log(Level.WARNING, resp.getStatus().getReasonPhrase()); } }); client.execute(request2).get(); + Request request3 = Request.get().setVersion(HttpVersion.HTTP_1_1) + .url(server.getServerConfig().getAddress().base() + .resolve("/static3/test3.txt")) + .content("{\"a\":\"b\"}","application/json") + .build() + .setResponseListener(resp -> { + if (resp.getStatus().getCode() == HttpResponseStatus.OK.code()) { + assertEquals("Hello Jörg 3",resp.getBodyAsString(StandardCharsets.UTF_8)); + success3.set(true); + } else { + logger.log(Level.WARNING, resp.getStatus().getReasonPhrase()); + } + }); + client.execute(request3).get(); } finally { server.shutdownGracefully(); client.shutdownGracefully(); - Files.delete(vartmp.resolve("test.txt")); + logger.log(Level.INFO, "server and client shut down"); Files.delete(vartmp.resolve("test1.txt")); Files.delete(vartmp.resolve("test2.txt")); - logger.log(Level.INFO, "server and client shut down"); + Files.delete(vartmp.resolve("test3.txt")); } - assertTrue(success.get()); assertTrue(success1.get()); assertTrue(success2.get()); + assertTrue(success3.get()); + } + + @Test + void testMultiResolver() throws Exception { + Path vartmp = Paths.get("/var/tmp/"); + Service service1 = new FileService(vartmp); + Service service2 = new FileService(vartmp); + Service service3 = new FileService(vartmp); + 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()); + service1.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()); + service2.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()); + service3.handle(req, resp); + }) + .build(); + Domain domain = Domain.builder(httpAddress) + .addEndpointResolver(httpEndpointResolver1) + .addEndpointResolver(httpEndpointResolver2) + .addEndpointResolver(httpEndpointResolver3) + .build(); + Server server = Server.builder(domain) + .build(); + Client client = Client.builder() + .build(); + final AtomicBoolean success1 = new AtomicBoolean(false); + final AtomicBoolean success2 = new AtomicBoolean(false); + final AtomicBoolean success3 = new AtomicBoolean(false); + try { + Files.write(vartmp.resolve("test1.txt"), "Hello Jörg 1".getBytes(StandardCharsets.UTF_8)); + Files.write(vartmp.resolve("test2.txt"), "Hello Jörg 2".getBytes(StandardCharsets.UTF_8)); + Files.write(vartmp.resolve("test3.txt"), "Hello Jörg 3".getBytes(StandardCharsets.UTF_8)); + server.accept(); + Request request1 = Request.get().setVersion(HttpVersion.HTTP_1_1) + .url(server.getServerConfig().getAddress().base() + .resolve("/static1/test1.txt")) + .addParameter("a", "b") + .build() + .setResponseListener(resp -> { + if (resp.getStatus().getCode() == HttpResponseStatus.OK.code()) { + assertEquals("Hello Jörg 1", resp.getBodyAsString(StandardCharsets.UTF_8)); + success1.set(true); + } else { + logger.log(Level.WARNING, resp.getStatus().getReasonPhrase()); + } + }); + client.execute(request1).get(); + Request request2 = Request.get().setVersion(HttpVersion.HTTP_1_1) + .url(server.getServerConfig().getAddress().base() + .resolve("/static2/test2.txt")) + .build() + .setResponseListener(resp -> { + if (resp.getStatus().getCode() == HttpResponseStatus.OK.code()) { + assertEquals("Hello Jörg 2", resp.getBodyAsString(StandardCharsets.UTF_8)); + success2.set(true); + } else { + logger.log(Level.WARNING, resp.getStatus().getReasonPhrase()); + } + }); + client.execute(request2).get(); + Request request3 = Request.get().setVersion(HttpVersion.HTTP_1_1) + .url(server.getServerConfig().getAddress().base() + .resolve("/static3/test3.txt")) + .content("{\"a\":\"b\"}","application/json") + .build() + .setResponseListener(resp -> { + if (resp.getStatus().getCode() == HttpResponseStatus.OK.code()) { + assertEquals("Hello Jörg 3",resp.getBodyAsString(StandardCharsets.UTF_8)); + success3.set(true); + } else { + logger.log(Level.WARNING, resp.getStatus().getReasonPhrase()); + } + }); + client.execute(request3).get(); + } finally { + server.shutdownGracefully(); + client.shutdownGracefully(); + logger.log(Level.INFO, "server and client shut down"); + Files.delete(vartmp.resolve("test1.txt")); + Files.delete(vartmp.resolve("test2.txt")); + Files.delete(vartmp.resolve("test3.txt")); + } + assertTrue(success1.get()); + assertTrue(success2.get()); + assertTrue(success3.get()); } @Test void testMassiveEndpoints() throws IOException { - int max = 1024; + int max = 1024; // the default limit, must work HttpAddress httpAddress = HttpAddress.http1("localhost", 8008); HttpEndpointResolver.Builder endpointResolverBuilder = HttpEndpointResolver.builder() .setPrefix("/static"); for (int i = 0; i < max; i++) { endpointResolverBuilder.addEndpoint(HttpEndpoint.builder() .setPath("/" + i + "/**") - .addFilter((req, resp) -> ServerResponse.write(resp, HttpResponseStatus.OK)) .build()); } Domain domain = Domain.builder(httpAddress) - .addEndpointResolver(endpointResolverBuilder.build()) + .addEndpointResolver(endpointResolverBuilder + .setDispatcher((endpoint,req, resp) -> ServerResponse.write(resp, HttpResponseStatus.OK)) + .build()) .build(); Server server = Server.builder(domain) .build(); @@ -305,4 +408,45 @@ class EndpointTest { } assertEquals(max, count.get()); } + + @Test + void testMassiveEndpointResolvers() throws IOException { + int max = 1024; // the default limit, must work + HttpAddress httpAddress = HttpAddress.http1("localhost", 8008); + HttpEndpointResolver.Builder endpointResolverBuilder = HttpEndpointResolver.builder() + .setPrefix("/static"); + Domain.Builder domainBuilder = Domain.builder(httpAddress); + 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)) + .build()); + } + Server server = Server.builder(domainBuilder.build()) + .build(); + Client client = Client.builder() + .build(); + final AtomicInteger count = new AtomicInteger(0); + try { + server.accept(); + for (int i = 0; i < max; i++) { + Request request = Request.get().setVersion(HttpVersion.HTTP_1_1) + .url(server.getServerConfig().getAddress().base().resolve("/static/" + i + "/test.txt")) + .build() + .setResponseListener(resp -> { + if (resp.getStatus().getCode() == HttpResponseStatus.OK.code()) { + count.incrementAndGet(); + } else { + logger.log(Level.WARNING, resp.getStatus().getReasonPhrase()); + } + }); + client.execute(request).get(); + } + } finally { + server.shutdownGracefully(); + client.shutdownGracefully(); + logger.log(Level.INFO, "server and client shut down"); + } + assertEquals(max, count.get()); + } } diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/endpoint/FileServiceTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/endpoint/FileServiceTest.java new file mode 100644 index 0000000..ac06c73 --- /dev/null +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/endpoint/FileServiceTest.java @@ -0,0 +1,210 @@ +package org.xbib.netty.http.server.test.endpoint; + +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.HttpVersion; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.xbib.netty.http.client.Client; +import org.xbib.netty.http.client.Request; +import org.xbib.netty.http.common.HttpAddress; +import org.xbib.netty.http.server.Server; +import org.xbib.netty.http.server.Domain; +import org.xbib.netty.http.server.endpoint.service.FileService; +import org.xbib.netty.http.server.test.NettyHttpTestExtension; + +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.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@ExtendWith(NettyHttpTestExtension.class) +class FileServiceTest { + + private static final Logger logger = Logger.getLogger(FileServiceTest.class.getName()); + + @Test + void testFileServiceHttp1() throws Exception { + Path vartmp = Paths.get("/var/tmp/"); + HttpAddress httpAddress = HttpAddress.http1("localhost", 8008); + Domain domain = Domain.builder(httpAddress) + .singleEndpoint("/static", "/**", new FileService(vartmp)) + .build(); + Server server = Server.builder(domain) + .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(resp -> { + assertEquals("Hello Jörg", resp.getBodyAsString(StandardCharsets.UTF_8)); + success.set(true); + }); + client.execute(request).get(); + } finally { + server.shutdownGracefully(); + client.shutdownGracefully(); + Files.delete(vartmp.resolve("test.txt")); + logger.log(Level.INFO, "server and client shut down"); + } + assertTrue(success.get()); + } + + @Test + void testFileServiceHttp2() throws Exception { + Path vartmp = Paths.get("/var/tmp/"); + HttpAddress httpAddress = HttpAddress.http2("localhost", 8008); + Domain domain = Domain.builder(httpAddress) + .singleEndpoint("/static", "/**", new FileService(vartmp)) + .build(); + Server server = Server.builder(domain) + .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.valueOf("HTTP/2.0")) + .url(server.getServerConfig().getAddress().base().resolve("/static/test.txt")) + .build() + .setResponseListener(resp -> { + assertEquals("Hello Jörg", resp.getBodyAsString(StandardCharsets.UTF_8)); + success.set(true); + }); + client.execute(request).get(); + } finally { + server.shutdownGracefully(); + client.shutdownGracefully(); + Files.delete(vartmp.resolve("test.txt")); + logger.log(Level.INFO, "server and client shut down"); + } + assertTrue(success.get()); + } + + @Test + void testIndexFileHttp1() throws Exception { + Path vartmp = Paths.get("/var/tmp"); + Path vartmpforward = vartmp.resolve("forward_test"); + Files.createDirectories(vartmpforward); + Files.write(vartmpforward.resolve("test.txt"), "Hello Jörg".getBytes(StandardCharsets.UTF_8)); + HttpAddress httpAddress = HttpAddress.http1("localhost", 8008); + Domain domain = Domain.builder(httpAddress) + .singleEndpoint("/static", "/**", new FileService(vartmp, "test.txt")) + .build(); + Server server = Server.builder(domain) + .build(); + Client client = Client.builder() + .build(); + final AtomicBoolean success = new AtomicBoolean(false); + try { + server.accept(); + Request request = Request.get().setVersion(HttpVersion.HTTP_1_1) + .url(server.getServerConfig().getAddress().base().resolve("/static/forward_test")) + .build() + .setResponseListener(resp -> { + if (resp.getStatus().getCode() == HttpResponseStatus.OK.code()) { + assertEquals("Hello Jörg", resp.getBodyAsString(StandardCharsets.UTF_8)); + success.set(true); + } + }); + client.execute(request).get(); + } finally { + server.shutdownGracefully(); + client.shutdownGracefully(); + Files.delete(vartmpforward.resolve("test.txt")); + Files.delete(vartmpforward); + logger.log(Level.INFO, "server and client shut down"); + } + assertTrue(success.get()); + } + + @Test + void testIndexFileHttp2() throws Exception { + Path vartmp = Paths.get("/var/tmp"); + Path vartmpforward = vartmp.resolve("forward_test"); + Files.createDirectories(vartmpforward); + Files.write(vartmpforward.resolve("test.txt"), "Hello Jörg".getBytes(StandardCharsets.UTF_8)); + HttpAddress httpAddress = HttpAddress.http2("localhost", 8008); + Domain domain = Domain.builder(httpAddress) + .singleEndpoint("/static", "/**", new FileService(vartmp, "test.txt")) + .build(); + Server server = Server.builder(domain) + .build(); + Client client = Client.builder() + .build(); + final AtomicBoolean success = new AtomicBoolean(false); + try { + server.accept(); + Request request = Request.get() + .setVersion(HttpVersion.valueOf("HTTP/2.0")) + .url(server.getServerConfig().getAddress().base().resolve("/static/forward_test")) + .build() + .setResponseListener(resp -> { + if (resp.getStatus().getCode() == HttpResponseStatus.OK.code()) { + assertEquals("Hello Jörg", resp.getBodyAsString(StandardCharsets.UTF_8)); + success.set(true); + } + }); + client.execute(request).get(); + // client waits for settings, we wait too + Thread.sleep(1000L); + } finally { + server.shutdownGracefully(); + client.shutdownGracefully(); + Files.delete(vartmpforward.resolve("test.txt")); + Files.delete(vartmpforward); + logger.log(Level.INFO, "server and client shut down"); + } + assertTrue(success.get()); + } + + @Test + void testIndexFileParamsHttp1() throws Exception { + Path vartmp = Paths.get("/var/tmp"); + Path vartmpforward = vartmp.resolve("forward_test"); + Files.createDirectories(vartmpforward); + Files.write(vartmpforward.resolve("test.txt"), "Hello Jörg".getBytes(StandardCharsets.UTF_8)); + HttpAddress httpAddress = HttpAddress.http1("localhost", 8008); + Domain domain = Domain.builder(httpAddress) + .singleEndpoint("/static", "/**", + new FileService(vartmp, "test.txt")) + .build(); + Server server = Server.builder(domain) + .build(); + Client client = Client.builder() + .build(); + final AtomicBoolean success = new AtomicBoolean(false); + try { + server.accept(); + Request request = Request.get().setVersion(HttpVersion.HTTP_1_1) + .url(server.getServerConfig().getAddress().base().resolve("/static/forward_test?a=b")) + .build() + .setResponseListener(resp -> { + if (resp.getStatus().getCode() == HttpResponseStatus.OK.code()) { + assertEquals("Hello Jörg", resp.getBodyAsString(StandardCharsets.UTF_8)); + success.set(true); + } + }); + client.execute(request).get(); + } finally { + server.shutdownGracefully(); + client.shutdownGracefully(); + Files.delete(vartmpforward.resolve("test.txt")); + Files.delete(vartmpforward); + } + assertTrue(success.get()); + } +} diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/SecureFileServiceTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/endpoint/SecureFileServiceTest.java similarity index 90% rename from netty-http-server/src/test/java/org/xbib/netty/http/server/test/SecureFileServiceTest.java rename to netty-http-server/src/test/java/org/xbib/netty/http/server/test/endpoint/SecureFileServiceTest.java index fb20572..732c3c2 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/SecureFileServiceTest.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/endpoint/SecureFileServiceTest.java @@ -1,4 +1,4 @@ -package org.xbib.netty.http.server.test; +package org.xbib.netty.http.server.test.endpoint; import io.netty.handler.codec.http.HttpVersion; import org.junit.jupiter.api.Disabled; @@ -10,6 +10,7 @@ import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.server.Server; import org.xbib.netty.http.server.Domain; import org.xbib.netty.http.server.endpoint.service.FileService; +import org.xbib.netty.http.server.test.NettyHttpTestExtension; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -27,7 +28,6 @@ class SecureFileServiceTest { private static final Logger logger = Logger.getLogger(SecureFileServiceTest.class.getName()); - @Disabled @Test void testSecureFileServerHttp1() throws Exception { Path vartmp = Paths.get("/var/tmp/"); @@ -39,12 +39,10 @@ class SecureFileServiceTest { .build()) .setChildThreadCount(8) .build(); - //server.logDiagnostics(Level.INFO); Client client = Client.builder() .setJdkSslProvider() .trustInsecure() .build(); - //client.logDiagnostics(Level.INFO); final AtomicBoolean success = new AtomicBoolean(false); try { Files.write(vartmp.resolve("test.txt"), "Hello Jörg".getBytes(StandardCharsets.UTF_8)); @@ -57,14 +55,11 @@ class SecureFileServiceTest { assertEquals("Hello Jörg", resp.getBodyAsString(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")); - logger.log(Level.INFO, "server and client shut down"); } assertTrue(success.get()); } @@ -105,7 +100,6 @@ class SecureFileServiceTest { server.shutdownGracefully(); client.shutdownGracefully(); Files.delete(vartmp.resolve("test.txt")); - logger.log(Level.INFO, "server and client shut down"); } assertTrue(success.get()); } @@ -134,20 +128,18 @@ class SecureFileServiceTest { server.accept(); Request request = Request.get() .setVersion(HttpVersion.HTTP_1_1) - .url(server.getServerConfig().getAddress().base().resolve("/static/test.txt")) + .url(server.getServerConfig().getAddress().base() + .resolve("/static/test.txt")) .build() .setResponseListener(resp -> { assertEquals("Hello Jörg", resp.getBodyAsString(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")); - logger.log(Level.INFO, "server and client shut down"); } assertTrue(success.get()); } 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 0d1b019..23e871a 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 @@ -19,7 +19,6 @@ import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.codec.http.LastHttpContent; import io.netty.handler.codec.http.QueryStringDecoder; import org.junit.jupiter.api.AfterAll; -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; @@ -49,8 +48,6 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -/** flaky */ -@Disabled @TestInstance(TestInstance.Lifecycle.PER_CLASS) @ExtendWith(NettyHttpTestExtension.class) class HttpPipeliningHandlerTest { @@ -72,7 +69,7 @@ class HttpPipeliningHandlerTest { EmbeddedChannel embeddedChannel = new EmbeddedChannel(new HttpPipeliningHandler(10000), handler); for (int i = 0; i < 5; i++) { - embeddedChannel.writeInbound(createHttpRequest("/" + String.valueOf(i))); + embeddedChannel.writeInbound(createHttpRequest("/" + i)); } for (String url : waitingRequests.keySet()) { finishRequest(url); @@ -90,7 +87,7 @@ class HttpPipeliningHandlerTest { EmbeddedChannel embeddedChannel = new EmbeddedChannel(new HttpPipeliningHandler(10000), handler); for (int i = 0; i < 5; i++) { - embeddedChannel.writeInbound(createHttpRequest("/" + String.valueOf(i))); + embeddedChannel.writeInbound(createHttpRequest("/" + i)); } List urls = new ArrayList<>(waitingRequests.keySet()); Collections.shuffle(urls);