fix resolving endpoints, fix Unpooled.buffer(o) in flush, update to Netty 4.1.41

This commit is contained in:
Jörg Prante 2019-09-13 15:25:29 +02:00
parent 5c14695785
commit 833e502a7c
28 changed files with 931 additions and 357 deletions

View file

@ -1,13 +1,13 @@
group = org.xbib group = org.xbib
name = netty-http name = netty-http
version = 4.1.39.2 version = 4.1.41.0
# netty # netty
netty.version = 4.1.39.Final netty.version = 4.1.41.Final
tcnative.version = 2.0.25.Final tcnative.version = 2.0.25.Final
# for netty-http-common # for netty-http-common
xbib-net-url.version = 2.0.0 xbib-net-url.version = 2.0.1
# for netty-http-server # for netty-http-server
bouncycastle.version = 1.62 bouncycastle.version = 1.62

View file

@ -172,8 +172,8 @@ public class Request {
@Override @Override
public String toString() { public String toString() {
return "Request[url='" + url + return "Request[url=" + url +
"',version=" + httpVersion + ",version=" + httpVersion +
",method=" + httpMethod + ",method=" + httpMethod +
",headers=" + headers.entries() + ",headers=" + headers.entries() +
",content=" + (content != null && content.readableBytes() >= 16 ? ",content=" + (content != null && content.readableBytes() >= 16 ?

View file

@ -102,7 +102,7 @@ abstract class BaseTransport implements Transport {
flow.close(); flow.close();
} }
channels.clear(); channels.clear();
requests.clear(); // do not clear requests
} }
@Override @Override

View file

@ -1,6 +1,7 @@
package org.xbib.netty.http.client.test.htt2push; package org.xbib.netty.http.client.test.htt2push;
import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpMethod;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.xbib.netty.http.client.Client; import org.xbib.netty.http.client.Client;
@ -11,6 +12,7 @@ import java.io.IOException;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@Disabled // /http2-push.io "connection refused"
@ExtendWith(NettyHttpTestExtension.class) @ExtendWith(NettyHttpTestExtension.class)
class Http2PushTest { class Http2PushTest {

View file

@ -0,0 +1,6 @@
package org.xbib.netty.http.common;
public enum HttpMethod {
GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH
}

View file

@ -1,4 +1,4 @@
package org.xbib.netty.http.server.util; package org.xbib.netty.http.common.net;
/** /**
* The network classes. * The network classes.

View file

@ -1,4 +1,4 @@
package org.xbib.netty.http.server.util; package org.xbib.netty.http.common.net;
/** /**
* The TCP/IP network protocol versions. * The TCP/IP network protocol versions.

View file

@ -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<K, V> extends ConcurrentHashMap<K, V> {
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;
}
}

View file

@ -3,11 +3,11 @@ package org.xbib.netty.http.common.util;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@SuppressWarnings("serial") @SuppressWarnings("serial")
public class LimitedMap<K, V> extends LinkedHashMap<K, V> { public class LimitedLinkedHashMap<K, V> extends LinkedHashMap<K, V> {
private final int limit; private final int limit;
public LimitedMap(int limit) { public LimitedLinkedHashMap(int limit) {
super(16, 0.75f, true); super(16, 0.75f, true);
this.limit = limit; this.limit = limit;
} }

View file

@ -24,6 +24,7 @@ import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Set; import java.util.Set;
/** /**
@ -99,10 +100,25 @@ public class Domain {
return aliases; 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 { public void handle(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
if (httpEndpointResolvers != null && !httpEndpointResolvers.isEmpty()) { if (httpEndpointResolvers != null) {
boolean found = false;
for (HttpEndpointResolver httpEndpointResolver : httpEndpointResolvers) { for (HttpEndpointResolver httpEndpointResolver : httpEndpointResolvers) {
httpEndpointResolver.handle(serverRequest, serverResponse); List<HttpEndpoint> 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 { } else {
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_IMPLEMENTED); ServerResponse.write(serverResponse, HttpResponseStatus.NOT_IMPLEMENTED);
@ -143,6 +159,8 @@ public class Domain {
private String keyPassword; private String keyPassword;
Builder(HttpAddress httpAddress, String serverName) { Builder(HttpAddress httpAddress, String serverName) {
Objects.requireNonNull(httpAddress);
Objects.requireNonNull(serverName);
this.httpAddress = httpAddress; this.httpAddress = httpAddress;
this.serverName = serverName; this.serverName = serverName;
this.aliases = new LinkedHashSet<>(); this.aliases = new LinkedHashSet<>();
@ -154,31 +172,37 @@ public class Domain {
} }
public Builder setTrustManagerFactory(TrustManagerFactory trustManagerFactory) { public Builder setTrustManagerFactory(TrustManagerFactory trustManagerFactory) {
Objects.requireNonNull(trustManagerFactory);
this.trustManagerFactory = trustManagerFactory; this.trustManagerFactory = trustManagerFactory;
return this; return this;
} }
public Builder setTrustManagerKeyStore(KeyStore trustManagerKeyStore) { public Builder setTrustManagerKeyStore(KeyStore trustManagerKeyStore) {
Objects.requireNonNull(trustManagerKeyStore);
this.trustManagerKeyStore = trustManagerKeyStore; this.trustManagerKeyStore = trustManagerKeyStore;
return this; return this;
} }
public Builder setSslContextProvider(Provider sslContextProvider) { public Builder setSslContextProvider(Provider sslContextProvider) {
Objects.requireNonNull(sslContextProvider);
this.sslContextProvider = sslContextProvider; this.sslContextProvider = sslContextProvider;
return this; return this;
} }
public Builder setSslProvider(SslProvider sslProvider) { public Builder setSslProvider(SslProvider sslProvider) {
Objects.requireNonNull(sslProvider);
this.sslProvider = sslProvider; this.sslProvider = sslProvider;
return this; return this;
} }
public Builder setCiphers(Iterable<String> ciphers) { public Builder setCiphers(Iterable<String> ciphers) {
Objects.requireNonNull(ciphers);
this.ciphers = ciphers; this.ciphers = ciphers;
return this; return this;
} }
public Builder setCipherSuiteFilter(CipherSuiteFilter cipherSuiteFilter) { public Builder setCipherSuiteFilter(CipherSuiteFilter cipherSuiteFilter) {
Objects.requireNonNull(cipherSuiteFilter);
this.cipherSuiteFilter = cipherSuiteFilter; this.cipherSuiteFilter = cipherSuiteFilter;
return this; return this;
} }
@ -196,21 +220,26 @@ public class Domain {
} }
public Builder setKeyCertChainInputStream(InputStream keyCertChainInputStream) { public Builder setKeyCertChainInputStream(InputStream keyCertChainInputStream) {
Objects.requireNonNull(keyCertChainInputStream);
this.keyCertChainInputStream = keyCertChainInputStream; this.keyCertChainInputStream = keyCertChainInputStream;
return this; return this;
} }
public Builder setKeyInputStream(InputStream keyInputStream) { public Builder setKeyInputStream(InputStream keyInputStream) {
Objects.requireNonNull(keyInputStream);
this.keyInputStream = keyInputStream; this.keyInputStream = keyInputStream;
return this; return this;
} }
public Builder setKeyPassword(String keyPassword) { public Builder setKeyPassword(String keyPassword) {
// null in keyPassword allowed, it means no password
this.keyPassword = keyPassword; this.keyPassword = keyPassword;
return this; return this;
} }
public Builder setKeyCert(InputStream keyCertChainInputStream, InputStream keyInputStream) { public Builder setKeyCert(InputStream keyCertChainInputStream, InputStream keyInputStream) {
Objects.requireNonNull(keyCertChainInputStream);
Objects.requireNonNull(keyInputStream);
setKeyCertChainInputStream(keyCertChainInputStream); setKeyCertChainInputStream(keyCertChainInputStream);
setKeyInputStream(keyInputStream); setKeyInputStream(keyInputStream);
return this; return this;
@ -218,6 +247,9 @@ public class Domain {
public Builder setKeyCert(InputStream keyCertChainInputStream, InputStream keyInputStream, public Builder setKeyCert(InputStream keyCertChainInputStream, InputStream keyInputStream,
String keyPassword) { String keyPassword) {
Objects.requireNonNull(keyCertChainInputStream);
Objects.requireNonNull(keyInputStream);
Objects.requireNonNull(keyPassword);
setKeyCertChainInputStream(keyCertChainInputStream); setKeyCertChainInputStream(keyCertChainInputStream);
setKeyInputStream(keyInputStream); setKeyInputStream(keyInputStream);
setKeyPassword(keyPassword); setKeyPassword(keyPassword);
@ -239,31 +271,56 @@ public class Domain {
* @return this builder * @return this builder
*/ */
public Builder addAlias(String alias) { public Builder addAlias(String alias) {
Objects.requireNonNull(alias);
aliases.add(alias); aliases.add(alias);
return this; return this;
} }
public Builder addEndpointResolver(HttpEndpointResolver httpEndpointResolver) { public Builder addEndpointResolver(HttpEndpointResolver httpEndpointResolver) {
Objects.requireNonNull(httpEndpointResolver);
this.httpEndpointResolvers.add(httpEndpointResolver); this.httpEndpointResolvers.add(httpEndpointResolver);
return this; return this;
} }
public Builder singleEndpoint(String path, Service service) { public Builder singleEndpoint(String path, Service service) {
Objects.requireNonNull(path);
Objects.requireNonNull(service);
addEndpointResolver(HttpEndpointResolver.builder() 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; return this;
} }
public Builder singleEndpoint(String prefix, String path, Service service) { public Builder singleEndpoint(String prefix, String path, Service service) {
Objects.requireNonNull(prefix);
Objects.requireNonNull(path);
Objects.requireNonNull(service);
addEndpointResolver(HttpEndpointResolver.builder() 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; 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() addEndpointResolver(HttpEndpointResolver.builder()
.addEndpoint(HttpEndpoint.builder().setPrefix(prefix).setPath(path).addFilter(service) .addEndpoint(HttpEndpoint.builder()
.setMethods(Arrays.asList(methods)).build()).build()); .setPrefix(prefix)
.setPath(path)
.setMethods(Arrays.asList(methods))
.build())
.setDispatcher((endpoint, req, resp) -> service.handle(req, resp))
.build());
return this; return this;
} }

View file

@ -55,5 +55,4 @@ public interface ServerRequest {
InetSocketAddress getLocalAddress(); InetSocketAddress getLocalAddress();
InetSocketAddress getRemoteAddress(); InetSocketAddress getRemoteAddress();
} }

View file

@ -4,33 +4,40 @@ import org.xbib.net.Pair;
import org.xbib.net.QueryParameters; import org.xbib.net.QueryParameters;
import org.xbib.net.path.PathMatcher; import org.xbib.net.path.PathMatcher;
import org.xbib.net.path.PathNormalizer; 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.ServerRequest;
import org.xbib.netty.http.server.ServerResponse; import org.xbib.netty.http.server.ServerResponse;
import org.xbib.netty.http.server.endpoint.service.Service; import org.xbib.netty.http.server.endpoint.service.Service;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
import java.util.EnumSet;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
public class HttpEndpoint implements Endpoint<HttpEndpointDescriptor> { public class HttpEndpoint implements Endpoint<HttpEndpointDescriptor> {
private static final PathMatcher pathMatcher = new PathMatcher(); public static final EnumSet<HttpMethod> DEFAULT_METHODS =
EnumSet.of(HttpMethod.GET, HttpMethod.HEAD);
private static final List<String> DEFAULT_METHODS = Arrays.asList("GET", "HEAD"); private static final PathMatcher pathMatcher = new PathMatcher();
private final String prefix; private final String prefix;
private final String path; private final String path;
private final List<String> methods; private final EnumSet<HttpMethod> methods;
private final List<String> contentTypes; private final List<String> contentTypes;
private final List<Service> filters; private final List<Service> filters;
private HttpEndpoint(String prefix, String path, List<String> methods, List<String> contentTypes, List<Service> filters) { private HttpEndpoint(String prefix, String path,
EnumSet<HttpMethod> methods,
List<String> contentTypes,
List<Service> filters) {
this.prefix = PathNormalizer.normalize(prefix); this.prefix = PathNormalizer.normalize(prefix);
this.path = PathNormalizer.normalize(path); this.path = PathNormalizer.normalize(path);
this.methods = methods; this.methods = methods;
@ -80,6 +87,7 @@ public class HttpEndpoint implements Endpoint<HttpEndpointDescriptor> {
} }
} }
@Override
public void handle(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException { public void handle(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
serverRequest.setContext(pathMatcher.tokenizePath(getPrefix())); serverRequest.setContext(pathMatcher.tokenizePath(getPrefix()));
for (Service service : filters) { for (Service service : filters) {
@ -92,7 +100,7 @@ public class HttpEndpoint implements Endpoint<HttpEndpointDescriptor> {
@Override @Override
public String toString() { 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<HttpEndpoint> { static class EndpointPathComparator implements Comparator<HttpEndpoint> {
@ -115,7 +123,7 @@ public class HttpEndpoint implements Endpoint<HttpEndpointDescriptor> {
private String path; private String path;
private List<String> methods; private EnumSet<HttpMethod> methods;
private List<String> contentTypes; private List<String> contentTypes;
@ -124,55 +132,62 @@ public class HttpEndpoint implements Endpoint<HttpEndpointDescriptor> {
Builder() { Builder() {
this.prefix = "/"; this.prefix = "/";
this.path = "/**"; this.path = "/**";
this.methods = new ArrayList<>(); this.methods = DEFAULT_METHODS;
this.contentTypes = new ArrayList<>(); this.contentTypes = new ArrayList<>();
this.filters = new ArrayList<>(); this.filters = new ArrayList<>();
} }
public Builder setPrefix(String prefix) { public Builder setPrefix(String prefix) {
Objects.requireNonNull(prefix);
this.prefix = prefix; this.prefix = prefix;
return this; return this;
} }
public Builder setPath(String path) { public Builder setPath(String path) {
Objects.requireNonNull(path);
this.path = path; this.path = path;
return this; return this;
} }
public Builder setMethods(List<String> methods) { public Builder setMethods(EnumSet<HttpMethod> methods) {
Objects.requireNonNull(methods);
this.methods = methods; this.methods = methods;
return this; return this;
} }
public Builder addMethod(String method) { public Builder setMethods(List<String> methods) {
methods.add(method); Objects.requireNonNull(methods);
this.methods = methods.stream()
.map(HttpMethod::valueOf)
.collect(Collectors.toCollection(() -> EnumSet.noneOf(HttpMethod.class)));
return this; return this;
} }
public Builder setContentTypes(List<String> contentTypes) { public Builder setContentTypes(List<String> contentTypes) {
Objects.requireNonNull(contentTypes);
this.contentTypes = contentTypes; this.contentTypes = contentTypes;
return this; return this;
} }
public Builder addContentType(String contentType) { public Builder addContentType(String contentType) {
Objects.requireNonNull(contentType);
this.contentTypes.add(contentType); this.contentTypes.add(contentType);
return this; return this;
} }
public Builder setFilters(List<Service> filters) { public Builder setFilters(List<Service> filters) {
Objects.requireNonNull(filters);
this.filters = filters; this.filters = filters;
return this; return this;
} }
public Builder addFilter(Service filter) { public Builder addFilter(Service filter) {
Objects.requireNonNull(filter);
this.filters.add(filter); this.filters.add(filter);
return this; return this;
} }
public HttpEndpoint build() { public HttpEndpoint build() {
if (methods.isEmpty()) {
methods = DEFAULT_METHODS;
}
return new HttpEndpoint(prefix, path, methods, contentTypes, filters); return new HttpEndpoint(prefix, path, methods, contentTypes, filters);
} }
} }

View file

@ -1,5 +1,6 @@
package org.xbib.netty.http.server.endpoint; package org.xbib.netty.http.server.endpoint;
import org.xbib.netty.http.common.HttpMethod;
import org.xbib.netty.http.server.transport.HttpServerRequest; import org.xbib.netty.http.server.transport.HttpServerRequest;
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
@ -8,13 +9,13 @@ public class HttpEndpointDescriptor implements EndpointDescriptor, Comparable<Ht
private final String path; private final String path;
private final String method; private final HttpMethod method;
private final String contentType; private final String contentType;
public HttpEndpointDescriptor(HttpServerRequest serverRequest) { public HttpEndpointDescriptor(HttpServerRequest serverRequest) {
this.path = extractPath(serverRequest.getRequestURI()); this.path = extractPath(serverRequest.getRequestURI());
this.method = serverRequest.getMethod().name(); this.method = Enum.valueOf(HttpMethod.class, serverRequest.getMethod().name());
this.contentType = serverRequest.getHeaders().get(CONTENT_TYPE); this.contentType = serverRequest.getHeaders().get(CONTENT_TYPE);
} }
@ -22,7 +23,7 @@ public class HttpEndpointDescriptor implements EndpointDescriptor, Comparable<Ht
return path; return path;
} }
public String getMethod() { public HttpMethod getMethod() {
return method; return method;
} }

View file

@ -1,7 +1,6 @@
package org.xbib.netty.http.server.endpoint; package org.xbib.netty.http.server.endpoint;
import io.netty.handler.codec.http.HttpResponseStatus; import org.xbib.netty.http.common.util.LimitedConcurrentHashMap;
import org.xbib.netty.http.common.util.LimitedMap;
import org.xbib.netty.http.server.ServerRequest; import org.xbib.netty.http.server.ServerRequest;
import org.xbib.netty.http.server.ServerResponse; import org.xbib.netty.http.server.ServerResponse;
import org.xbib.netty.http.server.annotation.Endpoint; import org.xbib.netty.http.server.annotation.Endpoint;
@ -13,6 +12,7 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -21,79 +21,60 @@ public class HttpEndpointResolver {
private static final Logger logger = Logger.getLogger(HttpEndpointResolver.class.getName()); private static final Logger logger = Logger.getLogger(HttpEndpointResolver.class.getName());
private final HttpEndpoint defaultEndpoint; private static final int DEFAULT_LIMIT = 1024;
private final List<HttpEndpoint> endpoints; private final List<HttpEndpoint> endpoints;
private final EndpointDispatcher endpointDispatcher; private final EndpointDispatcher endpointDispatcher;
private final LimitedMap<HttpEndpointDescriptor, List<HttpEndpoint>> endpointDescriptors; private final Map<HttpEndpointDescriptor, List<HttpEndpoint>> endpointDescriptors;
private HttpEndpointResolver(HttpEndpoint defaultEndpoint, private HttpEndpointResolver(List<HttpEndpoint> endpoints,
List<HttpEndpoint> endpoints,
EndpointDispatcher endpointDispatcher, EndpointDispatcher endpointDispatcher,
int limit) { int limit) {
this.defaultEndpoint = defaultEndpoint == null ? createDefaultEndpoint() : defaultEndpoint; Objects.requireNonNull(endpointDispatcher);
this.endpoints = endpoints; this.endpoints = endpoints;
this.endpointDispatcher = endpointDispatcher; 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<HttpEndpoint> matchingEndpointsFor(ServerRequest serverRequest) {
HttpEndpointDescriptor httpEndpointDescriptor = serverRequest.getEndpointDescriptor(); HttpEndpointDescriptor httpEndpointDescriptor = serverRequest.getEndpointDescriptor();
endpointDescriptors.putIfAbsent(httpEndpointDescriptor, endpoints.stream() endpointDescriptors.putIfAbsent(httpEndpointDescriptor, endpoints.stream()
.filter(endpoint -> endpoint.matches(httpEndpointDescriptor)) .filter(endpoint -> endpoint.matches(httpEndpointDescriptor))
.sorted(new HttpEndpoint.EndpointPathComparator(httpEndpointDescriptor.getPath())) .sorted(new HttpEndpoint.EndpointPathComparator(httpEndpointDescriptor.getPath()))
.collect(Collectors.toList())); .collect(Collectors.toList()));
List<HttpEndpoint> matchingEndpoints = endpointDescriptors.get(httpEndpointDescriptor); return endpointDescriptors.get(httpEndpointDescriptor);
}
public void handle(List<HttpEndpoint> matchingEndpoints,
ServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
Objects.requireNonNull(matchingEndpoints);
if (logger.isLoggable(Level.FINE)) { if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, () -> "endpoint = " + httpEndpointDescriptor + logger.log(Level.FINE, () ->
" matching endpoints = " + matchingEndpoints); "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) { for (HttpEndpoint endpoint : matchingEndpoints) {
endpoint.resolveUriTemplate(serverRequest); endpoint.resolveUriTemplate(serverRequest);
endpoint.handle(serverRequest, serverResponse); endpoint.handle(serverRequest, serverResponse);
if (serverResponse.getStatus() != null) {
break;
}
}
if (endpointDispatcher != null) {
for (HttpEndpoint endpoint : matchingEndpoints) {
endpointDispatcher.dispatch(endpoint, serverRequest, serverResponse); endpointDispatcher.dispatch(endpoint, serverRequest, serverResponse);
if (serverResponse.getStatus() != null) { if (serverResponse.getStatus() != null) {
logger.log(Level.FINEST, () -> "endpoint " + endpoint + " status = " + serverResponse.getStatus());
break; break;
} }
} }
} }
}
}
public Map<HttpEndpointDescriptor, List<HttpEndpoint>> getEndpointDescriptors() { public Map<HttpEndpointDescriptor, List<HttpEndpoint>> getEndpointDescriptors() {
return endpointDescriptors; 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() { public static Builder builder() {
return new Builder(); return new Builder();
} }
@ -104,32 +85,26 @@ public class HttpEndpointResolver {
private String prefix; private String prefix;
private HttpEndpoint defaultEndpoint;
private List<HttpEndpoint> endpoints; private List<HttpEndpoint> endpoints;
private EndpointDispatcher endpointDispatcher; private EndpointDispatcher endpointDispatcher;
Builder() { Builder() {
this.limit = 1024; this.limit = DEFAULT_LIMIT;
this.endpoints = new ArrayList<>(); this.endpoints = new ArrayList<>();
} }
public Builder setLimit(int limit) { public Builder setLimit(int limit) {
this.limit = limit; this.limit = limit > 0 ? limit < 1024 * DEFAULT_LIMIT ? limit : DEFAULT_LIMIT : DEFAULT_LIMIT;
return this; return this;
} }
public Builder setPrefix(String prefix) { public Builder setPrefix(String prefix) {
Objects.requireNonNull(prefix);
this.prefix = prefix; this.prefix = prefix;
return this; return this;
} }
public Builder setDefaultEndpoint(HttpEndpoint endpoint) {
this.defaultEndpoint = endpoint;
return this;
}
/** /**
* Add endpoint. * Add endpoint.
* *
@ -137,12 +112,19 @@ public class HttpEndpointResolver {
* @return this builder * @return this builder
*/ */
public Builder addEndpoint(HttpEndpoint endpoint) { public Builder addEndpoint(HttpEndpoint endpoint) {
if (endpoint.getPrefix().equals("/") && prefix != null && !prefix.isEmpty()) { Objects.requireNonNull(endpoint);
HttpEndpoint thisEndpoint = HttpEndpoint.builder(endpoint).setPrefix(prefix).build(); if (prefix != null && !prefix.isEmpty()) {
logger.log(Level.FINE, "adding endpoint = " + thisEndpoint); HttpEndpoint prefixedEndpoint = HttpEndpoint.builder(endpoint)
endpoints.add(thisEndpoint); .setPrefix(prefix + endpoint.getPrefix())
.build();
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, () -> "prefix " + prefix + ": adding endpoint = " + prefixedEndpoint);
}
endpoints.add(prefixedEndpoint);
} else { } else {
logger.log(Level.FINE, "adding endpoint = " + endpoint); if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, () -> "adding endpoint = " + endpoint);
}
endpoints.add(endpoint); endpoints.add(endpoint);
} }
return this; return this;
@ -155,6 +137,7 @@ public class HttpEndpointResolver {
* @return this builder * @return this builder
*/ */
public Builder addEndpoint(Object classWithAnnotatedMethods) { public Builder addEndpoint(Object classWithAnnotatedMethods) {
Objects.requireNonNull(classWithAnnotatedMethods);
for (Class<?> clazz = classWithAnnotatedMethods.getClass(); clazz != null; clazz = clazz.getSuperclass()) { for (Class<?> clazz = classWithAnnotatedMethods.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
for (Method method : clazz.getDeclaredMethods()) { for (Method method : clazz.getDeclaredMethods()) {
Endpoint endpoint = method.getAnnotation(Endpoint.class); Endpoint endpoint = method.getAnnotation(Endpoint.class);
@ -174,12 +157,13 @@ public class HttpEndpointResolver {
} }
public Builder setDispatcher(EndpointDispatcher endpointDispatcher) { public Builder setDispatcher(EndpointDispatcher endpointDispatcher) {
Objects.requireNonNull(endpointDispatcher);
this.endpointDispatcher = endpointDispatcher; this.endpointDispatcher = endpointDispatcher;
return this; return this;
} }
public HttpEndpointResolver build() { public HttpEndpointResolver build() {
return new HttpEndpointResolver(defaultEndpoint, endpoints, endpointDispatcher, limit); return new HttpEndpointResolver(endpoints, endpointDispatcher, limit);
} }
} }
} }

View file

@ -7,16 +7,27 @@ import java.io.IOException;
import java.net.URL; import java.net.URL;
import java.net.URLConnection; import java.net.URLConnection;
import java.time.Instant; import java.time.Instant;
import java.util.logging.Level;
import java.util.logging.Logger;
public class ClassLoaderService extends ResourceService { public class ClassLoaderService extends ResourceService {
private static final Logger logger = Logger.getLogger(ClassLoaderService.class.getName());
private final Class<?> clazz; private final Class<?> clazz;
private final String prefix; private final String prefix;
private final String indexFileName;
public ClassLoaderService(Class<?> clazz, String prefix) { public ClassLoaderService(Class<?> clazz, String prefix) {
this(clazz, prefix, "index.html");
}
public ClassLoaderService(Class<?> clazz, String prefix, String indexFileName) {
this.clazz = clazz; this.clazz = clazz;
this.prefix = prefix; this.prefix = prefix;
this.indexFileName = indexFileName;
} }
@Override @Override
@ -50,11 +61,24 @@ public class ClassLoaderService extends ResourceService {
private final long length; private final long length;
ClassLoaderResource(ServerRequest serverRequest) throws IOException { ClassLoaderResource(ServerRequest serverRequest) throws IOException {
this.resourcePath = serverRequest.getEffectiveRequestPath().substring(1); String effectivePath = serverRequest.getEffectiveRequestPath();
this.url = clazz.getResource(prefix + "/" + resourcePath); 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(); URLConnection urlConnection = url.openConnection();
this.lastModified = Instant.ofEpochMilli(urlConnection.getLastModified()); this.lastModified = Instant.ofEpochMilli(urlConnection.getLastModified());
this.length = urlConnection.getContentLength(); 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 @Override
@ -76,5 +100,15 @@ public class ClassLoaderService extends ResourceService {
public long getLength() { public long getLength() {
return length; return length;
} }
@Override
public boolean isDirectory() {
return resourcePath.endsWith("/");
}
@Override
public String indexFileName() {
return indexFileName;
}
} }
} }

View file

@ -13,14 +13,15 @@ public class FileService extends ResourceService {
private final Path prefix; private final Path prefix;
private final String indexFileName;
public FileService(Path prefix) { public FileService(Path prefix) {
this(prefix, "index.html");
}
public FileService(Path prefix, String indexFileName) {
this.prefix = prefix; this.prefix = prefix;
if (!Files.exists(prefix)) { this.indexFileName = indexFileName;
throw new IllegalArgumentException("prefix: " + prefix + " (does not exist)");
}
if (!Files.isDirectory(prefix)) {
throw new IllegalArgumentException("prefix: " + prefix + " (not a directory)");
}
} }
@Override @Override
@ -49,16 +50,26 @@ public class FileService extends ResourceService {
private final URL url; private final URL url;
private final boolean isDirectory;
private final Instant lastModified; private final Instant lastModified;
private final long length; private final long length;
ChunkedFileResource(ServerRequest serverRequest) throws IOException { 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); Path path = prefix.resolve(resourcePath);
this.url = path.toUri().toURL(); this.url = path.toUri().toURL();
boolean isExists = Files.exists(path);
this.isDirectory = Files.isDirectory(path);
if (isExists) {
this.lastModified = Files.getLastModifiedTime(path).toInstant(); this.lastModified = Files.getLastModifiedTime(path).toInstant();
this.length = Files.size(path); this.length = Files.size(path);
} else {
this.lastModified = Instant.now();
this.length = 0;
}
} }
@Override @Override
@ -71,6 +82,16 @@ public class FileService extends ResourceService {
return url; return url;
} }
@Override
public boolean isDirectory() {
return isDirectory;
}
@Override
public String indexFileName() {
return indexFileName;
}
@Override @Override
public Instant getLastModified() { public Instant getLastModified() {
return lastModified; return lastModified;

View file

@ -12,4 +12,8 @@ public interface Resource {
Instant getLastModified(); Instant getLastModified();
long getLength(); long getLength();
boolean isDirectory();
String indexFileName();
} }

View file

@ -24,6 +24,7 @@ import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SeekableByteChannel; import java.nio.channels.SeekableByteChannel;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.time.Instant; import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
@ -50,6 +51,23 @@ public abstract class ResourceService implements Service {
protected abstract boolean isRangeResponseEnabled(); protected abstract boolean isRangeResponseEnabled();
private void handleResource(ServerRequest serverRequest, ServerResponse serverResponse, Resource resource) { 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(); HttpHeaders headers = serverRequest.getHeaders();
String contentType = MimeTypeUtils.guessFromPath(resource.getResourcePath(), false); String contentType = MimeTypeUtils.guessFromPath(resource.getResourcePath(), false);
long maxAgeSeconds = 24 * 3600; long maxAgeSeconds = 24 * 3600;
@ -187,7 +205,9 @@ public abstract class ResourceService implements Service {
private void send(URL url, String contentType, private void send(URL url, String contentType,
ServerRequest serverRequest, ServerResponse serverResponse) { 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); ServerResponse.write(serverResponse, HttpResponseStatus.OK, contentType);
} else { } else {
if ("file".equals(url.getProtocol())) { if ("file".equals(url.getProtocol())) {
@ -211,15 +231,19 @@ public abstract class ResourceService implements Service {
private void send(URL url, HttpResponseStatus httpResponseStatus, String contentType, private void send(URL url, HttpResponseStatus httpResponseStatus, String contentType,
ServerRequest serverRequest, ServerResponse serverResponse, long offset, long size) { ServerRequest serverRequest, ServerResponse serverResponse, long offset, long size) {
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); ServerResponse.write(serverResponse, HttpResponseStatus.OK, contentType);
} else { } else {
if ("file".equals(url.getProtocol())) { if ("file".equals(url.getProtocol())) {
Path path = null;
try { 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); contentType, serverResponse, offset, size);
} catch (URISyntaxException | IOException e) { } 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); ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND);
} }
} else { } else {
@ -240,27 +264,47 @@ public abstract class ResourceService implements Service {
private void send(FileChannel fileChannel, HttpResponseStatus httpResponseStatus, String contentType, private void send(FileChannel fileChannel, HttpResponseStatus httpResponseStatus, String contentType,
ServerResponse serverResponse, long offset, long size) throws IOException { ServerResponse serverResponse, long offset, long size) throws IOException {
MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, offset, size); 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) serverResponse.withStatus(httpResponseStatus)
.withContentType(contentType) .withContentType(contentType)
.write(Unpooled.wrappedBuffer(mappedByteBuffer)); .write(Unpooled.wrappedBuffer(mappedByteBuffer));
} }
}
}
private void send(InputStream inputStream, HttpResponseStatus httpResponseStatus, String contentType, private void send(InputStream inputStream, HttpResponseStatus httpResponseStatus, String contentType,
ServerResponse serverResponse) throws IOException { ServerResponse serverResponse) throws IOException {
if (inputStream == null) {
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND);
} else {
try (ReadableByteChannel channel = Channels.newChannel(inputStream)) { try (ReadableByteChannel channel = Channels.newChannel(inputStream)) {
serverResponse.withStatus(httpResponseStatus) serverResponse.withStatus(httpResponseStatus)
.withContentType(contentType) .withContentType(contentType)
.write(new ChunkedNioStream(channel)); .write(new ChunkedNioStream(channel));
} }
} }
}
private void send(InputStream inputStream, HttpResponseStatus httpResponseStatus, String contentType, private void send(InputStream inputStream, HttpResponseStatus httpResponseStatus, String contentType,
ServerResponse serverResponse, long offset, long size) throws IOException { ServerResponse serverResponse, long offset, long size) throws IOException {
if (inputStream == null) {
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND);
} else {
serverResponse.withStatus(httpResponseStatus) serverResponse.withStatus(httpResponseStatus)
.withContentType(contentType) .withContentType(contentType)
.write(Unpooled.wrappedBuffer(readBuffer(inputStream, offset, size))); .write(Unpooled.wrappedBuffer(readBuffer(inputStream, offset, size)));
} }
}
private static ByteBuffer readBuffer(URL url, long offset, long size) throws IOException, URISyntaxException { private static ByteBuffer readBuffer(URL url, long offset, long size) throws IOException, URISyntaxException {
if ("file".equals(url.getProtocol())) { if ("file".equals(url.getProtocol())) {

View file

@ -61,7 +61,6 @@ public class HttpServerRequest implements ServerRequest {
HttpServerRequest(Server server, FullHttpRequest fullHttpRequest, HttpServerRequest(Server server, FullHttpRequest fullHttpRequest,
ChannelHandlerContext ctx) { ChannelHandlerContext ctx) {
// server not required yet
this.httpRequest = fullHttpRequest.retainedDuplicate(); this.httpRequest = fullHttpRequest.retainedDuplicate();
this.ctx = ctx; this.ctx = ctx;
this.httpEndpointDescriptor = new HttpEndpointDescriptor(this); this.httpEndpointDescriptor = new HttpEndpointDescriptor(this);
@ -70,7 +69,9 @@ public class HttpServerRequest implements ServerRequest {
void handleParameters() throws IOException { void handleParameters() throws IOException {
try { try {
HttpParameters httpParameters = new HttpParameters(); 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(); QueryParameters queryParameters = url.getQueryParams();
ByteBuf byteBuf = httpRequest.content(); ByteBuf byteBuf = httpRequest.content();
if (APPLICATION_FORM_URL_ENCODED.equals(HttpUtil.getMimeType(httpRequest)) && byteBuf != null) { if (APPLICATION_FORM_URL_ENCODED.equals(HttpUtil.getMimeType(httpRequest)) && byteBuf != null) {

View file

@ -38,8 +38,6 @@ public class HttpServerResponse implements ServerResponse {
private static final Logger logger = Logger.getLogger(HttpServerResponse.class.getName()); private static final Logger logger = Logger.getLogger(HttpServerResponse.class.getName());
private static final ByteBuf EMPTY = Unpooled.buffer(0);
private final Server server; private final Server server;
private final ServerRequest serverRequest; private final ServerRequest serverRequest;
@ -113,7 +111,7 @@ public class HttpServerResponse implements ServerResponse {
@Override @Override
public void flush() { public void flush() {
write((ByteBuf) null); write(Unpooled.buffer(0));
} }
@Override @Override
@ -130,6 +128,7 @@ public class HttpServerResponse implements ServerResponse {
@Override @Override
public void write(ByteBuf byteBuf) { public void write(ByteBuf byteBuf) {
Objects.requireNonNull(byteBuf);
if (httpResponseStatus == null) { if (httpResponseStatus == null) {
httpResponseStatus = HttpResponseStatus.OK; httpResponseStatus = HttpResponseStatus.OK;
} }
@ -139,11 +138,9 @@ public class HttpServerResponse implements ServerResponse {
} }
if (httpResponseStatus.code() >= 200 && httpResponseStatus.code() != 204) { if (httpResponseStatus.code() >= 200 && httpResponseStatus.code() != 204) {
if (!headers.contains(HttpHeaderNames.CONTENT_LENGTH) && !headers.contains(HttpHeaderNames.TRANSFER_ENCODING)) { 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)) && if (serverRequest != null && "close".equalsIgnoreCase(serverRequest.getHeaders().get(HttpHeaderNames.CONNECTION)) &&
!headers.contains(HttpHeaderNames.CONNECTION)) { !headers.contains(HttpHeaderNames.CONNECTION)) {
headers.add(HttpHeaderNames.CONNECTION, "close"); headers.add(HttpHeaderNames.CONNECTION, "close");
@ -154,11 +151,7 @@ public class HttpServerResponse implements ServerResponse {
headers.add(HttpHeaderNames.SERVER, ServerName.getServerName()); headers.add(HttpHeaderNames.SERVER, ServerName.getServerName());
if (ctx.channel().isWritable()) { if (ctx.channel().isWritable()) {
FullHttpResponse fullHttpResponse; FullHttpResponse fullHttpResponse;
if (byteBuf != null) {
fullHttpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, httpResponseStatus, byteBuf, headers, trailingHeaders); fullHttpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, httpResponseStatus, byteBuf, headers, trailingHeaders);
} else {
fullHttpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, httpResponseStatus, EMPTY, headers, trailingHeaders);
}
if (serverRequest != null && serverRequest.getSequenceId() != null) { if (serverRequest != null && serverRequest.getSequenceId() != null) {
HttpPipelinedResponse httpPipelinedResponse = new HttpPipelinedResponse(fullHttpResponse, HttpPipelinedResponse httpPipelinedResponse = new HttpPipelinedResponse(fullHttpResponse,
ctx.channel().newPromise(), serverRequest.getSequenceId()); ctx.channel().newPromise(), serverRequest.getSequenceId());

View file

@ -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());
}
}

View file

@ -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<HttpResponse> 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<HttpResponse> 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());
}
}

View file

@ -105,7 +105,7 @@ class SecureHttp2Test {
break; break;
} }
} }
transport.get(); transport.get(60, TimeUnit.SECONDS);
} finally { } finally {
client.shutdownGracefully(); client.shutdownGracefully();
server.shutdownGracefully(); server.shutdownGracefully();

View file

@ -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.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion; 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.Server;
import org.xbib.netty.http.server.Domain; import org.xbib.netty.http.server.Domain;
import org.xbib.netty.http.server.endpoint.service.ClassLoaderService; import org.xbib.netty.http.server.endpoint.service.ClassLoaderService;
import org.xbib.netty.http.server.test.NettyHttpTestExtension;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@ -24,16 +25,14 @@ class ClassloaderServiceTest {
private static final Logger logger = Logger.getLogger(ClassloaderServiceTest.class.getName()); private static final Logger logger = Logger.getLogger(ClassloaderServiceTest.class.getName());
@Test @Test
void testSimpleClassloader() throws Exception { void testClassloaderFileResource() throws Exception {
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008); HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
Domain domain = Domain.builder(httpAddress) Domain domain = Domain.builder(httpAddress)
.singleEndpoint("/classloader", "/**", .singleEndpoint("/classloader", "/**",
new ClassLoaderService(ClassloaderServiceTest.class, "/cl")) new ClassLoaderService(ClassloaderServiceTest.class, "/cl"))
.build(); .build();
Server server = Server.builder(domain) Server server = Server.builder(domain)
.enableDebug()
.build(); .build();
server.logDiagnostics(Level.INFO);
Client client = Client.builder() Client client = Client.builder()
.build(); .build();
int max = 1; int max = 1;
@ -41,12 +40,52 @@ class ClassloaderServiceTest {
try { try {
server.accept(); server.accept();
Request request = Request.get().setVersion(HttpVersion.HTTP_1_1) 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() .build()
.setResponseListener(resp -> { .setResponseListener(resp -> {
if (resp.getStatus().getCode() == HttpResponseStatus.OK.code()) { if (resp.getStatus().getCode() == HttpResponseStatus.OK.code()) {
assertEquals("Hello Jörg", resp.getBodyAsString(StandardCharsets.UTF_8)); assertEquals("Hello Jörg", resp.getBodyAsString(StandardCharsets.UTF_8));
count.incrementAndGet(); 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++) { for (int i = 0; i < max; i++) {

View file

@ -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.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion; 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.Domain;
import org.xbib.netty.http.server.endpoint.service.FileService; import org.xbib.netty.http.server.endpoint.service.FileService;
import org.xbib.netty.http.server.endpoint.service.Service; import org.xbib.netty.http.server.endpoint.service.Service;
import org.xbib.netty.http.server.test.NettyHttpTestExtension;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -41,7 +42,8 @@ class EndpointTest {
HttpEndpointResolver httpEndpointResolver = HttpEndpointResolver.builder() HttpEndpointResolver httpEndpointResolver = HttpEndpointResolver.builder()
.addEndpoint(HttpEndpoint.builder().setPath("/**").build()) .addEndpoint(HttpEndpoint.builder().setPath("/**").build())
.setDispatcher((endpoint, req, resp) -> { .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); service.handle(req, resp);
}) })
.build(); .build();
@ -81,7 +83,8 @@ class EndpointTest {
HttpEndpointResolver httpEndpointResolver = HttpEndpointResolver.builder() HttpEndpointResolver httpEndpointResolver = HttpEndpointResolver.builder()
.addEndpoint(HttpEndpoint.builder().setPrefix("/").setPath("/**").build()) .addEndpoint(HttpEndpoint.builder().setPrefix("/").setPath("/**").build())
.setDispatcher((endpoint, req, resp) -> { .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); service.handle(req, resp);
}) })
.build(); .build();
@ -113,16 +116,82 @@ class EndpointTest {
assertTrue(success.get()); assertTrue(success.get());
} }
@Test @Test
void testSimplePathEndpoints() throws Exception { void testSimplePathEndpoints() throws Exception {
Path vartmp = Paths.get("/var/tmp/"); Path vartmp = Paths.get("/var/tmp/");
Service service = new FileService(vartmp); Service service = new FileService(vartmp);
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008); HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
HttpEndpointResolver httpEndpointResolver = HttpEndpointResolver.builder() HttpEndpointResolver httpEndpointResolver = HttpEndpointResolver.builder()
.addEndpoint(HttpEndpoint.builder().setPrefix("/static").setPath("/**").build())
.addEndpoint(HttpEndpoint.builder().setPrefix("/static1").setPath("/**").build()) .addEndpoint(HttpEndpoint.builder().setPrefix("/static1").setPath("/**").build())
.addEndpoint(HttpEndpoint.builder().setPrefix("/static2").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) -> { .setDispatcher((endpoint, req, resp) -> {
logger.log(Level.FINE, "dispatching endpoint = " + endpoint + " req = " + req); logger.log(Level.FINE, "dispatching endpoint = " + endpoint + " req = " + req);
service.handle(req, resp); service.handle(req, resp);
@ -135,97 +204,18 @@ class EndpointTest {
.build(); .build();
Client client = Client.builder() Client client = Client.builder()
.build(); .build();
final AtomicBoolean success = new AtomicBoolean(false);
final AtomicBoolean success1 = new AtomicBoolean(false); final AtomicBoolean success1 = new AtomicBoolean(false);
final AtomicBoolean success2 = new AtomicBoolean(false); final AtomicBoolean success2 = new AtomicBoolean(false);
final AtomicBoolean success3 = new AtomicBoolean(false);
try { 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("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("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(); 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"))
.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) Request request1 = Request.get().setVersion(HttpVersion.HTTP_1_1)
.url(server.getServerConfig().getAddress().base() .url(server.getServerConfig().getAddress().base()
.resolve("/static1/test1.txt").mutator().fragment("frag").build()) .resolve("/static1/test1.txt"))
.addParameter("a", "b")
.build() .build()
.setResponseListener(resp -> { .setResponseListener(resp -> {
if (resp.getStatus().getCode() == HttpResponseStatus.OK.code()) { if (resp.getStatus().getCode() == HttpResponseStatus.OK.code()) {
@ -239,44 +229,157 @@ class EndpointTest {
Request request2 = Request.get().setVersion(HttpVersion.HTTP_1_1) Request request2 = Request.get().setVersion(HttpVersion.HTTP_1_1)
.url(server.getServerConfig().getAddress().base() .url(server.getServerConfig().getAddress().base()
.resolve("/static2/test2.txt")) .resolve("/static2/test2.txt"))
.content("{\"a\":\"b\"}","application/json")
.build() .build()
.setResponseListener(resp -> { .setResponseListener(resp -> {
if (resp.getStatus().getCode() == HttpResponseStatus.OK.code()) { if (resp.getStatus().getCode() == HttpResponseStatus.OK.code()) {
assertEquals("Hello Jörg 2",resp.getBodyAsString(StandardCharsets.UTF_8)); assertEquals("Hello Jörg 2", resp.getBodyAsString(StandardCharsets.UTF_8));
success2.set(true); success2.set(true);
} else { } else {
logger.log(Level.WARNING, resp.getStatus().getReasonPhrase()); logger.log(Level.WARNING, resp.getStatus().getReasonPhrase());
} }
}); });
client.execute(request2).get(); 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 { } finally {
server.shutdownGracefully(); server.shutdownGracefully();
client.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("test1.txt"));
Files.delete(vartmp.resolve("test2.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(success1.get());
assertTrue(success2.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 @Test
void testMassiveEndpoints() throws IOException { void testMassiveEndpoints() throws IOException {
int max = 1024; int max = 1024; // the default limit, must work
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008); HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
HttpEndpointResolver.Builder endpointResolverBuilder = HttpEndpointResolver.builder() HttpEndpointResolver.Builder endpointResolverBuilder = HttpEndpointResolver.builder()
.setPrefix("/static"); .setPrefix("/static");
for (int i = 0; i < max; i++) { for (int i = 0; i < max; i++) {
endpointResolverBuilder.addEndpoint(HttpEndpoint.builder() endpointResolverBuilder.addEndpoint(HttpEndpoint.builder()
.setPath("/" + i + "/**") .setPath("/" + i + "/**")
.addFilter((req, resp) -> ServerResponse.write(resp, HttpResponseStatus.OK))
.build()); .build());
} }
Domain domain = Domain.builder(httpAddress) Domain domain = Domain.builder(httpAddress)
.addEndpointResolver(endpointResolverBuilder.build()) .addEndpointResolver(endpointResolverBuilder
.setDispatcher((endpoint,req, resp) -> ServerResponse.write(resp, HttpResponseStatus.OK))
.build())
.build(); .build();
Server server = Server.builder(domain) Server server = Server.builder(domain)
.build(); .build();
@ -305,4 +408,45 @@ class EndpointTest {
} }
assertEquals(max, count.get()); 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());
}
} }

View file

@ -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());
}
}

View file

@ -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 io.netty.handler.codec.http.HttpVersion;
import org.junit.jupiter.api.Disabled; 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.Server;
import org.xbib.netty.http.server.Domain; import org.xbib.netty.http.server.Domain;
import org.xbib.netty.http.server.endpoint.service.FileService; 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.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
@ -27,7 +28,6 @@ class SecureFileServiceTest {
private static final Logger logger = Logger.getLogger(SecureFileServiceTest.class.getName()); private static final Logger logger = Logger.getLogger(SecureFileServiceTest.class.getName());
@Disabled
@Test @Test
void testSecureFileServerHttp1() throws Exception { void testSecureFileServerHttp1() throws Exception {
Path vartmp = Paths.get("/var/tmp/"); Path vartmp = Paths.get("/var/tmp/");
@ -39,12 +39,10 @@ class SecureFileServiceTest {
.build()) .build())
.setChildThreadCount(8) .setChildThreadCount(8)
.build(); .build();
//server.logDiagnostics(Level.INFO);
Client client = Client.builder() Client client = Client.builder()
.setJdkSslProvider() .setJdkSslProvider()
.trustInsecure() .trustInsecure()
.build(); .build();
//client.logDiagnostics(Level.INFO);
final AtomicBoolean success = new AtomicBoolean(false); final AtomicBoolean success = new AtomicBoolean(false);
try { try {
Files.write(vartmp.resolve("test.txt"), "Hello Jörg".getBytes(StandardCharsets.UTF_8)); 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)); assertEquals("Hello Jörg", resp.getBodyAsString(StandardCharsets.UTF_8));
success.set(true); success.set(true);
}); });
logger.log(Level.INFO, request.toString());
client.execute(request).get(); client.execute(request).get();
logger.log(Level.INFO, "request complete");
} finally { } finally {
server.shutdownGracefully(); server.shutdownGracefully();
client.shutdownGracefully(); client.shutdownGracefully();
Files.delete(vartmp.resolve("test.txt")); Files.delete(vartmp.resolve("test.txt"));
logger.log(Level.INFO, "server and client shut down");
} }
assertTrue(success.get()); assertTrue(success.get());
} }
@ -105,7 +100,6 @@ class SecureFileServiceTest {
server.shutdownGracefully(); server.shutdownGracefully();
client.shutdownGracefully(); client.shutdownGracefully();
Files.delete(vartmp.resolve("test.txt")); Files.delete(vartmp.resolve("test.txt"));
logger.log(Level.INFO, "server and client shut down");
} }
assertTrue(success.get()); assertTrue(success.get());
} }
@ -134,20 +128,18 @@ class SecureFileServiceTest {
server.accept(); server.accept();
Request request = Request.get() Request request = Request.get()
.setVersion(HttpVersion.HTTP_1_1) .setVersion(HttpVersion.HTTP_1_1)
.url(server.getServerConfig().getAddress().base().resolve("/static/test.txt")) .url(server.getServerConfig().getAddress().base()
.resolve("/static/test.txt"))
.build() .build()
.setResponseListener(resp -> { .setResponseListener(resp -> {
assertEquals("Hello Jörg", resp.getBodyAsString(StandardCharsets.UTF_8)); assertEquals("Hello Jörg", resp.getBodyAsString(StandardCharsets.UTF_8));
success.set(true); success.set(true);
}); });
logger.log(Level.INFO, request.toString());
client.execute(request).get(); client.execute(request).get();
logger.log(Level.INFO, "request complete");
} finally { } finally {
server.shutdownGracefully(); server.shutdownGracefully();
client.shutdownGracefully(); client.shutdownGracefully();
Files.delete(vartmp.resolve("test.txt")); Files.delete(vartmp.resolve("test.txt"));
logger.log(Level.INFO, "server and client shut down");
} }
assertTrue(success.get()); assertTrue(success.get());
} }

View file

@ -19,7 +19,6 @@ import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.LastHttpContent; import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.codec.http.QueryStringDecoder; import io.netty.handler.codec.http.QueryStringDecoder;
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.extension.ExtendWith; 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.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
/** flaky */
@Disabled
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
@ExtendWith(NettyHttpTestExtension.class) @ExtendWith(NettyHttpTestExtension.class)
class HttpPipeliningHandlerTest { class HttpPipeliningHandlerTest {
@ -72,7 +69,7 @@ class HttpPipeliningHandlerTest {
EmbeddedChannel embeddedChannel = new EmbeddedChannel(new HttpPipeliningHandler(10000), EmbeddedChannel embeddedChannel = new EmbeddedChannel(new HttpPipeliningHandler(10000),
handler); handler);
for (int i = 0; i < 5; i++) { for (int i = 0; i < 5; i++) {
embeddedChannel.writeInbound(createHttpRequest("/" + String.valueOf(i))); embeddedChannel.writeInbound(createHttpRequest("/" + i));
} }
for (String url : waitingRequests.keySet()) { for (String url : waitingRequests.keySet()) {
finishRequest(url); finishRequest(url);
@ -90,7 +87,7 @@ class HttpPipeliningHandlerTest {
EmbeddedChannel embeddedChannel = new EmbeddedChannel(new HttpPipeliningHandler(10000), EmbeddedChannel embeddedChannel = new EmbeddedChannel(new HttpPipeliningHandler(10000),
handler); handler);
for (int i = 0; i < 5; i++) { for (int i = 0; i < 5; i++) {
embeddedChannel.writeInbound(createHttpRequest("/" + String.valueOf(i))); embeddedChannel.writeInbound(createHttpRequest("/" + i));
} }
List<String> urls = new ArrayList<>(waitingRequests.keySet()); List<String> urls = new ArrayList<>(waitingRequests.keySet());
Collections.shuffle(urls); Collections.shuffle(urls);