fix resolving endpoints, fix Unpooled.buffer(o) in flush, update to Netty 4.1.41
This commit is contained in:
parent
5c14695785
commit
833e502a7c
28 changed files with 931 additions and 357 deletions
|
@ -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
|
||||||
|
|
|
@ -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 ?
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
package org.xbib.netty.http.common;
|
||||||
|
|
||||||
|
public enum HttpMethod {
|
||||||
|
|
||||||
|
GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package org.xbib.netty.http.server.util;
|
package org.xbib.netty.http.common.net;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The network classes.
|
* The network classes.
|
|
@ -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.
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
}
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -55,5 +55,4 @@ public interface ServerRequest {
|
||||||
InetSocketAddress getLocalAddress();
|
InetSocketAddress getLocalAddress();
|
||||||
|
|
||||||
InetSocketAddress getRemoteAddress();
|
InetSocketAddress getRemoteAddress();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,60 +21,52 @@ 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()) {
|
for (HttpEndpoint endpoint : matchingEndpoints) {
|
||||||
if (defaultEndpoint != null) {
|
endpoint.resolveUriTemplate(serverRequest);
|
||||||
defaultEndpoint.resolveUriTemplate(serverRequest);
|
endpoint.handle(serverRequest, serverResponse);
|
||||||
defaultEndpoint.handle(serverRequest, serverResponse);
|
endpointDispatcher.dispatch(endpoint, serverRequest, serverResponse);
|
||||||
if (endpointDispatcher != null) {
|
if (serverResponse.getStatus() != null) {
|
||||||
endpointDispatcher.dispatch(defaultEndpoint, serverRequest, serverResponse);
|
logger.log(Level.FINEST, () -> "endpoint " + endpoint + " status = " + serverResponse.getStatus());
|
||||||
}
|
break;
|
||||||
} 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -83,17 +75,6 @@ public class HttpEndpointResolver {
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
URLConnection urlConnection = url.openConnection();
|
String path = prefix.endsWith("/") ? prefix : prefix + "/";
|
||||||
this.lastModified = Instant.ofEpochMilli(urlConnection.getLastModified());
|
path = resourcePath.startsWith("/") ? path + resourcePath.substring(1) : path + resourcePath;
|
||||||
this.length = urlConnection.getContentLength();
|
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
|
@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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
this.lastModified = Files.getLastModifiedTime(path).toInstant();
|
boolean isExists = Files.exists(path);
|
||||||
this.length = Files.size(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
|
@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;
|
||||||
|
|
|
@ -12,4 +12,8 @@ public interface Resource {
|
||||||
Instant getLastModified();
|
Instant getLastModified();
|
||||||
|
|
||||||
long getLength();
|
long getLength();
|
||||||
|
|
||||||
|
boolean isDirectory();
|
||||||
|
|
||||||
|
String indexFileName();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,26 +264,46 @@ 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.withStatus(httpResponseStatus)
|
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND);
|
||||||
.withContentType(contentType)
|
} else {
|
||||||
.write(Unpooled.wrappedBuffer(mappedByteBuffer));
|
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,
|
private void send(InputStream inputStream, HttpResponseStatus httpResponseStatus, String contentType,
|
||||||
ServerResponse serverResponse) throws IOException {
|
ServerResponse serverResponse) throws IOException {
|
||||||
try (ReadableByteChannel channel = Channels.newChannel(inputStream)) {
|
if (inputStream == null) {
|
||||||
serverResponse.withStatus(httpResponseStatus)
|
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND);
|
||||||
.withContentType(contentType)
|
} else {
|
||||||
.write(new ChunkedNioStream(channel));
|
try (ReadableByteChannel channel = Channels.newChannel(inputStream)) {
|
||||||
|
serverResponse.withStatus(httpResponseStatus)
|
||||||
|
.withContentType(contentType)
|
||||||
|
.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 {
|
||||||
serverResponse.withStatus(httpResponseStatus)
|
if (inputStream == null) {
|
||||||
.withContentType(contentType)
|
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND);
|
||||||
.write(Unpooled.wrappedBuffer(readBuffer(inputStream, offset, size)));
|
} 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 {
|
private static ByteBuffer readBuffer(URL url, long offset, long size) throws IOException, URISyntaxException {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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,9 +138,7 @@ 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)) &&
|
||||||
|
@ -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());
|
||||||
|
|
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
|
|
@ -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++) {
|
|
@ -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,100 +204,21 @@ 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)
|
Request request1 = Request.get().setVersion(HttpVersion.HTTP_1_1)
|
||||||
.url(server.getServerConfig().getAddress().base().resolve("/static1/test1.txt"))
|
.url(server.getServerConfig().getAddress().base()
|
||||||
.build()
|
.resolve("/static1/test1.txt"))
|
||||||
.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")
|
.addParameter("a", "b")
|
||||||
.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));
|
|
||||||
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));
|
assertEquals("Hello Jörg 1", resp.getBodyAsString(StandardCharsets.UTF_8));
|
||||||
success1.set(true);
|
success1.set(true);
|
||||||
} else {
|
} else {
|
||||||
|
@ -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());
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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());
|
||||||
}
|
}
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in a new issue