Netty 4.1.36, introducing endpoints
This commit is contained in:
parent
7f14fce6fd
commit
09d82f576e
61 changed files with 2686 additions and 2310 deletions
28
build.gradle
28
build.gradle
|
@ -2,7 +2,7 @@ import java.time.ZonedDateTime
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id "com.github.spotbugs" version "1.7.1"
|
id "com.github.spotbugs" version "2.0.0"
|
||||||
id "org.sonarqube" version "2.6.1"
|
id "org.sonarqube" version "2.6.1"
|
||||||
id "io.codearte.nexus-staging" version "0.11.0"
|
id "io.codearte.nexus-staging" version "0.11.0"
|
||||||
id "org.xbib.gradle.plugin.asciidoctor" version "1.5.6.0.1"
|
id "org.xbib.gradle.plugin.asciidoctor" version "1.5.6.0.1"
|
||||||
|
@ -33,7 +33,6 @@ subprojects {
|
||||||
configurations {
|
configurations {
|
||||||
alpnagent
|
alpnagent
|
||||||
asciidoclet
|
asciidoclet
|
||||||
wagon
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
@ -42,7 +41,6 @@ subprojects {
|
||||||
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${project.property('junit.version')}"
|
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${project.property('junit.version')}"
|
||||||
alpnagent "org.mortbay.jetty.alpn:jetty-alpn-agent:${project.property('alpnagent.version')}"
|
alpnagent "org.mortbay.jetty.alpn:jetty-alpn-agent:${project.property('alpnagent.version')}"
|
||||||
asciidoclet "org.asciidoctor:asciidoclet:${project.property('asciidoclet.version')}"
|
asciidoclet "org.asciidoctor:asciidoclet:${project.property('asciidoclet.version')}"
|
||||||
wagon "org.apache.maven.wagon:wagon-ssh:${project.property('wagon.version')}"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
compileJava {
|
compileJava {
|
||||||
|
@ -148,22 +146,6 @@ subprojects {
|
||||||
scmDeveloperConnection = 'scm:git:git://github.com/' + user + '/' + name + '.git'
|
scmDeveloperConnection = 'scm:git:git://github.com/' + user + '/' + name + '.git'
|
||||||
}
|
}
|
||||||
|
|
||||||
/*task xbibUpload(type: Upload) {
|
|
||||||
group = 'publish'
|
|
||||||
configuration = configurations.archives
|
|
||||||
uploadDescriptor = true
|
|
||||||
repositories {
|
|
||||||
if (project.hasProperty("xbibUsername")) {
|
|
||||||
mavenDeployer {
|
|
||||||
configuration = configurations.wagon
|
|
||||||
repository(url: 'sftp://xbib.org/repository') {
|
|
||||||
authentication(userName: xbibUsername, privateKey: xbibPrivateKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
task sonaTypeUpload(type: Upload) {
|
task sonaTypeUpload(type: Upload) {
|
||||||
group = 'publish'
|
group = 'publish'
|
||||||
configuration = configurations.archives
|
configuration = configurations.archives
|
||||||
|
@ -225,14 +207,6 @@ subprojects {
|
||||||
// includeFilter = file("config/findbugs/findbugs-include.xml")
|
// includeFilter = file("config/findbugs/findbugs-include.xml")
|
||||||
// excludeFilter = file("config/findbugs/findbugs-excludes.xml")
|
// excludeFilter = file("config/findbugs/findbugs-excludes.xml")
|
||||||
}
|
}
|
||||||
spotbugsMain.reports {
|
|
||||||
xml.enabled = false
|
|
||||||
html.enabled = true
|
|
||||||
}
|
|
||||||
spotbugsTest.reports {
|
|
||||||
xml.enabled = false
|
|
||||||
html.enabled = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sonarqube {
|
sonarqube {
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
group = org.xbib
|
group = org.xbib
|
||||||
name = netty-http
|
name = netty-http
|
||||||
version = 4.1.35.2
|
version = 4.1.36.2
|
||||||
|
|
||||||
# main packages
|
# main packages
|
||||||
netty.version = 4.1.35.Final
|
netty.version = 4.1.36.Final
|
||||||
tcnative.version = 2.0.22.Final
|
tcnative.version = 2.0.25.Final
|
||||||
alpnagent.version = 2.0.9
|
alpnagent.version = 2.0.9
|
||||||
|
|
||||||
# common
|
# common
|
||||||
xbib-net-url.version = 1.2.2
|
xbib-net-url.version = 1.3.1
|
||||||
|
|
||||||
# server
|
# server
|
||||||
bouncycastle.version = 1.61
|
bouncycastle.version = 1.61
|
||||||
|
@ -21,7 +21,6 @@ xbib-guice.version = 4.0.4
|
||||||
junit.version = 5.4.2
|
junit.version = 5.4.2
|
||||||
conscrypt.version = 2.0.0
|
conscrypt.version = 2.0.0
|
||||||
jackson.version = 2.8.11.1
|
jackson.version = 2.8.11.1
|
||||||
wagon.version = 3.0.0
|
|
||||||
asciidoclet.version = 1.5.4
|
asciidoclet.version = 1.5.4
|
||||||
|
|
||||||
org.gradle.warning.mode = all
|
org.gradle.warning.mode = all
|
||||||
|
|
|
@ -6,6 +6,7 @@ import io.netty.channel.Channel;
|
||||||
import io.netty.channel.ChannelInitializer;
|
import io.netty.channel.ChannelInitializer;
|
||||||
import io.netty.channel.ChannelOption;
|
import io.netty.channel.ChannelOption;
|
||||||
import io.netty.channel.EventLoopGroup;
|
import io.netty.channel.EventLoopGroup;
|
||||||
|
import io.netty.channel.WriteBufferWaterMark;
|
||||||
import io.netty.channel.epoll.EpollEventLoopGroup;
|
import io.netty.channel.epoll.EpollEventLoopGroup;
|
||||||
import io.netty.channel.epoll.EpollSocketChannel;
|
import io.netty.channel.epoll.EpollSocketChannel;
|
||||||
import io.netty.channel.nio.NioEventLoopGroup;
|
import io.netty.channel.nio.NioEventLoopGroup;
|
||||||
|
@ -14,13 +15,17 @@ import io.netty.channel.socket.SocketChannel;
|
||||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||||
import io.netty.handler.codec.http.FullHttpResponse;
|
import io.netty.handler.codec.http.FullHttpResponse;
|
||||||
import io.netty.handler.codec.http.HttpVersion;
|
import io.netty.handler.codec.http.HttpVersion;
|
||||||
import io.netty.handler.codec.http2.Http2SecurityUtil;
|
import io.netty.handler.codec.http2.Http2Settings;
|
||||||
|
import io.netty.handler.proxy.HttpProxyHandler;
|
||||||
import io.netty.handler.ssl.ApplicationProtocolConfig;
|
import io.netty.handler.ssl.ApplicationProtocolConfig;
|
||||||
import io.netty.handler.ssl.ApplicationProtocolNames;
|
import io.netty.handler.ssl.ApplicationProtocolNames;
|
||||||
|
import io.netty.handler.ssl.CipherSuiteFilter;
|
||||||
import io.netty.handler.ssl.OpenSsl;
|
import io.netty.handler.ssl.OpenSsl;
|
||||||
import io.netty.handler.ssl.SslContext;
|
import io.netty.handler.ssl.SslContext;
|
||||||
import io.netty.handler.ssl.SslContextBuilder;
|
import io.netty.handler.ssl.SslContextBuilder;
|
||||||
import io.netty.handler.ssl.SslHandler;
|
import io.netty.handler.ssl.SslHandler;
|
||||||
|
import io.netty.handler.ssl.SslProvider;
|
||||||
|
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
|
||||||
import org.xbib.netty.http.client.handler.http.HttpChannelInitializer;
|
import org.xbib.netty.http.client.handler.http.HttpChannelInitializer;
|
||||||
import org.xbib.netty.http.client.handler.http2.Http2ChannelInitializer;
|
import org.xbib.netty.http.client.handler.http2.Http2ChannelInitializer;
|
||||||
import org.xbib.netty.http.client.pool.BoundedChannelPool;
|
import org.xbib.netty.http.client.pool.BoundedChannelPool;
|
||||||
|
@ -29,6 +34,7 @@ import org.xbib.netty.http.client.transport.HttpTransport;
|
||||||
import org.xbib.netty.http.client.transport.Transport;
|
import org.xbib.netty.http.client.transport.Transport;
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
import org.xbib.netty.http.common.HttpAddress;
|
||||||
import org.xbib.netty.http.common.NetworkUtils;
|
import org.xbib.netty.http.common.NetworkUtils;
|
||||||
|
import org.xbib.netty.http.common.SecurityUtil;
|
||||||
|
|
||||||
import javax.net.ssl.SNIHostName;
|
import javax.net.ssl.SNIHostName;
|
||||||
import javax.net.ssl.SNIServerName;
|
import javax.net.ssl.SNIServerName;
|
||||||
|
@ -37,14 +43,17 @@ import javax.net.ssl.SSLException;
|
||||||
import javax.net.ssl.SSLParameters;
|
import javax.net.ssl.SSLParameters;
|
||||||
import javax.net.ssl.TrustManagerFactory;
|
import javax.net.ssl.TrustManagerFactory;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.security.KeyStoreException;
|
import java.security.KeyStoreException;
|
||||||
|
import java.security.Provider;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Queue;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
import java.util.concurrent.Semaphore;
|
import java.util.concurrent.Semaphore;
|
||||||
import java.util.concurrent.ThreadFactory;
|
import java.util.concurrent.ThreadFactory;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
@ -81,7 +90,7 @@ public final class Client {
|
||||||
|
|
||||||
private final Bootstrap bootstrap;
|
private final Bootstrap bootstrap;
|
||||||
|
|
||||||
private final List<Transport> transports;
|
private final Queue<Transport> transports;
|
||||||
|
|
||||||
private BoundedChannelPool<HttpAddress> pool;
|
private BoundedChannelPool<HttpAddress> pool;
|
||||||
|
|
||||||
|
@ -116,7 +125,7 @@ public final class Client {
|
||||||
.option(ChannelOption.SO_RCVBUF, clientConfig.getTcpReceiveBufferSize())
|
.option(ChannelOption.SO_RCVBUF, clientConfig.getTcpReceiveBufferSize())
|
||||||
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, clientConfig.getConnectTimeoutMillis())
|
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, clientConfig.getConnectTimeoutMillis())
|
||||||
.option(ChannelOption.WRITE_BUFFER_WATER_MARK, clientConfig.getWriteBufferWaterMark());
|
.option(ChannelOption.WRITE_BUFFER_WATER_MARK, clientConfig.getWriteBufferWaterMark());
|
||||||
this.transports = new CopyOnWriteArrayList<>();
|
this.transports = new ConcurrentLinkedQueue<>();
|
||||||
if (!clientConfig.getPoolNodes().isEmpty()) {
|
if (!clientConfig.getPoolNodes().isEmpty()) {
|
||||||
List<HttpAddress> nodes = clientConfig.getPoolNodes();
|
List<HttpAddress> nodes = clientConfig.getPoolNodes();
|
||||||
Integer limit = clientConfig.getPoolNodeConnectionLimit();
|
Integer limit = clientConfig.getPoolNodeConnectionLimit();
|
||||||
|
@ -161,12 +170,15 @@ public final class Client {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void logDiagnostics(Level level) {
|
public void logDiagnostics(Level level) {
|
||||||
logger.log(level, () -> "OpenSSL available: " + OpenSsl.isAvailable() +
|
logger.log(level, () -> "JDK ciphers: " + SecurityUtil.Defaults.JDK_CIPHERS);
|
||||||
" OpenSSL ALPN support: " + OpenSsl.isAlpnSupported() +
|
logger.log(level, () -> "OpenSSL ciphers: " + SecurityUtil.Defaults.OPENSSL_CIPHERS);
|
||||||
" Local host name: " + NetworkUtils.getLocalHostName("localhost") +
|
logger.log(level, () -> "OpenSSL available: " + OpenSsl.isAvailable());
|
||||||
" event loop group: " + eventLoopGroup +
|
logger.log(level, () -> "OpenSSL ALPN support: " + OpenSsl.isAlpnSupported());
|
||||||
" socket: " + socketChannelClass.getName() +
|
logger.log(level, () -> "Candidate ciphers on client: " + clientConfig.getCiphers());
|
||||||
" allocator: " + byteBufAllocator.getClass().getName());
|
logger.log(level, () -> "Local host name: " + NetworkUtils.getLocalHostName("localhost"));
|
||||||
|
logger.log(level, () -> "Event loop group: " + eventLoopGroup + " threads=" + clientConfig.getThreadCount());
|
||||||
|
logger.log(level, () -> "Socket: " + socketChannelClass.getName());
|
||||||
|
logger.log(level, () -> "Allocator: " + byteBufAllocator.getClass().getName());
|
||||||
logger.log(level, NetworkUtils::displayNetworkInterfaces);
|
logger.log(level, NetworkUtils::displayNetworkInterfaces);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -243,9 +255,8 @@ public final class Client {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Transport execute(Request request) throws IOException {
|
public Transport execute(Request request) throws IOException {
|
||||||
Transport transport = newTransport(HttpAddress.of(request.url(), request.httpVersion()));
|
return newTransport(HttpAddress.of(request.url(), request.httpVersion()))
|
||||||
transport.execute(request);
|
.execute(request);
|
||||||
return transport;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T> CompletableFuture<T> execute(Request request,
|
public <T> CompletableFuture<T> execute(Request request,
|
||||||
|
@ -328,6 +339,7 @@ public final class Client {
|
||||||
private static SslHandler newSslHandler(ClientConfig clientConfig, ByteBufAllocator allocator, HttpAddress httpAddress) {
|
private static SslHandler newSslHandler(ClientConfig clientConfig, ByteBufAllocator allocator, HttpAddress httpAddress) {
|
||||||
try {
|
try {
|
||||||
SslContext sslContext = newSslContext(clientConfig, httpAddress.getVersion());
|
SslContext sslContext = newSslContext(clientConfig, httpAddress.getVersion());
|
||||||
|
logger.log(Level.FINE, () -> "installed ciphers: " + sslContext.cipherSuites());
|
||||||
InetSocketAddress peer = httpAddress.getInetSocketAddress();
|
InetSocketAddress peer = httpAddress.getInetSocketAddress();
|
||||||
SslHandler sslHandler = sslContext.newHandler(allocator, peer.getHostName(), peer.getPort());
|
SslHandler sslHandler = sslContext.newHandler(allocator, peer.getHostName(), peer.getPort());
|
||||||
SSLEngine engine = sslHandler.engine();
|
SSLEngine engine = sslHandler.engine();
|
||||||
|
@ -363,7 +375,7 @@ public final class Client {
|
||||||
private static SslContext newSslContext(ClientConfig clientConfig, HttpVersion httpVersion) throws SSLException {
|
private static SslContext newSslContext(ClientConfig clientConfig, HttpVersion httpVersion) throws SSLException {
|
||||||
SslContextBuilder sslContextBuilder = SslContextBuilder.forClient()
|
SslContextBuilder sslContextBuilder = SslContextBuilder.forClient()
|
||||||
.sslProvider(clientConfig.getSslProvider())
|
.sslProvider(clientConfig.getSslProvider())
|
||||||
.ciphers(Http2SecurityUtil.CIPHERS, clientConfig.getCipherSuiteFilter())
|
.ciphers(clientConfig.getCiphers(), clientConfig.getCipherSuiteFilter())
|
||||||
.applicationProtocolConfig(newApplicationProtocolConfig(httpVersion));
|
.applicationProtocolConfig(newApplicationProtocolConfig(httpVersion));
|
||||||
if (clientConfig.getSslContextProvider() != null) {
|
if (clientConfig.getSslContextProvider() != null) {
|
||||||
sslContextBuilder.sslContextProvider(clientConfig.getSslContextProvider());
|
sslContextBuilder.sslContextProvider(clientConfig.getSslContextProvider());
|
||||||
|
@ -442,4 +454,223 @@ public final class Client {
|
||||||
return newSslHandler(clientConfig, allocator, httpAddress);
|
return newSslHandler(clientConfig, allocator, httpAddress);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class ClientBuilder {
|
||||||
|
|
||||||
|
private ByteBufAllocator byteBufAllocator;
|
||||||
|
|
||||||
|
private EventLoopGroup eventLoopGroup;
|
||||||
|
|
||||||
|
private Class<? extends SocketChannel> socketChannelClass;
|
||||||
|
|
||||||
|
private ClientConfig clientConfig;
|
||||||
|
|
||||||
|
private ClientBuilder() {
|
||||||
|
this.clientConfig = new ClientConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientBuilder enableDebug() {
|
||||||
|
clientConfig.enableDebug();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientBuilder disableDebug() {
|
||||||
|
clientConfig.disableDebug();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set byte buf allocator for payload in HTTP requests.
|
||||||
|
* @param byteBufAllocator the byte buf allocator
|
||||||
|
* @return this builder
|
||||||
|
*/
|
||||||
|
public ClientBuilder setByteBufAllocator(ByteBufAllocator byteBufAllocator) {
|
||||||
|
this.byteBufAllocator = byteBufAllocator;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientBuilder setEventLoop(EventLoopGroup eventLoopGroup) {
|
||||||
|
this.eventLoopGroup = eventLoopGroup;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientBuilder setChannelClass(Class<SocketChannel> socketChannelClass) {
|
||||||
|
this.socketChannelClass = socketChannelClass;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientBuilder setThreadCount(int threadCount) {
|
||||||
|
clientConfig.setThreadCount(threadCount);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientBuilder setConnectTimeoutMillis(int connectTimeoutMillis) {
|
||||||
|
clientConfig.setConnectTimeoutMillis(connectTimeoutMillis);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientBuilder setTcpSendBufferSize(int tcpSendBufferSize) {
|
||||||
|
clientConfig.setTcpSendBufferSize(tcpSendBufferSize);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientBuilder setTcpReceiveBufferSize(int tcpReceiveBufferSize) {
|
||||||
|
clientConfig.setTcpReceiveBufferSize(tcpReceiveBufferSize);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientBuilder setTcpNodelay(boolean tcpNodelay) {
|
||||||
|
clientConfig.setTcpNodelay(tcpNodelay);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientBuilder setKeepAlive(boolean keepAlive) {
|
||||||
|
clientConfig.setKeepAlive(keepAlive);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientBuilder setReuseAddr(boolean reuseAddr) {
|
||||||
|
clientConfig.setReuseAddr(reuseAddr);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientBuilder setMaxChunkSize(int maxChunkSize) {
|
||||||
|
clientConfig.setMaxChunkSize(maxChunkSize);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientBuilder setMaxInitialLineLength(int maxInitialLineLength) {
|
||||||
|
clientConfig.setMaxInitialLineLength(maxInitialLineLength);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientBuilder setMaxHeadersSize(int maxHeadersSize) {
|
||||||
|
clientConfig.setMaxHeadersSize(maxHeadersSize);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientBuilder setMaxContentLength(int maxContentLength) {
|
||||||
|
clientConfig.setMaxContentLength(maxContentLength);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientBuilder setMaxCompositeBufferComponents(int maxCompositeBufferComponents) {
|
||||||
|
clientConfig.setMaxCompositeBufferComponents(maxCompositeBufferComponents);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientBuilder setReadTimeoutMillis(int readTimeoutMillis) {
|
||||||
|
clientConfig.setReadTimeoutMillis(readTimeoutMillis);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientBuilder setEnableGzip(boolean enableGzip) {
|
||||||
|
clientConfig.setEnableGzip(enableGzip);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientBuilder setSslProvider(SslProvider sslProvider) {
|
||||||
|
clientConfig.setSslProvider(sslProvider);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientBuilder setJdkSslProvider() {
|
||||||
|
clientConfig.setJdkSslProvider();
|
||||||
|
clientConfig.setCiphers(SecurityUtil.Defaults.JDK_CIPHERS);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientBuilder setOpenSSLSslProvider() {
|
||||||
|
clientConfig.setOpenSSLSslProvider();
|
||||||
|
clientConfig.setCiphers(SecurityUtil.Defaults.OPENSSL_CIPHERS);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientBuilder setSslContextProvider(Provider provider) {
|
||||||
|
clientConfig.setSslContextProvider(provider);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientBuilder setCiphers(Iterable<String> ciphers) {
|
||||||
|
clientConfig.setCiphers(ciphers);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientBuilder setCipherSuiteFilter(CipherSuiteFilter cipherSuiteFilter) {
|
||||||
|
clientConfig.setCipherSuiteFilter(cipherSuiteFilter);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientBuilder setKeyCert(InputStream keyCertChainInputStream, InputStream keyInputStream) {
|
||||||
|
clientConfig.setKeyCert(keyCertChainInputStream, keyInputStream);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientBuilder setKeyCert(InputStream keyCertChainInputStream, InputStream keyInputStream,
|
||||||
|
String keyPassword) {
|
||||||
|
clientConfig.setKeyCert(keyCertChainInputStream, keyInputStream, keyPassword);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientBuilder setTrustManagerFactory(TrustManagerFactory trustManagerFactory) {
|
||||||
|
clientConfig.setTrustManagerFactory(trustManagerFactory);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientBuilder trustInsecure() {
|
||||||
|
clientConfig.setTrustManagerFactory(InsecureTrustManagerFactory.INSTANCE);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientBuilder setClientAuthMode(ClientAuthMode clientAuthMode) {
|
||||||
|
clientConfig.setClientAuthMode(clientAuthMode);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientBuilder setHttpProxyHandler(HttpProxyHandler httpProxyHandler) {
|
||||||
|
clientConfig.setHttpProxyHandler(httpProxyHandler);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientBuilder addPoolNode(HttpAddress httpAddress) {
|
||||||
|
clientConfig.addPoolNode(httpAddress);
|
||||||
|
clientConfig.setPoolVersion(httpAddress.getVersion());
|
||||||
|
clientConfig.setPoolSecure(httpAddress.isSecure());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientBuilder setPoolNodeConnectionLimit(int nodeConnectionLimit) {
|
||||||
|
clientConfig.setPoolNodeConnectionLimit(nodeConnectionLimit);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientBuilder setRetriesPerPoolNode(int retriesPerNode) {
|
||||||
|
clientConfig.setRetriesPerPoolNode(retriesPerNode);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientBuilder addServerNameForIdentification(String serverName) {
|
||||||
|
clientConfig.addServerNameForIdentification(serverName);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientBuilder setHttp2Settings(Http2Settings http2Settings) {
|
||||||
|
clientConfig.setHttp2Settings(http2Settings);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientBuilder setWriteBufferWaterMark(WriteBufferWaterMark writeBufferWaterMark) {
|
||||||
|
clientConfig.setWriteBufferWaterMark(writeBufferWaterMark);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientBuilder enableNegotiation(boolean enableNegotiation) {
|
||||||
|
clientConfig.setEnableNegotiation(enableNegotiation);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Client build() {
|
||||||
|
return new Client(clientConfig, byteBufAllocator, eventLoopGroup, socketChannelClass);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,233 +0,0 @@
|
||||||
package org.xbib.netty.http.client;
|
|
||||||
|
|
||||||
import io.netty.buffer.ByteBufAllocator;
|
|
||||||
import io.netty.channel.EventLoopGroup;
|
|
||||||
import io.netty.channel.WriteBufferWaterMark;
|
|
||||||
import io.netty.channel.socket.SocketChannel;
|
|
||||||
import io.netty.handler.codec.http2.Http2Settings;
|
|
||||||
import io.netty.handler.proxy.HttpProxyHandler;
|
|
||||||
import io.netty.handler.ssl.CipherSuiteFilter;
|
|
||||||
import io.netty.handler.ssl.SslProvider;
|
|
||||||
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
|
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
|
||||||
|
|
||||||
import javax.net.ssl.TrustManagerFactory;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.security.Provider;
|
|
||||||
|
|
||||||
public class ClientBuilder {
|
|
||||||
|
|
||||||
private ByteBufAllocator byteBufAllocator;
|
|
||||||
|
|
||||||
private EventLoopGroup eventLoopGroup;
|
|
||||||
|
|
||||||
private Class<? extends SocketChannel> socketChannelClass;
|
|
||||||
|
|
||||||
private ClientConfig clientConfig;
|
|
||||||
|
|
||||||
public ClientBuilder() {
|
|
||||||
this.clientConfig = new ClientConfig();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientBuilder enableDebug() {
|
|
||||||
clientConfig.enableDebug();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientBuilder disableDebug() {
|
|
||||||
clientConfig.disableDebug();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set byte buf allocator for payload in HTTP requests.
|
|
||||||
* @param byteBufAllocator the byte buf allocator
|
|
||||||
* @return this builder
|
|
||||||
*/
|
|
||||||
public ClientBuilder setByteBufAllocator(ByteBufAllocator byteBufAllocator) {
|
|
||||||
this.byteBufAllocator = byteBufAllocator;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientBuilder setEventLoop(EventLoopGroup eventLoopGroup) {
|
|
||||||
this.eventLoopGroup = eventLoopGroup;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientBuilder setChannelClass(Class<SocketChannel> socketChannelClass) {
|
|
||||||
this.socketChannelClass = socketChannelClass;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientBuilder setThreadCount(int threadCount) {
|
|
||||||
clientConfig.setThreadCount(threadCount);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientBuilder setConnectTimeoutMillis(int connectTimeoutMillis) {
|
|
||||||
clientConfig.setConnectTimeoutMillis(connectTimeoutMillis);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientBuilder setTcpSendBufferSize(int tcpSendBufferSize) {
|
|
||||||
clientConfig.setTcpSendBufferSize(tcpSendBufferSize);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientBuilder setTcpReceiveBufferSize(int tcpReceiveBufferSize) {
|
|
||||||
clientConfig.setTcpReceiveBufferSize(tcpReceiveBufferSize);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientBuilder setTcpNodelay(boolean tcpNodelay) {
|
|
||||||
clientConfig.setTcpNodelay(tcpNodelay);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientBuilder setKeepAlive(boolean keepAlive) {
|
|
||||||
clientConfig.setKeepAlive(keepAlive);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientBuilder setReuseAddr(boolean reuseAddr) {
|
|
||||||
clientConfig.setReuseAddr(reuseAddr);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientBuilder setMaxChunkSize(int maxChunkSize) {
|
|
||||||
clientConfig.setMaxChunkSize(maxChunkSize);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientBuilder setMaxInitialLineLength(int maxInitialLineLength) {
|
|
||||||
clientConfig.setMaxInitialLineLength(maxInitialLineLength);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientBuilder setMaxHeadersSize(int maxHeadersSize) {
|
|
||||||
clientConfig.setMaxHeadersSize(maxHeadersSize);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientBuilder setMaxContentLength(int maxContentLength) {
|
|
||||||
clientConfig.setMaxContentLength(maxContentLength);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientBuilder setMaxCompositeBufferComponents(int maxCompositeBufferComponents) {
|
|
||||||
clientConfig.setMaxCompositeBufferComponents(maxCompositeBufferComponents);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientBuilder setReadTimeoutMillis(int readTimeoutMillis) {
|
|
||||||
clientConfig.setReadTimeoutMillis(readTimeoutMillis);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientBuilder setEnableGzip(boolean enableGzip) {
|
|
||||||
clientConfig.setEnableGzip(enableGzip);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientBuilder setSslProvider(SslProvider sslProvider) {
|
|
||||||
clientConfig.setSslProvider(sslProvider);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientBuilder setJdkSslProvider() {
|
|
||||||
clientConfig.setJdkSslProvider();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientBuilder setOpenSSLSslProvider() {
|
|
||||||
clientConfig.setOpenSSLSslProvider();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientBuilder setSslContextProvider(Provider provider) {
|
|
||||||
clientConfig.setSslContextProvider(provider);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientBuilder setCiphers(Iterable<String> ciphers) {
|
|
||||||
clientConfig.setCiphers(ciphers);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientBuilder setCipherSuiteFilter(CipherSuiteFilter cipherSuiteFilter) {
|
|
||||||
clientConfig.setCipherSuiteFilter(cipherSuiteFilter);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientBuilder setKeyCert(InputStream keyCertChainInputStream, InputStream keyInputStream) {
|
|
||||||
clientConfig.setKeyCert(keyCertChainInputStream, keyInputStream);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientBuilder setKeyCert(InputStream keyCertChainInputStream, InputStream keyInputStream,
|
|
||||||
String keyPassword) {
|
|
||||||
clientConfig.setKeyCert(keyCertChainInputStream, keyInputStream, keyPassword);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientBuilder setTrustManagerFactory(TrustManagerFactory trustManagerFactory) {
|
|
||||||
clientConfig.setTrustManagerFactory(trustManagerFactory);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientBuilder trustInsecure() {
|
|
||||||
clientConfig.setTrustManagerFactory(InsecureTrustManagerFactory.INSTANCE);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientBuilder setClientAuthMode(ClientAuthMode clientAuthMode) {
|
|
||||||
clientConfig.setClientAuthMode(clientAuthMode);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientBuilder setHttpProxyHandler(HttpProxyHandler httpProxyHandler) {
|
|
||||||
clientConfig.setHttpProxyHandler(httpProxyHandler);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientBuilder addPoolNode(HttpAddress httpAddress) {
|
|
||||||
clientConfig.addPoolNode(httpAddress);
|
|
||||||
clientConfig.setPoolVersion(httpAddress.getVersion());
|
|
||||||
clientConfig.setPoolSecure(httpAddress.isSecure());
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientBuilder setPoolNodeConnectionLimit(int nodeConnectionLimit) {
|
|
||||||
clientConfig.setPoolNodeConnectionLimit(nodeConnectionLimit);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientBuilder setRetriesPerPoolNode(int retriesPerNode) {
|
|
||||||
clientConfig.setRetriesPerPoolNode(retriesPerNode);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientBuilder addServerNameForIdentification(String serverName) {
|
|
||||||
clientConfig.addServerNameForIdentification(serverName);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientBuilder setHttp2Settings(Http2Settings http2Settings) {
|
|
||||||
clientConfig.setHttp2Settings(http2Settings);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientBuilder setWriteBufferWaterMark(WriteBufferWaterMark writeBufferWaterMark) {
|
|
||||||
clientConfig.setWriteBufferWaterMark(writeBufferWaterMark);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientBuilder enableNegotiation(boolean enableNegotiation) {
|
|
||||||
clientConfig.setEnableNegotiation(enableNegotiation);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Client build() {
|
|
||||||
return new Client(clientConfig, byteBufAllocator, eventLoopGroup, socketChannelClass);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,15 +3,14 @@ package org.xbib.netty.http.client;
|
||||||
import io.netty.channel.WriteBufferWaterMark;
|
import io.netty.channel.WriteBufferWaterMark;
|
||||||
import io.netty.channel.epoll.Epoll;
|
import io.netty.channel.epoll.Epoll;
|
||||||
import io.netty.handler.codec.http.HttpVersion;
|
import io.netty.handler.codec.http.HttpVersion;
|
||||||
import io.netty.handler.codec.http2.Http2SecurityUtil;
|
|
||||||
import io.netty.handler.codec.http2.Http2Settings;
|
import io.netty.handler.codec.http2.Http2Settings;
|
||||||
import io.netty.handler.logging.LogLevel;
|
import io.netty.handler.logging.LogLevel;
|
||||||
import io.netty.handler.proxy.HttpProxyHandler;
|
import io.netty.handler.proxy.HttpProxyHandler;
|
||||||
import io.netty.handler.ssl.CipherSuiteFilter;
|
import io.netty.handler.ssl.CipherSuiteFilter;
|
||||||
import io.netty.handler.ssl.SslProvider;
|
import io.netty.handler.ssl.SslProvider;
|
||||||
import io.netty.handler.ssl.SupportedCipherSuiteFilter;
|
|
||||||
import org.xbib.netty.http.client.retry.BackOff;
|
import org.xbib.netty.http.client.retry.BackOff;
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
import org.xbib.netty.http.common.HttpAddress;
|
||||||
|
import org.xbib.netty.http.common.SecurityUtil;
|
||||||
|
|
||||||
import javax.net.ssl.TrustManagerFactory;
|
import javax.net.ssl.TrustManagerFactory;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
@ -95,7 +94,7 @@ public class ClientConfig {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is Netty's default.
|
* This is Netty's default.
|
||||||
* See {@link io.netty.handler.codec.MessageAggregator#DEFAULT_MAX_COMPOSITEBUFFER_COMPONENTS}.
|
* See {@link io.netty.handler.codec.MessageAggregator}.
|
||||||
*/
|
*/
|
||||||
int MAX_COMPOSITE_BUFFER_COMPONENTS = 1024;
|
int MAX_COMPOSITE_BUFFER_COMPONENTS = 1024;
|
||||||
|
|
||||||
|
@ -112,7 +111,7 @@ public class ClientConfig {
|
||||||
/**
|
/**
|
||||||
* Default SSL provider.
|
* Default SSL provider.
|
||||||
*/
|
*/
|
||||||
SslProvider SSL_PROVIDER = SslProvider.JDK;
|
SslProvider SSL_PROVIDER = SecurityUtil.Defaults.DEFAULT_SSL_PROVIDER;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default SSL context provider (for JDK SSL only).
|
* Default SSL context provider (for JDK SSL only).
|
||||||
|
@ -122,12 +121,12 @@ public class ClientConfig {
|
||||||
/**
|
/**
|
||||||
* Default ciphers. We care about HTTP/2.
|
* Default ciphers. We care about HTTP/2.
|
||||||
*/
|
*/
|
||||||
Iterable<String> CIPHERS = Http2SecurityUtil.CIPHERS;
|
Iterable<String> CIPHERS = SecurityUtil.Defaults.DEFAULT_CIPHERS;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default cipher suite filter.
|
* Default cipher suite filter.
|
||||||
*/
|
*/
|
||||||
CipherSuiteFilter CIPHER_SUITE_FILTER = SupportedCipherSuiteFilter.INSTANCE;
|
CipherSuiteFilter CIPHER_SUITE_FILTER = SecurityUtil.Defaults.DEFAULT_CIPHER_SUITE_FILTER;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default for SSL client authentication.
|
* Default for SSL client authentication.
|
||||||
|
@ -167,16 +166,6 @@ public class ClientConfig {
|
||||||
Boolean ENABLE_NEGOTIATION = false;
|
Boolean ENABLE_NEGOTIATION = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TrustManagerFactory TRUST_MANAGER_FACTORY;
|
|
||||||
|
|
||||||
static {
|
|
||||||
try {
|
|
||||||
TRUST_MANAGER_FACTORY = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
|
||||||
} catch (Exception e) {
|
|
||||||
TRUST_MANAGER_FACTORY = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean debug = Defaults.DEBUG;
|
private boolean debug = Defaults.DEBUG;
|
||||||
|
|
||||||
private LogLevel debugLogLevel = Defaults.DEFAULT_DEBUG_LOG_LEVEL;
|
private LogLevel debugLogLevel = Defaults.DEFAULT_DEBUG_LOG_LEVEL;
|
||||||
|
@ -219,7 +208,7 @@ public class ClientConfig {
|
||||||
|
|
||||||
private CipherSuiteFilter cipherSuiteFilter = Defaults.CIPHER_SUITE_FILTER;
|
private CipherSuiteFilter cipherSuiteFilter = Defaults.CIPHER_SUITE_FILTER;
|
||||||
|
|
||||||
private TrustManagerFactory trustManagerFactory = TRUST_MANAGER_FACTORY;
|
private TrustManagerFactory trustManagerFactory = SecurityUtil.Defaults.DEFAULT_TRUST_MANAGER_FACTORY;
|
||||||
|
|
||||||
private KeyStore trustManagerKeyStore = null;
|
private KeyStore trustManagerKeyStore = null;
|
||||||
|
|
||||||
|
@ -430,6 +419,24 @@ public class ClientConfig {
|
||||||
return http2Settings;
|
return http2Settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ClientConfig setTrustManagerFactory(TrustManagerFactory trustManagerFactory) {
|
||||||
|
this.trustManagerFactory = trustManagerFactory;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TrustManagerFactory getTrustManagerFactory() {
|
||||||
|
return trustManagerFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientConfig setTrustManagerKeyStore(KeyStore trustManagerKeyStore) {
|
||||||
|
this.trustManagerKeyStore = trustManagerKeyStore;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeyStore getTrustManagerKeyStore() {
|
||||||
|
return trustManagerKeyStore;
|
||||||
|
}
|
||||||
|
|
||||||
public ClientConfig setSslProvider(SslProvider sslProvider) {
|
public ClientConfig setSslProvider(SslProvider sslProvider) {
|
||||||
this.sslProvider = sslProvider;
|
this.sslProvider = sslProvider;
|
||||||
return this;
|
return this;
|
||||||
|
@ -511,24 +518,6 @@ public class ClientConfig {
|
||||||
return clientAuthMode;
|
return clientAuthMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ClientConfig setTrustManagerFactory(TrustManagerFactory trustManagerFactory) {
|
|
||||||
this.trustManagerFactory = trustManagerFactory;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TrustManagerFactory getTrustManagerFactory() {
|
|
||||||
return trustManagerFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientConfig setTrustManagerKeyStore(KeyStore trustManagerKeyStore) {
|
|
||||||
this.trustManagerKeyStore = trustManagerKeyStore;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public KeyStore getTrustManagerKeyStore() {
|
|
||||||
return trustManagerKeyStore;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientConfig setHttpProxyHandler(HttpProxyHandler httpProxyHandler) {
|
public ClientConfig setHttpProxyHandler(HttpProxyHandler httpProxyHandler) {
|
||||||
this.httpProxyHandler = httpProxyHandler;
|
this.httpProxyHandler = httpProxyHandler;
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -11,6 +11,7 @@ import io.netty.handler.codec.http.cookie.Cookie;
|
||||||
import org.xbib.net.URL;
|
import org.xbib.net.URL;
|
||||||
import org.xbib.netty.http.client.listener.CookieListener;
|
import org.xbib.netty.http.client.listener.CookieListener;
|
||||||
import org.xbib.netty.http.client.listener.ResponseListener;
|
import org.xbib.netty.http.client.listener.ResponseListener;
|
||||||
|
import org.xbib.netty.http.client.listener.StatusListener;
|
||||||
import org.xbib.netty.http.client.retry.BackOff;
|
import org.xbib.netty.http.client.retry.BackOff;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
@ -24,6 +25,8 @@ public class Request {
|
||||||
|
|
||||||
private final URL url;
|
private final URL url;
|
||||||
|
|
||||||
|
private final String uri;
|
||||||
|
|
||||||
private final HttpVersion httpVersion;
|
private final HttpVersion httpVersion;
|
||||||
|
|
||||||
private final HttpMethod httpMethod;
|
private final HttpMethod httpMethod;
|
||||||
|
@ -32,8 +35,6 @@ public class Request {
|
||||||
|
|
||||||
private final Collection<Cookie> cookies;
|
private final Collection<Cookie> cookies;
|
||||||
|
|
||||||
private final String uri;
|
|
||||||
|
|
||||||
private final ByteBuf content;
|
private final ByteBuf content;
|
||||||
|
|
||||||
private final long timeoutInMillis;
|
private final long timeoutInMillis;
|
||||||
|
@ -54,17 +55,18 @@ public class Request {
|
||||||
|
|
||||||
private CookieListener cookieListener;
|
private CookieListener cookieListener;
|
||||||
|
|
||||||
Request(URL url, HttpVersion httpVersion, HttpMethod httpMethod,
|
private StatusListener statusListener;
|
||||||
HttpHeaders headers, Collection<Cookie> cookies,
|
|
||||||
String uri, ByteBuf content,
|
Request(URL url, String uri, HttpVersion httpVersion, HttpMethod httpMethod,
|
||||||
|
HttpHeaders headers, Collection<Cookie> cookies, ByteBuf content,
|
||||||
long timeoutInMillis, boolean followRedirect, int maxRedirect, int redirectCount,
|
long timeoutInMillis, boolean followRedirect, int maxRedirect, int redirectCount,
|
||||||
boolean isBackOff, BackOff backOff) {
|
boolean isBackOff, BackOff backOff) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
|
this.uri = uri;
|
||||||
this.httpVersion = httpVersion;
|
this.httpVersion = httpVersion;
|
||||||
this.httpMethod = httpMethod;
|
this.httpMethod = httpMethod;
|
||||||
this.headers = headers;
|
this.headers = headers;
|
||||||
this.cookies = cookies;
|
this.cookies = cookies;
|
||||||
this.uri = uri;
|
|
||||||
this.content = content;
|
this.content = content;
|
||||||
this.timeoutInMillis = timeoutInMillis;
|
this.timeoutInMillis = timeoutInMillis;
|
||||||
this.followRedirect = followRedirect;
|
this.followRedirect = followRedirect;
|
||||||
|
@ -78,6 +80,15 @@ public class Request {
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String absolute() {
|
||||||
|
return url.toExternalForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String relative() {
|
||||||
|
// is already in external form
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
public HttpVersion httpVersion() {
|
public HttpVersion httpVersion() {
|
||||||
return httpVersion;
|
return httpVersion;
|
||||||
}
|
}
|
||||||
|
@ -86,10 +97,6 @@ public class Request {
|
||||||
return httpMethod;
|
return httpMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String relativeUri() {
|
|
||||||
return uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
public HttpHeaders headers() {
|
public HttpHeaders headers() {
|
||||||
return headers;
|
return headers;
|
||||||
}
|
}
|
||||||
|
@ -145,7 +152,6 @@ public class Request {
|
||||||
sb.append("Request[url='").append(url)
|
sb.append("Request[url='").append(url)
|
||||||
.append("',version=").append(httpVersion)
|
.append("',version=").append(httpVersion)
|
||||||
.append(",method=").append(httpMethod)
|
.append(",method=").append(httpMethod)
|
||||||
.append(",uri=").append(uri)
|
|
||||||
.append(",headers=").append(headers.entries())
|
.append(",headers=").append(headers.entries())
|
||||||
.append(",content=").append(content != null && content.readableBytes() >= 16 ?
|
.append(",content=").append(content != null && content.readableBytes() >= 16 ?
|
||||||
content.copy(0,16).toString(StandardCharsets.UTF_8) + "..." :
|
content.copy(0,16).toString(StandardCharsets.UTF_8) + "..." :
|
||||||
|
@ -173,6 +179,15 @@ public class Request {
|
||||||
return cookieListener;
|
return cookieListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Request setStatusListener(StatusListener statusListener) {
|
||||||
|
this.statusListener = statusListener;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StatusListener getStatusListener() {
|
||||||
|
return statusListener;
|
||||||
|
}
|
||||||
|
|
||||||
public Request setResponseListener(ResponseListener responseListener) {
|
public Request setResponseListener(ResponseListener responseListener) {
|
||||||
this.responseListener = responseListener;
|
this.responseListener = responseListener;
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -16,13 +16,11 @@ import io.netty.handler.codec.http2.HttpConversionUtil;
|
||||||
import io.netty.util.AsciiString;
|
import io.netty.util.AsciiString;
|
||||||
import org.xbib.net.PercentEncoder;
|
import org.xbib.net.PercentEncoder;
|
||||||
import org.xbib.net.PercentEncoders;
|
import org.xbib.net.PercentEncoders;
|
||||||
import org.xbib.net.QueryParameters;
|
|
||||||
import org.xbib.net.URL;
|
import org.xbib.net.URL;
|
||||||
import org.xbib.net.URLSyntaxException;
|
|
||||||
import org.xbib.netty.http.client.retry.BackOff;
|
import org.xbib.netty.http.client.retry.BackOff;
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
import org.xbib.netty.http.common.HttpAddress;
|
||||||
|
import org.xbib.netty.http.common.HttpParameters;
|
||||||
|
|
||||||
import java.net.URI;
|
|
||||||
import java.nio.charset.MalformedInputException;
|
import java.nio.charset.MalformedInputException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.charset.UnmappableCharacterException;
|
import java.nio.charset.UnmappableCharacterException;
|
||||||
|
@ -79,9 +77,7 @@ public class RequestBuilder {
|
||||||
|
|
||||||
private URL url;
|
private URL url;
|
||||||
|
|
||||||
private String uri;
|
private HttpParameters queryParameters;
|
||||||
|
|
||||||
private QueryParameters queryParameters;
|
|
||||||
|
|
||||||
private ByteBuf content;
|
private ByteBuf content;
|
||||||
|
|
||||||
|
@ -110,7 +106,7 @@ public class RequestBuilder {
|
||||||
removeHeaders = new ArrayList<>();
|
removeHeaders = new ArrayList<>();
|
||||||
cookies = new HashSet<>();
|
cookies = new HashSet<>();
|
||||||
encoder = PercentEncoders.getQueryEncoder(StandardCharsets.UTF_8);
|
encoder = PercentEncoders.getQueryEncoder(StandardCharsets.UTF_8);
|
||||||
queryParameters = new QueryParameters();
|
queryParameters = new HttpParameters();
|
||||||
}
|
}
|
||||||
|
|
||||||
public RequestBuilder setMethod(HttpMethod httpMethod) {
|
public RequestBuilder setMethod(HttpMethod httpMethod) {
|
||||||
|
@ -163,7 +159,7 @@ public class RequestBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
public RequestBuilder uri(String uri) {
|
public RequestBuilder uri(String uri) {
|
||||||
this.uri = uri;
|
this.url = url.resolve(uri);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -285,41 +281,31 @@ public class RequestBuilder {
|
||||||
if (url.getHost() == null) {
|
if (url.getHost() == null) {
|
||||||
throw new IllegalStateException("host in URL not defined: " + url);
|
throw new IllegalStateException("host in URL not defined: " + url);
|
||||||
}
|
}
|
||||||
// add path from uri()
|
// attach user query parameters to URL
|
||||||
if (uri != null) {
|
URL.Builder builder = url.newBuilder();
|
||||||
try {
|
queryParameters.forEach((k, v) -> v.forEach(value -> builder.queryParam(k, value)));
|
||||||
url = URL.base(url).resolve(uri);
|
url = builder.build();
|
||||||
} catch (URLSyntaxException | MalformedInputException | UnmappableCharacterException e) {
|
|
||||||
throw new IllegalArgumentException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// let Netty's query string decoder/encoder work over the URL to add parameters given implicitly in url()
|
// let Netty's query string decoder/encoder work over the URL to add parameters given implicitly in url()
|
||||||
QueryStringDecoder queryStringDecoder = new QueryStringDecoder(URI.create(url.toString()), StandardCharsets.UTF_8);
|
String path = url.getPath();
|
||||||
|
String query = url.getQuery();
|
||||||
|
QueryStringDecoder queryStringDecoder = new QueryStringDecoder(query != null ? path + "?" + query : path, StandardCharsets.UTF_8);
|
||||||
QueryStringEncoder queryStringEncoder = new QueryStringEncoder(queryStringDecoder.path());
|
QueryStringEncoder queryStringEncoder = new QueryStringEncoder(queryStringDecoder.path());
|
||||||
for (Map.Entry<String, List<String>> entry : queryStringDecoder.parameters().entrySet()) {
|
for (Map.Entry<String, List<String>> entry : queryStringDecoder.parameters().entrySet()) {
|
||||||
for (String value : entry.getValue()) {
|
for (String value : entry.getValue()) {
|
||||||
queryStringEncoder.addParam(entry.getKey(), value);
|
queryStringEncoder.addParam(entry.getKey(), value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// attach user query parameters
|
|
||||||
queryParameters.forEach(param -> queryStringEncoder.addParam(param.getFirst(), param.getSecond()));
|
|
||||||
// build uri from QueryStringDecoder
|
// build uri from QueryStringDecoder
|
||||||
String pathAndQuery = queryStringEncoder.toString();
|
String pathAndQuery = queryStringEncoder.toString();
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append(pathAndQuery.isEmpty() ? "/" : pathAndQuery);
|
if (!pathAndQuery.isEmpty()) {
|
||||||
String ref = url.getFragment();
|
sb.append(pathAndQuery);
|
||||||
if (ref != null && !ref.isEmpty()) {
|
|
||||||
sb.append('#').append(ref);
|
|
||||||
}
|
|
||||||
String uri = sb.toString();
|
|
||||||
// resolve again
|
|
||||||
if (!uri.equals("/")) {
|
|
||||||
try {
|
|
||||||
url = uri.startsWith("/") ? URL.base(url).resolve(uri) : URL.base(url).resolve("/" + uri) ;
|
|
||||||
} catch (URLSyntaxException | MalformedInputException | UnmappableCharacterException e) {
|
|
||||||
throw new IllegalArgumentException(e);
|
|
||||||
}
|
}
|
||||||
|
String fragment = url.getFragment();
|
||||||
|
if (fragment != null && !fragment.isEmpty()) {
|
||||||
|
sb.append('#').append(fragment);
|
||||||
}
|
}
|
||||||
|
String uri = sb.toString(); // the encoded form of path/query/fragment
|
||||||
DefaultHttpHeaders validatedHeaders = new DefaultHttpHeaders(true);
|
DefaultHttpHeaders validatedHeaders = new DefaultHttpHeaders(true);
|
||||||
validatedHeaders.set(headers);
|
validatedHeaders.set(headers);
|
||||||
String scheme = url.getScheme();
|
String scheme = url.getScheme();
|
||||||
|
@ -355,7 +341,7 @@ public class RequestBuilder {
|
||||||
for (String headerName : removeHeaders) {
|
for (String headerName : removeHeaders) {
|
||||||
validatedHeaders.remove(headerName);
|
validatedHeaders.remove(headerName);
|
||||||
}
|
}
|
||||||
return new Request(url, httpVersion, httpMethod, validatedHeaders, cookies, uri, content,
|
return new Request(url, uri, httpVersion, httpMethod, validatedHeaders, cookies, content,
|
||||||
timeoutInMillis, followRedirect, maxRedirects, 0, enableBackOff, backOff);
|
timeoutInMillis, followRedirect, maxRedirects, 0, enableBackOff, backOff);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ public final class UserAgent {
|
||||||
/**
|
/**
|
||||||
* The default value for {@code User-Agent}.
|
* The default value for {@code User-Agent}.
|
||||||
*/
|
*/
|
||||||
private static final String USER_AGENT = String.format("XbibHttpClient/%s (Java/%s/%s) (Netty/%s)",
|
private static final String USER_AGENT = String.format("NettyHttpClient/%s (Java/%s/%s) (Netty/%s)",
|
||||||
httpClientVersion(), javaVendor(), javaVersion(), nettyVersion());
|
httpClientVersion(), javaVendor(), javaVersion(), nettyVersion());
|
||||||
|
|
||||||
private UserAgent() {
|
private UserAgent() {
|
||||||
|
|
|
@ -10,6 +10,7 @@ import io.netty.handler.codec.http.HttpObjectAggregator;
|
||||||
import io.netty.handler.logging.LogLevel;
|
import io.netty.handler.logging.LogLevel;
|
||||||
import io.netty.handler.ssl.ApplicationProtocolNames;
|
import io.netty.handler.ssl.ApplicationProtocolNames;
|
||||||
import io.netty.handler.ssl.ApplicationProtocolNegotiationHandler;
|
import io.netty.handler.ssl.ApplicationProtocolNegotiationHandler;
|
||||||
|
import io.netty.handler.ssl.SslHandler;
|
||||||
import org.xbib.netty.http.client.Client;
|
import org.xbib.netty.http.client.Client;
|
||||||
import org.xbib.netty.http.client.ClientConfig;
|
import org.xbib.netty.http.client.ClientConfig;
|
||||||
import org.xbib.netty.http.client.handler.http2.Http2ChannelInitializer;
|
import org.xbib.netty.http.client.handler.http2.Http2ChannelInitializer;
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
package org.xbib.netty.http.client.listener;
|
||||||
|
|
||||||
|
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface StatusListener {
|
||||||
|
|
||||||
|
void onStatus(HttpResponseStatus httpResponseStatus);
|
||||||
|
}
|
|
@ -17,11 +17,13 @@ import io.netty.handler.codec.http2.Http2Settings;
|
||||||
import io.netty.handler.codec.http2.Http2StreamChannel;
|
import io.netty.handler.codec.http2.Http2StreamChannel;
|
||||||
import io.netty.handler.codec.http2.Http2StreamChannelBootstrap;
|
import io.netty.handler.codec.http2.Http2StreamChannelBootstrap;
|
||||||
import io.netty.handler.codec.http2.HttpConversionUtil;
|
import io.netty.handler.codec.http2.HttpConversionUtil;
|
||||||
|
import io.netty.util.AsciiString;
|
||||||
import org.xbib.net.URLSyntaxException;
|
import org.xbib.net.URLSyntaxException;
|
||||||
import org.xbib.netty.http.client.Client;
|
import org.xbib.netty.http.client.Client;
|
||||||
import org.xbib.netty.http.client.handler.http2.Http2ResponseHandler;
|
import org.xbib.netty.http.client.handler.http2.Http2ResponseHandler;
|
||||||
import org.xbib.netty.http.client.handler.http2.Http2StreamFrameToHttpObjectCodec;
|
import org.xbib.netty.http.client.handler.http2.Http2StreamFrameToHttpObjectCodec;
|
||||||
import org.xbib.netty.http.client.listener.CookieListener;
|
import org.xbib.netty.http.client.listener.CookieListener;
|
||||||
|
import org.xbib.netty.http.client.listener.StatusListener;
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
import org.xbib.netty.http.common.HttpAddress;
|
||||||
import org.xbib.netty.http.client.Request;
|
import org.xbib.netty.http.client.Request;
|
||||||
import org.xbib.netty.http.client.listener.ResponseListener;
|
import org.xbib.netty.http.client.listener.ResponseListener;
|
||||||
|
@ -73,14 +75,12 @@ public class Http2Transport extends BaseTransport {
|
||||||
channelFlowMap.putIfAbsent(channelId, new Flow());
|
channelFlowMap.putIfAbsent(channelId, new Flow());
|
||||||
Http2StreamChannel childChannel = new Http2StreamChannelBootstrap(channel)
|
Http2StreamChannel childChannel = new Http2StreamChannelBootstrap(channel)
|
||||||
.handler(initializer).open().syncUninterruptibly().getNow();
|
.handler(initializer).open().syncUninterruptibly().getNow();
|
||||||
|
AsciiString method = request.httpMethod().asciiName();
|
||||||
|
String scheme = request.url().getScheme();
|
||||||
String authority = request.url().getHost() + (request.url().getPort() != null ? ":" + request.url().getPort() : "");
|
String authority = request.url().getHost() + (request.url().getPort() != null ? ":" + request.url().getPort() : "");
|
||||||
String path = request.url().getPath() != null && !request.url().getPath().isEmpty() ?
|
String path = request.relative().isEmpty() ? "/" : request.relative();
|
||||||
request.url().getPath() : "/";
|
|
||||||
Http2Headers http2Headers = new DefaultHttp2Headers()
|
Http2Headers http2Headers = new DefaultHttp2Headers()
|
||||||
.method(request.httpMethod().asciiName())
|
.method(method).scheme(scheme).authority(authority).path(path);
|
||||||
.scheme(request.url().getScheme())
|
|
||||||
.authority(authority)
|
|
||||||
.path(path);
|
|
||||||
final Integer streamId = channelFlowMap.get(channelId).nextStreamId();
|
final Integer streamId = channelFlowMap.get(channelId).nextStreamId();
|
||||||
if (streamId == null) {
|
if (streamId == null) {
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
|
@ -158,6 +158,10 @@ public class Http2Transport extends BaseTransport {
|
||||||
if (request == null) {
|
if (request == null) {
|
||||||
promise.completeExceptionally(new IllegalStateException());
|
promise.completeExceptionally(new IllegalStateException());
|
||||||
} else {
|
} else {
|
||||||
|
StatusListener statusListener = request.getStatusListener();
|
||||||
|
if (statusListener != null) {
|
||||||
|
statusListener.onStatus(fullHttpResponse.status());
|
||||||
|
}
|
||||||
for (String cookieString : fullHttpResponse.headers().getAll(HttpHeaderNames.SET_COOKIE)) {
|
for (String cookieString : fullHttpResponse.headers().getAll(HttpHeaderNames.SET_COOKIE)) {
|
||||||
Cookie cookie = ClientCookieDecoder.STRICT.decode(cookieString);
|
Cookie cookie = ClientCookieDecoder.STRICT.decode(cookieString);
|
||||||
addCookie(cookie);
|
addCookie(cookie);
|
||||||
|
|
|
@ -14,6 +14,7 @@ import io.netty.handler.codec.http2.HttpConversionUtil;
|
||||||
import org.xbib.net.URLSyntaxException;
|
import org.xbib.net.URLSyntaxException;
|
||||||
import org.xbib.netty.http.client.Client;
|
import org.xbib.netty.http.client.Client;
|
||||||
import org.xbib.netty.http.client.listener.CookieListener;
|
import org.xbib.netty.http.client.listener.CookieListener;
|
||||||
|
import org.xbib.netty.http.client.listener.StatusListener;
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
import org.xbib.netty.http.common.HttpAddress;
|
||||||
import org.xbib.netty.http.client.Request;
|
import org.xbib.netty.http.client.Request;
|
||||||
import org.xbib.netty.http.client.listener.ResponseListener;
|
import org.xbib.netty.http.client.listener.ResponseListener;
|
||||||
|
@ -45,8 +46,7 @@ public class HttpTransport extends BaseTransport {
|
||||||
// The "origin form" requires a "Host" header.
|
// The "origin form" requires a "Host" header.
|
||||||
// Our algorithm is: use always "origin form" for HTTP 1, use absolute form for HTTP 2.
|
// Our algorithm is: use always "origin form" for HTTP 1, use absolute form for HTTP 2.
|
||||||
// The reason is that Netty derives the HTTP/2 scheme header from the absolute form.
|
// The reason is that Netty derives the HTTP/2 scheme header from the absolute form.
|
||||||
String uri = request.httpVersion().majorVersion() == 1 ?
|
String uri = request.httpVersion().majorVersion() == 1 ? request.relative() : request.absolute();
|
||||||
request.url().relativeReference() : request.url().toString();
|
|
||||||
FullHttpRequest fullHttpRequest = request.content() == null ?
|
FullHttpRequest fullHttpRequest = request.content() == null ?
|
||||||
new DefaultFullHttpRequest(request.httpVersion(), request.httpMethod(), uri) :
|
new DefaultFullHttpRequest(request.httpVersion(), request.httpMethod(), uri) :
|
||||||
new DefaultFullHttpRequest(request.httpVersion(), request.httpMethod(), uri, request.content());
|
new DefaultFullHttpRequest(request.httpVersion(), request.httpMethod(), uri, request.content());
|
||||||
|
@ -80,9 +80,17 @@ public class HttpTransport extends BaseTransport {
|
||||||
logger.log(Level.WARNING, "throwable not null for response " + fullHttpResponse, throwable);
|
logger.log(Level.WARNING, "throwable not null for response " + fullHttpResponse, throwable);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (requests.isEmpty()) {
|
||||||
|
logger.log(Level.WARNING, "no request present for responding");
|
||||||
|
return;
|
||||||
|
}
|
||||||
// streamID is expected to be null, last request on memory is expected to be current, remove request from memory
|
// streamID is expected to be null, last request on memory is expected to be current, remove request from memory
|
||||||
Request request = requests.remove(requests.isEmpty() ? null : requests.lastKey());
|
Request request = requests.remove(requests.lastKey());
|
||||||
if (request != null) {
|
if (request != null) {
|
||||||
|
StatusListener statusListener = request.getStatusListener();
|
||||||
|
if (statusListener != null) {
|
||||||
|
statusListener.onStatus(fullHttpResponse.status());
|
||||||
|
}
|
||||||
for (String cookieString : fullHttpResponse.headers().getAll(HttpHeaderNames.SET_COOKIE)) {
|
for (String cookieString : fullHttpResponse.headers().getAll(HttpHeaderNames.SET_COOKIE)) {
|
||||||
Cookie cookie = ClientCookieDecoder.STRICT.decode(cookieString);
|
Cookie cookie = ClientCookieDecoder.STRICT.decode(cookieString);
|
||||||
addCookie(cookie);
|
addCookie(cookie);
|
||||||
|
|
|
@ -1,140 +0,0 @@
|
||||||
package org.xbib.netty.http.client.test;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Disabled;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
|
||||||
import org.xbib.netty.http.client.Client;
|
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
|
||||||
import org.xbib.netty.http.client.Request;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
|
|
||||||
@Disabled
|
|
||||||
@ExtendWith(NettyHttpExtension.class)
|
|
||||||
class ElasticsearchTest {
|
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(ElasticsearchTest.class.getName());
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testElasticsearch() throws IOException {
|
|
||||||
Client client = Client.builder()
|
|
||||||
.build();
|
|
||||||
try {
|
|
||||||
Request request = Request.get().url("http://localhost:9200")
|
|
||||||
.build()
|
|
||||||
.setResponseListener(fullHttpResponse -> {
|
|
||||||
String response = fullHttpResponse.content().toString(StandardCharsets.UTF_8);
|
|
||||||
logger.log(Level.INFO, "status = " + fullHttpResponse.status() + " response body = " + response);
|
|
||||||
});
|
|
||||||
logger.info("request = " + request.toString());
|
|
||||||
client.execute(request);
|
|
||||||
} finally {
|
|
||||||
client.shutdownGracefully();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testElasticsearchCreateDocument() throws IOException {
|
|
||||||
Client client = Client.builder()
|
|
||||||
.build();
|
|
||||||
try {
|
|
||||||
Request request = Request.put().url("http://localhost:9200/test/test/1")
|
|
||||||
.json("{\"text\":\"Hello World\"}")
|
|
||||||
.build()
|
|
||||||
.setResponseListener(fullHttpResponse -> {
|
|
||||||
String response = fullHttpResponse.content().toString(StandardCharsets.UTF_8);
|
|
||||||
logger.log(Level.INFO, "status = " + fullHttpResponse.status() + " response body = " + response);
|
|
||||||
});
|
|
||||||
logger.info("request = " + request.toString());
|
|
||||||
client.execute(request);
|
|
||||||
} finally {
|
|
||||||
client.shutdownGracefully();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testElasticsearchMatchQuery() throws IOException {
|
|
||||||
Client client = new Client();
|
|
||||||
try {
|
|
||||||
Request request = Request.post().url("http://localhost:9200/test/_search")
|
|
||||||
.json("{\"query\":{\"match\":{\"text\":\"Hello World\"}}}")
|
|
||||||
.build()
|
|
||||||
.setResponseListener(fullHttpResponse -> {
|
|
||||||
String response = fullHttpResponse.content().toString(StandardCharsets.UTF_8);
|
|
||||||
logger.log(Level.INFO, "status = " + fullHttpResponse.status() + " response body = " + response);
|
|
||||||
});
|
|
||||||
client.execute(request).get();
|
|
||||||
} finally {
|
|
||||||
client.shutdownGracefully();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This shows the usage of 4 concurrent pooled connections on 4 threads, querying Elasticsearch.
|
|
||||||
* @throws IOException if test fails
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
void testElasticsearchPooled() throws IOException {
|
|
||||||
HttpAddress httpAddress = HttpAddress.http1("localhost", 9200);
|
|
||||||
int limit = 4;
|
|
||||||
Client client = Client.builder()
|
|
||||||
.addPoolNode(httpAddress)
|
|
||||||
.setPoolNodeConnectionLimit(limit)
|
|
||||||
.build();
|
|
||||||
int max = 1000;
|
|
||||||
int threads = 4;
|
|
||||||
try {
|
|
||||||
ExecutorService executorService = Executors.newFixedThreadPool(threads);
|
|
||||||
for (int n = 0; n < threads; n++) {
|
|
||||||
executorService.submit(() -> {
|
|
||||||
List<Request> queries = new ArrayList<>();
|
|
||||||
for (int i = 0; i < max; i++) {
|
|
||||||
queries.add(newRequest());
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
for (int i = 0; i < max; i++) {
|
|
||||||
client.newTransport().execute(queries.get(i)).get();
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.log(Level.WARNING, e.getMessage(), e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
executorService.shutdown();
|
|
||||||
executorService.awaitTermination(60, TimeUnit.SECONDS);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
logger.log(Level.WARNING, e.getMessage(), e);
|
|
||||||
} finally {
|
|
||||||
client.shutdownGracefully();
|
|
||||||
}
|
|
||||||
logger.log(Level.INFO, "count=" + count);
|
|
||||||
assertEquals(max * threads, count.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Request newRequest() {
|
|
||||||
return Request.post()
|
|
||||||
.url("http://localhost:9200/test/_search")
|
|
||||||
.json("{\"query\":{\"match\":{\"text\":\"Hello World\"}}}")
|
|
||||||
.addHeader("connection", "keep-alive")
|
|
||||||
.build()
|
|
||||||
.setResponseListener(fullHttpResponse -> {
|
|
||||||
count.getAndIncrement();
|
|
||||||
if (fullHttpResponse.status().code() != 200) {
|
|
||||||
logger.log(Level.WARNING,"error: " + fullHttpResponse.toString());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private final AtomicInteger count = new AtomicInteger();
|
|
||||||
}
|
|
|
@ -1,116 +0,0 @@
|
||||||
package org.xbib.netty.http.client.test;
|
|
||||||
|
|
||||||
import io.netty.handler.codec.http.HttpMethod;
|
|
||||||
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 java.io.IOException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
@ExtendWith(NettyHttpExtension.class)
|
|
||||||
class Http2Test {
|
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(Http2Test.class.getName());
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Problems with akamai:
|
|
||||||
*
|
|
||||||
* 2018-03-07 16:02:52.385 FEIN [client] io.netty.handler.codec.http2.Http2FrameLogger logRstStream
|
|
||||||
* [id: 0x57cc65bb, L:/10.1.1.94:52834 - R:http2.akamai.com/104.94.191.203:443] INBOUND RST_STREAM: streamId=2 errorCode=8
|
|
||||||
* 2018-03-07 16:02:52.385 FEIN [client] io.netty.handler.codec.http2.Http2FrameLogger logGoAway
|
|
||||||
* [id: 0x57cc65bb, L:/10.1.1.94:52834 - R:http2.akamai.com/104.94.191.203:443] OUTBOUND GO_AWAY: lastStreamId=2 errorCode=0 length=0 bytes=
|
|
||||||
*
|
|
||||||
* demo/h2_demo_frame.html sends no content, only a push promise, and does not continue
|
|
||||||
*
|
|
||||||
* @throws IOException if test fails
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
void testAkamai() throws IOException {
|
|
||||||
Client client = Client.builder()
|
|
||||||
.addServerNameForIdentification("http2.akamai.com")
|
|
||||||
.build();
|
|
||||||
try {
|
|
||||||
Request request = Request.get()
|
|
||||||
.url("https://http2.akamai.com/demo/h2_demo_frame.html")
|
|
||||||
//.url("https://http2.akamai.com/")
|
|
||||||
.setVersion("HTTP/2.0")
|
|
||||||
.build()
|
|
||||||
.setResponseListener(msg -> {
|
|
||||||
String response = msg.content().toString(StandardCharsets.UTF_8);
|
|
||||||
logger.log(Level.INFO, "status = " + msg.status() +
|
|
||||||
msg.headers().entries() + " " + response);
|
|
||||||
});
|
|
||||||
client.execute(request).get();
|
|
||||||
} finally {
|
|
||||||
client.shutdownGracefully();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testWebtide() throws Exception {
|
|
||||||
Client client = Client.builder()
|
|
||||||
.build();
|
|
||||||
client.logDiagnostics(Level.INFO);
|
|
||||||
try {
|
|
||||||
Request request = Request.get().url("https://webtide.com").setVersion("HTTP/2.0").build()
|
|
||||||
.setResponseListener(msg -> logger.log(Level.INFO, "got response: " +
|
|
||||||
msg.headers().entries() +
|
|
||||||
//msg.content().toString(StandardCharsets.UTF_8) +
|
|
||||||
" status=" + msg.status().code()));
|
|
||||||
client.execute(request).get();
|
|
||||||
} finally {
|
|
||||||
client.shutdown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testHttp2PushIO() throws IOException {
|
|
||||||
String url = "https://http2-push.io";
|
|
||||||
Client client = Client.builder()
|
|
||||||
.addServerNameForIdentification("http2-push.io")
|
|
||||||
.build();
|
|
||||||
try {
|
|
||||||
Request request = Request.builder(HttpMethod.GET)
|
|
||||||
.url(url).setVersion("HTTP/2.0")
|
|
||||||
.build()
|
|
||||||
.setResponseListener(msg -> logger.log(Level.INFO, "got response: " +
|
|
||||||
msg.headers().entries() +
|
|
||||||
//msg.content().toString(StandardCharsets.UTF_8) +
|
|
||||||
" status=" + msg.status().code()));
|
|
||||||
client.execute(request).get();
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
client.shutdownGracefully();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testWebtideTwoRequestsOnSameConnection() throws IOException {
|
|
||||||
Client client = new Client();
|
|
||||||
try {
|
|
||||||
Request request1 = Request.builder(HttpMethod.GET)
|
|
||||||
.url("https://webtide.com").setVersion("HTTP/2.0")
|
|
||||||
.build()
|
|
||||||
.setResponseListener(msg -> logger.log(Level.INFO, "got response: " +
|
|
||||||
msg.headers().entries() +
|
|
||||||
//msg.content().toString(StandardCharsets.UTF_8) +
|
|
||||||
" status=" + msg.status().code()));
|
|
||||||
|
|
||||||
Request request2 = Request.builder(HttpMethod.GET)
|
|
||||||
.url("https://webtide.com/why-choose-jetty/").setVersion("HTTP/2.0")
|
|
||||||
.build()
|
|
||||||
.setResponseListener(msg -> logger.log(Level.INFO, "got response: " +
|
|
||||||
msg.headers().entries() +
|
|
||||||
//msg.content().toString(StandardCharsets.UTF_8) +
|
|
||||||
" status=" + msg.status().code()));
|
|
||||||
|
|
||||||
client.execute(request1).execute(request2);
|
|
||||||
} finally {
|
|
||||||
client.shutdownGracefully();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,8 +2,11 @@ package org.xbib.netty.http.client.test;
|
||||||
|
|
||||||
import io.netty.handler.codec.http.HttpMethod;
|
import io.netty.handler.codec.http.HttpMethod;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.xbib.net.URL;
|
||||||
import org.xbib.netty.http.client.Request;
|
import org.xbib.netty.http.client.Request;
|
||||||
|
import org.xbib.netty.http.client.RequestBuilder;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
@ -11,6 +14,38 @@ import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
|
||||||
class RequestBuilderTest {
|
class RequestBuilderTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testResolve() {
|
||||||
|
URI uri = URI.create("http://localhost");
|
||||||
|
URI uri2 = uri.resolve("/path");
|
||||||
|
assertEquals("http://localhost/path", uri2.toString());
|
||||||
|
|
||||||
|
uri = URI.create("http://localhost/path1?a=b");
|
||||||
|
uri2 = uri.resolve("path2?c=d");
|
||||||
|
assertEquals("http://localhost/path2?c=d", uri2.toString());
|
||||||
|
|
||||||
|
URL url = URL.from("http://localhost");
|
||||||
|
URL url2 = url.resolve("/path");
|
||||||
|
assertEquals("http://localhost/path", url2.toString());
|
||||||
|
|
||||||
|
url = URL.from("http://localhost/path1?a=b");
|
||||||
|
url2 = url.resolve("path2?c=d");
|
||||||
|
assertEquals("http://localhost/path2?c=d", url2.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRelativeUri() {
|
||||||
|
RequestBuilder httpRequestBuilder = Request.get();
|
||||||
|
httpRequestBuilder.url("https://localhost").uri("/path");
|
||||||
|
assertEquals("/path", httpRequestBuilder.build().relative());
|
||||||
|
httpRequestBuilder.uri("/foobar");
|
||||||
|
assertEquals("/foobar", httpRequestBuilder.build().relative());
|
||||||
|
httpRequestBuilder.uri("/path1?a=b");
|
||||||
|
assertEquals("/path1?a=b", httpRequestBuilder.build().relative());
|
||||||
|
httpRequestBuilder.uri("/path2?c=d");
|
||||||
|
assertEquals("/path2?c=d", httpRequestBuilder.build().relative());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSimpleRequest() {
|
void testSimpleRequest() {
|
||||||
Request request = Request.builder(HttpMethod.GET)
|
Request request = Request.builder(HttpMethod.GET)
|
||||||
|
@ -28,8 +63,8 @@ class RequestBuilderTest {
|
||||||
.addParameter("param1", "value1")
|
.addParameter("param1", "value1")
|
||||||
.addParameter("param2", "value2")
|
.addParameter("param2", "value2")
|
||||||
.build();
|
.build();
|
||||||
assertEquals("?param1=value1¶m2=value2", request.relativeUri());
|
assertEquals("?param1=value1¶m2=value2", request.relative());
|
||||||
assertEquals("http://xbib.org/?param1=value1¶m2=value2", request.url().toString());
|
assertEquals("http://xbib.org?param1=value1¶m2=value2", request.url().toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -40,8 +75,9 @@ class RequestBuilderTest {
|
||||||
.addParameter("param2", "value2")
|
.addParameter("param2", "value2")
|
||||||
.content("Hello", "text/plain")
|
.content("Hello", "text/plain")
|
||||||
.build();
|
.build();
|
||||||
assertEquals("?param1=value1¶m2=value2", request.relativeUri());
|
assertEquals("xbib.org", request.url().getHost());
|
||||||
assertEquals("http://xbib.org/?param1=value1¶m2=value2", request.url().toString());
|
assertEquals("?param1=value1¶m2=value2", request.relative());
|
||||||
|
assertEquals("http://xbib.org?param1=value1¶m2=value2", request.url().toExternalForm());
|
||||||
assertEquals("Hello", request.content().toString(StandardCharsets.UTF_8));
|
assertEquals("Hello", request.content().toString(StandardCharsets.UTF_8));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,9 +85,38 @@ class RequestBuilderTest {
|
||||||
void testRequest() {
|
void testRequest() {
|
||||||
Request request = Request.get()
|
Request request = Request.get()
|
||||||
.url("https://google.com")
|
.url("https://google.com")
|
||||||
.setVersion("HTTP/1.1")
|
|
||||||
.build();
|
.build();
|
||||||
assertEquals("google.com", request.url().getHost());
|
assertEquals("google.com", request.url().getHost());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRequestWithSpaceInParameters() {
|
||||||
|
Request request = Request.get()
|
||||||
|
.url("https://google.com? a = b")
|
||||||
|
.build();
|
||||||
|
assertEquals("google.com", request.url().getHost());
|
||||||
|
assertEquals("https://google.com?%20a%20=%20b", request.absolute());
|
||||||
|
assertEquals("?%20a%20=%20b", request.relative());
|
||||||
|
assertEquals("https://google.com? a = b", request.url().toString());
|
||||||
|
assertEquals("https://google.com?%20a%20=%20b", request.url().toExternalForm());
|
||||||
|
|
||||||
|
request = Request.get()
|
||||||
|
.url("https://google.com?%20a%20=%20b")
|
||||||
|
.build();
|
||||||
|
assertEquals("google.com", request.url().getHost());
|
||||||
|
assertEquals("https://google.com?%20a%20=%20b", request.absolute());
|
||||||
|
assertEquals("?%20a%20=%20b", request.relative());
|
||||||
|
assertEquals("https://google.com? a = b", request.url().toString());
|
||||||
|
assertEquals("https://google.com?%20a%20=%20b", request.url().toExternalForm());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testMassiveQueryParameters() {
|
||||||
|
RequestBuilder requestBuilder = Request.builder(HttpMethod.GET);
|
||||||
|
for (int i = 0; i < 2000; i++) {
|
||||||
|
requestBuilder.addParameter("param" + i, "value" + i);
|
||||||
|
}
|
||||||
|
Request request = requestBuilder.build();
|
||||||
|
assertEquals(18276, request.absolute().length());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
package org.xbib.netty.http.client.test;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.xbib.netty.http.client.Request;
|
|
||||||
import org.xbib.netty.http.client.RequestBuilder;
|
|
||||||
|
|
||||||
import java.net.URI;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
|
|
||||||
class URITest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testURIResolve() {
|
|
||||||
URI uri = URI.create("http://localhost");
|
|
||||||
URI uri2 = uri.resolve("/path");
|
|
||||||
assertEquals("http://localhost/path", uri2.toString());
|
|
||||||
uri = URI.create("http://localhost/path1?a=b");
|
|
||||||
uri2 = uri.resolve("path2?c=d");
|
|
||||||
assertEquals("http://localhost/path2?c=d", uri2.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testRelativeUri() {
|
|
||||||
RequestBuilder httpRequestBuilder = Request.get();
|
|
||||||
httpRequestBuilder.url("https://localhost").uri("/path");
|
|
||||||
assertEquals("/path", httpRequestBuilder.build().relativeUri());
|
|
||||||
httpRequestBuilder.uri("/foobar");
|
|
||||||
assertEquals("/foobar", httpRequestBuilder.build().relativeUri());
|
|
||||||
httpRequestBuilder.uri("/path1?a=b");
|
|
||||||
assertEquals("/path1?a=b", httpRequestBuilder.build().relativeUri());
|
|
||||||
httpRequestBuilder.uri("/path2?c=d");
|
|
||||||
assertEquals("/path2?c=d", httpRequestBuilder.build().relativeUri());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
package org.xbib.netty.http.client.test.akamai;
|
||||||
|
|
||||||
|
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.test.NettyHttpExtension;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
@ExtendWith(NettyHttpExtension.class)
|
||||||
|
public class AkamaiTest {
|
||||||
|
|
||||||
|
private static Logger logger = Logger.getLogger(AkamaiTest.class.getName());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Problems with akamai:
|
||||||
|
*
|
||||||
|
* 2018-03-07 16:02:52.385 FEIN [client] io.netty.handler.codec.http2.Http2FrameLogger logRstStream
|
||||||
|
* [id: 0x57cc65bb, L:/10.1.1.94:52834 - R:http2.akamai.com/104.94.191.203:443] INBOUND RST_STREAM: streamId=2 errorCode=8
|
||||||
|
* 2018-03-07 16:02:52.385 FEIN [client] io.netty.handler.codec.http2.Http2FrameLogger logGoAway
|
||||||
|
* [id: 0x57cc65bb, L:/10.1.1.94:52834 - R:http2.akamai.com/104.94.191.203:443] OUTBOUND GO_AWAY: lastStreamId=2 errorCode=0 length=0 bytes=
|
||||||
|
*
|
||||||
|
* demo/h2_demo_frame.html sends no content, only a push promise, and does not continue
|
||||||
|
*
|
||||||
|
* @throws IOException if test fails
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testAkamai() throws IOException {
|
||||||
|
Client client = Client.builder()
|
||||||
|
.addServerNameForIdentification("http2.akamai.com")
|
||||||
|
.build();
|
||||||
|
try {
|
||||||
|
Request request = Request.get()
|
||||||
|
.url("https://http2.akamai.com/demo/h2_demo_frame.html")
|
||||||
|
//.url("https://http2.akamai.com/")
|
||||||
|
.setVersion("HTTP/2.0")
|
||||||
|
.build()
|
||||||
|
.setResponseListener(msg -> {
|
||||||
|
String response = msg.content().toString(StandardCharsets.UTF_8);
|
||||||
|
logger.log(Level.INFO, "status = " + msg.status() +
|
||||||
|
msg.headers().entries() + " " + response);
|
||||||
|
});
|
||||||
|
client.execute(request).get();
|
||||||
|
} finally {
|
||||||
|
client.shutdownGracefully();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package org.xbib.netty.http.client.test.htt2push;
|
||||||
|
|
||||||
|
import io.netty.handler.codec.http.HttpMethod;
|
||||||
|
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.test.NettyHttpExtension;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
@ExtendWith(NettyHttpExtension.class)
|
||||||
|
class Http2PushTest {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(Http2PushTest.class.getName());
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testHttp2PushIO() throws IOException {
|
||||||
|
String url = "https://http2-push.io";
|
||||||
|
Client client = Client.builder()
|
||||||
|
.addServerNameForIdentification("http2-push.io")
|
||||||
|
.build();
|
||||||
|
try {
|
||||||
|
Request request = Request.builder(HttpMethod.GET)
|
||||||
|
.url(url).setVersion("HTTP/2.0")
|
||||||
|
.build()
|
||||||
|
.setResponseListener(msg -> logger.log(Level.INFO, "got response: " +
|
||||||
|
msg.headers().entries() +
|
||||||
|
//msg.content().toString(StandardCharsets.UTF_8) +
|
||||||
|
" status=" + msg.status().code()));
|
||||||
|
client.execute(request).get();
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
client.shutdownGracefully();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
package org.xbib.netty.http.client.test.webtide;
|
||||||
|
|
||||||
|
import io.netty.handler.codec.http.HttpMethod;
|
||||||
|
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.test.NettyHttpExtension;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
@ExtendWith(NettyHttpExtension.class)
|
||||||
|
class WebtideTest {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(WebtideTest.class.getName());
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testWebtide() throws Exception {
|
||||||
|
Client client = Client.builder()
|
||||||
|
.build();
|
||||||
|
try {
|
||||||
|
Request request = Request.get().url("https://webtide.com").setVersion("HTTP/2.0").build()
|
||||||
|
.setResponseListener(msg -> logger.log(Level.INFO, "got response: " + msg));
|
||||||
|
client.execute(request).get();
|
||||||
|
} finally {
|
||||||
|
client.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testWebtideTwoRequestsOnSameConnection() throws IOException {
|
||||||
|
Client client = new Client();
|
||||||
|
try {
|
||||||
|
Request request1 = Request.builder(HttpMethod.GET)
|
||||||
|
.url("https://webtide.com").setVersion("HTTP/2.0")
|
||||||
|
.build()
|
||||||
|
.setResponseListener(msg -> logger.log(Level.INFO, "got response: " +
|
||||||
|
msg.headers().entries() +
|
||||||
|
//msg.content().toString(StandardCharsets.UTF_8) +
|
||||||
|
" status=" + msg.status().code()));
|
||||||
|
|
||||||
|
Request request2 = Request.builder(HttpMethod.GET)
|
||||||
|
.url("https://webtide.com/why-choose-jetty/").setVersion("HTTP/2.0")
|
||||||
|
.build()
|
||||||
|
.setResponseListener(msg -> logger.log(Level.INFO, "got response: " +
|
||||||
|
msg.headers().entries() +
|
||||||
|
//msg.content().toString(StandardCharsets.UTF_8) +
|
||||||
|
" status=" + msg.status().code()));
|
||||||
|
|
||||||
|
client.execute(request1).execute(request2);
|
||||||
|
} finally {
|
||||||
|
client.shutdownGracefully();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,6 +22,7 @@ public class HttpAddress implements PoolKey {
|
||||||
|
|
||||||
private InetSocketAddress inetSocketAddress;
|
private InetSocketAddress inetSocketAddress;
|
||||||
|
|
||||||
|
|
||||||
public static HttpAddress http1(String host) {
|
public static HttpAddress http1(String host) {
|
||||||
return new HttpAddress(host, 80, HttpVersion.HTTP_1_1, false);
|
return new HttpAddress(host, 80, HttpVersion.HTTP_1_1, false);
|
||||||
}
|
}
|
||||||
|
@ -62,6 +63,10 @@ public class HttpAddress implements PoolKey {
|
||||||
return new HttpAddress(url, HTTP_2_0);
|
return new HttpAddress(url, HTTP_2_0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static HttpAddress of(URL url) {
|
||||||
|
return new HttpAddress(url, HttpVersion.HTTP_1_1);
|
||||||
|
}
|
||||||
|
|
||||||
public static HttpAddress of(URL url, HttpVersion httpVersion) {
|
public static HttpAddress of(URL url, HttpVersion httpVersion) {
|
||||||
return new HttpAddress(url, httpVersion);
|
return new HttpAddress(url, httpVersion);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,319 @@
|
||||||
|
package org.xbib.netty.http.common;
|
||||||
|
|
||||||
|
import org.xbib.net.PercentDecoder;
|
||||||
|
import org.xbib.net.PercentEncoder;
|
||||||
|
import org.xbib.net.PercentEncoders;
|
||||||
|
import org.xbib.netty.http.common.util.LimitedSortedStringSet;
|
||||||
|
import org.xbib.netty.http.common.util.LimitedStringMap;
|
||||||
|
|
||||||
|
import java.nio.charset.MalformedInputException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.charset.UnmappableCharacterException;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.SortedSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A limited multi-map of HTTP request parameters. Each key references a
|
||||||
|
* limited set of parameters collected from the request during message
|
||||||
|
* signing. Parameter values are sorted as per
|
||||||
|
* <a href="http://oauth.net/core/1.0a/#anchor13">OAuth specification</a>.
|
||||||
|
* Every key/value pair will be percent-encoded upon insertion.
|
||||||
|
* This class has special semantics tailored to
|
||||||
|
* being useful for message signing; it's not a general purpose collection class
|
||||||
|
* to handle request parameters.
|
||||||
|
*/
|
||||||
|
public class HttpParameters implements Map<String, SortedSet<String>> {
|
||||||
|
|
||||||
|
private final int maxParam;
|
||||||
|
|
||||||
|
private final int sizeLimit;
|
||||||
|
|
||||||
|
private final int elementSizeLimit;
|
||||||
|
|
||||||
|
private final LimitedStringMap map;
|
||||||
|
|
||||||
|
private final PercentEncoder percentEncoder;
|
||||||
|
|
||||||
|
private final PercentDecoder percentDecoder;
|
||||||
|
|
||||||
|
public HttpParameters() {
|
||||||
|
this(1024, 1024, 65536);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpParameters(int maxParam, int sizeLimit, int elementSizeLimit) {
|
||||||
|
this.maxParam = maxParam;
|
||||||
|
this.sizeLimit = sizeLimit;
|
||||||
|
this.elementSizeLimit = elementSizeLimit;
|
||||||
|
this.map = new LimitedStringMap(maxParam);
|
||||||
|
this.percentEncoder = PercentEncoders.getQueryEncoder(StandardCharsets.UTF_8);
|
||||||
|
this.percentDecoder = new PercentDecoder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SortedSet<String> put(String key, SortedSet<String> value) {
|
||||||
|
return map.put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SortedSet<String> get(Object key) {
|
||||||
|
return map.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void putAll(Map<? extends String, ? extends SortedSet<String>> m) {
|
||||||
|
map.putAll(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsKey(Object key) {
|
||||||
|
return map.containsKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsValue(Object value) {
|
||||||
|
if (value instanceof String) {
|
||||||
|
for (Set<String> values : map.values()) {
|
||||||
|
if (values.contains(value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
int count = 0;
|
||||||
|
for (String key : map.keySet()) {
|
||||||
|
count += map.get(key).size();
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return map.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
map.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SortedSet<String> remove(Object key) {
|
||||||
|
return map.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> keySet() {
|
||||||
|
return map.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<SortedSet<String>> values() {
|
||||||
|
return map.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Entry<String, SortedSet<String>>> entrySet() {
|
||||||
|
return map.entrySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SortedSet<String> put(String key, SortedSet<String> values, boolean percentEncode)
|
||||||
|
throws MalformedInputException, UnmappableCharacterException {
|
||||||
|
if (percentEncode) {
|
||||||
|
remove(key);
|
||||||
|
for (String v : values) {
|
||||||
|
add(key, v, true);
|
||||||
|
}
|
||||||
|
return get(key);
|
||||||
|
} else {
|
||||||
|
return map.put(key, values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method to add a single value for the parameter specified by 'key'.
|
||||||
|
*
|
||||||
|
* @param key the parameter name
|
||||||
|
* @param value the parameter value
|
||||||
|
* @return the value
|
||||||
|
* @throws MalformedInputException if input is malformed
|
||||||
|
* @throws UnmappableCharacterException if characters are unmappable
|
||||||
|
*/
|
||||||
|
public String add(String key, String value)
|
||||||
|
throws MalformedInputException, UnmappableCharacterException {
|
||||||
|
return add(key, value, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method to add a single value for the parameter specified by
|
||||||
|
* 'key'.
|
||||||
|
*
|
||||||
|
* @param key the parameter name
|
||||||
|
* @param value the parameter value
|
||||||
|
* @param percentEncode whether key and value should be percent encoded before being
|
||||||
|
* inserted into the map
|
||||||
|
* @return the value
|
||||||
|
* @throws MalformedInputException if input is malformed
|
||||||
|
* @throws UnmappableCharacterException if characters are unmappable
|
||||||
|
*/
|
||||||
|
public String add(String key, String value, boolean percentEncode)
|
||||||
|
throws MalformedInputException, UnmappableCharacterException {
|
||||||
|
String k = percentEncode ? percentEncoder.encode(key) : key;
|
||||||
|
SortedSet<String> values = map.get(k);
|
||||||
|
if (values == null) {
|
||||||
|
values = new LimitedSortedStringSet(sizeLimit, elementSizeLimit);
|
||||||
|
map.put(k, values);
|
||||||
|
}
|
||||||
|
String v = null;
|
||||||
|
if (value != null) {
|
||||||
|
v = percentEncode ? percentEncoder.encode(value) : value;
|
||||||
|
values.add(v);
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method to allow for storing null values. {@link #put} doesn't
|
||||||
|
* allow null values, because that would be ambiguous.
|
||||||
|
*
|
||||||
|
* @param key the parameter name
|
||||||
|
* @param nullString can be anything, but probably... null?
|
||||||
|
* @return null
|
||||||
|
* @throws MalformedInputException if input is malformed
|
||||||
|
* @throws UnmappableCharacterException if characters are unmappable
|
||||||
|
*/
|
||||||
|
public String addNull(String key, String nullString)
|
||||||
|
throws MalformedInputException, UnmappableCharacterException {
|
||||||
|
return add(key, nullString);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addAll(Map<? extends String, ? extends SortedSet<String>> m, boolean percentEncode)
|
||||||
|
throws MalformedInputException, UnmappableCharacterException {
|
||||||
|
if (percentEncode) {
|
||||||
|
for (String key : m.keySet()) {
|
||||||
|
put(key, m.get(key), true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
map.putAll(m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addAll(String[] keyValuePairs, boolean percentEncode)
|
||||||
|
throws MalformedInputException, UnmappableCharacterException {
|
||||||
|
for (int i = 0; i < keyValuePairs.length - 1; i += 2) {
|
||||||
|
add(keyValuePairs[i], keyValuePairs[i + 1], percentEncode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method to merge a {@code Map<String, List<String>>}.
|
||||||
|
*
|
||||||
|
* @param m the map
|
||||||
|
*/
|
||||||
|
public void addMap(Map<String, List<String>> m) {
|
||||||
|
for (String key : m.keySet()) {
|
||||||
|
SortedSet<String> vals = get(key);
|
||||||
|
if (vals == null) {
|
||||||
|
vals = new LimitedSortedStringSet(sizeLimit, elementSizeLimit);
|
||||||
|
put(key, vals);
|
||||||
|
}
|
||||||
|
vals.addAll(m.get(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFirst(String key) {
|
||||||
|
SortedSet<String> values = map.get(key);
|
||||||
|
if (values == null || values.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return values.first();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the first value from the set of all values for the given
|
||||||
|
* parameter name. If the key passed to this method contains special
|
||||||
|
* characters, you must first percent encode it, otherwise the lookup will fail
|
||||||
|
* (that's because upon storing values in this map, keys get
|
||||||
|
* percent-encoded).
|
||||||
|
*
|
||||||
|
* @param key the parameter name (must be percent encoded if it contains unsafe
|
||||||
|
* characters!)
|
||||||
|
* @return the first value found for this parameter
|
||||||
|
* @throws MalformedInputException if input is malformed
|
||||||
|
* @throws UnmappableCharacterException if characters are unmappable
|
||||||
|
*/
|
||||||
|
public String getFirstDecoded(String key)
|
||||||
|
throws MalformedInputException, UnmappableCharacterException {
|
||||||
|
SortedSet<String> values = map.get(key);
|
||||||
|
if (values == null || values.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String value = values.first();
|
||||||
|
return percentDecoder.decode(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Concatenates all values for the given key to a list of key/value pairs
|
||||||
|
* suitable for use in a URL query string.
|
||||||
|
*
|
||||||
|
* @param key the parameter name
|
||||||
|
* @return the query string
|
||||||
|
* @throws MalformedInputException if input is malformed
|
||||||
|
* @throws UnmappableCharacterException if characters are unmappable
|
||||||
|
*/
|
||||||
|
public String getAsQueryString(String key)
|
||||||
|
throws MalformedInputException, UnmappableCharacterException {
|
||||||
|
return getAsQueryString(key, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Concatenates all values for the given key to a list of key/value pairs
|
||||||
|
* suitable for use in a URL query string.
|
||||||
|
*
|
||||||
|
* @param key the parameter name
|
||||||
|
* @param percentEncode whether key should be percent encoded before being
|
||||||
|
* used with the map
|
||||||
|
* @return the query string
|
||||||
|
* @throws MalformedInputException if input is malformed
|
||||||
|
* @throws UnmappableCharacterException if characters are unmappable
|
||||||
|
*/
|
||||||
|
public String getAsQueryString(String key, boolean percentEncode)
|
||||||
|
throws MalformedInputException, UnmappableCharacterException {
|
||||||
|
String k = percentEncode ? percentEncoder.encode(key) : key;
|
||||||
|
SortedSet<String> values = map.get(k);
|
||||||
|
if (values == null) {
|
||||||
|
return k + "=";
|
||||||
|
}
|
||||||
|
Iterator<String> it = values.iterator();
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
sb.append(k).append("=").append(it.next());
|
||||||
|
if (it.hasNext()) {
|
||||||
|
sb.append("&");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAsHeaderElement(String key) {
|
||||||
|
String value = getFirst(key);
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return key + "=\"" + value + "\"";
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpParameters getOAuthParameters() {
|
||||||
|
HttpParameters oauthParams = new HttpParameters(maxParam, sizeLimit, elementSizeLimit);
|
||||||
|
entrySet().stream().filter(entry -> entry.getKey().startsWith("oauth_") || entry.getKey().startsWith("x_oauth_"))
|
||||||
|
.forEach(entry -> oauthParams.put(entry.getKey(), entry.getValue()));
|
||||||
|
return oauthParams;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package org.xbib.netty.http.common;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A representation of an HTTP request. Contains methods to access all those parts of an HTTP request.
|
||||||
|
*/
|
||||||
|
public interface HttpRequest {
|
||||||
|
|
||||||
|
String getMethod();
|
||||||
|
|
||||||
|
String getRequestUrl();
|
||||||
|
|
||||||
|
void setRequestUrl(String url);
|
||||||
|
|
||||||
|
void setHeader(String name, String value);
|
||||||
|
|
||||||
|
String getHeader(String name);
|
||||||
|
|
||||||
|
Map<String, String> getHeaders();
|
||||||
|
|
||||||
|
InputStream getContent() throws IOException;
|
||||||
|
|
||||||
|
String getContentType();
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package org.xbib.netty.http.common;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
public interface HttpResponse {
|
||||||
|
|
||||||
|
int getStatusCode() throws IOException;
|
||||||
|
|
||||||
|
String getReason() throws Exception;
|
||||||
|
|
||||||
|
InputStream getContent() throws IOException;
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package org.xbib.netty.http.common;
|
||||||
|
|
||||||
|
import io.netty.handler.codec.http2.Http2SecurityUtil;
|
||||||
|
import io.netty.handler.ssl.CipherSuiteFilter;
|
||||||
|
import io.netty.handler.ssl.OpenSsl;
|
||||||
|
import io.netty.handler.ssl.SslProvider;
|
||||||
|
import io.netty.handler.ssl.SupportedCipherSuiteFilter;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
|
import javax.net.ssl.TrustManagerFactory;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class SecurityUtil {
|
||||||
|
|
||||||
|
private static TrustManagerFactory TRUST_MANAGER_FACTORY;
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
TRUST_MANAGER_FACTORY = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||||
|
} catch (Exception e) {
|
||||||
|
TRUST_MANAGER_FACTORY = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Defaults {
|
||||||
|
|
||||||
|
List<String> OPENSSL_CIPHERS = Http2SecurityUtil.CIPHERS;
|
||||||
|
|
||||||
|
List<String> JDK_CIPHERS =
|
||||||
|
Arrays.asList(((SSLSocketFactory) SSLSocketFactory.getDefault()).getDefaultCipherSuites());
|
||||||
|
|
||||||
|
|
||||||
|
TrustManagerFactory DEFAULT_TRUST_MANAGER_FACTORY = TRUST_MANAGER_FACTORY;
|
||||||
|
/**
|
||||||
|
* Default SSL provider.
|
||||||
|
*/
|
||||||
|
SslProvider DEFAULT_SSL_PROVIDER = OpenSsl.isAvailable() ? SslProvider.OPENSSL : SslProvider.JDK;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default ciphers.
|
||||||
|
*/
|
||||||
|
Iterable<String> DEFAULT_CIPHERS = OpenSsl.isAvailable() ? OPENSSL_CIPHERS : JDK_CIPHERS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default cipher suite filter.
|
||||||
|
*/
|
||||||
|
CipherSuiteFilter DEFAULT_CIPHER_SUITE_FILTER = SupportedCipherSuiteFilter.INSTANCE;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package org.xbib.netty.http.common.util;
|
||||||
|
|
||||||
|
import java.util.SortedSet;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public class LimitedSortedStringSet extends TreeSet<String> implements SortedSet<String> {
|
||||||
|
|
||||||
|
private final int sizeLimit;
|
||||||
|
|
||||||
|
private final int elementSizeLimit;
|
||||||
|
|
||||||
|
public LimitedSortedStringSet() {
|
||||||
|
this(1024, 65536);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LimitedSortedStringSet(int sizeLimit, int elementSizeLimit) {
|
||||||
|
this.sizeLimit = sizeLimit;
|
||||||
|
this.elementSizeLimit = elementSizeLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean add(String string) {
|
||||||
|
if (size() < sizeLimit && string.length() <= elementSizeLimit ) {
|
||||||
|
return super.add(string);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package org.xbib.netty.http.common.util;
|
||||||
|
|
||||||
|
import java.util.SortedSet;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public class LimitedStringMap extends TreeMap<String, SortedSet<String>> {
|
||||||
|
|
||||||
|
private final int limit;
|
||||||
|
|
||||||
|
public LimitedStringMap(int limit) {
|
||||||
|
this.limit = limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SortedSet<String> put(String key, SortedSet<String> value) {
|
||||||
|
if (size() < limit) {
|
||||||
|
return super.put(key, value);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ import io.netty.buffer.ByteBufAllocator;
|
||||||
import io.netty.channel.ChannelFuture;
|
import io.netty.channel.ChannelFuture;
|
||||||
import io.netty.channel.ChannelOption;
|
import io.netty.channel.ChannelOption;
|
||||||
import io.netty.channel.EventLoopGroup;
|
import io.netty.channel.EventLoopGroup;
|
||||||
|
import io.netty.channel.WriteBufferWaterMark;
|
||||||
import io.netty.channel.epoll.Epoll;
|
import io.netty.channel.epoll.Epoll;
|
||||||
import io.netty.channel.epoll.EpollEventLoopGroup;
|
import io.netty.channel.epoll.EpollEventLoopGroup;
|
||||||
import io.netty.channel.epoll.EpollServerSocketChannel;
|
import io.netty.channel.epoll.EpollServerSocketChannel;
|
||||||
|
@ -13,28 +14,24 @@ import io.netty.channel.socket.ServerSocketChannel;
|
||||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||||
import io.netty.handler.codec.http.HttpVersion;
|
import io.netty.handler.codec.http.HttpVersion;
|
||||||
import io.netty.handler.logging.LoggingHandler;
|
import io.netty.handler.logging.LoggingHandler;
|
||||||
import io.netty.handler.ssl.ApplicationProtocolConfig;
|
|
||||||
import io.netty.handler.ssl.ApplicationProtocolNames;
|
|
||||||
import io.netty.handler.ssl.OpenSsl;
|
import io.netty.handler.ssl.OpenSsl;
|
||||||
import io.netty.handler.ssl.SslContext;
|
import io.netty.handler.ssl.SslContext;
|
||||||
import io.netty.handler.ssl.SslContextBuilder;
|
|
||||||
import io.netty.util.DomainNameMapping;
|
import io.netty.util.DomainNameMapping;
|
||||||
import io.netty.util.DomainNameMappingBuilder;
|
import io.netty.util.DomainNameMappingBuilder;
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
import org.xbib.netty.http.common.HttpAddress;
|
||||||
|
import org.xbib.netty.http.common.NetworkUtils;
|
||||||
import org.xbib.netty.http.server.endpoint.NamedServer;
|
import org.xbib.netty.http.server.endpoint.NamedServer;
|
||||||
import org.xbib.netty.http.server.handler.http.HttpChannelInitializer;
|
import org.xbib.netty.http.server.handler.http.HttpChannelInitializer;
|
||||||
import org.xbib.netty.http.server.handler.http2.Http2ChannelInitializer;
|
import org.xbib.netty.http.server.handler.http2.Http2ChannelInitializer;
|
||||||
|
import org.xbib.netty.http.common.SecurityUtil;
|
||||||
|
import org.xbib.netty.http.server.transport.Http2ServerResponse;
|
||||||
|
import org.xbib.netty.http.server.transport.HttpServerRequest;
|
||||||
|
import org.xbib.netty.http.server.transport.HttpServerResponse;
|
||||||
import org.xbib.netty.http.server.transport.HttpServerTransport;
|
import org.xbib.netty.http.server.transport.HttpServerTransport;
|
||||||
import org.xbib.netty.http.server.transport.Http2ServerTransport;
|
import org.xbib.netty.http.server.transport.Http2ServerTransport;
|
||||||
import org.xbib.netty.http.server.transport.ServerTransport;
|
import org.xbib.netty.http.server.transport.ServerTransport;
|
||||||
import org.xbib.netty.http.server.util.NetworkUtils;
|
|
||||||
|
|
||||||
import javax.net.ssl.SSLException;
|
|
||||||
import javax.net.ssl.TrustManagerFactory;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.KeyStoreException;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.ThreadFactory;
|
import java.util.concurrent.ThreadFactory;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
@ -73,27 +70,23 @@ public final class Server {
|
||||||
|
|
||||||
private final ServerBootstrap bootstrap;
|
private final ServerBootstrap bootstrap;
|
||||||
|
|
||||||
private final Map<String, NamedServer> virtualServerMap;
|
|
||||||
|
|
||||||
private ChannelFuture channelFuture;
|
private ChannelFuture channelFuture;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new HTTP server. Use {@link #builder()} to build HTTP client instance.
|
* Create a new HTTP server. Use {@link #builder(HttpAddress)} to build HTTP client instance.
|
||||||
* @param serverConfig server configuration
|
* @param serverConfig server configuration
|
||||||
* @param byteBufAllocator byte buf allocator
|
* @param byteBufAllocator byte buf allocator
|
||||||
* @param parentEventLoopGroup parent event loop group
|
* @param parentEventLoopGroup parent event loop group
|
||||||
* @param childEventLoopGroup child event loop group
|
* @param childEventLoopGroup child event loop group
|
||||||
* @param socketChannelClass socket channel class
|
* @param socketChannelClass socket channel class
|
||||||
* @throws SSLException if SSL can not be configured
|
|
||||||
*/
|
*/
|
||||||
public Server(ServerConfig serverConfig,
|
private Server(ServerConfig serverConfig,
|
||||||
ByteBufAllocator byteBufAllocator,
|
ByteBufAllocator byteBufAllocator,
|
||||||
EventLoopGroup parentEventLoopGroup,
|
EventLoopGroup parentEventLoopGroup,
|
||||||
EventLoopGroup childEventLoopGroup,
|
EventLoopGroup childEventLoopGroup,
|
||||||
Class<? extends ServerSocketChannel> socketChannelClass) throws SSLException {
|
Class<? extends ServerSocketChannel> socketChannelClass) {
|
||||||
Objects.requireNonNull(serverConfig);
|
Objects.requireNonNull(serverConfig);
|
||||||
this.serverConfig = serverConfig;
|
this.serverConfig = serverConfig;
|
||||||
initializeTrustManagerFactory(serverConfig);
|
|
||||||
this.byteBufAllocator = byteBufAllocator != null ?
|
this.byteBufAllocator = byteBufAllocator != null ?
|
||||||
byteBufAllocator : ByteBufAllocator.DEFAULT;
|
byteBufAllocator : ByteBufAllocator.DEFAULT;
|
||||||
this.parentEventLoopGroup = createParentEventLoopGroup(serverConfig, parentEventLoopGroup);
|
this.parentEventLoopGroup = createParentEventLoopGroup(serverConfig, parentEventLoopGroup);
|
||||||
|
@ -117,45 +110,28 @@ public final class Server {
|
||||||
if (serverConfig.isDebug()) {
|
if (serverConfig.isDebug()) {
|
||||||
bootstrap.handler(new LoggingHandler("bootstrap-server", serverConfig.getDebugLogLevel()));
|
bootstrap.handler(new LoggingHandler("bootstrap-server", serverConfig.getDebugLogLevel()));
|
||||||
}
|
}
|
||||||
this.virtualServerMap = new HashMap<>();
|
DomainNameMapping<SslContext> domainNameMapping = createDomainNameMapping();
|
||||||
for (NamedServer namedServer : serverConfig.getNamedServers()) {
|
if (serverConfig.getAddress().getVersion().majorVersion() == 1) {
|
||||||
String name = namedServer.getName();
|
|
||||||
virtualServerMap.put(name, namedServer);
|
|
||||||
for (String alias : namedServer.getAliases()) {
|
|
||||||
virtualServerMap.put(alias, namedServer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DomainNameMapping<SslContext> domainNameMapping = null;
|
|
||||||
if (serverConfig.getAddress().isSecure()) {
|
|
||||||
SslContextBuilder sslContextBuilder = SslContextBuilder.forServer(serverConfig.getKeyCertChainInputStream(),
|
|
||||||
serverConfig.getKeyInputStream(), serverConfig.getKeyPassword())
|
|
||||||
.sslProvider(serverConfig.getSslProvider())
|
|
||||||
.ciphers(serverConfig.getCiphers(), serverConfig.getCipherSuiteFilter());
|
|
||||||
if (serverConfig.getAddress().getVersion().majorVersion() == 2) {
|
|
||||||
sslContextBuilder.applicationProtocolConfig(newApplicationProtocolConfig());
|
|
||||||
}
|
|
||||||
SslContext sslContext = sslContextBuilder.build();
|
|
||||||
DomainNameMappingBuilder<SslContext> mappingBuilder = new DomainNameMappingBuilder<>(sslContext);
|
|
||||||
for (NamedServer namedServer : serverConfig.getNamedServers()) {
|
|
||||||
String name = namedServer.getName();
|
|
||||||
mappingBuilder.add(name == null ? "*" : name, sslContext);
|
|
||||||
}
|
|
||||||
domainNameMapping = mappingBuilder.build();
|
|
||||||
}
|
|
||||||
HttpAddress httpAddress = serverConfig.getAddress();
|
|
||||||
if (httpAddress.getVersion().majorVersion() == 1) {
|
|
||||||
HttpChannelInitializer httpChannelInitializer = new HttpChannelInitializer(this,
|
HttpChannelInitializer httpChannelInitializer = new HttpChannelInitializer(this,
|
||||||
httpAddress, domainNameMapping);
|
serverConfig.getAddress(), domainNameMapping);
|
||||||
bootstrap.childHandler(httpChannelInitializer);
|
bootstrap.childHandler(httpChannelInitializer);
|
||||||
} else {
|
} else {
|
||||||
Http2ChannelInitializer initializer = new Http2ChannelInitializer(this,
|
Http2ChannelInitializer http2ChannelInitializer = new Http2ChannelInitializer(this,
|
||||||
httpAddress, domainNameMapping);
|
serverConfig.getAddress(), domainNameMapping);
|
||||||
bootstrap.childHandler(initializer);
|
bootstrap.childHandler(http2ChannelInitializer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ServerBuilder builder() {
|
public static Builder builder() {
|
||||||
return new ServerBuilder();
|
return new Builder(HttpAddress.http1("localhost", 8008));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Builder builder(HttpAddress httpAddress) {
|
||||||
|
return new Builder(httpAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Builder builder(NamedServer namedServer) {
|
||||||
|
return new Builder(namedServer.getHttpAddress(), namedServer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServerConfig getServerConfig() {
|
public ServerConfig getServerConfig() {
|
||||||
|
@ -163,18 +139,18 @@ public final class Server {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the virtual host with the given name.
|
* Returns the named server with the given name.
|
||||||
*
|
*
|
||||||
* @param name the name of the virtual host to return, or null for
|
* @param name the name of the virtual host to return, or null for
|
||||||
* the default virtual host
|
* the default virtual host
|
||||||
* @return the virtual host with the given name, or null if it doesn't exist
|
* @return the virtual host with the given name, or null if it doesn't exist
|
||||||
*/
|
*/
|
||||||
public NamedServer getVirtualServer(String name) {
|
public NamedServer getNamedServer(String name) {
|
||||||
return virtualServerMap.get(name);
|
return serverConfig.getNamedServers().get(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public NamedServer getDefaultVirtualServer() {
|
public NamedServer getDefaultNamedServer() {
|
||||||
return virtualServerMap.get(null);
|
return serverConfig.getDefaultNamedServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -189,16 +165,29 @@ public final class Server {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void logDiagnostics(Level level) {
|
public void logDiagnostics(Level level) {
|
||||||
logger.log(level, () -> "OpenSSL available: " + OpenSsl.isAvailable() +
|
logger.log(level, () -> "JDK ciphers: " + SecurityUtil.Defaults.JDK_CIPHERS);
|
||||||
" OpenSSL ALPN support: " + OpenSsl.isAlpnSupported() +
|
logger.log(level, () -> "OpenSSL ciphers: " + SecurityUtil.Defaults.OPENSSL_CIPHERS);
|
||||||
" Local host name: " + NetworkUtils.getLocalHostName("localhost") +
|
logger.log(level, () -> "OpenSSL available: " + OpenSsl.isAvailable());
|
||||||
" parent event loop group: " + parentEventLoopGroup +
|
logger.log(level, () -> "OpenSSL ALPN support: " + OpenSsl.isAlpnSupported());
|
||||||
" child event loop group: " + childEventLoopGroup +
|
logger.log(level, () -> "Installed ciphers on default server: " +
|
||||||
" socket: " + socketChannelClass.getName() +
|
(serverConfig.getAddress().isSecure() ? getDefaultNamedServer().getSslContext().cipherSuites() : ""));
|
||||||
" allocator: " + byteBufAllocator.getClass().getName());
|
logger.log(level, () -> "Local host name: " + NetworkUtils.getLocalHostName("localhost"));
|
||||||
|
logger.log(level, () -> "Parent event loop group: " + parentEventLoopGroup + " threads=" + serverConfig.getParentThreadCount());
|
||||||
|
logger.log(level, () -> "Child event loop group: " + childEventLoopGroup + " threads=" +serverConfig.getChildThreadCount());
|
||||||
|
logger.log(level, () -> "Socket: " + socketChannelClass.getName());
|
||||||
|
logger.log(level, () -> "Allocator: " + byteBufAllocator.getClass().getName());
|
||||||
logger.log(level, NetworkUtils::displayNetworkInterfaces);
|
logger.log(level, NetworkUtils::displayNetworkInterfaces);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ServerRequest newRequest() {
|
||||||
|
return new HttpServerRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerResponse newResponse(ServerRequest serverRequest) {
|
||||||
|
return serverRequest.getNamedServer().getHttpAddress().getVersion().majorVersion() == 1 ?
|
||||||
|
new HttpServerResponse(serverRequest) : new Http2ServerResponse(serverRequest);
|
||||||
|
}
|
||||||
|
|
||||||
public ServerTransport newTransport(HttpVersion httpVersion) {
|
public ServerTransport newTransport(HttpVersion httpVersion) {
|
||||||
return httpVersion.majorVersion() == 1 ? new HttpServerTransport(this) : new Http2ServerTransport(this);
|
return httpVersion.majorVersion() == 1 ? new HttpServerTransport(this) : new Http2ServerTransport(this);
|
||||||
}
|
}
|
||||||
|
@ -249,27 +238,23 @@ public final class Server {
|
||||||
return channelClass;
|
return channelClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private DomainNameMapping<SslContext> createDomainNameMapping() {
|
||||||
* Initialize trust manager factory once per server lifecycle.
|
if (serverConfig.getDefaultNamedServer() == null) {
|
||||||
* @param serverConfig the server config
|
throw new IllegalStateException("no default named server (with name '*') configured, unable to continue");
|
||||||
*/
|
}
|
||||||
private static void initializeTrustManagerFactory(ServerConfig serverConfig) {
|
DomainNameMapping<SslContext> domainNameMapping = null;
|
||||||
TrustManagerFactory trustManagerFactory = serverConfig.getTrustManagerFactory();
|
if (serverConfig.getAddress().isSecure() && serverConfig.getDefaultNamedServer().getSslContext() != null) {
|
||||||
if (trustManagerFactory != null) {
|
DomainNameMappingBuilder<SslContext> mappingBuilder =
|
||||||
try {
|
new DomainNameMappingBuilder<>(serverConfig.getDefaultNamedServer().getSslContext());
|
||||||
trustManagerFactory.init(serverConfig.getTrustManagerKeyStore());
|
for (NamedServer namedServer : serverConfig.getNamedServers().values()) {
|
||||||
} catch (KeyStoreException e) {
|
String name = namedServer.getName();
|
||||||
logger.log(Level.WARNING, e.getMessage(), e);
|
if (!"*".equals(name)) {
|
||||||
|
mappingBuilder.add(name, namedServer.getSslContext());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
domainNameMapping = mappingBuilder.build();
|
||||||
}
|
}
|
||||||
|
return domainNameMapping;
|
||||||
private static ApplicationProtocolConfig newApplicationProtocolConfig() {
|
|
||||||
return new ApplicationProtocolConfig(ApplicationProtocolConfig.Protocol.ALPN,
|
|
||||||
ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE,
|
|
||||||
ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT,
|
|
||||||
ApplicationProtocolNames.HTTP_2,
|
|
||||||
ApplicationProtocolNames.HTTP_1_1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static class HttpServerParentThreadFactory implements ThreadFactory {
|
static class HttpServerParentThreadFactory implements ThreadFactory {
|
||||||
|
@ -295,4 +280,165 @@ public final class Server {
|
||||||
return thread;
|
return thread;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP server builder.
|
||||||
|
*/
|
||||||
|
public static class Builder {
|
||||||
|
|
||||||
|
private ByteBufAllocator byteBufAllocator;
|
||||||
|
|
||||||
|
private EventLoopGroup parentEventLoopGroup;
|
||||||
|
|
||||||
|
private EventLoopGroup childEventLoopGroup;
|
||||||
|
|
||||||
|
private Class<? extends ServerSocketChannel> socketChannelClass;
|
||||||
|
|
||||||
|
private ServerConfig serverConfig;
|
||||||
|
|
||||||
|
Builder(HttpAddress httpAddress) {
|
||||||
|
this(httpAddress, NamedServer.builder(httpAddress, "*").build());
|
||||||
|
}
|
||||||
|
|
||||||
|
Builder(HttpAddress httpAddress, NamedServer defaultNamedServer) {
|
||||||
|
this.serverConfig = new ServerConfig();
|
||||||
|
this.serverConfig.setAddress(httpAddress);
|
||||||
|
this.serverConfig.add(defaultNamedServer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder enableDebug() {
|
||||||
|
this.serverConfig.enableDebug();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setByteBufAllocator(ByteBufAllocator byteBufAllocator) {
|
||||||
|
this.byteBufAllocator = byteBufAllocator;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setParentEventLoopGroup(EventLoopGroup parentEventLoopGroup) {
|
||||||
|
this.parentEventLoopGroup = parentEventLoopGroup;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setChildEventLoopGroup(EventLoopGroup childEventLoopGroup) {
|
||||||
|
this.childEventLoopGroup = childEventLoopGroup;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setChannelClass(Class<? extends ServerSocketChannel> socketChannelClass) {
|
||||||
|
this.socketChannelClass = socketChannelClass;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setUseEpoll(boolean useEpoll) {
|
||||||
|
this.serverConfig.setEpoll(useEpoll);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setConnectTimeoutMillis(int connectTimeoutMillis) {
|
||||||
|
this.serverConfig.setConnectTimeoutMillis(connectTimeoutMillis);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setParentThreadCount(int parentThreadCount) {
|
||||||
|
this.serverConfig.setParentThreadCount(parentThreadCount);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setChildThreadCount(int childThreadCount) {
|
||||||
|
this.serverConfig.setChildThreadCount(childThreadCount);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setTcpSendBufferSize(int tcpSendBufferSize) {
|
||||||
|
this.serverConfig.setTcpSendBufferSize(tcpSendBufferSize);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setTcpReceiveBufferSize(int tcpReceiveBufferSize) {
|
||||||
|
this.serverConfig.setTcpReceiveBufferSize(tcpReceiveBufferSize);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setTcpNoDelay(boolean tcpNoDelay) {
|
||||||
|
this.serverConfig.setTcpNodelay(tcpNoDelay);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setReuseAddr(boolean reuseAddr) {
|
||||||
|
this.serverConfig.setReuseAddr(reuseAddr);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setBacklogSize(int backlogSize) {
|
||||||
|
this.serverConfig.setBackLogSize(backlogSize);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setMaxChunkSize(int maxChunkSize) {
|
||||||
|
this.serverConfig.setMaxChunkSize(maxChunkSize);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setMaxInitialLineLength(int maxInitialLineLength) {
|
||||||
|
this.serverConfig.setMaxInitialLineLength(maxInitialLineLength);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setMaxHeadersSize(int maxHeadersSize) {
|
||||||
|
this.serverConfig.setMaxHeadersSize(maxHeadersSize);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setMaxContentLength(int maxContentLength) {
|
||||||
|
this.serverConfig.setMaxContentLength(maxContentLength);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setMaxCompositeBufferComponents(int maxCompositeBufferComponents) {
|
||||||
|
this.serverConfig.setMaxCompositeBufferComponents(maxCompositeBufferComponents);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setReadTimeoutMillis(int readTimeoutMillis) {
|
||||||
|
this.serverConfig.setReadTimeoutMillis(readTimeoutMillis);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setConnectionTimeoutMillis(int connectionTimeoutMillis) {
|
||||||
|
this.serverConfig.setConnectTimeoutMillis(connectionTimeoutMillis);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setIdleTimeoutMillis(int idleTimeoutMillis) {
|
||||||
|
this.serverConfig.setIdleTimeoutMillis(idleTimeoutMillis);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setWriteBufferWaterMark(WriteBufferWaterMark writeBufferWaterMark) {
|
||||||
|
this.serverConfig.setWriteBufferWaterMark(writeBufferWaterMark);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setEnableGzip(boolean enableGzip) {
|
||||||
|
this.serverConfig.setEnableGzip(enableGzip);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setInstallHttp2Upgrade(boolean installHttp2Upgrade) {
|
||||||
|
this.serverConfig.setInstallHttp2Upgrade(installHttp2Upgrade);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder addServer(NamedServer namedServer) {
|
||||||
|
this.serverConfig.add(namedServer);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Server build() {
|
||||||
|
return new Server(serverConfig, byteBufAllocator, parentEventLoopGroup, childEventLoopGroup, socketChannelClass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,244 +0,0 @@
|
||||||
package org.xbib.netty.http.server;
|
|
||||||
|
|
||||||
import io.netty.buffer.ByteBufAllocator;
|
|
||||||
import io.netty.channel.EventLoopGroup;
|
|
||||||
import io.netty.channel.WriteBufferWaterMark;
|
|
||||||
import io.netty.channel.socket.ServerSocketChannel;
|
|
||||||
import io.netty.handler.ssl.CipherSuiteFilter;
|
|
||||||
import io.netty.handler.ssl.SslProvider;
|
|
||||||
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
|
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
|
||||||
import org.xbib.netty.http.server.endpoint.Handler;
|
|
||||||
import org.xbib.netty.http.server.endpoint.NamedServer;
|
|
||||||
import org.xbib.netty.http.server.security.tls.SelfSignedCertificate;
|
|
||||||
|
|
||||||
import javax.net.ssl.SSLException;
|
|
||||||
import javax.net.ssl.TrustManagerFactory;
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* HTTP server builder.
|
|
||||||
*/
|
|
||||||
public class ServerBuilder {
|
|
||||||
|
|
||||||
private ByteBufAllocator byteBufAllocator;
|
|
||||||
|
|
||||||
private EventLoopGroup parentEventLoopGroup;
|
|
||||||
|
|
||||||
private EventLoopGroup childEventLoopGroup;
|
|
||||||
|
|
||||||
private Class<? extends ServerSocketChannel> socketChannelClass;
|
|
||||||
|
|
||||||
private ServerConfig serverConfig;
|
|
||||||
|
|
||||||
public ServerBuilder() {
|
|
||||||
this.serverConfig = new ServerConfig();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerBuilder enableDebug() {
|
|
||||||
this.serverConfig.enableDebug();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerBuilder bind(HttpAddress httpAddress) {
|
|
||||||
this.serverConfig.setAddress(httpAddress);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerBuilder host(String bindhost, int bindPort) {
|
|
||||||
this.serverConfig.setAddress(HttpAddress.http2(bindhost, bindPort));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerBuilder port(int bindPort) {
|
|
||||||
this.serverConfig.setAddress(HttpAddress.http2(null, bindPort));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerBuilder setByteBufAllocator(ByteBufAllocator byteBufAllocator) {
|
|
||||||
this.byteBufAllocator = byteBufAllocator;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerBuilder setParentEventLoopGroup(EventLoopGroup parentEventLoopGroup) {
|
|
||||||
this.parentEventLoopGroup = parentEventLoopGroup;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerBuilder setChildEventLoopGroup(EventLoopGroup childEventLoopGroup) {
|
|
||||||
this.childEventLoopGroup = childEventLoopGroup;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerBuilder setChannelClass(Class<? extends ServerSocketChannel> socketChannelClass) {
|
|
||||||
this.socketChannelClass = socketChannelClass;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerBuilder setUseEpoll(boolean useEpoll) {
|
|
||||||
this.serverConfig.setEpoll(useEpoll);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerBuilder setConnectTimeoutMillis(int connectTimeoutMillis) {
|
|
||||||
this.serverConfig.setConnectTimeoutMillis(connectTimeoutMillis);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerBuilder setParentThreadCount(int parentThreadCount) {
|
|
||||||
this.serverConfig.setParentThreadCount(parentThreadCount);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerBuilder setChildThreadCount(int childThreadCount) {
|
|
||||||
this.serverConfig.setChildThreadCount(childThreadCount);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerBuilder setTcpSendBufferSize(int tcpSendBufferSize) {
|
|
||||||
this.serverConfig.setTcpSendBufferSize(tcpSendBufferSize);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerBuilder setTcpReceiveBufferSize(int tcpReceiveBufferSize) {
|
|
||||||
this.serverConfig.setTcpReceiveBufferSize(tcpReceiveBufferSize);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerBuilder setTcpNoDelay(boolean tcpNoDelay) {
|
|
||||||
this.serverConfig.setTcpNodelay(tcpNoDelay);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerBuilder setReuseAddr(boolean reuseAddr) {
|
|
||||||
this.serverConfig.setReuseAddr(reuseAddr);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerBuilder setBacklogSize(int backlogSize) {
|
|
||||||
this.serverConfig.setBackLogSize(backlogSize);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerBuilder setMaxChunkSize(int maxChunkSize) {
|
|
||||||
this.serverConfig.setMaxChunkSize(maxChunkSize);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerBuilder setMaxInitialLineLength(int maxInitialLineLength) {
|
|
||||||
this.serverConfig.setMaxInitialLineLength(maxInitialLineLength);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerBuilder setMaxHeadersSize(int maxHeadersSize) {
|
|
||||||
this.serverConfig.setMaxHeadersSize(maxHeadersSize);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerBuilder setMaxContentLength(int maxContentLength) {
|
|
||||||
this.serverConfig.setMaxContentLength(maxContentLength);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerBuilder setMaxCompositeBufferComponents(int maxCompositeBufferComponents) {
|
|
||||||
this.serverConfig.setMaxCompositeBufferComponents(maxCompositeBufferComponents);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerBuilder setReadTimeoutMillis(int readTimeoutMillis) {
|
|
||||||
this.serverConfig.setReadTimeoutMillis(readTimeoutMillis);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerBuilder setConnectionTimeoutMillis(int connectionTimeoutMillis) {
|
|
||||||
this.serverConfig.setConnectTimeoutMillis(connectionTimeoutMillis);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerBuilder setIdleTimeoutMillis(int idleTimeoutMillis) {
|
|
||||||
this.serverConfig.setIdleTimeoutMillis(idleTimeoutMillis);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerBuilder setWriteBufferWaterMark(WriteBufferWaterMark writeBufferWaterMark) {
|
|
||||||
this.serverConfig.setWriteBufferWaterMark(writeBufferWaterMark);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerBuilder setEnableGzip(boolean enableGzip) {
|
|
||||||
this.serverConfig.setEnableGzip(enableGzip);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerBuilder setInstallHttp2Upgrade(boolean installHttp2Upgrade) {
|
|
||||||
this.serverConfig.setInstallHttp2Upgrade(installHttp2Upgrade);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerBuilder setSslProvider(SslProvider sslProvider) {
|
|
||||||
this.serverConfig.setSslProvider(sslProvider);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerBuilder setJdkSslProvider() {
|
|
||||||
this.serverConfig.setSslProvider(SslProvider.JDK);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerBuilder setOpenSSLSslProvider() {
|
|
||||||
this.serverConfig.setSslProvider(SslProvider.OPENSSL);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerBuilder setCiphers(Iterable<String> ciphers) {
|
|
||||||
this.serverConfig.setCiphers(ciphers);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerBuilder setCipherSuiteFilter(CipherSuiteFilter cipherSuiteFilter) {
|
|
||||||
this.serverConfig.setCipherSuiteFilter(cipherSuiteFilter);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerBuilder setKeyCert(InputStream keyCertChainInputStream, InputStream keyInputStream) {
|
|
||||||
this.serverConfig.setKeyCertChainInputStream(keyCertChainInputStream);
|
|
||||||
this.serverConfig.setKeyInputStream(keyInputStream);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerBuilder setKeyCert(InputStream keyCertChainInputStream, InputStream keyInputStream,
|
|
||||||
String keyPassword) {
|
|
||||||
this.serverConfig.setKeyCertChainInputStream(keyCertChainInputStream);
|
|
||||||
this.serverConfig.setKeyInputStream(keyInputStream);
|
|
||||||
this.serverConfig.setKeyPassword(keyPassword);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerBuilder setSelfCert() throws Exception {
|
|
||||||
TrustManagerFactory trustManagerFactory = InsecureTrustManagerFactory.INSTANCE;
|
|
||||||
this.serverConfig.setTrustManagerFactory(trustManagerFactory);
|
|
||||||
String hostName = serverConfig.getAddress().getInetSocketAddress().getHostString();
|
|
||||||
SelfSignedCertificate selfSignedCertificate = new SelfSignedCertificate(hostName);
|
|
||||||
this.serverConfig.setKeyCertChainInputStream(selfSignedCertificate.certificate());
|
|
||||||
this.serverConfig.setKeyInputStream(selfSignedCertificate.privateKey());
|
|
||||||
this.serverConfig.setKeyPassword(null);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerBuilder addServer(NamedServer namedServer) {
|
|
||||||
this.serverConfig.add(namedServer);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerBuilder addHandler(String path, Handler handler, String... methods) {
|
|
||||||
if (serverConfig.getNamedServers().isEmpty()) {
|
|
||||||
serverConfig.add(new NamedServer());
|
|
||||||
}
|
|
||||||
serverConfig.getNamedServers().getLast().addHandler(path, handler, methods);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Server build() throws SSLException {
|
|
||||||
return new Server(serverConfig, byteBufAllocator, parentEventLoopGroup, childEventLoopGroup, socketChannelClass);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,23 +2,13 @@ package org.xbib.netty.http.server;
|
||||||
|
|
||||||
import io.netty.channel.WriteBufferWaterMark;
|
import io.netty.channel.WriteBufferWaterMark;
|
||||||
import io.netty.channel.epoll.Epoll;
|
import io.netty.channel.epoll.Epoll;
|
||||||
import io.netty.handler.codec.http2.Http2SecurityUtil;
|
|
||||||
import io.netty.handler.codec.http2.Http2Settings;
|
import io.netty.handler.codec.http2.Http2Settings;
|
||||||
import io.netty.handler.logging.LogLevel;
|
import io.netty.handler.logging.LogLevel;
|
||||||
import io.netty.handler.ssl.CipherSuiteFilter;
|
|
||||||
import io.netty.handler.ssl.OpenSsl;
|
|
||||||
import io.netty.handler.ssl.SslProvider;
|
|
||||||
import io.netty.handler.ssl.SupportedCipherSuiteFilter;
|
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
import org.xbib.netty.http.common.HttpAddress;
|
||||||
import org.xbib.netty.http.server.endpoint.NamedServer;
|
import org.xbib.netty.http.server.endpoint.NamedServer;
|
||||||
|
|
||||||
import javax.net.ssl.TrustManagerFactory;
|
import java.util.LinkedHashMap;
|
||||||
import java.io.InputStream;
|
import java.util.Map;
|
||||||
import java.security.KeyStore;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Deque;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class ServerConfig {
|
public class ServerConfig {
|
||||||
|
|
||||||
|
@ -143,30 +133,6 @@ public class ServerConfig {
|
||||||
*/
|
*/
|
||||||
boolean INSTALL_HTTP_UPGRADE2 = false;
|
boolean INSTALL_HTTP_UPGRADE2 = false;
|
||||||
|
|
||||||
/**
|
|
||||||
* Default SSL provider.
|
|
||||||
*/
|
|
||||||
SslProvider DEFAULT_SSL_PROVIDER = OpenSsl.isAlpnSupported() ? SslProvider.OPENSSL : SslProvider.JDK;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default ciphers.
|
|
||||||
*/
|
|
||||||
Iterable<String> DEFAULT_CIPHERS = Http2SecurityUtil.CIPHERS;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default cipher suite filter.
|
|
||||||
*/
|
|
||||||
CipherSuiteFilter DEFAULT_CIPHER_SUITE_FILTER = SupportedCipherSuiteFilter.INSTANCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static TrustManagerFactory TRUST_MANAGER_FACTORY;
|
|
||||||
|
|
||||||
static {
|
|
||||||
try {
|
|
||||||
TRUST_MANAGER_FACTORY = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
|
||||||
} catch (Exception e) {
|
|
||||||
TRUST_MANAGER_FACTORY = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private HttpAddress httpAddress = Defaults.ADDRESS;
|
private HttpAddress httpAddress = Defaults.ADDRESS;
|
||||||
|
@ -215,27 +181,10 @@ public class ServerConfig {
|
||||||
|
|
||||||
private boolean installHttp2Upgrade = Defaults.INSTALL_HTTP_UPGRADE2;
|
private boolean installHttp2Upgrade = Defaults.INSTALL_HTTP_UPGRADE2;
|
||||||
|
|
||||||
private SslProvider sslProvider = Defaults.DEFAULT_SSL_PROVIDER;
|
private Map<String, NamedServer> namedServers;
|
||||||
|
|
||||||
private Iterable<String> ciphers = Defaults.DEFAULT_CIPHERS;
|
|
||||||
|
|
||||||
private CipherSuiteFilter cipherSuiteFilter = Defaults.DEFAULT_CIPHER_SUITE_FILTER;
|
|
||||||
|
|
||||||
private InputStream keyCertChainInputStream;
|
|
||||||
|
|
||||||
private InputStream keyInputStream;
|
|
||||||
|
|
||||||
private String keyPassword;
|
|
||||||
|
|
||||||
private Deque<NamedServer> namedServers;
|
|
||||||
|
|
||||||
private TrustManagerFactory trustManagerFactory = TRUST_MANAGER_FACTORY;
|
|
||||||
|
|
||||||
private KeyStore trustManagerKeyStore = null;
|
|
||||||
|
|
||||||
public ServerConfig() {
|
public ServerConfig() {
|
||||||
this.namedServers = new LinkedList<>();
|
this.namedServers = new LinkedHashMap<>();
|
||||||
add(new NamedServer(null));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServerConfig enableDebug() {
|
public ServerConfig enableDebug() {
|
||||||
|
@ -460,84 +409,20 @@ public class ServerConfig {
|
||||||
return http2Settings;
|
return http2Settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServerConfig setSslProvider(SslProvider sslProvider) {
|
|
||||||
this.sslProvider = sslProvider;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SslProvider getSslProvider() {
|
|
||||||
return sslProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerConfig setCiphers(Iterable<String> ciphers) {
|
|
||||||
this.ciphers = ciphers;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Iterable<String> getCiphers() {
|
|
||||||
return ciphers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerConfig setCipherSuiteFilter(CipherSuiteFilter cipherSuiteFilter) {
|
|
||||||
this.cipherSuiteFilter = cipherSuiteFilter;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CipherSuiteFilter getCipherSuiteFilter() {
|
|
||||||
return cipherSuiteFilter;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerConfig setKeyCertChainInputStream(InputStream keyCertChainInputStream) {
|
|
||||||
this.keyCertChainInputStream = keyCertChainInputStream;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public InputStream getKeyCertChainInputStream() {
|
|
||||||
return keyCertChainInputStream;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerConfig setKeyInputStream(InputStream keyInputStream) {
|
|
||||||
this.keyInputStream = keyInputStream;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public InputStream getKeyInputStream() {
|
|
||||||
return keyInputStream;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerConfig setKeyPassword(String keyPassword) {
|
|
||||||
this.keyPassword = keyPassword;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getKeyPassword() {
|
|
||||||
return keyPassword;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerConfig add(NamedServer namedServer) {
|
public ServerConfig add(NamedServer namedServer) {
|
||||||
this.namedServers.add(namedServer);
|
this.namedServers.put(namedServer.getName(), namedServer);
|
||||||
|
for (String alias : namedServer.getAliases()) {
|
||||||
|
this.namedServers.put(alias, namedServer);
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Deque<NamedServer> getNamedServers() {
|
public NamedServer getDefaultNamedServer() {
|
||||||
|
return namedServers.get("*");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, NamedServer> getNamedServers() {
|
||||||
return namedServers;
|
return namedServers;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServerConfig setTrustManagerFactory(TrustManagerFactory trustManagerFactory) {
|
|
||||||
this.trustManagerFactory = trustManagerFactory;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TrustManagerFactory getTrustManagerFactory() {
|
|
||||||
return trustManagerFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerConfig setTrustManagerKeyStore(KeyStore trustManagerKeyStore) {
|
|
||||||
this.trustManagerKeyStore = trustManagerKeyStore;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public KeyStore getTrustManagerKeyStore() {
|
|
||||||
return trustManagerKeyStore;
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package org.xbib.netty.http.server;
|
||||||
|
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.handler.codec.http.FullHttpRequest;
|
||||||
|
import org.xbib.netty.http.server.endpoint.NamedServer;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public interface ServerRequest {
|
||||||
|
|
||||||
|
NamedServer getNamedServer();
|
||||||
|
|
||||||
|
ChannelHandlerContext getChannelHandlerContext();
|
||||||
|
|
||||||
|
FullHttpRequest getRequest();
|
||||||
|
|
||||||
|
void setContext(List<String> context);
|
||||||
|
|
||||||
|
List<String> getContext();
|
||||||
|
|
||||||
|
void setRawParameters(Map<String, String> rawParameters);
|
||||||
|
|
||||||
|
Map<String, String> getRawParameters();
|
||||||
|
|
||||||
|
String getContextPath();
|
||||||
|
|
||||||
|
String getEffectiveRequestPath();
|
||||||
|
|
||||||
|
Integer getSequenceId();
|
||||||
|
|
||||||
|
Integer streamId();
|
||||||
|
|
||||||
|
Integer requestId();
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package org.xbib.netty.http.server.transport;
|
package org.xbib.netty.http.server;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||||
|
@ -13,6 +13,8 @@ public interface ServerResponse {
|
||||||
|
|
||||||
void setHeader(AsciiString name, String value);
|
void setHeader(AsciiString name, String value);
|
||||||
|
|
||||||
|
HttpResponseStatus getLastStatus();
|
||||||
|
|
||||||
void write(String text);
|
void write(String text);
|
||||||
|
|
||||||
void writeError(HttpResponseStatus status);
|
void writeError(HttpResponseStatus status);
|
|
@ -1,5 +1,7 @@
|
||||||
package org.xbib.netty.http.server.endpoint;
|
package org.xbib.netty.http.server.endpoint;
|
||||||
|
|
||||||
|
import org.xbib.netty.http.server.endpoint.service.Service;
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
import java.lang.annotation.ElementType;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
@ -9,9 +11,7 @@ import java.lang.annotation.Target;
|
||||||
* The {@code Context} annotation decorates methods which are mapped
|
* The {@code Context} annotation decorates methods which are mapped
|
||||||
* to a context path within the server, and provide its contents.
|
* to a context path within the server, and provide its contents.
|
||||||
* The annotated methods must have the same signature and contract
|
* The annotated methods must have the same signature and contract
|
||||||
* as {@link Handler#handle}, but can have arbitrary names.
|
* as {@link Service#handle}, but can have arbitrary names.
|
||||||
*
|
|
||||||
* @see NamedServer#addHandlers(Object)
|
|
||||||
*/
|
*/
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target(ElementType.METHOD)
|
@Target(ElementType.METHOD)
|
||||||
|
@ -25,9 +25,9 @@ public @interface Context {
|
||||||
String value();
|
String value();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The HTTP methods supported by this context handler (default is "GET").
|
* The HTTP methods supported by this context handler (default is "GET" and "HEAD").
|
||||||
*
|
*
|
||||||
* @return the HTTP methods supported by this context handler
|
* @return the HTTP methods supported by this context handler
|
||||||
*/
|
*/
|
||||||
String[] methods() default "GET";
|
String[] methods() default {"GET", "HEAD"};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,46 +1,215 @@
|
||||||
package org.xbib.netty.http.server.endpoint;
|
package org.xbib.netty.http.server.endpoint;
|
||||||
|
|
||||||
|
import org.xbib.net.QueryParameters;
|
||||||
|
import org.xbib.net.path.PathMatcher;
|
||||||
|
import org.xbib.netty.http.server.ServerRequest;
|
||||||
|
import org.xbib.netty.http.server.ServerResponse;
|
||||||
|
import org.xbib.netty.http.server.endpoint.service.Service;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
|
||||||
* The {@code Endpoint} class holds an endpoint information.
|
|
||||||
*/
|
|
||||||
public class Endpoint {
|
public class Endpoint {
|
||||||
|
|
||||||
private final NamedServer namedServer;
|
private static final PathMatcher pathMatcher = new PathMatcher();
|
||||||
|
|
||||||
private final Map<String, Handler> handlerMap;
|
public static final List<String> DEFAULT_METHODS = Arrays.asList("GET", "HEAD");
|
||||||
|
|
||||||
public Endpoint(NamedServer namedServer) {
|
private final String prefix;
|
||||||
this.namedServer = namedServer;
|
|
||||||
this.handlerMap = new LinkedHashMap<>();
|
private final String path;
|
||||||
|
|
||||||
|
private final List<String> methods;
|
||||||
|
|
||||||
|
private final List<String> contentTypes;
|
||||||
|
|
||||||
|
private final List<Service> filters;
|
||||||
|
|
||||||
|
private Endpoint(String prefix, String path,
|
||||||
|
List<String> methods, List<String> contentTypes, List<Service> filters) {
|
||||||
|
this.prefix = prefix;
|
||||||
|
this.path = path == null || path.isEmpty() ?
|
||||||
|
prefix + "/**" : path.startsWith("/") ? prefix + path : prefix + "/" + path;
|
||||||
|
this.methods = methods;
|
||||||
|
this.contentTypes = contentTypes;
|
||||||
|
this.filters = filters;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static Builder builder() {
|
||||||
* Returns the map of supported HTTP methods and their corresponding handlers.
|
return new Builder();
|
||||||
*
|
|
||||||
* @return the map of supported HTTP methods and their corresponding handlers
|
|
||||||
*/
|
|
||||||
public Map<String, Handler> getHandlerMap() {
|
|
||||||
return handlerMap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static Builder builder(Endpoint endpoint) {
|
||||||
* Adds (or replaces) a handler for the given HTTP methods.
|
return new Builder()
|
||||||
*
|
.setPrefix(endpoint.prefix)
|
||||||
* @param handler the handler
|
.setPath(endpoint.path)
|
||||||
* @param methods the HTTP methods supported by the handler (default is "GET")
|
.setMethods(endpoint.methods)
|
||||||
*/
|
.setContentTypes(endpoint.contentTypes)
|
||||||
public void addHandler(Handler handler, String... methods) {
|
.setFilters(endpoint.filters);
|
||||||
if (methods.length == 0) {
|
}
|
||||||
handlerMap.put("GET", handler);
|
|
||||||
namedServer.getMethods().add("GET");
|
public String getPrefix() {
|
||||||
} else {
|
return prefix;
|
||||||
for (String method : methods) {
|
}
|
||||||
handlerMap.put(method, handler);
|
|
||||||
namedServer.getMethods().add(method);
|
public String getPath() {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean matches(EndpointInfo info) {
|
||||||
|
return pathMatcher.match(path, info.path) &&
|
||||||
|
(methods == null || methods.isEmpty() || (methods.contains(info.method))) &&
|
||||||
|
(contentTypes == null || contentTypes.isEmpty() || info.contentType == null ||
|
||||||
|
contentTypes.stream().anyMatch(info.contentType::startsWith));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resolveUriTemplate(ServerRequest serverRequest) {
|
||||||
|
if (pathMatcher.match(path, serverRequest.getEffectiveRequestPath())) {
|
||||||
|
QueryParameters queryParameters = pathMatcher.extractUriTemplateVariables(path, serverRequest.getEffectiveRequestPath());
|
||||||
|
Map<String, String> map = new LinkedHashMap<>();
|
||||||
|
for (QueryParameters.Pair<String, String> pair : queryParameters) {
|
||||||
|
map.put(pair.getFirst(), pair.getSecond());
|
||||||
|
}
|
||||||
|
serverRequest.setRawParameters(map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void executeFilters(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
|
||||||
|
serverRequest.setContext(pathMatcher.tokenizePath(getPrefix()));
|
||||||
|
for (Service service : filters) {
|
||||||
|
service.handle(serverRequest, serverResponse);
|
||||||
|
if (serverResponse.getLastStatus() != null) {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return path + "_" + methods + "_" + contentTypes + " --> " + filters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class EndpointInfo implements Comparable<EndpointInfo> {
|
||||||
|
|
||||||
|
private final String path;
|
||||||
|
|
||||||
|
private final String method;
|
||||||
|
|
||||||
|
private final String contentType;
|
||||||
|
|
||||||
|
public EndpointInfo(ServerRequest serverRequest) {
|
||||||
|
this.path = serverRequest.getEffectiveRequestPath();
|
||||||
|
this.method = serverRequest.getRequest().method().name();
|
||||||
|
this.contentType = serverRequest.getRequest().headers().get(CONTENT_TYPE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return path + "_" + method + "_" + contentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return toString().hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
return o instanceof EndpointInfo && toString().equals(o.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(EndpointInfo o) {
|
||||||
|
return toString().compareTo(o.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class EndpointPathComparator implements Comparator<Endpoint> {
|
||||||
|
|
||||||
|
private final Comparator<String> pathComparator;
|
||||||
|
|
||||||
|
EndpointPathComparator(String path) {
|
||||||
|
this.pathComparator = pathMatcher.getPatternComparator(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compare(Endpoint endpoint1, Endpoint endpoint2) {
|
||||||
|
return pathComparator.compare(endpoint1.path, endpoint2.path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Builder {
|
||||||
|
|
||||||
|
private String prefix;
|
||||||
|
|
||||||
|
private String path;
|
||||||
|
|
||||||
|
private List<String> methods;
|
||||||
|
|
||||||
|
private List<String> contentTypes;
|
||||||
|
|
||||||
|
private List<Service> filters;
|
||||||
|
|
||||||
|
Builder() {
|
||||||
|
this.prefix = "/";
|
||||||
|
this.path = "/**";
|
||||||
|
this.methods = new ArrayList<>();
|
||||||
|
this.contentTypes = new ArrayList<>();
|
||||||
|
this.filters = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setPrefix(String prefix) {
|
||||||
|
this.prefix = prefix;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setPath(String path) {
|
||||||
|
this.path = path;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setMethods(List<String> methods) {
|
||||||
|
this.methods = methods;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder addMethod(String method) {
|
||||||
|
methods.add(method);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setContentTypes(List<String> contentTypes) {
|
||||||
|
this.contentTypes = contentTypes;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder addContentType(String contentType) {
|
||||||
|
this.contentTypes.add(contentType);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setFilters(List<Service> filters) {
|
||||||
|
this.filters = filters;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder addFilter(Service filter) {
|
||||||
|
this.filters.add(filter);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Endpoint build() {
|
||||||
|
if (methods.isEmpty()) {
|
||||||
|
methods = DEFAULT_METHODS;
|
||||||
|
}
|
||||||
|
return new Endpoint(prefix, path, methods, contentTypes, filters);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
package org.xbib.netty.http.server.endpoint;
|
||||||
|
|
||||||
|
import org.xbib.netty.http.server.ServerRequest;
|
||||||
|
import org.xbib.netty.http.server.ServerResponse;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface EndpointDispatcher {
|
||||||
|
|
||||||
|
void dispatch(Endpoint endpoint, ServerRequest serverRequest, ServerResponse serverResponse) throws IOException;
|
||||||
|
}
|
|
@ -0,0 +1,194 @@
|
||||||
|
package org.xbib.netty.http.server.endpoint;
|
||||||
|
|
||||||
|
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||||
|
import org.xbib.netty.http.server.ServerRequest;
|
||||||
|
import org.xbib.netty.http.server.ServerResponse;
|
||||||
|
import org.xbib.netty.http.server.endpoint.service.MethodService;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class EndpointResolver {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(EndpointResolver.class.getName());
|
||||||
|
|
||||||
|
private final Endpoint defaultEndpoint;
|
||||||
|
|
||||||
|
private final List<Endpoint> endpoints;
|
||||||
|
|
||||||
|
private final EndpointDispatcher endpointDispatcher;
|
||||||
|
|
||||||
|
private final LRUCache<Endpoint.EndpointInfo, List<Endpoint>> cache;
|
||||||
|
|
||||||
|
private EndpointResolver(Endpoint defaultEndpoint,
|
||||||
|
List<Endpoint> endpoints,
|
||||||
|
EndpointDispatcher endpointDispatcher,
|
||||||
|
int cacheSize) {
|
||||||
|
this.defaultEndpoint = defaultEndpoint == null ? createDefaultEndpoint() : defaultEndpoint;
|
||||||
|
this.endpoints = endpoints;
|
||||||
|
this.endpointDispatcher = endpointDispatcher;
|
||||||
|
this.cache = new LRUCache<>(cacheSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resolve(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
|
||||||
|
Endpoint.EndpointInfo endpointInfo = new Endpoint.EndpointInfo(serverRequest);
|
||||||
|
cache.putIfAbsent(endpointInfo, endpoints.stream()
|
||||||
|
.filter(endpoint -> endpoint.matches(endpointInfo))
|
||||||
|
.sorted(new Endpoint.EndpointPathComparator(serverRequest.getEffectiveRequestPath())).collect(Collectors.toList()));
|
||||||
|
List<Endpoint> matchingEndpoints = cache.get(endpointInfo);
|
||||||
|
if (logger.isLoggable(Level.FINER)) {
|
||||||
|
logger.log(Level.FINER, "matching endpoints = " + matchingEndpoints);
|
||||||
|
}
|
||||||
|
if (matchingEndpoints.isEmpty()) {
|
||||||
|
if (defaultEndpoint != null) {
|
||||||
|
defaultEndpoint.resolveUriTemplate(serverRequest);
|
||||||
|
defaultEndpoint.executeFilters(serverRequest, serverResponse);
|
||||||
|
if (endpointDispatcher != null) {
|
||||||
|
endpointDispatcher.dispatch(defaultEndpoint, serverRequest, serverResponse);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
serverResponse.write(HttpResponseStatus.NOT_IMPLEMENTED);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (Endpoint endpoint : matchingEndpoints) {
|
||||||
|
endpoint.resolveUriTemplate(serverRequest);
|
||||||
|
endpoint.executeFilters(serverRequest, serverResponse);
|
||||||
|
if (serverResponse.getLastStatus() != null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (endpointDispatcher != null) {
|
||||||
|
for (Endpoint endpoint : matchingEndpoints) {
|
||||||
|
endpointDispatcher.dispatch(endpoint, serverRequest, serverResponse);
|
||||||
|
if (serverResponse.getLastStatus() != null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public LRUCache<Endpoint.EndpointInfo, List<Endpoint>> getCache() {
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Endpoint createDefaultEndpoint() {
|
||||||
|
return Endpoint.builder()
|
||||||
|
.setPath("/**")
|
||||||
|
.addMethod("GET")
|
||||||
|
.addMethod("HEAD")
|
||||||
|
.addFilter((req, resp) -> {
|
||||||
|
resp.writeError(HttpResponseStatus.NOT_FOUND,"No endpoint configured");
|
||||||
|
}).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple LRU cache, based on a {@link LinkedHashMap}.
|
||||||
|
*
|
||||||
|
* @param <K> the key type parameter
|
||||||
|
* @param <V> the vale type parameter
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
private static class LRUCache<K, V> extends LinkedHashMap<K, V> {
|
||||||
|
|
||||||
|
private final int cacheSize;
|
||||||
|
|
||||||
|
LRUCache(int cacheSize) {
|
||||||
|
super(16, 0.75f, true);
|
||||||
|
this.cacheSize = cacheSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
|
||||||
|
return size() >= cacheSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Builder builder() {
|
||||||
|
return new Builder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Builder {
|
||||||
|
|
||||||
|
private int cacheSize;
|
||||||
|
|
||||||
|
private String prefix;
|
||||||
|
|
||||||
|
private Endpoint defaultEndpoint;
|
||||||
|
|
||||||
|
private List<Endpoint> endpoints;
|
||||||
|
|
||||||
|
private EndpointDispatcher endpointDispatcher;
|
||||||
|
|
||||||
|
Builder() {
|
||||||
|
this.cacheSize = 1024;
|
||||||
|
this.endpoints = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setCacheSize(int cacheSize) {
|
||||||
|
this.cacheSize = cacheSize;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setPrefix(String prefix) {
|
||||||
|
this.prefix = prefix;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setDefaultEndpoint(Endpoint endpoint) {
|
||||||
|
this.defaultEndpoint = endpoint;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param endpoint
|
||||||
|
* @return this builder
|
||||||
|
*/
|
||||||
|
public Builder addEndpoint(Endpoint endpoint) {
|
||||||
|
if (endpoint.getPrefix().equals("/") && prefix != null && !prefix.isEmpty()) {
|
||||||
|
endpoints.add(Endpoint.builder(endpoint).setPrefix(prefix).build());
|
||||||
|
} else {
|
||||||
|
endpoints.add(endpoint);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a service for the methods of the given object that
|
||||||
|
* are annotated with the {@link Context} annotation.
|
||||||
|
*/
|
||||||
|
public Builder addEndpoint(Object classWithAnnotatedMethods) {
|
||||||
|
for (Class<?> clazz = classWithAnnotatedMethods.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
|
||||||
|
for (Method method : clazz.getDeclaredMethods()) {
|
||||||
|
Context context = method.getAnnotation(Context.class);
|
||||||
|
if (context != null) {
|
||||||
|
addEndpoint(Endpoint.builder()
|
||||||
|
.setPrefix(prefix)
|
||||||
|
.setPath(context.value())
|
||||||
|
.setMethods(Arrays.asList(context.methods()))
|
||||||
|
.addFilter(new MethodService(method, classWithAnnotatedMethods))
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setDispatcher(EndpointDispatcher endpointDispatcher) {
|
||||||
|
this.endpointDispatcher = endpointDispatcher;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EndpointResolver build() {
|
||||||
|
return new EndpointResolver(defaultEndpoint, endpoints, endpointDispatcher, cacheSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,24 +0,0 @@
|
||||||
package org.xbib.netty.http.server.endpoint;
|
|
||||||
|
|
||||||
import org.xbib.netty.http.server.transport.ServerRequest;
|
|
||||||
import org.xbib.netty.http.server.transport.ServerResponse;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A {@code Handler} is capable of serving content for resources within its context.
|
|
||||||
*
|
|
||||||
* @see NamedServer#addHandler
|
|
||||||
*/
|
|
||||||
@FunctionalInterface
|
|
||||||
public interface Handler {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the given request by using the given response.
|
|
||||||
*
|
|
||||||
* @param serverRequest the request to be served
|
|
||||||
* @param serverResponse the response to be generated
|
|
||||||
* @throws IOException if an IO error occurs
|
|
||||||
*/
|
|
||||||
void handle(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException;
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
package org.xbib.netty.http.server.endpoint;
|
|
||||||
|
|
||||||
public class NamedEndpoint {
|
|
||||||
|
|
||||||
private final String name;
|
|
||||||
|
|
||||||
private final Endpoint endpoint;
|
|
||||||
|
|
||||||
NamedEndpoint(String name, Endpoint endpoint) {
|
|
||||||
this.name = name;
|
|
||||||
this.endpoint = endpoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Endpoint getEndpoint() {
|
|
||||||
return endpoint;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,45 +1,83 @@
|
||||||
package org.xbib.netty.http.server.endpoint;
|
package org.xbib.netty.http.server.endpoint;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||||
|
import io.netty.handler.ssl.ApplicationProtocolConfig;
|
||||||
|
import io.netty.handler.ssl.ApplicationProtocolNames;
|
||||||
|
import io.netty.handler.ssl.CipherSuiteFilter;
|
||||||
|
import io.netty.handler.ssl.SslContext;
|
||||||
|
import io.netty.handler.ssl.SslContextBuilder;
|
||||||
|
import io.netty.handler.ssl.SslProvider;
|
||||||
|
import org.xbib.netty.http.common.HttpAddress;
|
||||||
|
import org.xbib.netty.http.common.SecurityUtil;
|
||||||
|
import org.xbib.netty.http.server.ServerRequest;
|
||||||
|
import org.xbib.netty.http.server.ServerResponse;
|
||||||
|
import org.xbib.netty.http.server.endpoint.service.Service;
|
||||||
|
import org.xbib.netty.http.server.security.tls.SelfSignedCertificate;
|
||||||
|
|
||||||
|
import javax.net.ssl.TrustManagerFactory;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.Provider;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.HashSet;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@code VirtualServer} class represents a virtual server.
|
* The {@code NamedServer} class represents a virtual server, with or without SSL.
|
||||||
*/
|
*/
|
||||||
public class NamedServer {
|
public class NamedServer {
|
||||||
|
|
||||||
|
private final HttpAddress httpAddress;
|
||||||
|
|
||||||
private final String name;
|
private final String name;
|
||||||
|
|
||||||
|
private final SslContext sslContext;
|
||||||
|
|
||||||
private final Set<String> aliases;
|
private final Set<String> aliases;
|
||||||
|
|
||||||
private final Set<String> methods;
|
private final List<EndpointResolver> endpointResolvers;
|
||||||
|
|
||||||
private final Endpoint defaultEndpoint;
|
protected NamedServer(HttpAddress httpAddress, String name, Set<String> aliases,
|
||||||
|
List<EndpointResolver> endpointResolvers) {
|
||||||
private final Map<String, Endpoint> endpointMap;
|
this(httpAddress, name, aliases, endpointResolvers, null);
|
||||||
|
|
||||||
private volatile boolean allowGeneratedIndex;
|
|
||||||
|
|
||||||
public NamedServer() {
|
|
||||||
this(null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a {@code NamedServer} with the given name.
|
* Constructs a {@code NamedServer} with the given name.
|
||||||
*
|
*
|
||||||
|
* @param httpAddress HTTP address, used for determining if named server is secure or not
|
||||||
* @param name the name, or null if it is the default server
|
* @param name the name, or null if it is the default server
|
||||||
|
* @param aliases alias names for the named server
|
||||||
|
* @param endpointResolvers the endpoint resolvers
|
||||||
|
* @param sslContext SSL context or null
|
||||||
*/
|
*/
|
||||||
public NamedServer(String name) {
|
protected NamedServer(HttpAddress httpAddress, String name, Set<String> aliases,
|
||||||
|
List<EndpointResolver> endpointResolvers,
|
||||||
|
SslContext sslContext) {
|
||||||
|
this.httpAddress = httpAddress;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.aliases = new HashSet<>();
|
this.sslContext = sslContext;
|
||||||
this.methods = new HashSet<>();
|
this.aliases = aliases;
|
||||||
this.endpointMap = new HashMap<>();
|
this.endpointResolvers = endpointResolvers;
|
||||||
this.defaultEndpoint = new Endpoint(this);
|
}
|
||||||
endpointMap.put("*", new Endpoint(this)); // for "OPTIONS *"
|
|
||||||
|
public static Builder builder() {
|
||||||
|
return new Builder(HttpAddress.http1("localhost", 8008), "*");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Builder builder(HttpAddress httpAddress) {
|
||||||
|
return new Builder(httpAddress, "*");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Builder builder(HttpAddress httpAddress, String serverName) {
|
||||||
|
return new Builder(httpAddress, serverName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpAddress getHttpAddress() {
|
||||||
|
return httpAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -51,13 +89,8 @@ public class NamedServer {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public SslContext getSslContext() {
|
||||||
* Adds an alias for this virtual server.
|
return sslContext;
|
||||||
*
|
|
||||||
* @param alias the alias
|
|
||||||
*/
|
|
||||||
public void addAlias(String alias) {
|
|
||||||
aliases.add(alias);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -69,127 +102,192 @@ public class NamedServer {
|
||||||
return Collections.unmodifiableSet(aliases);
|
return Collections.unmodifiableSet(aliases);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public void execute(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
|
||||||
* Returns whether auto-generated indices are allowed.
|
if (endpointResolvers != null && !endpointResolvers.isEmpty()) {
|
||||||
*
|
for (EndpointResolver endpointResolver : endpointResolvers) {
|
||||||
* @return whether auto-generated indices are allowed
|
endpointResolver.resolve(serverRequest, serverResponse);
|
||||||
*/
|
}
|
||||||
public boolean isAllowGeneratedIndex() {
|
} else {
|
||||||
return allowGeneratedIndex;
|
serverResponse.writeError(HttpResponseStatus.NOT_IMPLEMENTED);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static class Builder {
|
||||||
* Sets whether auto-generated indices are allowed. If false, and a
|
|
||||||
* directory resource is requested, an error will be returned instead.
|
private HttpAddress httpAddress;
|
||||||
*
|
|
||||||
* @param allowed specifies whether generated indices are allowed
|
private String serverName;
|
||||||
*/
|
|
||||||
public void setAllowGeneratedIndex(boolean allowed) {
|
private Set<String> aliases;
|
||||||
this.allowGeneratedIndex = allowed;
|
|
||||||
|
private List<EndpointResolver> endpointResolvers;
|
||||||
|
|
||||||
|
private TrustManagerFactory trustManagerFactory;
|
||||||
|
|
||||||
|
private KeyStore trustManagerKeyStore;
|
||||||
|
|
||||||
|
private Provider sslContextProvider;
|
||||||
|
|
||||||
|
private SslProvider sslProvider;
|
||||||
|
|
||||||
|
private Iterable<String> ciphers;
|
||||||
|
|
||||||
|
private CipherSuiteFilter cipherSuiteFilter;
|
||||||
|
|
||||||
|
private InputStream keyCertChainInputStream;
|
||||||
|
|
||||||
|
private InputStream keyInputStream;
|
||||||
|
|
||||||
|
private String keyPassword;
|
||||||
|
|
||||||
|
Builder(HttpAddress httpAddress, String serverName) {
|
||||||
|
this.httpAddress = httpAddress;
|
||||||
|
this.serverName = serverName;
|
||||||
|
this.aliases = new LinkedHashSet<>();
|
||||||
|
this.endpointResolvers = new ArrayList<>();
|
||||||
|
this.trustManagerFactory = SecurityUtil.Defaults.DEFAULT_TRUST_MANAGER_FACTORY; // InsecureTrustManagerFactory.INSTANCE;
|
||||||
|
this.sslProvider = SecurityUtil.Defaults.DEFAULT_SSL_PROVIDER;
|
||||||
|
this.ciphers = SecurityUtil.Defaults.DEFAULT_CIPHERS;
|
||||||
|
this.cipherSuiteFilter = SecurityUtil.Defaults.DEFAULT_CIPHER_SUITE_FILTER;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public Builder setTrustManagerFactory(TrustManagerFactory trustManagerFactory) {
|
||||||
* Returns all HTTP methods explicitly supported by at least one context
|
this.trustManagerFactory = trustManagerFactory;
|
||||||
* (this may or may not include the methods with required or built-in support).
|
return this;
|
||||||
*
|
|
||||||
* @return all HTTP methods explicitly supported by at least one context
|
|
||||||
*/
|
|
||||||
public Set<String> getMethods() {
|
|
||||||
return methods;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public Builder setTrustManagerKeyStore(KeyStore trustManagerKeyStore) {
|
||||||
* Adds a context and its corresponding context handler to this server.
|
this.trustManagerKeyStore = trustManagerKeyStore;
|
||||||
* Paths are normalized by removing trailing slashes (except the root).
|
return this;
|
||||||
*
|
|
||||||
* @param path the context's path (must start with '/')
|
|
||||||
* @param handler the context handler for the given path
|
|
||||||
* @param methods the HTTP methods supported by the context handler (default is "GET")
|
|
||||||
* @return this virtual server
|
|
||||||
* @throws IllegalArgumentException if path is malformed
|
|
||||||
*/
|
|
||||||
public NamedServer addHandler(String path, Handler handler, String... methods) {
|
|
||||||
if (path == null || !path.startsWith("/") && !path.equals("*")) {
|
|
||||||
throw new IllegalArgumentException("invalid path: " + path);
|
|
||||||
}
|
}
|
||||||
String s = trimRight(path, '/');
|
|
||||||
Endpoint info = new Endpoint(this);
|
public Builder setSslContextProvider(Provider sslContextProvider) {
|
||||||
Endpoint existing = endpointMap.putIfAbsent(s, info);
|
this.sslContextProvider = sslContextProvider;
|
||||||
info = existing != null ? existing : info;
|
return this;
|
||||||
info.addHandler(handler, methods);
|
}
|
||||||
|
|
||||||
|
public Builder setSslProvider(SslProvider sslProvider) {
|
||||||
|
this.sslProvider = sslProvider;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setCiphers(Iterable<String> ciphers) {
|
||||||
|
this.ciphers = ciphers;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setCipherSuiteFilter(CipherSuiteFilter cipherSuiteFilter) {
|
||||||
|
this.cipherSuiteFilter = cipherSuiteFilter;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setJdkSslProvider() {
|
||||||
|
setSslProvider(SslProvider.JDK);
|
||||||
|
setCiphers(SecurityUtil.Defaults.JDK_CIPHERS);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setOpenSSLSslProvider() {
|
||||||
|
setSslProvider(SslProvider.OPENSSL);
|
||||||
|
setCiphers(SecurityUtil.Defaults.OPENSSL_CIPHERS);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setKeyCertChainInputStream(InputStream keyCertChainInputStream) {
|
||||||
|
this.keyCertChainInputStream = keyCertChainInputStream;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setKeyInputStream(InputStream keyInputStream) {
|
||||||
|
this.keyInputStream = keyInputStream;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setKeyPassword(String keyPassword) {
|
||||||
|
this.keyPassword = keyPassword;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setKeyCert(InputStream keyCertChainInputStream, InputStream keyInputStream) {
|
||||||
|
setKeyCertChainInputStream(keyCertChainInputStream);
|
||||||
|
setKeyInputStream(keyInputStream);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setKeyCert(InputStream keyCertChainInputStream, InputStream keyInputStream,
|
||||||
|
String keyPassword) {
|
||||||
|
setKeyCertChainInputStream(keyCertChainInputStream);
|
||||||
|
setKeyInputStream(keyInputStream);
|
||||||
|
setKeyPassword(keyPassword);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setSelfCert() throws Exception {
|
||||||
|
SelfSignedCertificate selfSignedCertificate = new SelfSignedCertificate(serverName);
|
||||||
|
setKeyCertChainInputStream(selfSignedCertificate.certificate());
|
||||||
|
setKeyInputStream(selfSignedCertificate.privateKey());
|
||||||
|
setKeyPassword(null);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds handler for all methods of the given object that
|
* Adds an alias for this virtual server.
|
||||||
* are annotated with the {@link Context} annotation.
|
|
||||||
*
|
*
|
||||||
* @param o the object whose annotated methods are added
|
* @param alias the alias
|
||||||
* @return this virtual server
|
* @return this builder
|
||||||
* @throws IllegalArgumentException if a Context-annotated
|
|
||||||
* method has an {@link Context invalid signature}
|
|
||||||
*/
|
*/
|
||||||
public NamedServer addHandlers(Object o) throws IllegalArgumentException {
|
public Builder addAlias(String alias) {
|
||||||
for (Class<?> c = o.getClass(); c != null; c = c.getSuperclass()) {
|
aliases.add(alias);
|
||||||
for (Method m : c.getDeclaredMethods()) {
|
|
||||||
Context context = m.getAnnotation(Context.class);
|
|
||||||
if (context != null) {
|
|
||||||
addHandler(context.value(), new MethodHandler(m, o), context.methods());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public Builder addEndpointResolver(EndpointResolver endpointResolver) {
|
||||||
* Returns the endpoint for the given path.
|
this.endpointResolvers.add(endpointResolver);
|
||||||
* If an endpoint is not found for the given path, the search is repeated for
|
return this;
|
||||||
* its parent path, and so on until a base context is found. If neither the
|
|
||||||
* given path nor any of its parents has a context, an empty context is returned.
|
|
||||||
*
|
|
||||||
* @param path the context's path
|
|
||||||
* @return the context info for the given path, or an empty context if none exists
|
|
||||||
*/
|
|
||||||
public NamedEndpoint getNamedEndpoint(String path) {
|
|
||||||
String s = trimRight(path, '/');
|
|
||||||
Endpoint info = null;
|
|
||||||
String hook = null;
|
|
||||||
while (info == null && s != null) {
|
|
||||||
hook = s;
|
|
||||||
info = endpointMap.get(s);
|
|
||||||
s = getParentPath(s);
|
|
||||||
}
|
|
||||||
return new NamedEndpoint(hook, info != null ? info : defaultEndpoint);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public Builder singleEndpoint(String path, Service service) {
|
||||||
* Returns the given string with all occurrences of the given character
|
addEndpointResolver(EndpointResolver.builder()
|
||||||
* removed from its right side.
|
.addEndpoint(Endpoint.builder().setPath(path).addFilter(service).build()).build());
|
||||||
*
|
return this;
|
||||||
* @param s the string to trim
|
|
||||||
* @param c the character to remove
|
|
||||||
* @return the trimmed string
|
|
||||||
*/
|
|
||||||
private static String trimRight(String s, char c) {
|
|
||||||
int len = s.length() - 1;
|
|
||||||
int end = len;
|
|
||||||
while (end >= 0 && s.charAt(end) == c) {
|
|
||||||
end--;
|
|
||||||
}
|
|
||||||
return end == len ? s : s.substring(0, end + 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public Builder singleEndpoint(String prefix, String path, Service service) {
|
||||||
* Returns the parent of the given path.
|
addEndpointResolver(EndpointResolver.builder()
|
||||||
*
|
.addEndpoint(Endpoint.builder().setPrefix(prefix).setPath(path).addFilter(service).build()).build());
|
||||||
* @param path the path whose parent is returned (must start with '/')
|
return this;
|
||||||
* @return the parent of the given path (excluding trailing slash),
|
|
||||||
* or null if given path is the root path
|
|
||||||
*/
|
|
||||||
private static String getParentPath(String path) {
|
|
||||||
String s = trimRight(path, '/'); // remove trailing slash
|
|
||||||
int slash = s.lastIndexOf('/');
|
|
||||||
return slash == -1 ? null : s.substring(0, slash);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public NamedServer build() {
|
||||||
|
if (httpAddress.isSecure()) {
|
||||||
|
try {
|
||||||
|
trustManagerFactory.init(trustManagerKeyStore);
|
||||||
|
SslContextBuilder sslContextBuilder = SslContextBuilder
|
||||||
|
.forServer(keyCertChainInputStream, keyInputStream, keyPassword)
|
||||||
|
.trustManager(trustManagerFactory)
|
||||||
|
.sslProvider(sslProvider)
|
||||||
|
.ciphers(ciphers, cipherSuiteFilter);
|
||||||
|
if (sslContextProvider != null) {
|
||||||
|
sslContextBuilder.sslContextProvider(sslContextProvider);
|
||||||
|
}
|
||||||
|
if (httpAddress.getVersion().majorVersion() == 2) {
|
||||||
|
sslContextBuilder.applicationProtocolConfig(newApplicationProtocolConfig());
|
||||||
|
}
|
||||||
|
return new NamedServer(httpAddress, serverName, aliases, endpointResolvers, sslContextBuilder.build());
|
||||||
|
} catch (Throwable t) {
|
||||||
|
throw new RuntimeException(t);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return new NamedServer(httpAddress, serverName, aliases, endpointResolvers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ApplicationProtocolConfig newApplicationProtocolConfig() {
|
||||||
|
return new ApplicationProtocolConfig(ApplicationProtocolConfig.Protocol.ALPN,
|
||||||
|
ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE,
|
||||||
|
ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT,
|
||||||
|
ApplicationProtocolNames.HTTP_2,
|
||||||
|
ApplicationProtocolNames.HTTP_1_1);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package org.xbib.netty.http.server.endpoint;
|
package org.xbib.netty.http.server.endpoint.service;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||||
import org.xbib.netty.http.server.transport.ServerRequest;
|
import org.xbib.netty.http.server.ServerRequest;
|
||||||
import org.xbib.netty.http.server.transport.ServerResponse;
|
import org.xbib.netty.http.server.ServerResponse;
|
||||||
import org.xbib.netty.http.server.util.MimeTypeUtils;
|
import org.xbib.netty.http.server.util.MimeTypeUtils;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -13,32 +13,30 @@ import java.net.URL;
|
||||||
import java.nio.MappedByteBuffer;
|
import java.nio.MappedByteBuffer;
|
||||||
import java.nio.channels.FileChannel;
|
import java.nio.channels.FileChannel;
|
||||||
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;
|
||||||
|
|
||||||
public class ClasspathHandler implements Handler {
|
public class ClasspathService implements Service {
|
||||||
|
|
||||||
private final ClassLoader classLoader;
|
private final ClassLoader classLoader;
|
||||||
|
|
||||||
private final String prefix;
|
private final String prefix;
|
||||||
|
|
||||||
public ClasspathHandler(ClassLoader classLoader, String prefix) {
|
public ClasspathService(ClassLoader classLoader, String prefix) {
|
||||||
this.classLoader = classLoader;
|
this.classLoader = classLoader;
|
||||||
this.prefix = prefix;
|
this.prefix = prefix;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
|
public void handle(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
|
||||||
String contextPath = serverRequest.getContextPath();
|
String requestPath = serverRequest.getEffectiveRequestPath();
|
||||||
URL url = classLoader.getResource(prefix + contextPath);
|
URL url = classLoader.getResource(prefix + requestPath);
|
||||||
if (url != null) {
|
if (url != null) {
|
||||||
try {
|
try {
|
||||||
Path path = Paths.get(url.toURI());
|
FileChannel fileChannel = (FileChannel) Files.newByteChannel(Paths.get(url.toURI()));
|
||||||
FileChannel fileChannel = (FileChannel) Files.newByteChannel(path);
|
|
||||||
MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileChannel.size());
|
MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileChannel.size());
|
||||||
ByteBuf byteBuf = Unpooled.wrappedBuffer(mappedByteBuffer);
|
ByteBuf byteBuf = Unpooled.wrappedBuffer(mappedByteBuffer);
|
||||||
try {
|
try {
|
||||||
String contentType = MimeTypeUtils.guessFromPath(contextPath, false);
|
String contentType = MimeTypeUtils.guessFromPath(requestPath, false);
|
||||||
serverResponse.write(HttpResponseStatus.OK, contentType, byteBuf);
|
serverResponse.write(HttpResponseStatus.OK, contentType, byteBuf);
|
||||||
} finally {
|
} finally {
|
||||||
byteBuf.release();
|
byteBuf.release();
|
|
@ -0,0 +1,14 @@
|
||||||
|
package org.xbib.netty.http.server.endpoint.service;
|
||||||
|
|
||||||
|
import org.xbib.netty.http.server.ServerRequest;
|
||||||
|
import org.xbib.netty.http.server.ServerResponse;
|
||||||
|
import org.xbib.netty.http.server.endpoint.service.Service;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class EmptyService implements Service {
|
||||||
|
@Override
|
||||||
|
public void handle(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
package org.xbib.netty.http.server.endpoint;
|
package org.xbib.netty.http.server.endpoint.service;
|
||||||
|
|
||||||
import org.xbib.netty.http.server.transport.ServerRequest;
|
import org.xbib.netty.http.server.ServerRequest;
|
||||||
import org.xbib.netty.http.server.transport.ServerResponse;
|
import org.xbib.netty.http.server.ServerResponse;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
@ -10,24 +10,22 @@ import java.lang.reflect.Method;
|
||||||
/**
|
/**
|
||||||
* The {@code MethodHandler} invokes g a handler method on a specified object.
|
* The {@code MethodHandler} invokes g a handler method on a specified object.
|
||||||
* The method must have the same signature and contract as
|
* The method must have the same signature and contract as
|
||||||
* {@link Handler#handle}, but can have an arbitrary name.
|
* {@link Service#handle}, but can have an arbitrary name.
|
||||||
*
|
|
||||||
* @see NamedServer#addHandlers(Object)
|
|
||||||
*/
|
*/
|
||||||
public class MethodHandler implements Handler {
|
public class MethodService implements Service {
|
||||||
|
|
||||||
private final Method m;
|
private final Method m;
|
||||||
|
|
||||||
private final Object obj;
|
private final Object obj;
|
||||||
|
|
||||||
public MethodHandler(Method m, Object obj) throws IllegalArgumentException {
|
public MethodService(Method m, Object obj) throws IllegalArgumentException {
|
||||||
this.m = m;
|
this.m = m;
|
||||||
this.obj = obj;
|
this.obj = obj;
|
||||||
Class<?>[] params = m.getParameterTypes();
|
Class<?>[] params = m.getParameterTypes();
|
||||||
if (params.length != 2
|
if (params.length != 2 ||
|
||||||
|| !ServerRequest.class.isAssignableFrom(params[0])
|
!ServerRequest.class.isAssignableFrom(params[0]) ||
|
||||||
|| !ServerResponse.class.isAssignableFrom(params[1])
|
!ServerResponse.class.isAssignableFrom(params[1]) ||
|
||||||
|| !int.class.isAssignableFrom(m.getReturnType())) {
|
!int.class.isAssignableFrom(m.getReturnType())) {
|
||||||
throw new IllegalArgumentException("invalid method signature: " + m);
|
throw new IllegalArgumentException("invalid method signature: " + m);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,10 +1,10 @@
|
||||||
package org.xbib.netty.http.server.endpoint;
|
package org.xbib.netty.http.server.endpoint.service;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||||
import org.xbib.netty.http.server.transport.ServerRequest;
|
import org.xbib.netty.http.server.ServerRequest;
|
||||||
import org.xbib.netty.http.server.transport.ServerResponse;
|
import org.xbib.netty.http.server.ServerResponse;
|
||||||
import org.xbib.netty.http.server.util.MimeTypeUtils;
|
import org.xbib.netty.http.server.util.MimeTypeUtils;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -13,11 +13,11 @@ import java.nio.channels.FileChannel;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
|
||||||
public class NioHandler implements Handler {
|
public class NioService implements Service {
|
||||||
|
|
||||||
private final Path prefix;
|
private final Path prefix;
|
||||||
|
|
||||||
public NioHandler(Path prefix) {
|
public NioService(Path prefix) {
|
||||||
this.prefix = prefix;
|
this.prefix = prefix;
|
||||||
if (!Files.exists(prefix) || !Files.isDirectory(prefix)) {
|
if (!Files.exists(prefix) || !Files.isDirectory(prefix)) {
|
||||||
throw new IllegalArgumentException("prefix: " + prefix + " (not a directory");
|
throw new IllegalArgumentException("prefix: " + prefix + " (not a directory");
|
||||||
|
@ -26,8 +26,8 @@ public class NioHandler implements Handler {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
|
public void handle(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
|
||||||
String requestPath = serverRequest.getRequestPath();
|
String requestPath = serverRequest.getEffectiveRequestPath();
|
||||||
Path path = prefix.resolve(requestPath.substring(1)); // starts always with '/'
|
Path path = prefix.resolve(requestPath);
|
||||||
if (Files.exists(path) && Files.isReadable(path)) {
|
if (Files.exists(path) && Files.isReadable(path)) {
|
||||||
try (FileChannel fileChannel = (FileChannel) Files.newByteChannel(path)) {
|
try (FileChannel fileChannel = (FileChannel) Files.newByteChannel(path)) {
|
||||||
MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileChannel.size());
|
MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileChannel.size());
|
|
@ -1,10 +1,10 @@
|
||||||
package org.xbib.netty.http.server.endpoint;
|
package org.xbib.netty.http.server.endpoint.service;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufAllocator;
|
import io.netty.buffer.ByteBufAllocator;
|
||||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||||
import org.xbib.netty.http.server.transport.ServerRequest;
|
import org.xbib.netty.http.server.ServerRequest;
|
||||||
import org.xbib.netty.http.server.transport.ServerResponse;
|
import org.xbib.netty.http.server.ServerResponse;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
@ -13,28 +13,28 @@ import java.nio.channels.SeekableByteChannel;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
|
||||||
public class DirectoryHandler implements Handler {
|
public class PathReaderService implements Service {
|
||||||
|
|
||||||
private Path path;
|
private Path path;
|
||||||
|
|
||||||
private ByteBufAllocator allocator;
|
private ByteBufAllocator allocator;
|
||||||
|
|
||||||
public DirectoryHandler(Path path, ByteBufAllocator allocator) {
|
public PathReaderService(Path path, ByteBufAllocator allocator) {
|
||||||
this.path = path;
|
this.path = path;
|
||||||
this.allocator = allocator;
|
this.allocator = allocator;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
|
public void handle(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
|
||||||
String uri = serverRequest.getRequest().uri();
|
ByteBuf byteBuf = read(allocator, path.resolve(serverRequest.getEffectiveRequestPath()));
|
||||||
Path p = path.resolve(uri);
|
try {
|
||||||
ByteBuf byteBuf = read(allocator, p);
|
|
||||||
serverResponse.write(HttpResponseStatus.OK, "application/octet-stream", byteBuf);
|
serverResponse.write(HttpResponseStatus.OK, "application/octet-stream", byteBuf);
|
||||||
|
} finally {
|
||||||
byteBuf.release();
|
byteBuf.release();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static ByteBuf read(ByteBufAllocator allocator, Path path)
|
private static ByteBuf read(ByteBufAllocator allocator, Path path) throws IOException {
|
||||||
throws IOException {
|
|
||||||
try (SeekableByteChannel sbc = Files.newByteChannel(path);
|
try (SeekableByteChannel sbc = Files.newByteChannel(path);
|
||||||
InputStream in = Channels.newInputStream(sbc)) {
|
InputStream in = Channels.newInputStream(sbc)) {
|
||||||
int size = Math.toIntExact(sbc.size());
|
int size = Math.toIntExact(sbc.size());
|
|
@ -0,0 +1,22 @@
|
||||||
|
package org.xbib.netty.http.server.endpoint.service;
|
||||||
|
|
||||||
|
import org.xbib.netty.http.server.ServerRequest;
|
||||||
|
import org.xbib.netty.http.server.ServerResponse;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@code Service} is capable of serving requests for resources within its context.
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Service {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the given request by building and returning a response.
|
||||||
|
*
|
||||||
|
* @param serverRequest the request to be served
|
||||||
|
* @param serverResponse the response to be written
|
||||||
|
* @throws IOException if an IO error occurs
|
||||||
|
*/
|
||||||
|
void handle(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException;
|
||||||
|
}
|
|
@ -6,16 +6,10 @@ import io.netty.handler.codec.http.HttpHeaders;
|
||||||
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;
|
||||||
import org.xbib.netty.http.server.Server;
|
import org.xbib.netty.http.server.Server;
|
||||||
import org.xbib.netty.http.server.endpoint.Handler;
|
import org.xbib.netty.http.server.ServerRequest;
|
||||||
import org.xbib.netty.http.server.endpoint.NamedEndpoint;
|
import org.xbib.netty.http.server.ServerResponse;
|
||||||
import org.xbib.netty.http.server.endpoint.NamedServer;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.LinkedHashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
@ -24,13 +18,11 @@ abstract class BaseServerTransport implements ServerTransport {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(BaseServerTransport.class.getName());
|
private static final Logger logger = Logger.getLogger(BaseServerTransport.class.getName());
|
||||||
|
|
||||||
protected static final AtomicInteger requestCounter = new AtomicInteger();
|
static final AtomicInteger requestCounter = new AtomicInteger();
|
||||||
|
|
||||||
private static final List<String> METHODS = Arrays.asList("GET", "HEAD", "OPTIONS");
|
|
||||||
|
|
||||||
protected final Server server;
|
protected final Server server;
|
||||||
|
|
||||||
protected BaseServerTransport(Server server) {
|
BaseServerTransport(Server server) {
|
||||||
this.server = server;
|
this.server = server;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,9 +40,9 @@ abstract class BaseServerTransport implements ServerTransport {
|
||||||
* @param serverResponse the response
|
* @param serverResponse the response
|
||||||
* @return whether further processing should be performed
|
* @return whether further processing should be performed
|
||||||
*/
|
*/
|
||||||
protected static boolean acceptRequest(ServerRequest serverRequest, ServerResponse serverResponse) {
|
static boolean acceptRequest(ServerRequest serverRequest, ServerResponse serverResponse) {
|
||||||
HttpHeaders reqHeaders = serverRequest.getRequest().headers();
|
HttpHeaders reqHeaders = serverRequest.getRequest().headers();
|
||||||
HttpVersion version = serverRequest.getHttpAddress().getVersion();
|
HttpVersion version = serverRequest.getNamedServer().getHttpAddress().getVersion();
|
||||||
switch (version.majorVersion()) {
|
switch (version.majorVersion()) {
|
||||||
case 1:
|
case 1:
|
||||||
case 2:
|
case 2:
|
||||||
|
@ -86,35 +78,7 @@ abstract class BaseServerTransport implements ServerTransport {
|
||||||
* @param serverResponse the response (into which the response is written)
|
* @param serverResponse the response (into which the response is written)
|
||||||
* @throws IOException if and error occurs
|
* @throws IOException if and error occurs
|
||||||
*/
|
*/
|
||||||
protected static void handle(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
|
static void handle(HttpServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
|
||||||
String method = serverRequest.getRequest().method().name();
|
serverRequest.getNamedServer().execute(serverRequest, serverResponse);
|
||||||
String path = serverRequest.getRequest().uri();
|
|
||||||
NamedServer namedServer = serverRequest.getNamedServer();
|
|
||||||
NamedEndpoint namedEndpoint = namedServer.getNamedEndpoint(path);
|
|
||||||
serverRequest.setContextPath(namedEndpoint.getName());
|
|
||||||
Map<String, Handler> methodHandlerMap = namedEndpoint.getEndpoint().getHandlerMap();
|
|
||||||
// RFC 2616#5.1.1 - GET and HEAD must be supported
|
|
||||||
if (method.equals("GET") || method.equals("HEAD") || methodHandlerMap.containsKey(method)) {
|
|
||||||
Handler handler = methodHandlerMap.get(method);
|
|
||||||
if (handler == null) {
|
|
||||||
serverResponse.writeError(HttpResponseStatus.NOT_FOUND);
|
|
||||||
} else {
|
|
||||||
handler.handle(serverRequest, serverResponse);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Set<String> methods = new LinkedHashSet<>(METHODS);
|
|
||||||
// "*" is a special server-wide (no-context) request supported by OPTIONS
|
|
||||||
boolean isServerOptions = path.equals("*") && method.equals("OPTIONS");
|
|
||||||
methods.addAll(isServerOptions ? namedServer.getMethods() : methodHandlerMap.keySet());
|
|
||||||
serverResponse.setHeader(HttpHeaderNames.ALLOW, String.join(", ", methods));
|
|
||||||
if (method.equals("OPTIONS")) { // default OPTIONS handler
|
|
||||||
serverResponse.setHeader(HttpHeaderNames.CONTENT_LENGTH, "0"); // RFC2616#9.2
|
|
||||||
serverResponse.write(HttpResponseStatus.OK);
|
|
||||||
} else if (namedServer.getMethods().contains(method)) {
|
|
||||||
serverResponse.write(HttpResponseStatus.METHOD_NOT_ALLOWED); // supported by server, but not this context (nor built-in)
|
|
||||||
} else {
|
|
||||||
serverResponse.writeError(HttpResponseStatus.NOT_IMPLEMENTED); // unsupported method
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,28 +9,41 @@ import io.netty.handler.codec.http.HttpResponseStatus;
|
||||||
import io.netty.handler.codec.http2.DefaultHttp2DataFrame;
|
import io.netty.handler.codec.http2.DefaultHttp2DataFrame;
|
||||||
import io.netty.handler.codec.http2.DefaultHttp2Headers;
|
import io.netty.handler.codec.http2.DefaultHttp2Headers;
|
||||||
import io.netty.handler.codec.http2.DefaultHttp2HeadersFrame;
|
import io.netty.handler.codec.http2.DefaultHttp2HeadersFrame;
|
||||||
|
import io.netty.handler.codec.http2.Http2DataFrame;
|
||||||
import io.netty.handler.codec.http2.Http2Headers;
|
import io.netty.handler.codec.http2.Http2Headers;
|
||||||
|
import io.netty.handler.codec.http2.Http2HeadersFrame;
|
||||||
import io.netty.handler.codec.http2.HttpConversionUtil;
|
import io.netty.handler.codec.http2.HttpConversionUtil;
|
||||||
import io.netty.util.AsciiString;
|
import io.netty.util.AsciiString;
|
||||||
import org.xbib.netty.http.server.ServerName;
|
import org.xbib.netty.http.server.ServerName;
|
||||||
|
import org.xbib.netty.http.server.ServerRequest;
|
||||||
|
import org.xbib.netty.http.server.ServerResponse;
|
||||||
|
|
||||||
import java.nio.CharBuffer;
|
import java.nio.CharBuffer;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
public class Http2ServerResponse implements ServerResponse {
|
public class Http2ServerResponse implements ServerResponse {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(Http2ServerResponse.class.getName());
|
||||||
|
|
||||||
private final ServerRequest serverRequest;
|
private final ServerRequest serverRequest;
|
||||||
|
|
||||||
private final ChannelHandlerContext ctx;
|
private final ChannelHandlerContext ctx;
|
||||||
|
|
||||||
private Http2Headers headers;
|
private Http2Headers headers;
|
||||||
|
|
||||||
public Http2ServerResponse(ServerRequest serverRequest, ChannelHandlerContext ctx) {
|
private HttpResponseStatus httpResponseStatus;
|
||||||
|
|
||||||
|
public Http2ServerResponse(ServerRequest serverRequest) {
|
||||||
|
Objects.requireNonNull(serverRequest);
|
||||||
|
Objects.requireNonNull(serverRequest.getChannelHandlerContext());
|
||||||
this.serverRequest = serverRequest;
|
this.serverRequest = serverRequest;
|
||||||
this.ctx = ctx;
|
this.ctx = serverRequest.getChannelHandlerContext();
|
||||||
this.headers = new DefaultHttp2Headers();
|
this.headers = new DefaultHttp2Headers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,38 +52,30 @@ public class Http2ServerResponse implements ServerResponse {
|
||||||
headers.set(name, value);
|
headers.set(name, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HttpResponseStatus getLastStatus() {
|
||||||
|
return httpResponseStatus;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(String text) {
|
public void write(String text) {
|
||||||
write(HttpResponseStatus.OK, "text/plain; charset=utf-8", text);
|
write(HttpResponseStatus.OK, "text/plain; charset=utf-8", text);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends an error response with the given status and default body.
|
|
||||||
*
|
|
||||||
* @param status the response status
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void writeError(HttpResponseStatus status) {
|
public void writeError(HttpResponseStatus status) {
|
||||||
writeError(status, status.code() < 400 ? ":)" : "sorry it didn't work out :(");
|
writeError(status, status.reasonPhrase());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends an error response with the given status and detailed message.
|
* Sends an error response with the given status and detailed message.
|
||||||
* An HTML body is created containing the status and its description,
|
|
||||||
* as well as the message, which is escaped using the
|
|
||||||
* {@link #escapeHTML escape} method.
|
|
||||||
*
|
*
|
||||||
* @param status the response status
|
* @param status the response status
|
||||||
* @param text the text body (sent as text/html)
|
* @param text the text body
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void writeError(HttpResponseStatus status, String text) {
|
public void writeError(HttpResponseStatus status, String text) {
|
||||||
write(status, "text/html; charset=utf-8",
|
write(status, "text/plain; charset=utf-8", status.code() + " " + text);
|
||||||
String.format("<!DOCTYPE html>%n<html>%n<head><title>%d %s</title></head>%n" +
|
|
||||||
"<body><h1>%d %s</h1>%n<p>%s</p>%n</body></html>",
|
|
||||||
status.code(), status.reasonPhrase(),
|
|
||||||
status.code(), status.reasonPhrase(),
|
|
||||||
escapeHTML(text)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -119,12 +124,15 @@ public class Http2ServerResponse implements ServerResponse {
|
||||||
headers.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), streamId);
|
headers.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), streamId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Http2Headers http2Headers = new DefaultHttp2Headers()
|
Http2Headers http2Headers = new DefaultHttp2Headers().status(status.codeAsText()).add(headers);
|
||||||
.status(status.codeAsText())
|
Http2HeadersFrame http2HeadersFrame = new DefaultHttp2HeadersFrame(http2Headers,byteBuf == null);
|
||||||
.add(headers);
|
logger.log(Level.FINEST, http2HeadersFrame::toString);
|
||||||
ctx.channel().write(new DefaultHttp2HeadersFrame(http2Headers,byteBuf == null));
|
ctx.channel().write(http2HeadersFrame);
|
||||||
|
this.httpResponseStatus = status;
|
||||||
if (byteBuf != null) {
|
if (byteBuf != null) {
|
||||||
ctx.channel().write(new DefaultHttp2DataFrame(byteBuf, true));
|
Http2DataFrame http2DataFrame = new DefaultHttp2DataFrame(byteBuf, true);
|
||||||
|
logger.log(Level.FINEST, http2DataFrame::toString);
|
||||||
|
ctx.channel().write(http2DataFrame);
|
||||||
}
|
}
|
||||||
ctx.channel().flush();
|
ctx.channel().flush();
|
||||||
}
|
}
|
||||||
|
@ -166,7 +174,7 @@ public class Http2ServerResponse implements ServerResponse {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (ref != null) {
|
if (ref != null) {
|
||||||
es.append(s.substring(start, i)).append(ref);
|
es.append(s, start, i).append(ref);
|
||||||
start = i + 1;
|
start = i + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,8 @@ import io.netty.handler.codec.http.HttpHeaderNames;
|
||||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||||
import io.netty.handler.codec.http2.Http2Settings;
|
import io.netty.handler.codec.http2.Http2Settings;
|
||||||
import io.netty.handler.codec.http2.HttpConversionUtil;
|
import io.netty.handler.codec.http2.HttpConversionUtil;
|
||||||
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.ServerResponse;
|
||||||
import org.xbib.netty.http.server.endpoint.NamedServer;
|
import org.xbib.netty.http.server.endpoint.NamedServer;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -26,15 +26,19 @@ public class Http2ServerTransport extends BaseServerTransport {
|
||||||
@Override
|
@Override
|
||||||
public void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest, Integer sequenceId) throws IOException {
|
public void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest, Integer sequenceId) throws IOException {
|
||||||
int requestId = requestCounter.incrementAndGet();
|
int requestId = requestCounter.incrementAndGet();
|
||||||
NamedServer namedServer = server.getVirtualServer(fullHttpRequest.headers().get(HttpHeaderNames.HOST));
|
NamedServer namedServer = server.getNamedServer(fullHttpRequest.headers().get(HttpHeaderNames.HOST));
|
||||||
if (namedServer == null) {
|
if (namedServer == null) {
|
||||||
namedServer = server.getDefaultVirtualServer();
|
namedServer = server.getDefaultNamedServer();
|
||||||
}
|
}
|
||||||
HttpAddress httpAddress = server.getServerConfig().getAddress();
|
|
||||||
Integer streamId = fullHttpRequest.headers().getInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text());
|
Integer streamId = fullHttpRequest.headers().getInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text());
|
||||||
ServerRequest serverRequest = new ServerRequest(namedServer, httpAddress, fullHttpRequest,
|
HttpServerRequest serverRequest = new HttpServerRequest();
|
||||||
sequenceId, streamId, requestId);
|
serverRequest.setNamedServer(namedServer);
|
||||||
ServerResponse serverResponse = new Http2ServerResponse(serverRequest, ctx);
|
serverRequest.setChannelHandlerContext(ctx);
|
||||||
|
serverRequest.setRequest(fullHttpRequest);
|
||||||
|
serverRequest.setSequenceId(sequenceId);
|
||||||
|
serverRequest.setRequestId(requestId);
|
||||||
|
serverRequest.setStreamId(streamId);
|
||||||
|
ServerResponse serverResponse = new Http2ServerResponse(serverRequest);
|
||||||
if (acceptRequest(serverRequest, serverResponse)) {
|
if (acceptRequest(serverRequest, serverResponse)) {
|
||||||
handle(serverRequest, serverResponse);
|
handle(serverRequest, serverResponse);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -0,0 +1,124 @@
|
||||||
|
package org.xbib.netty.http.server.transport;
|
||||||
|
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.handler.codec.http.FullHttpRequest;
|
||||||
|
import org.xbib.netty.http.server.ServerRequest;
|
||||||
|
import org.xbib.netty.http.server.endpoint.NamedServer;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@code HttpServerRequest} class encapsulates a single request. There must be a default constructor.
|
||||||
|
*/
|
||||||
|
public class HttpServerRequest implements ServerRequest {
|
||||||
|
|
||||||
|
private static final String PATH_SEPARATOR = "/";
|
||||||
|
|
||||||
|
private NamedServer namedServer;
|
||||||
|
|
||||||
|
private ChannelHandlerContext ctx;
|
||||||
|
|
||||||
|
private List<String> context;
|
||||||
|
|
||||||
|
private Map<String, String> rawParameters;
|
||||||
|
|
||||||
|
private FullHttpRequest httpRequest;
|
||||||
|
|
||||||
|
private Integer sequenceId;
|
||||||
|
|
||||||
|
private Integer streamId;
|
||||||
|
|
||||||
|
private Integer requestId;
|
||||||
|
|
||||||
|
public void setNamedServer(NamedServer namedServer) {
|
||||||
|
this.namedServer = namedServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NamedServer getNamedServer() {
|
||||||
|
return namedServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setChannelHandlerContext(ChannelHandlerContext ctx) {
|
||||||
|
this.ctx = ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelHandlerContext getChannelHandlerContext() {
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRequest(FullHttpRequest fullHttpRequest) {
|
||||||
|
this.httpRequest = fullHttpRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FullHttpRequest getRequest() {
|
||||||
|
return httpRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContext(List<String> context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getContext() {
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getContextPath() {
|
||||||
|
return String.join(PATH_SEPARATOR, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getEffectiveRequestPath() {
|
||||||
|
String uri = httpRequest.uri();
|
||||||
|
return context != null && !context.isEmpty() && uri.length() > 1 ?
|
||||||
|
uri.substring(getContextPath().length() + 2) : uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRawParameters(Map<String, String> rawParameters) {
|
||||||
|
this.rawParameters = rawParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> getRawParameters() {
|
||||||
|
return rawParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSequenceId(Integer sequenceId) {
|
||||||
|
this.sequenceId = sequenceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getSequenceId() {
|
||||||
|
return sequenceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStreamId(Integer streamId) {
|
||||||
|
this.streamId = streamId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer streamId() {
|
||||||
|
return streamId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRequestId(Integer requestId) {
|
||||||
|
this.requestId = requestId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer requestId() {
|
||||||
|
return requestId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return "ServerRequest[namedServer=" + namedServer +
|
||||||
|
",context=" + context +
|
||||||
|
",request=" + httpRequest +
|
||||||
|
"]";
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,6 @@ package org.xbib.netty.http.server.transport;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufUtil;
|
import io.netty.buffer.ByteBufUtil;
|
||||||
import io.netty.buffer.Unpooled;
|
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||||
import io.netty.handler.codec.http.DefaultHttpHeaders;
|
import io.netty.handler.codec.http.DefaultHttpHeaders;
|
||||||
|
@ -14,6 +13,8 @@ import io.netty.handler.codec.http.HttpResponseStatus;
|
||||||
import io.netty.handler.codec.http.HttpVersion;
|
import io.netty.handler.codec.http.HttpVersion;
|
||||||
import io.netty.util.AsciiString;
|
import io.netty.util.AsciiString;
|
||||||
import org.xbib.netty.http.server.ServerName;
|
import org.xbib.netty.http.server.ServerName;
|
||||||
|
import org.xbib.netty.http.server.ServerRequest;
|
||||||
|
import org.xbib.netty.http.server.ServerResponse;
|
||||||
import org.xbib.netty.http.server.handler.http.HttpPipelinedResponse;
|
import org.xbib.netty.http.server.handler.http.HttpPipelinedResponse;
|
||||||
|
|
||||||
import java.nio.CharBuffer;
|
import java.nio.CharBuffer;
|
||||||
|
@ -21,9 +22,16 @@ import java.nio.charset.Charset;
|
||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
public class HttpServerResponse implements ServerResponse {
|
public class HttpServerResponse implements ServerResponse {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(HttpServerResponse.class.getName());
|
||||||
|
|
||||||
|
private static final String EMPTY_STRING = "";
|
||||||
|
|
||||||
private final ServerRequest serverRequest;
|
private final ServerRequest serverRequest;
|
||||||
|
|
||||||
private final ChannelHandlerContext ctx;
|
private final ChannelHandlerContext ctx;
|
||||||
|
@ -32,9 +40,13 @@ public class HttpServerResponse implements ServerResponse {
|
||||||
|
|
||||||
private HttpHeaders trailingHeaders;
|
private HttpHeaders trailingHeaders;
|
||||||
|
|
||||||
public HttpServerResponse(ServerRequest serverRequest, ChannelHandlerContext ctx) {
|
private HttpResponseStatus httpResponseStatus;
|
||||||
|
|
||||||
|
public HttpServerResponse(ServerRequest serverRequest) {
|
||||||
|
Objects.requireNonNull(serverRequest, "serverRequest");
|
||||||
|
Objects.requireNonNull(serverRequest.getChannelHandlerContext(), "serverRequest channelHandlerContext");
|
||||||
this.serverRequest = serverRequest;
|
this.serverRequest = serverRequest;
|
||||||
this.ctx = ctx;
|
this.ctx = serverRequest.getChannelHandlerContext();
|
||||||
this.headers = new DefaultHttpHeaders();
|
this.headers = new DefaultHttpHeaders();
|
||||||
this.trailingHeaders = new DefaultHttpHeaders();
|
this.trailingHeaders = new DefaultHttpHeaders();
|
||||||
}
|
}
|
||||||
|
@ -44,6 +56,11 @@ public class HttpServerResponse implements ServerResponse {
|
||||||
headers.set(name, value);
|
headers.set(name, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HttpResponseStatus getLastStatus() {
|
||||||
|
return httpResponseStatus;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(String text) {
|
public void write(String text) {
|
||||||
write(HttpResponseStatus.OK, "text/plain; charset=utf-8", text);
|
write(HttpResponseStatus.OK, "text/plain; charset=utf-8", text);
|
||||||
|
@ -56,31 +73,23 @@ public class HttpServerResponse implements ServerResponse {
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void writeError(HttpResponseStatus status) {
|
public void writeError(HttpResponseStatus status) {
|
||||||
writeError(status, status.code() < 400 ? ":)" : "sorry it didn't work out :(");
|
writeError(status, status.reasonPhrase());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends an error response with the given status and detailed message.
|
* Sends an error response with the given status and detailed message.
|
||||||
* An HTML body is created containing the status and its description,
|
|
||||||
* as well as the message, which is escaped using the
|
|
||||||
* {@link #escapeHTML escape} method.
|
|
||||||
*
|
*
|
||||||
* @param status the response status
|
* @param status the response status
|
||||||
* @param text the text body (sent as text/html)
|
* @param text the text body
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void writeError(HttpResponseStatus status, String text) {
|
public void writeError(HttpResponseStatus status, String text) {
|
||||||
write(status, "text/html; charset=utf-8",
|
write(status, "text/plain; charset=utf-8", status.code() + " " + text);
|
||||||
String.format("<!DOCTYPE html>%n<html>%n<head><title>%d %s</title></head>%n" +
|
|
||||||
"<body><h1>%d %s</h1>%n<p>%s</p>%n</body></html>",
|
|
||||||
status.code(), status.reasonPhrase(),
|
|
||||||
status.code(), status.reasonPhrase(),
|
|
||||||
escapeHTML(text)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(HttpResponseStatus status) {
|
public void write(HttpResponseStatus status) {
|
||||||
write(status, null, (ByteBuf) null);
|
write(status, "application/octet-stream", EMPTY_STRING);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -95,7 +104,7 @@ public class HttpServerResponse implements ServerResponse {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(HttpResponseStatus status, String contentType, ByteBuf byteBuf) {
|
public void write(HttpResponseStatus status, String contentType, ByteBuf byteBuf) {
|
||||||
if (byteBuf != null) {
|
Objects.requireNonNull(byteBuf);
|
||||||
CharSequence s = headers.get(HttpHeaderNames.CONTENT_TYPE);
|
CharSequence s = headers.get(HttpHeaderNames.CONTENT_TYPE);
|
||||||
if (s == null) {
|
if (s == null) {
|
||||||
s = contentType != null ? contentType : HttpHeaderValues.APPLICATION_OCTET_STREAM;
|
s = contentType != null ? contentType : HttpHeaderValues.APPLICATION_OCTET_STREAM;
|
||||||
|
@ -117,66 +126,26 @@ public class HttpServerResponse implements ServerResponse {
|
||||||
headers.add(HttpHeaderNames.DATE, DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now(ZoneOffset.UTC)));
|
headers.add(HttpHeaderNames.DATE, DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now(ZoneOffset.UTC)));
|
||||||
}
|
}
|
||||||
headers.add(HttpHeaderNames.SERVER, ServerName.getServerName());
|
headers.add(HttpHeaderNames.SERVER, ServerName.getServerName());
|
||||||
}
|
FullHttpResponse fullHttpResponse =
|
||||||
FullHttpResponse fullHttpResponse = byteBuf != null ?
|
new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, byteBuf, headers, trailingHeaders);
|
||||||
new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
|
|
||||||
status, byteBuf, headers, trailingHeaders) :
|
|
||||||
new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
|
|
||||||
status, Unpooled.EMPTY_BUFFER, 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());
|
||||||
if (ctx.channel().isWritable()) {
|
if (ctx.channel().isWritable()) {
|
||||||
|
logger.log(Level.FINEST, fullHttpResponse::toString);
|
||||||
ctx.channel().writeAndFlush(httpPipelinedResponse);
|
ctx.channel().writeAndFlush(httpPipelinedResponse);
|
||||||
|
httpResponseStatus = status;
|
||||||
|
} else {
|
||||||
|
logger.log(Level.WARNING, "channel not writeable");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (ctx.channel().isWritable()) {
|
if (ctx.channel().isWritable()) {
|
||||||
|
logger.log(Level.FINEST, fullHttpResponse::toString);
|
||||||
ctx.channel().writeAndFlush(fullHttpResponse);
|
ctx.channel().writeAndFlush(fullHttpResponse);
|
||||||
|
httpResponseStatus = status;
|
||||||
|
} else {
|
||||||
|
logger.log(Level.WARNING, "channel not writeable");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an HTML-escaped version of the given string for safe display
|
|
||||||
* within a web page. The characters '&', '>' and '<' must always
|
|
||||||
* be escaped, and single and double quotes must be escaped within
|
|
||||||
* attribute values; this method escapes them always. This method can
|
|
||||||
* be used for generating both HTML and XHTML valid content.
|
|
||||||
*
|
|
||||||
* @param s the string to escape
|
|
||||||
* @return the escaped string
|
|
||||||
* @see <a href="http://www.w3.org/International/questions/qa-escapes">The W3C FAQ</a>
|
|
||||||
*/
|
|
||||||
private static String escapeHTML(String s) {
|
|
||||||
int len = s.length();
|
|
||||||
StringBuilder es = new StringBuilder(len + 30);
|
|
||||||
int start = 0;
|
|
||||||
for (int i = 0; i < len; i++) {
|
|
||||||
String ref = null;
|
|
||||||
switch (s.charAt(i)) {
|
|
||||||
case '&':
|
|
||||||
ref = "&";
|
|
||||||
break;
|
|
||||||
case '>':
|
|
||||||
ref = ">";
|
|
||||||
break;
|
|
||||||
case '<':
|
|
||||||
ref = "<";
|
|
||||||
break;
|
|
||||||
case '"':
|
|
||||||
ref = """;
|
|
||||||
break;
|
|
||||||
case '\'':
|
|
||||||
ref = "'";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (ref != null) {
|
|
||||||
es.append(s, start, i).append(ref);
|
|
||||||
start = i + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return start == 0 ? s : es.append(s.substring(start)).toString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ import io.netty.handler.codec.http.FullHttpRequest;
|
||||||
import io.netty.handler.codec.http.HttpHeaderNames;
|
import io.netty.handler.codec.http.HttpHeaderNames;
|
||||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||||
import io.netty.handler.codec.http2.Http2Settings;
|
import io.netty.handler.codec.http2.Http2Settings;
|
||||||
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.endpoint.NamedServer;
|
import org.xbib.netty.http.server.endpoint.NamedServer;
|
||||||
|
|
||||||
|
@ -26,14 +25,17 @@ public class HttpServerTransport extends BaseServerTransport {
|
||||||
public void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest, Integer sequenceId)
|
public void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest, Integer sequenceId)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
int requestId = requestCounter.incrementAndGet();
|
int requestId = requestCounter.incrementAndGet();
|
||||||
NamedServer namedServer = server.getVirtualServer(fullHttpRequest.headers().get(HttpHeaderNames.HOST));
|
NamedServer namedServer = server.getNamedServer(fullHttpRequest.headers().get(HttpHeaderNames.HOST));
|
||||||
if (namedServer == null) {
|
if (namedServer == null) {
|
||||||
namedServer = server.getDefaultVirtualServer();
|
namedServer = server.getDefaultNamedServer();
|
||||||
}
|
}
|
||||||
HttpAddress httpAddress = server.getServerConfig().getAddress();
|
HttpServerRequest serverRequest = new HttpServerRequest();
|
||||||
ServerRequest serverRequest = new ServerRequest(namedServer, httpAddress, fullHttpRequest,
|
serverRequest.setNamedServer(namedServer);
|
||||||
sequenceId, null, requestId);
|
serverRequest.setChannelHandlerContext(ctx);
|
||||||
ServerResponse serverResponse = new HttpServerResponse(serverRequest, ctx);
|
serverRequest.setRequest(fullHttpRequest);
|
||||||
|
serverRequest.setSequenceId(sequenceId);
|
||||||
|
serverRequest.setRequestId(requestId);
|
||||||
|
HttpServerResponse serverResponse = new HttpServerResponse(serverRequest);
|
||||||
if (acceptRequest(serverRequest, serverResponse)) {
|
if (acceptRequest(serverRequest, serverResponse)) {
|
||||||
handle(serverRequest, serverResponse);
|
handle(serverRequest, serverResponse);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,71 +0,0 @@
|
||||||
package org.xbib.netty.http.server.transport;
|
|
||||||
|
|
||||||
import io.netty.handler.codec.http.FullHttpRequest;
|
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
|
||||||
import org.xbib.netty.http.server.endpoint.NamedServer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The {@code ServerRequest} class encapsulates a single request.
|
|
||||||
*/
|
|
||||||
public class ServerRequest {
|
|
||||||
|
|
||||||
private final NamedServer namedServer;
|
|
||||||
|
|
||||||
private final HttpAddress httpAddress;
|
|
||||||
|
|
||||||
private final FullHttpRequest httpRequest;
|
|
||||||
|
|
||||||
private final Integer sequenceId;
|
|
||||||
|
|
||||||
private final Integer streamId;
|
|
||||||
|
|
||||||
private final Integer requestId;
|
|
||||||
|
|
||||||
private String contextPath;
|
|
||||||
|
|
||||||
public ServerRequest(NamedServer namedServer, HttpAddress httpAddress,
|
|
||||||
FullHttpRequest httpRequest, Integer sequenceId, Integer streamId, Integer requestId) {
|
|
||||||
this.namedServer = namedServer;
|
|
||||||
this.httpAddress = httpAddress;
|
|
||||||
this.httpRequest = httpRequest;
|
|
||||||
this.sequenceId = sequenceId;
|
|
||||||
this.streamId = streamId;
|
|
||||||
this.requestId = requestId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public NamedServer getNamedServer() {
|
|
||||||
return namedServer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setContextPath(String contextPath) {
|
|
||||||
this.contextPath = contextPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getContextPath() {
|
|
||||||
return contextPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRequestPath() {
|
|
||||||
return contextPath != null ? httpRequest.uri().substring(contextPath.length()) : httpRequest.uri();
|
|
||||||
}
|
|
||||||
|
|
||||||
public HttpAddress getHttpAddress() {
|
|
||||||
return httpAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
public FullHttpRequest getRequest() {
|
|
||||||
return httpRequest;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getSequenceId() {
|
|
||||||
return sequenceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer streamId() {
|
|
||||||
return streamId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer requestId() {
|
|
||||||
return requestId;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,586 +0,0 @@
|
||||||
package org.xbib.netty.http.server.util;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.Inet4Address;
|
|
||||||
import java.net.Inet6Address;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.net.InterfaceAddress;
|
|
||||||
import java.net.NetworkInterface;
|
|
||||||
import java.net.SocketException;
|
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.EnumSet;
|
|
||||||
import java.util.Enumeration;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.function.Predicate;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper class for Java networking.
|
|
||||||
*/
|
|
||||||
public class NetworkUtils {
|
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(NetworkUtils.class.getName());
|
|
||||||
|
|
||||||
private static final String lf = System.lineSeparator();
|
|
||||||
|
|
||||||
private static final char[] hexDigit = new char[]{
|
|
||||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
|
|
||||||
};
|
|
||||||
|
|
||||||
private static final String IPV4_SETTING = "java.net.preferIPv4Stack";
|
|
||||||
|
|
||||||
private static final String IPV6_SETTING = "java.net.preferIPv6Addresses";
|
|
||||||
|
|
||||||
private static InetAddress localAddress;
|
|
||||||
|
|
||||||
public static void extendSystemProperties() {
|
|
||||||
InetAddress address;
|
|
||||||
try {
|
|
||||||
address = InetAddress.getLocalHost();
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.log(Level.WARNING, e.getMessage(), e);
|
|
||||||
address = InetAddress.getLoopbackAddress();
|
|
||||||
}
|
|
||||||
localAddress = address;
|
|
||||||
try {
|
|
||||||
Map<String, String> map = new HashMap<>();
|
|
||||||
map.put("net.localhost", address.getCanonicalHostName());
|
|
||||||
String hostname = address.getHostName();
|
|
||||||
map.put("net.hostname", hostname);
|
|
||||||
InetAddress[] hostnameAddresses = InetAddress.getAllByName(hostname);
|
|
||||||
int i = 0;
|
|
||||||
for (InetAddress hostnameAddress : hostnameAddresses) {
|
|
||||||
map.put("net.hostaddress." + (i++), hostnameAddress.getCanonicalHostName());
|
|
||||||
}
|
|
||||||
for (NetworkInterface networkInterface : getAllRunningAndUpInterfaces()) {
|
|
||||||
InetAddress inetAddress = getFirstNonLoopbackAddress(networkInterface, NetworkProtocolVersion.IPV4);
|
|
||||||
if (inetAddress != null) {
|
|
||||||
map.put("net." + networkInterface.getDisplayName(), inetAddress.getCanonicalHostName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logger.log(Level.FINE, "found network properties for system properties: " + map);
|
|
||||||
for (Map.Entry<String, String> entry : map.entrySet()) {
|
|
||||||
System.setProperty(entry.getKey(), entry.getValue());
|
|
||||||
}
|
|
||||||
} catch (Throwable e) {
|
|
||||||
logger.log(Level.WARNING, e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private NetworkUtils() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isPreferIPv4() {
|
|
||||||
return Boolean.getBoolean(System.getProperty(IPV4_SETTING));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isPreferIPv6() {
|
|
||||||
return Boolean.getBoolean(System.getProperty(IPV6_SETTING));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static InetAddress getIPv4Localhost() throws UnknownHostException {
|
|
||||||
return getLocalhost(NetworkProtocolVersion.IPV4);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static InetAddress getIPv6Localhost() throws UnknownHostException {
|
|
||||||
return getLocalhost(NetworkProtocolVersion.IPV6);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static InetAddress getLocalhost(NetworkProtocolVersion ipversion) throws UnknownHostException {
|
|
||||||
return ipversion == NetworkProtocolVersion.IPV4 ?
|
|
||||||
InetAddress.getByName("127.0.0.1") : InetAddress.getByName("::1");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getLocalHostName(String defaultHostName) {
|
|
||||||
if (localAddress == null) {
|
|
||||||
return defaultHostName;
|
|
||||||
}
|
|
||||||
String hostName = localAddress.getHostName();
|
|
||||||
if (hostName == null) {
|
|
||||||
return defaultHostName;
|
|
||||||
}
|
|
||||||
return hostName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getLocalHostAddress(String defaultHostAddress) {
|
|
||||||
if (localAddress == null) {
|
|
||||||
return defaultHostAddress;
|
|
||||||
}
|
|
||||||
String hostAddress = localAddress.getHostAddress();
|
|
||||||
if (hostAddress == null) {
|
|
||||||
return defaultHostAddress;
|
|
||||||
}
|
|
||||||
return hostAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static InetAddress getLocalAddress() {
|
|
||||||
return localAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static NetworkClass getNetworkClass(InetAddress address) {
|
|
||||||
if (address == null || address.isAnyLocalAddress()) {
|
|
||||||
return NetworkClass.ANY;
|
|
||||||
}
|
|
||||||
if (address.isLoopbackAddress()) {
|
|
||||||
return NetworkClass.LOOPBACK;
|
|
||||||
}
|
|
||||||
if (address.isLinkLocalAddress() || address.isSiteLocalAddress()) {
|
|
||||||
return NetworkClass.LOCAL;
|
|
||||||
}
|
|
||||||
return NetworkClass.PUBLIC;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String format(InetAddress address) {
|
|
||||||
return format(address, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String format(InetSocketAddress address) {
|
|
||||||
return format(address.getAddress(), address.getPort());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String format(InetAddress address, int port) {
|
|
||||||
Objects.requireNonNull(address);
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
if (port != -1 && address instanceof Inet6Address) {
|
|
||||||
sb.append(toUriString(address));
|
|
||||||
} else {
|
|
||||||
sb.append(toAddrString(address));
|
|
||||||
}
|
|
||||||
if (port != -1) {
|
|
||||||
sb.append(':').append(port);
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String toUriString(InetAddress ip) {
|
|
||||||
if (ip instanceof Inet6Address) {
|
|
||||||
return "[" + toAddrString(ip) + "]";
|
|
||||||
}
|
|
||||||
return toAddrString(ip);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String toAddrString(InetAddress ip) {
|
|
||||||
if (ip == null) {
|
|
||||||
throw new NullPointerException("ip");
|
|
||||||
}
|
|
||||||
if (ip instanceof Inet4Address) {
|
|
||||||
byte[] bytes = ip.getAddress();
|
|
||||||
return (bytes[0] & 0xff) + "." + (bytes[1] & 0xff) + "." + (bytes[2] & 0xff) + "." + (bytes[3] & 0xff);
|
|
||||||
}
|
|
||||||
if (!(ip instanceof Inet6Address)) {
|
|
||||||
throw new IllegalArgumentException("ip");
|
|
||||||
}
|
|
||||||
byte[] bytes = ip.getAddress();
|
|
||||||
int[] hextets = new int[8];
|
|
||||||
for (int i = 0; i < hextets.length; i++) {
|
|
||||||
hextets[i] = (bytes[2 * i] & 255) << 8 | bytes[2 * i + 1] & 255;
|
|
||||||
}
|
|
||||||
compressLongestRunOfZeroes(hextets);
|
|
||||||
return hextetsToIPv6String(hextets);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean matchesNetwork(NetworkClass given, NetworkClass expected) {
|
|
||||||
switch (expected) {
|
|
||||||
case ANY:
|
|
||||||
return EnumSet.of(NetworkClass.LOOPBACK, NetworkClass.LOCAL, NetworkClass.PUBLIC, NetworkClass.ANY)
|
|
||||||
.contains(given);
|
|
||||||
case PUBLIC:
|
|
||||||
return EnumSet.of(NetworkClass.LOOPBACK, NetworkClass.LOCAL, NetworkClass.PUBLIC)
|
|
||||||
.contains(given);
|
|
||||||
case LOCAL:
|
|
||||||
return EnumSet.of(NetworkClass.LOOPBACK, NetworkClass.LOCAL)
|
|
||||||
.contains(given);
|
|
||||||
case LOOPBACK:
|
|
||||||
return NetworkClass.LOOPBACK == given;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static InetAddress getFirstNonLoopbackAddress(NetworkProtocolVersion ipversion) {
|
|
||||||
InetAddress address;
|
|
||||||
for (NetworkInterface networkInterface : getAllNetworkInterfaces()) {
|
|
||||||
try {
|
|
||||||
if (!networkInterface.isUp() || networkInterface.isLoopback()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.log(Level.WARNING, e.getMessage(), e);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
address = getFirstNonLoopbackAddress(networkInterface, ipversion);
|
|
||||||
if (address != null) {
|
|
||||||
return address;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static InetAddress getFirstNonLoopbackAddress(NetworkInterface networkInterface, NetworkProtocolVersion ipVersion) {
|
|
||||||
if (networkInterface == null) {
|
|
||||||
throw new IllegalArgumentException("network interface is null");
|
|
||||||
}
|
|
||||||
for (Enumeration<InetAddress> addresses = networkInterface.getInetAddresses(); addresses.hasMoreElements(); ) {
|
|
||||||
InetAddress address = addresses.nextElement();
|
|
||||||
if (!address.isLoopbackAddress() && (address instanceof Inet4Address && ipVersion == NetworkProtocolVersion.IPV4) ||
|
|
||||||
(address instanceof Inet6Address && ipVersion == NetworkProtocolVersion.IPV6)) {
|
|
||||||
return address;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static InetAddress getFirstAddress(NetworkInterface networkInterface, NetworkProtocolVersion ipVersion) {
|
|
||||||
if (networkInterface == null) {
|
|
||||||
throw new IllegalArgumentException("network interface is null");
|
|
||||||
}
|
|
||||||
for (Enumeration<InetAddress> addresses = networkInterface.getInetAddresses(); addresses.hasMoreElements(); ) {
|
|
||||||
InetAddress address = addresses.nextElement();
|
|
||||||
if ((address instanceof Inet4Address && ipVersion == NetworkProtocolVersion.IPV4) ||
|
|
||||||
(address instanceof Inet6Address && ipVersion == NetworkProtocolVersion.IPV6)) {
|
|
||||||
return address;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean interfaceSupports(NetworkInterface networkInterface, NetworkProtocolVersion ipVersion) {
|
|
||||||
boolean supportsVersion = false;
|
|
||||||
if (networkInterface != null) {
|
|
||||||
Enumeration<InetAddress> addresses = networkInterface.getInetAddresses();
|
|
||||||
while (addresses.hasMoreElements()) {
|
|
||||||
InetAddress address = addresses.nextElement();
|
|
||||||
if ((address instanceof Inet4Address && (ipVersion == NetworkProtocolVersion.IPV4)) ||
|
|
||||||
(address instanceof Inet6Address && (ipVersion == NetworkProtocolVersion.IPV6))) {
|
|
||||||
supportsVersion = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return supportsVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static NetworkProtocolVersion getProtocolVersion() {
|
|
||||||
switch (findAvailableProtocols()) {
|
|
||||||
case IPV4:
|
|
||||||
return NetworkProtocolVersion.IPV4;
|
|
||||||
case IPV6:
|
|
||||||
return NetworkProtocolVersion.IPV6;
|
|
||||||
case IPV46:
|
|
||||||
if (Boolean.getBoolean(System.getProperty(IPV4_SETTING))) {
|
|
||||||
return NetworkProtocolVersion.IPV4;
|
|
||||||
}
|
|
||||||
if (Boolean.getBoolean(System.getProperty(IPV6_SETTING))) {
|
|
||||||
return NetworkProtocolVersion.IPV6;
|
|
||||||
}
|
|
||||||
return NetworkProtocolVersion.IPV6;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return NetworkProtocolVersion.NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static NetworkProtocolVersion findAvailableProtocols() {
|
|
||||||
boolean hasIPv4 = false;
|
|
||||||
boolean hasIPv6 = false;
|
|
||||||
for (InetAddress addr : getAllAvailableAddresses()) {
|
|
||||||
if (addr instanceof Inet4Address) {
|
|
||||||
hasIPv4 = true;
|
|
||||||
}
|
|
||||||
if (addr instanceof Inet6Address) {
|
|
||||||
hasIPv6 = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (hasIPv4 && hasIPv6) {
|
|
||||||
return NetworkProtocolVersion.IPV46;
|
|
||||||
}
|
|
||||||
if (hasIPv4) {
|
|
||||||
return NetworkProtocolVersion.IPV4;
|
|
||||||
}
|
|
||||||
if (hasIPv6) {
|
|
||||||
return NetworkProtocolVersion.IPV6;
|
|
||||||
}
|
|
||||||
return NetworkProtocolVersion.NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static InetAddress resolveInetAddress(String hostname, String defaultValue) throws IOException {
|
|
||||||
String host = hostname;
|
|
||||||
if (host == null) {
|
|
||||||
host = defaultValue;
|
|
||||||
}
|
|
||||||
String origHost = host;
|
|
||||||
int pos = host.indexOf(':');
|
|
||||||
if (pos > 0) {
|
|
||||||
host = host.substring(0, pos - 1);
|
|
||||||
}
|
|
||||||
if ((host.startsWith("#") && host.endsWith("#")) || (host.startsWith("_") && host.endsWith("_"))) {
|
|
||||||
host = host.substring(1, host.length() - 1);
|
|
||||||
if ("local".equals(host)) {
|
|
||||||
return getLocalAddress();
|
|
||||||
} else if (host.startsWith("non_loopback")) {
|
|
||||||
if (host.toLowerCase(Locale.ROOT).endsWith(":ipv4")) {
|
|
||||||
return getFirstNonLoopbackAddress(NetworkProtocolVersion.IPV4);
|
|
||||||
} else if (host.toLowerCase(Locale.ROOT).endsWith(":ipv6")) {
|
|
||||||
return getFirstNonLoopbackAddress(NetworkProtocolVersion.IPV6);
|
|
||||||
} else {
|
|
||||||
return getFirstNonLoopbackAddress(getProtocolVersion());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
NetworkProtocolVersion networkProtocolVersion = getProtocolVersion();
|
|
||||||
if (host.toLowerCase(Locale.ROOT).endsWith(":ipv4")) {
|
|
||||||
networkProtocolVersion = NetworkProtocolVersion.IPV4;
|
|
||||||
host = host.substring(0, host.length() - 5);
|
|
||||||
} else if (host.toLowerCase(Locale.ROOT).endsWith(":ipv6")) {
|
|
||||||
networkProtocolVersion = NetworkProtocolVersion.IPV6;
|
|
||||||
host = host.substring(0, host.length() - 5);
|
|
||||||
}
|
|
||||||
for (NetworkInterface ni : getInterfaces(NetworkUtils::isUp)) {
|
|
||||||
if (host.equals(ni.getName()) || host.equals(ni.getDisplayName())) {
|
|
||||||
if (ni.isLoopback()) {
|
|
||||||
return getFirstAddress(ni, networkProtocolVersion);
|
|
||||||
} else {
|
|
||||||
return getFirstNonLoopbackAddress(ni, networkProtocolVersion);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new IOException("failed to find network interface for [" + origHost + "]");
|
|
||||||
}
|
|
||||||
return InetAddress.getByName(host);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static InetAddress resolvePublicHostAddress(String host) throws IOException {
|
|
||||||
InetAddress address = resolveInetAddress(host, null);
|
|
||||||
if (address == null || address.isAnyLocalAddress()) {
|
|
||||||
address = getFirstNonLoopbackAddress(NetworkProtocolVersion.IPV4);
|
|
||||||
if (address == null) {
|
|
||||||
address = getFirstNonLoopbackAddress(getProtocolVersion());
|
|
||||||
if (address == null) {
|
|
||||||
address = getLocalAddress();
|
|
||||||
if (address == null) {
|
|
||||||
return getLocalhost(NetworkProtocolVersion.IPV4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return address;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<NetworkInterface> getAllNetworkInterfaces() {
|
|
||||||
return getInterfaces(n -> true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<NetworkInterface> getAllRunningAndUpInterfaces() {
|
|
||||||
return getInterfaces(NetworkUtils::isUp);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<NetworkInterface> getInterfaces(Predicate<NetworkInterface> predicate) {
|
|
||||||
List<NetworkInterface> networkInterfaces = new ArrayList<>();
|
|
||||||
Enumeration<NetworkInterface> interfaces;
|
|
||||||
try {
|
|
||||||
interfaces = NetworkInterface.getNetworkInterfaces();
|
|
||||||
} catch (Exception e) {
|
|
||||||
return networkInterfaces;
|
|
||||||
}
|
|
||||||
while (interfaces.hasMoreElements()) {
|
|
||||||
NetworkInterface networkInterface = interfaces.nextElement();
|
|
||||||
if (predicate.test(networkInterface)) {
|
|
||||||
networkInterfaces.add(networkInterface);
|
|
||||||
Enumeration<NetworkInterface> subInterfaces = networkInterface.getSubInterfaces();
|
|
||||||
while (subInterfaces.hasMoreElements()) {
|
|
||||||
networkInterfaces.add(subInterfaces.nextElement());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sortInterfaces(networkInterfaces);
|
|
||||||
return networkInterfaces;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<InetAddress> getAllAvailableAddresses() {
|
|
||||||
List<InetAddress> allAddresses = new ArrayList<>();
|
|
||||||
for (NetworkInterface networkInterface : getAllNetworkInterfaces()) {
|
|
||||||
Enumeration<InetAddress> addrs = networkInterface.getInetAddresses();
|
|
||||||
while (addrs.hasMoreElements()) {
|
|
||||||
allAddresses.add(addrs.nextElement());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sortAddresses(allAddresses);
|
|
||||||
return allAddresses;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String displayNetworkInterfaces() {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
for (NetworkInterface nic : getAllNetworkInterfaces()) {
|
|
||||||
sb.append(displayNetworkInterface(nic));
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String displayNetworkInterface(NetworkInterface nic) {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
sb.append(lf).append(nic.getName()).append(lf);
|
|
||||||
if (!nic.getName().equals(nic.getDisplayName())) {
|
|
||||||
sb.append("\t").append(nic.getDisplayName()).append(lf);
|
|
||||||
}
|
|
||||||
sb.append("\t").append("flags ");
|
|
||||||
List<String> flags = new ArrayList<>();
|
|
||||||
try {
|
|
||||||
if (nic.isUp()) {
|
|
||||||
flags.add("UP");
|
|
||||||
}
|
|
||||||
if (nic.supportsMulticast()) {
|
|
||||||
flags.add("MULTICAST");
|
|
||||||
}
|
|
||||||
if (nic.isLoopback()) {
|
|
||||||
flags.add("LOOPBACK");
|
|
||||||
}
|
|
||||||
if (nic.isPointToPoint()) {
|
|
||||||
flags.add("POINTTOPOINT");
|
|
||||||
}
|
|
||||||
if (nic.isVirtual()) {
|
|
||||||
flags.add("VIRTUAL");
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.log(Level.WARNING, e.getMessage(), e);
|
|
||||||
}
|
|
||||||
sb.append(String.join(",", flags));
|
|
||||||
try {
|
|
||||||
sb.append(" mtu ").append(nic.getMTU()).append(lf);
|
|
||||||
} catch (SocketException e) {
|
|
||||||
logger.log(Level.WARNING, e.getMessage(), e);
|
|
||||||
}
|
|
||||||
List<InterfaceAddress> addresses = nic.getInterfaceAddresses();
|
|
||||||
for (InterfaceAddress address : addresses) {
|
|
||||||
sb.append("\t").append(formatAddress(address)).append(lf);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
byte[] b = nic.getHardwareAddress();
|
|
||||||
if (b != null) {
|
|
||||||
sb.append("\t").append("ether ");
|
|
||||||
for (int i = 0; i < b.length; i++) {
|
|
||||||
if (i > 0) {
|
|
||||||
sb.append(":");
|
|
||||||
}
|
|
||||||
sb.append(hexDigit[(b[i] >> 4) & 0x0f]).append(hexDigit[b[i] & 0x0f]);
|
|
||||||
}
|
|
||||||
sb.append(lf);
|
|
||||||
}
|
|
||||||
} catch (SocketException e) {
|
|
||||||
logger.log(Level.WARNING, e.getMessage(), e);
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void sortInterfaces(List<NetworkInterface> interfaces) {
|
|
||||||
interfaces.sort(Comparator.comparingInt(NetworkInterface::getIndex));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void sortAddresses(List<InetAddress> addressList) {
|
|
||||||
addressList.sort((o1, o2) -> compareBytes(o1.getAddress(), o2.getAddress()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String formatAddress(InterfaceAddress interfaceAddress) {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
InetAddress address = interfaceAddress.getAddress();
|
|
||||||
if (address instanceof Inet6Address) {
|
|
||||||
sb.append("inet6 ").append(format(address))
|
|
||||||
.append(" prefixlen:").append(interfaceAddress.getNetworkPrefixLength());
|
|
||||||
} else {
|
|
||||||
int netmask = 0xFFFFFFFF << (32 - interfaceAddress.getNetworkPrefixLength());
|
|
||||||
byte[] b = new byte[] {
|
|
||||||
(byte) (netmask >>> 24),
|
|
||||||
(byte) (netmask >>> 16 & 0xFF),
|
|
||||||
(byte) (netmask >>> 8 & 0xFF),
|
|
||||||
(byte) (netmask & 0xFF)
|
|
||||||
};
|
|
||||||
sb.append("inet ").append(format(address));
|
|
||||||
try {
|
|
||||||
sb.append(" netmask:").append(format(InetAddress.getByAddress(b)));
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
logger.log(Level.WARNING, e.getMessage(), e);
|
|
||||||
}
|
|
||||||
InetAddress broadcast = interfaceAddress.getBroadcast();
|
|
||||||
if (broadcast != null) {
|
|
||||||
sb.append(" broadcast:").append(format(broadcast));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (address.isLoopbackAddress()) {
|
|
||||||
sb.append(" scope:host");
|
|
||||||
} else if (address.isLinkLocalAddress()) {
|
|
||||||
sb.append(" scope:link");
|
|
||||||
} else if (address.isSiteLocalAddress()) {
|
|
||||||
sb.append(" scope:site");
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isUp(NetworkInterface networkInterface) {
|
|
||||||
try {
|
|
||||||
return networkInterface.isUp();
|
|
||||||
} catch (SocketException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int compareBytes(byte[] left, byte[] right) {
|
|
||||||
for (int i = 0, j = 0; i < left.length && j < right.length; i++, j++) {
|
|
||||||
int a = left[i] & 0xff;
|
|
||||||
int b = right[j] & 0xff;
|
|
||||||
if (a != b) {
|
|
||||||
return a - b;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return left.length - right.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void compressLongestRunOfZeroes(int[] hextets) {
|
|
||||||
int bestRunStart = -1;
|
|
||||||
int bestRunLength = -1;
|
|
||||||
int runStart = -1;
|
|
||||||
for (int i = 0; i < hextets.length + 1; i++) {
|
|
||||||
if (i < hextets.length && hextets[i] == 0) {
|
|
||||||
if (runStart < 0) {
|
|
||||||
runStart = i;
|
|
||||||
}
|
|
||||||
} else if (runStart >= 0) {
|
|
||||||
int runLength = i - runStart;
|
|
||||||
if (runLength > bestRunLength) {
|
|
||||||
bestRunStart = runStart;
|
|
||||||
bestRunLength = runLength;
|
|
||||||
}
|
|
||||||
runStart = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (bestRunLength >= 2) {
|
|
||||||
Arrays.fill(hextets, bestRunStart, bestRunStart + bestRunLength, -1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String hextetsToIPv6String(int[] hextets) {
|
|
||||||
StringBuilder sb = new StringBuilder(39);
|
|
||||||
boolean lastWasNumber = false;
|
|
||||||
for (int i = 0; i < hextets.length; i++) {
|
|
||||||
boolean b = hextets[i] >= 0;
|
|
||||||
if (b) {
|
|
||||||
if (lastWasNumber) {
|
|
||||||
sb.append(':');
|
|
||||||
}
|
|
||||||
sb.append(Integer.toHexString(hextets[i]));
|
|
||||||
} else {
|
|
||||||
if (i == 0 || lastWasNumber) {
|
|
||||||
sb.append("::");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lastWasNumber = b;
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -10,6 +10,7 @@ import org.xbib.netty.http.client.listener.ResponseListener;
|
||||||
import org.xbib.netty.http.client.transport.Transport;
|
import org.xbib.netty.http.client.transport.Transport;
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
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.endpoint.NamedServer;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
|
@ -29,18 +30,21 @@ class CleartextHttp1Test {
|
||||||
@Test
|
@Test
|
||||||
void testSimpleClearTextHttp1() throws Exception {
|
void testSimpleClearTextHttp1() throws Exception {
|
||||||
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
||||||
Server server = Server.builder()
|
NamedServer namedServer = NamedServer.builder(httpAddress)
|
||||||
.bind(httpAddress).build();
|
.singleEndpoint("/**", (request, response) ->
|
||||||
server.getDefaultVirtualServer().addHandler("/", (request, response) ->
|
response.write(HttpResponseStatus.OK, "text/plain", request.getRequest().content().retain()))
|
||||||
response.write(HttpResponseStatus.OK, "text/plain", request.getRequest().content().retain()));
|
.build();
|
||||||
|
Server server = Server.builder(namedServer).build();
|
||||||
server.accept();
|
server.accept();
|
||||||
Client client = Client.builder()
|
Client client = Client.builder()
|
||||||
.build();
|
.build();
|
||||||
AtomicInteger counter = new AtomicInteger();
|
AtomicInteger counter = new AtomicInteger();
|
||||||
final ResponseListener responseListener = fullHttpResponse -> {
|
final ResponseListener responseListener = fullHttpResponse -> {
|
||||||
|
if (fullHttpResponse.status().equals(HttpResponseStatus.OK)) {
|
||||||
String response = fullHttpResponse.content().toString(StandardCharsets.UTF_8);
|
String response = fullHttpResponse.content().toString(StandardCharsets.UTF_8);
|
||||||
//logger.log(Level.INFO, "status = " + fullHttpResponse.status() + " response body = " + response);
|
//logger.log(Level.INFO, "status = " + fullHttpResponse.status() + " response body = " + response);
|
||||||
counter.incrementAndGet();
|
counter.incrementAndGet();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
Request request = Request.get().setVersion(HttpVersion.HTTP_1_1)
|
Request request = Request.get().setVersion(HttpVersion.HTTP_1_1)
|
||||||
|
@ -60,11 +64,11 @@ class CleartextHttp1Test {
|
||||||
void testPooledClearTextHttp1() throws Exception {
|
void testPooledClearTextHttp1() throws Exception {
|
||||||
int loop = 4096;
|
int loop = 4096;
|
||||||
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
||||||
Server server = Server.builder()
|
NamedServer namedServer = NamedServer.builder(httpAddress)
|
||||||
.bind(httpAddress).build();
|
.singleEndpoint("/**", (request, response) ->
|
||||||
server.getDefaultVirtualServer().addHandler("/", (request, response) -> {
|
response.write(HttpResponseStatus.OK, "text/plain", request.getRequest().content().retain()))
|
||||||
response.write(HttpResponseStatus.OK, "text/plain", request.getRequest().content().retain());
|
.build();
|
||||||
});
|
Server server = Server.builder(namedServer).build();
|
||||||
server.accept();
|
server.accept();
|
||||||
Client client = Client.builder()
|
Client client = Client.builder()
|
||||||
.addPoolNode(httpAddress)
|
.addPoolNode(httpAddress)
|
||||||
|
@ -72,9 +76,11 @@ class CleartextHttp1Test {
|
||||||
.build();
|
.build();
|
||||||
AtomicInteger counter = new AtomicInteger();
|
AtomicInteger counter = new AtomicInteger();
|
||||||
final ResponseListener responseListener = fullHttpResponse -> {
|
final ResponseListener responseListener = fullHttpResponse -> {
|
||||||
|
if (fullHttpResponse.status().equals(HttpResponseStatus.OK)) {
|
||||||
String response = fullHttpResponse.content().toString(StandardCharsets.UTF_8);
|
String response = fullHttpResponse.content().toString(StandardCharsets.UTF_8);
|
||||||
//logger.log(Level.INFO, "status = " + fullHttpResponse.status() + " response body = " + response);
|
//logger.log(Level.INFO, "status = " + fullHttpResponse.status() + " response body = " + response);
|
||||||
counter.incrementAndGet();
|
counter.incrementAndGet();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
for (int i = 0; i < loop; i++) {
|
for (int i = 0; i < loop; i++) {
|
||||||
|
@ -103,11 +109,11 @@ class CleartextHttp1Test {
|
||||||
int threads = 4;
|
int threads = 4;
|
||||||
int loop = 4 * 1024;
|
int loop = 4 * 1024;
|
||||||
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
||||||
Server server = Server.builder()
|
NamedServer namedServer = NamedServer.builder(httpAddress)
|
||||||
.bind(httpAddress).build();
|
.singleEndpoint("/**", (request, response) ->
|
||||||
server.getDefaultVirtualServer().addHandler("/", (request, response) -> {
|
response.write(HttpResponseStatus.OK, "text/plain", request.getRequest().content().retain()))
|
||||||
response.write(HttpResponseStatus.OK, "text/plain", request.getRequest().content().retain());
|
.build();
|
||||||
});
|
Server server = Server.builder(namedServer).build();
|
||||||
server.accept();
|
server.accept();
|
||||||
Client client = Client.builder()
|
Client client = Client.builder()
|
||||||
.addPoolNode(httpAddress)
|
.addPoolNode(httpAddress)
|
||||||
|
@ -115,10 +121,12 @@ class CleartextHttp1Test {
|
||||||
.build();
|
.build();
|
||||||
AtomicInteger counter = new AtomicInteger();
|
AtomicInteger counter = new AtomicInteger();
|
||||||
final ResponseListener responseListener = fullHttpResponse -> {
|
final ResponseListener responseListener = fullHttpResponse -> {
|
||||||
|
if (fullHttpResponse.status().equals(HttpResponseStatus.OK)) {
|
||||||
String response = fullHttpResponse.content().toString(StandardCharsets.UTF_8);
|
String response = fullHttpResponse.content().toString(StandardCharsets.UTF_8);
|
||||||
//logger.log(Level.INFO, "status = " + fullHttpResponse.status() +
|
//logger.log(Level.INFO, "status = " + fullHttpResponse.status() +
|
||||||
// " response=" + response + " payload=" + payload);
|
// " response=" + response + " payload=" + payload);
|
||||||
counter.incrementAndGet();
|
counter.incrementAndGet();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
ExecutorService executorService = Executors.newFixedThreadPool(threads);
|
ExecutorService executorService = Executors.newFixedThreadPool(threads);
|
||||||
|
|
|
@ -9,6 +9,7 @@ import org.xbib.netty.http.client.listener.ResponseListener;
|
||||||
import org.xbib.netty.http.client.transport.Transport;
|
import org.xbib.netty.http.client.transport.Transport;
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
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.endpoint.NamedServer;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
@ -29,11 +30,11 @@ class CleartextHttp2Test {
|
||||||
@Test
|
@Test
|
||||||
void testSimpleCleartextHttp2() throws Exception {
|
void testSimpleCleartextHttp2() throws Exception {
|
||||||
HttpAddress httpAddress = HttpAddress.http2("localhost", 8008);
|
HttpAddress httpAddress = HttpAddress.http2("localhost", 8008);
|
||||||
Server server = Server.builder()
|
NamedServer namedServer = NamedServer.builder(httpAddress)
|
||||||
.bind(httpAddress)
|
.singleEndpoint("/", (request, response) ->
|
||||||
|
response.write(HttpResponseStatus.OK, "text/plain", request.getRequest().content().retain()))
|
||||||
.build();
|
.build();
|
||||||
server.getDefaultVirtualServer().addHandler("/", (request, response) ->
|
Server server = Server.builder(namedServer).build();
|
||||||
response.write(HttpResponseStatus.OK, "text/plain", request.getRequest().content().retain()));
|
|
||||||
server.accept();
|
server.accept();
|
||||||
Client client = Client.builder()
|
Client client = Client.builder()
|
||||||
.build();
|
.build();
|
||||||
|
@ -46,7 +47,7 @@ class CleartextHttp2Test {
|
||||||
counter.incrementAndGet();
|
counter.incrementAndGet();
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
String payload = Integer.toString(0) + "/" + Integer.toString(0);
|
String payload = 0 + "/" + 0;
|
||||||
Request request = Request.get().setVersion("HTTP/2.0")
|
Request request = Request.get().setVersion("HTTP/2.0")
|
||||||
.url(server.getServerConfig().getAddress().base())
|
.url(server.getServerConfig().getAddress().base())
|
||||||
.content(payload, "text/plain")
|
.content(payload, "text/plain")
|
||||||
|
@ -69,10 +70,11 @@ class CleartextHttp2Test {
|
||||||
void testPooledClearTextHttp2() throws Exception {
|
void testPooledClearTextHttp2() throws Exception {
|
||||||
int loop = 4096;
|
int loop = 4096;
|
||||||
HttpAddress httpAddress = HttpAddress.http2("localhost", 8008);
|
HttpAddress httpAddress = HttpAddress.http2("localhost", 8008);
|
||||||
Server server = Server.builder()
|
NamedServer namedServer = NamedServer.builder(httpAddress)
|
||||||
.bind(httpAddress).build();
|
.singleEndpoint("/", (request, response) ->
|
||||||
server.getDefaultVirtualServer().addHandler("/", (request, response) ->
|
response.write(HttpResponseStatus.OK, "text/plain", request.getRequest().content().retain()))
|
||||||
response.write(HttpResponseStatus.OK, "text/plain", request.getRequest().content().retain()));
|
.build();
|
||||||
|
Server server = Server.builder(namedServer).build();
|
||||||
server.accept();
|
server.accept();
|
||||||
Client client = Client.builder()
|
Client client = Client.builder()
|
||||||
.addPoolNode(httpAddress)
|
.addPoolNode(httpAddress)
|
||||||
|
@ -116,12 +118,12 @@ class CleartextHttp2Test {
|
||||||
int threads = 2;
|
int threads = 2;
|
||||||
int loop = 4 * 1024;
|
int loop = 4 * 1024;
|
||||||
HttpAddress httpAddress = HttpAddress.http2("localhost", 8008);
|
HttpAddress httpAddress = HttpAddress.http2("localhost", 8008);
|
||||||
Server server = Server.builder()
|
NamedServer namedServer = NamedServer.builder(httpAddress)
|
||||||
.bind(httpAddress)
|
.singleEndpoint("/", (request, response) ->
|
||||||
|
response.write(HttpResponseStatus.OK, "text/plain",
|
||||||
|
request.getRequest().content().toString(StandardCharsets.UTF_8)))
|
||||||
.build();
|
.build();
|
||||||
server.getDefaultVirtualServer().addHandler("/", (request, response) ->
|
Server server = Server.builder(namedServer).build();
|
||||||
response.write(request.getRequest().content().toString(StandardCharsets.UTF_8))
|
|
||||||
);
|
|
||||||
server.accept();
|
server.accept();
|
||||||
Client client = Client.builder()
|
Client client = Client.builder()
|
||||||
.addPoolNode(httpAddress)
|
.addPoolNode(httpAddress)
|
||||||
|
@ -144,7 +146,7 @@ class CleartextHttp2Test {
|
||||||
executorService.submit(() -> {
|
executorService.submit(() -> {
|
||||||
try {
|
try {
|
||||||
for (int i = 0; i < loop; i++) {
|
for (int i = 0; i < loop; i++) {
|
||||||
String payload = Integer.toString(t) + "/" + Integer.toString(i);
|
String payload = t + "/" + i;
|
||||||
Request request = Request.get().setVersion("HTTP/2.0")
|
Request request = Request.get().setVersion("HTTP/2.0")
|
||||||
.url(server.getServerConfig().getAddress().base())
|
.url(server.getServerConfig().getAddress().base())
|
||||||
.content(payload, "text/plain")
|
.content(payload, "text/plain")
|
||||||
|
@ -180,24 +182,26 @@ class CleartextHttp2Test {
|
||||||
|
|
||||||
HttpAddress httpAddress1 = HttpAddress.http2("localhost", 8008);
|
HttpAddress httpAddress1 = HttpAddress.http2("localhost", 8008);
|
||||||
AtomicInteger counter1 = new AtomicInteger();
|
AtomicInteger counter1 = new AtomicInteger();
|
||||||
Server server1 = Server.builder()
|
NamedServer namedServer1 = NamedServer.builder(httpAddress1)
|
||||||
.bind(httpAddress1).build();
|
.singleEndpoint("/", (request, response) -> {
|
||||||
server1.getDefaultVirtualServer().addHandler("/", (request, response) -> {
|
response.write(HttpResponseStatus.OK, "text/plain",
|
||||||
response.write(request.getRequest().content().toString(StandardCharsets.UTF_8));
|
request.getRequest().content().toString(StandardCharsets.UTF_8));
|
||||||
counter1.incrementAndGet();
|
counter1.incrementAndGet();
|
||||||
});
|
})
|
||||||
|
.build();
|
||||||
|
Server server1 = Server.builder(namedServer1).build();
|
||||||
server1.accept();
|
server1.accept();
|
||||||
|
|
||||||
HttpAddress httpAddress2 = HttpAddress.http2("localhost", 8009);
|
HttpAddress httpAddress2 = HttpAddress.http2("localhost", 8009);
|
||||||
AtomicInteger counter2 = new AtomicInteger();
|
AtomicInteger counter2 = new AtomicInteger();
|
||||||
Server server2 = Server.builder()
|
NamedServer namedServer2 = NamedServer.builder(httpAddress2)
|
||||||
.bind(httpAddress2).build();
|
.singleEndpoint("/", (request, response) -> {
|
||||||
server2.getDefaultVirtualServer().addHandler("/", (request, response) -> {
|
response.write(HttpResponseStatus.OK, "text/plain",
|
||||||
response.write(request.getRequest().content().toString(StandardCharsets.UTF_8));
|
request.getRequest().content().toString(StandardCharsets.UTF_8));
|
||||||
counter2.incrementAndGet();
|
counter2.incrementAndGet();
|
||||||
});
|
})
|
||||||
|
.build();
|
||||||
|
Server server2 = Server.builder(namedServer2).build();
|
||||||
server2.accept();
|
server2.accept();
|
||||||
|
|
||||||
Client client = Client.builder()
|
Client client = Client.builder()
|
||||||
.addPoolNode(httpAddress1)
|
.addPoolNode(httpAddress1)
|
||||||
.addPoolNode(httpAddress2)
|
.addPoolNode(httpAddress2)
|
||||||
|
@ -220,7 +224,7 @@ class CleartextHttp2Test {
|
||||||
executorService.submit(() -> {
|
executorService.submit(() -> {
|
||||||
try {
|
try {
|
||||||
for (int i = 0; i < loop; i++) {
|
for (int i = 0; i < loop; i++) {
|
||||||
String payload = Integer.toString(t) + "/" + Integer.toString(i);
|
String payload = t + "/" + i;
|
||||||
Request request = Request.get().setVersion("HTTP/2.0")
|
Request request = Request.get().setVersion("HTTP/2.0")
|
||||||
.uri("/")
|
.uri("/")
|
||||||
.content(payload, "text/plain")
|
.content(payload, "text/plain")
|
||||||
|
|
|
@ -0,0 +1,149 @@
|
||||||
|
package org.xbib.netty.http.server.test;
|
||||||
|
|
||||||
|
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.endpoint.Endpoint;
|
||||||
|
import org.xbib.netty.http.server.endpoint.EndpointResolver;
|
||||||
|
import org.xbib.netty.http.server.endpoint.NamedServer;
|
||||||
|
import org.xbib.netty.http.server.endpoint.service.NioService;
|
||||||
|
import org.xbib.netty.http.server.endpoint.service.Service;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
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.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
@ExtendWith(NettyHttpExtension.class)
|
||||||
|
class EndpointTest {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(EndpointTest.class.getName());
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testEndpoints() throws Exception {
|
||||||
|
Path vartmp = Paths.get("/var/tmp/");
|
||||||
|
Service service = new NioService(vartmp);
|
||||||
|
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
||||||
|
EndpointResolver endpointResolver = EndpointResolver.builder()
|
||||||
|
.addEndpoint(Endpoint.builder().setPrefix("/static").setPath("/**").build())
|
||||||
|
.addEndpoint(Endpoint.builder().setPrefix("/static1").setPath("/**").build())
|
||||||
|
.addEndpoint(Endpoint.builder().setPrefix("/static2").setPath("/**").build())
|
||||||
|
.setDispatcher((endpoint, req, resp) -> {
|
||||||
|
logger.log(Level.FINE, "endpoint=" + endpoint + " req=" + req);
|
||||||
|
service.handle(req, resp);
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
NamedServer namedServer = NamedServer.builder(httpAddress)
|
||||||
|
.addEndpointResolver(endpointResolver)
|
||||||
|
.build();
|
||||||
|
Server server = Server.builder(namedServer)
|
||||||
|
.build();
|
||||||
|
server.logDiagnostics(Level.INFO);
|
||||||
|
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"))
|
||||||
|
.build()
|
||||||
|
.setResponseListener(r -> {
|
||||||
|
assertEquals("Hello Jörg", r.content().toString(StandardCharsets.UTF_8));
|
||||||
|
success.set(true);
|
||||||
|
});
|
||||||
|
client.execute(request).get();
|
||||||
|
Request request1 = Request.get().setVersion(HttpVersion.HTTP_1_1)
|
||||||
|
.url(server.getServerConfig().getAddress().base().resolve("/static1/test1.txt"))
|
||||||
|
.build()
|
||||||
|
.setResponseListener(r -> {
|
||||||
|
assertEquals("Hello Jörg 1", r.content().toString(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(r -> {
|
||||||
|
assertEquals("Hello Jörg 2", r.content().toString(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 testMassiveEndpoints() throws IOException {
|
||||||
|
int max = 1000;
|
||||||
|
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
||||||
|
EndpointResolver.Builder endpointResolverBuilder = EndpointResolver.builder()
|
||||||
|
.setPrefix("/static");
|
||||||
|
for (int i = 0; i < max; i++) {
|
||||||
|
endpointResolverBuilder.addEndpoint(Endpoint.builder()
|
||||||
|
.setPath(i + "/**")
|
||||||
|
.addFilter((req, resp) -> resp.write(HttpResponseStatus.OK))
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
endpointResolverBuilder.setDispatcher((endpoint, req, resp) -> {
|
||||||
|
logger.log(Level.FINEST, "endpoint=" + endpoint + " req=" + req + " resp=" + resp);
|
||||||
|
});
|
||||||
|
NamedServer namedServer = NamedServer.builder(httpAddress)
|
||||||
|
.addEndpointResolver(endpointResolverBuilder.build())
|
||||||
|
.build();
|
||||||
|
Server server = Server.builder(namedServer)
|
||||||
|
.build();
|
||||||
|
server.logDiagnostics(Level.INFO);
|
||||||
|
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(r -> {
|
||||||
|
if (r.status().equals(HttpResponseStatus.OK)) {
|
||||||
|
count.incrementAndGet();
|
||||||
|
logger.log(Level.INFO, r.status().reasonPhrase());
|
||||||
|
} else {
|
||||||
|
logger.log(Level.WARNING, r.status().reasonPhrase());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
client.execute(request).get();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
server.shutdownGracefully();
|
||||||
|
client.shutdownGracefully();
|
||||||
|
logger.log(Level.INFO, "server and client shut down");
|
||||||
|
}
|
||||||
|
assertEquals(max, count.get());
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,6 +10,7 @@ import org.xbib.netty.http.client.listener.ResponseListener;
|
||||||
import org.xbib.netty.http.client.transport.Transport;
|
import org.xbib.netty.http.client.transport.Transport;
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
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.endpoint.NamedServer;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
@ -29,13 +30,14 @@ class SecureHttp1Test {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSimpleSecureHttp1() throws Exception {
|
void testSimpleSecureHttp1() throws Exception {
|
||||||
Server server = Server.builder()
|
HttpAddress httpAddress = HttpAddress.secureHttp1("localhost", 8143);
|
||||||
.setJdkSslProvider()
|
Server server = Server.builder(NamedServer.builder(httpAddress)
|
||||||
.setSelfCert()
|
.setSelfCert()
|
||||||
.bind(HttpAddress.secureHttp1("localhost", 8143))
|
.singleEndpoint("/", (request, response) ->
|
||||||
|
response.write(HttpResponseStatus.OK, "text/plain", request.getRequest().content().retain()))
|
||||||
|
.build())
|
||||||
.build();
|
.build();
|
||||||
Client client = Client.builder()
|
Client client = Client.builder()
|
||||||
.setJdkSslProvider()
|
|
||||||
.trustInsecure()
|
.trustInsecure()
|
||||||
.build();
|
.build();
|
||||||
AtomicInteger counter = new AtomicInteger();
|
AtomicInteger counter = new AtomicInteger();
|
||||||
|
@ -45,8 +47,6 @@ class SecureHttp1Test {
|
||||||
counter.getAndIncrement();
|
counter.getAndIncrement();
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
server.getDefaultVirtualServer().addHandler("/", (request, response) ->
|
|
||||||
response.write(HttpResponseStatus.OK, "text/plain", request.getRequest().content().retain()));
|
|
||||||
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())
|
.url(server.getServerConfig().getAddress().base())
|
||||||
|
@ -64,15 +64,14 @@ class SecureHttp1Test {
|
||||||
void testPooledSecureHttp1() throws Exception {
|
void testPooledSecureHttp1() throws Exception {
|
||||||
int loop = 4096;
|
int loop = 4096;
|
||||||
HttpAddress httpAddress = HttpAddress.secureHttp1("localhost", 8143);
|
HttpAddress httpAddress = HttpAddress.secureHttp1("localhost", 8143);
|
||||||
Server server = Server.builder()
|
Server server = Server.builder(NamedServer.builder(httpAddress)
|
||||||
.setJdkSslProvider()
|
|
||||||
.setSelfCert()
|
.setSelfCert()
|
||||||
.bind(httpAddress).build();
|
.singleEndpoint("/", (request, response) ->
|
||||||
server.getDefaultVirtualServer().addHandler("/", (request, response) ->
|
response.write(HttpResponseStatus.OK, "text/plain", request.getRequest().content().retain()))
|
||||||
response.write(HttpResponseStatus.OK, "text/plain", request.getRequest().content().retain()));
|
.build())
|
||||||
|
.build();
|
||||||
server.accept();
|
server.accept();
|
||||||
Client client = Client.builder()
|
Client client = Client.builder()
|
||||||
.setJdkSslProvider()
|
|
||||||
.trustInsecure()
|
.trustInsecure()
|
||||||
.addPoolNode(httpAddress)
|
.addPoolNode(httpAddress)
|
||||||
.setPoolNodeConnectionLimit(2)
|
.setPoolNodeConnectionLimit(2)
|
||||||
|
@ -111,17 +110,15 @@ class SecureHttp1Test {
|
||||||
int threads = 4;
|
int threads = 4;
|
||||||
int loop = 4 * 1024;
|
int loop = 4 * 1024;
|
||||||
HttpAddress httpAddress = HttpAddress.secureHttp1("localhost", 8143);
|
HttpAddress httpAddress = HttpAddress.secureHttp1("localhost", 8143);
|
||||||
Server server = Server.builder()
|
Server server = Server.builder(NamedServer.builder(httpAddress)
|
||||||
.setJdkSslProvider()
|
|
||||||
.setSelfCert()
|
.setSelfCert()
|
||||||
.bind(httpAddress)
|
.singleEndpoint("/", (request, response) ->
|
||||||
.build();
|
|
||||||
server.getDefaultVirtualServer().addHandler("/", (request, response) ->
|
|
||||||
response.write(HttpResponseStatus.OK, "text/plain", request.getRequest().content().retain())
|
response.write(HttpResponseStatus.OK, "text/plain", request.getRequest().content().retain())
|
||||||
);
|
)
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
server.accept();
|
server.accept();
|
||||||
Client client = Client.builder()
|
Client client = Client.builder()
|
||||||
.setJdkSslProvider()
|
|
||||||
.trustInsecure()
|
.trustInsecure()
|
||||||
.addPoolNode(httpAddress)
|
.addPoolNode(httpAddress)
|
||||||
.setPoolNodeConnectionLimit(threads)
|
.setPoolNodeConnectionLimit(threads)
|
||||||
|
|
|
@ -9,6 +9,7 @@ import org.xbib.netty.http.client.listener.ResponseListener;
|
||||||
import org.xbib.netty.http.client.transport.Transport;
|
import org.xbib.netty.http.client.transport.Transport;
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
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.endpoint.NamedServer;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
@ -29,16 +30,14 @@ class SecureHttp2Test {
|
||||||
@Test
|
@Test
|
||||||
void testSimpleSecureHttp2() throws Exception {
|
void testSimpleSecureHttp2() throws Exception {
|
||||||
HttpAddress httpAddress = HttpAddress.secureHttp2("localhost", 8143);
|
HttpAddress httpAddress = HttpAddress.secureHttp2("localhost", 8143);
|
||||||
Server server = Server.builder()
|
Server server = Server.builder(NamedServer.builder(httpAddress)
|
||||||
.setJdkSslProvider()
|
|
||||||
.setSelfCert()
|
.setSelfCert()
|
||||||
.bind(httpAddress)
|
.singleEndpoint("/", (request, response) ->
|
||||||
|
response.write(HttpResponseStatus.OK, "text/plain", request.getRequest().content().retain()))
|
||||||
|
.build())
|
||||||
.build();
|
.build();
|
||||||
server.getDefaultVirtualServer().addHandler("/", (request, response) ->
|
|
||||||
response.write(HttpResponseStatus.OK, "text/plain", request.getRequest().content().retain()));
|
|
||||||
server.accept();
|
server.accept();
|
||||||
Client client = Client.builder()
|
Client client = Client.builder()
|
||||||
.setJdkSslProvider()
|
|
||||||
.trustInsecure()
|
.trustInsecure()
|
||||||
.build();
|
.build();
|
||||||
AtomicInteger counter = new AtomicInteger();
|
AtomicInteger counter = new AtomicInteger();
|
||||||
|
@ -72,16 +71,14 @@ class SecureHttp2Test {
|
||||||
void testPooledSecureHttp2() throws Exception {
|
void testPooledSecureHttp2() throws Exception {
|
||||||
int loop = 4096;
|
int loop = 4096;
|
||||||
HttpAddress httpAddress = HttpAddress.secureHttp2("localhost", 8143);
|
HttpAddress httpAddress = HttpAddress.secureHttp2("localhost", 8143);
|
||||||
Server server = Server.builder()
|
Server server = Server.builder(NamedServer.builder(httpAddress)
|
||||||
.setJdkSslProvider()
|
|
||||||
.setSelfCert()
|
.setSelfCert()
|
||||||
.bind(httpAddress)
|
.singleEndpoint("/", (request, response) ->
|
||||||
|
response.write(HttpResponseStatus.OK, "text/plain", request.getRequest().content().retain()))
|
||||||
|
.build())
|
||||||
.build();
|
.build();
|
||||||
server.getDefaultVirtualServer().addHandler("/", (request, response) ->
|
|
||||||
response.write(HttpResponseStatus.OK, "text/plain", request.getRequest().content().retain()));
|
|
||||||
server.accept();
|
server.accept();
|
||||||
Client client = Client.builder()
|
Client client = Client.builder()
|
||||||
.setJdkSslProvider()
|
|
||||||
.trustInsecure()
|
.trustInsecure()
|
||||||
.addPoolNode(httpAddress)
|
.addPoolNode(httpAddress)
|
||||||
.setPoolNodeConnectionLimit(2)
|
.setPoolNodeConnectionLimit(2)
|
||||||
|
@ -123,17 +120,15 @@ class SecureHttp2Test {
|
||||||
int threads = 4;
|
int threads = 4;
|
||||||
int loop = 4 * 1024;
|
int loop = 4 * 1024;
|
||||||
HttpAddress httpAddress = HttpAddress.secureHttp2("localhost", 8143);
|
HttpAddress httpAddress = HttpAddress.secureHttp2("localhost", 8143);
|
||||||
Server server = Server.builder()
|
Server server = Server.builder(NamedServer.builder(httpAddress)
|
||||||
.setJdkSslProvider()
|
|
||||||
.setSelfCert()
|
.setSelfCert()
|
||||||
.bind(httpAddress)
|
.singleEndpoint("/", (request, response) ->
|
||||||
.build();
|
|
||||||
server.getDefaultVirtualServer().addHandler("/", (request, response) ->
|
|
||||||
response.write(HttpResponseStatus.OK, "text/plain", request.getRequest().content().retain())
|
response.write(HttpResponseStatus.OK, "text/plain", request.getRequest().content().retain())
|
||||||
);
|
)
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
server.accept();
|
server.accept();
|
||||||
Client client = Client.builder()
|
Client client = Client.builder()
|
||||||
.setJdkSslProvider()
|
|
||||||
.trustInsecure()
|
.trustInsecure()
|
||||||
.addPoolNode(httpAddress)
|
.addPoolNode(httpAddress)
|
||||||
.setPoolNodeConnectionLimit(threads)
|
.setPoolNodeConnectionLimit(threads)
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
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.endpoint.NamedServer;
|
||||||
|
import org.xbib.netty.http.server.endpoint.service.NioService;
|
||||||
|
|
||||||
|
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(NettyHttpExtension.class)
|
||||||
|
class SecureStaticFileServerTest {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(SecureStaticFileServerTest.class.getName());
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSecureStaticFileServerHttp1() throws Exception {
|
||||||
|
Path vartmp = Paths.get("/var/tmp/");
|
||||||
|
HttpAddress httpAddress = HttpAddress.secureHttp1("localhost", 8143);
|
||||||
|
Server server = Server.builder(NamedServer.builder(httpAddress, "*")
|
||||||
|
.setJdkSslProvider()
|
||||||
|
.setSelfCert()
|
||||||
|
.singleEndpoint("/static", "/**", new NioService(vartmp))
|
||||||
|
.build())
|
||||||
|
.setChildThreadCount(8)
|
||||||
|
.build();
|
||||||
|
server.logDiagnostics(Level.INFO);
|
||||||
|
Client client = Client.builder()
|
||||||
|
.setJdkSslProvider()
|
||||||
|
.trustInsecure()
|
||||||
|
.build();
|
||||||
|
client.logDiagnostics(Level.INFO);
|
||||||
|
final AtomicBoolean success = new AtomicBoolean(false);
|
||||||
|
try {
|
||||||
|
Files.write(vartmp.resolve("test.txt"), "Hello Jörg".getBytes(StandardCharsets.UTF_8));
|
||||||
|
server.accept();
|
||||||
|
Request request = Request.get().setVersion(HttpVersion.HTTP_1_1)
|
||||||
|
.url(server.getServerConfig().getAddress().base().resolve("/static/test.txt"))
|
||||||
|
.build()
|
||||||
|
.setResponseListener(r -> {
|
||||||
|
assertEquals("Hello Jörg", r.content().toString(StandardCharsets.UTF_8));
|
||||||
|
success.set(true);
|
||||||
|
});
|
||||||
|
logger.log(Level.INFO, request.toString());
|
||||||
|
client.execute(request).get();
|
||||||
|
logger.log(Level.INFO, "request complete");
|
||||||
|
} finally {
|
||||||
|
server.shutdownGracefully();
|
||||||
|
client.shutdownGracefully();
|
||||||
|
Files.delete(vartmp.resolve("test.txt"));
|
||||||
|
logger.log(Level.INFO, "server and client shut down");
|
||||||
|
}
|
||||||
|
assertTrue(success.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSecureStaticFileServerHttp2() throws Exception {
|
||||||
|
Path vartmp = Paths.get("/var/tmp/");
|
||||||
|
HttpAddress httpAddress = HttpAddress.secureHttp2("localhost", 8143);
|
||||||
|
Server server = Server.builder(NamedServer.builder(httpAddress, "*")
|
||||||
|
.setOpenSSLSslProvider()
|
||||||
|
.setSelfCert()
|
||||||
|
.singleEndpoint("/static", "/**", new NioService(vartmp))
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
Client client = Client.builder()
|
||||||
|
.setOpenSSLSslProvider()
|
||||||
|
.trustInsecure()
|
||||||
|
.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(r -> {
|
||||||
|
assertEquals("Hello Jörg", r.content().toString(StandardCharsets.UTF_8));
|
||||||
|
success.set(true);
|
||||||
|
});
|
||||||
|
logger.log(Level.INFO, request.toString());
|
||||||
|
client.execute(request).get();
|
||||||
|
logger.log(Level.INFO, "request complete");
|
||||||
|
} finally {
|
||||||
|
server.shutdownGracefully();
|
||||||
|
client.shutdownGracefully();
|
||||||
|
Files.delete(vartmp.resolve("test.txt"));
|
||||||
|
logger.log(Level.INFO, "server and client shut down");
|
||||||
|
}
|
||||||
|
assertTrue(success.get());
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,17 +2,19 @@ package org.xbib.netty.http.server.test;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.xbib.netty.http.common.HttpAddress;
|
||||||
import org.xbib.netty.http.server.Server;
|
import org.xbib.netty.http.server.Server;
|
||||||
|
import org.xbib.netty.http.server.endpoint.NamedServer;
|
||||||
|
|
||||||
@Disabled
|
@Disabled
|
||||||
class ServerTest {
|
class ServerTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testServer() throws Exception {
|
void testServer() throws Exception {
|
||||||
Server server = Server.builder()
|
NamedServer namedServer = NamedServer.builder(HttpAddress.http1("localhost", 8008), "*")
|
||||||
|
.singleEndpoint("/", (request, response) -> response.write("Hello World"))
|
||||||
.build();
|
.build();
|
||||||
server.getDefaultVirtualServer().addHandler("/", (request, response) ->
|
Server server = Server.builder(namedServer).build();
|
||||||
response.write("Hello World"));
|
|
||||||
try {
|
try {
|
||||||
server.accept().channel().closeFuture().sync();
|
server.accept().channel().closeFuture().sync();
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
@ -2,11 +2,13 @@ package org.xbib.netty.http.server.test;
|
||||||
|
|
||||||
import io.netty.handler.codec.http.HttpVersion;
|
import io.netty.handler.codec.http.HttpVersion;
|
||||||
import org.junit.jupiter.api.Test;
|
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.Client;
|
||||||
import org.xbib.netty.http.client.Request;
|
import org.xbib.netty.http.client.Request;
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
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.endpoint.NioHandler;
|
import org.xbib.netty.http.server.endpoint.NamedServer;
|
||||||
|
import org.xbib.netty.http.server.endpoint.service.NioService;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
@ -19,17 +21,21 @@ import java.util.logging.Logger;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
@ExtendWith(NettyHttpExtension.class)
|
||||||
class StaticFileServerTest {
|
class StaticFileServerTest {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(StaticFileServerTest.class.getName());
|
private static final Logger logger = Logger.getLogger(StaticFileServerTest.class.getName());
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testStaticFileServer() throws Exception {
|
void testStaticFileServerHttp1() throws Exception {
|
||||||
Path vartmp = Paths.get("/var/tmp/");
|
Path vartmp = Paths.get("/var/tmp/");
|
||||||
Server server = Server.builder()
|
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
||||||
.bind(HttpAddress.http1("localhost", 8008))
|
NamedServer namedServer = NamedServer.builder(httpAddress)
|
||||||
.addHandler("/static", new NioHandler(vartmp))
|
.singleEndpoint("/static", "/**", new NioService(vartmp))
|
||||||
.build();
|
.build();
|
||||||
|
Server server = Server.builder(namedServer)
|
||||||
|
.build();
|
||||||
|
server.logDiagnostics(Level.INFO);
|
||||||
Client client = Client.builder()
|
Client client = Client.builder()
|
||||||
.build();
|
.build();
|
||||||
final AtomicBoolean success = new AtomicBoolean(false);
|
final AtomicBoolean success = new AtomicBoolean(false);
|
||||||
|
@ -50,6 +56,42 @@ class StaticFileServerTest {
|
||||||
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testStaticFileServerHttp2() throws Exception {
|
||||||
|
Path vartmp = Paths.get("/var/tmp/");
|
||||||
|
HttpAddress httpAddress = HttpAddress.http2("localhost", 8008);
|
||||||
|
NamedServer namedServer = NamedServer.builder(httpAddress)
|
||||||
|
.singleEndpoint("/static", "/**", new NioService(vartmp))
|
||||||
|
.build();
|
||||||
|
Server server = Server.builder(namedServer)
|
||||||
|
.build();
|
||||||
|
server.logDiagnostics(Level.INFO);
|
||||||
|
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(r -> {
|
||||||
|
assertEquals("Hello Jörg", r.content().toString(StandardCharsets.UTF_8));
|
||||||
|
success.set(true);
|
||||||
|
});
|
||||||
|
logger.log(Level.INFO, request.toString());
|
||||||
|
client.execute(request).get();
|
||||||
|
logger.log(Level.INFO, "request complete");
|
||||||
|
} finally {
|
||||||
|
server.shutdownGracefully();
|
||||||
|
client.shutdownGracefully();
|
||||||
|
Files.delete(vartmp.resolve("test.txt"));
|
||||||
|
logger.log(Level.INFO, "server and client shut down");
|
||||||
}
|
}
|
||||||
assertTrue(success.get());
|
assertTrue(success.get());
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ 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;
|
||||||
import org.xbib.netty.http.server.Server;
|
import org.xbib.netty.http.server.Server;
|
||||||
|
import org.xbib.netty.http.server.endpoint.NamedServer;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -20,11 +21,13 @@ class ThreadLeakTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testForLeaks() throws IOException {
|
void testForLeaks() throws IOException {
|
||||||
Server server = Server.builder()
|
NamedServer namedServer = NamedServer.builder()
|
||||||
|
.singleEndpoint("/", (request, response) ->
|
||||||
|
response.write("Hello World"))
|
||||||
|
.build();
|
||||||
|
Server server = Server.builder(namedServer)
|
||||||
.setByteBufAllocator(UnpooledByteBufAllocator.DEFAULT)
|
.setByteBufAllocator(UnpooledByteBufAllocator.DEFAULT)
|
||||||
.build();
|
.build();
|
||||||
server.getDefaultVirtualServer().addHandler("/", (request, response) ->
|
|
||||||
response.write("Hello World"));
|
|
||||||
try {
|
try {
|
||||||
server.accept();
|
server.accept();
|
||||||
} finally {
|
} finally {
|
||||||
|
|
Loading…
Reference in a new issue