Netty 4.1.36, introducing endpoints

This commit is contained in:
Jörg Prante 2019-05-20 15:02:02 +02:00
parent 7f14fce6fd
commit 09d82f576e
61 changed files with 2686 additions and 2310 deletions

View file

@ -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"
@ -29,11 +29,10 @@ subprojects {
apply plugin: 'maven' apply plugin: 'maven'
apply plugin: 'signing' apply plugin: 'signing'
apply plugin: "com.github.spotbugs" apply plugin: "com.github.spotbugs"
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 {
@ -147,23 +145,7 @@ subprojects {
scmConnection = 'scm:git:git://github.com/' + user + '/' + name + '.git' scmConnection = 'scm:git:git://github.com/' + user + '/' + name + '.git'
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 {

View file

@ -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

View file

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

View file

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

View file

@ -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;

View file

@ -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;

View file

@ -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(); String fragment = url.getFragment();
// resolve again if (fragment != null && !fragment.isEmpty()) {
if (!uri.equals("/")) { sb.append('#').append(fragment);
try {
url = uri.startsWith("/") ? URL.base(url).resolve(uri) : URL.base(url).resolve("/" + uri) ;
} catch (URLSyntaxException | MalformedInputException | UnmappableCharacterException e) {
throw new IllegalArgumentException(e);
}
} }
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);
} }

View file

@ -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() {

View file

@ -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;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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&param2=value2", request.relativeUri()); assertEquals("?param1=value1&param2=value2", request.relative());
assertEquals("http://xbib.org/?param1=value1&param2=value2", request.url().toString()); assertEquals("http://xbib.org?param1=value1&param2=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&param2=value2", request.relativeUri()); assertEquals("xbib.org", request.url().getHost());
assertEquals("http://xbib.org/?param1=value1&param2=value2", request.url().toString()); assertEquals("?param1=value1&param2=value2", request.relative());
assertEquals("http://xbib.org?param1=value1&param2=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());
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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) {
TrustManagerFactory trustManagerFactory = serverConfig.getTrustManagerFactory();
if (trustManagerFactory != null) {
try {
trustManagerFactory.init(serverConfig.getTrustManagerKeyStore());
} catch (KeyStoreException e) {
logger.log(Level.WARNING, e.getMessage(), e);
}
} }
} DomainNameMapping<SslContext> domainNameMapping = null;
if (serverConfig.getAddress().isSecure() && serverConfig.getDefaultNamedServer().getSslContext() != null) {
private static ApplicationProtocolConfig newApplicationProtocolConfig() { DomainNameMappingBuilder<SslContext> mappingBuilder =
return new ApplicationProtocolConfig(ApplicationProtocolConfig.Protocol.ALPN, new DomainNameMappingBuilder<>(serverConfig.getDefaultNamedServer().getSslContext());
ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE, for (NamedServer namedServer : serverConfig.getNamedServers().values()) {
ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT, String name = namedServer.getName();
ApplicationProtocolNames.HTTP_2, if (!"*".equals(name)) {
ApplicationProtocolNames.HTTP_1_1); mappingBuilder.add(name, namedServer.getSslContext());
}
}
domainNameMapping = mappingBuilder.build();
}
return domainNameMapping;
} }
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);
}
}
} }

View file

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

View file

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

View file

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

View file

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

View file

@ -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"};
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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);
}
/**
* Sets whether auto-generated indices are allowed. If false, and a
* directory resource is requested, an error will be returned instead.
*
* @param allowed specifies whether generated indices are allowed
*/
public void setAllowGeneratedIndex(boolean allowed) {
this.allowGeneratedIndex = allowed;
}
/**
* Returns all HTTP methods explicitly supported by at least one context
* (this may or may not include the methods with required or built-in support).
*
* @return all HTTP methods explicitly supported by at least one context
*/
public Set<String> getMethods() {
return methods;
}
/**
* Adds a context and its corresponding context handler to this server.
* Paths are normalized by removing trailing slashes (except the root).
*
* @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);
Endpoint existing = endpointMap.putIfAbsent(s, info);
info = existing != null ? existing : info;
info.addHandler(handler, methods);
return this;
} }
/** public static class Builder {
* Adds handler for all methods of the given object that
* are annotated with the {@link Context} annotation. private HttpAddress httpAddress;
*
* @param o the object whose annotated methods are added private String serverName;
* @return this virtual server
* @throws IllegalArgumentException if a Context-annotated private Set<String> aliases;
* method has an {@link Context invalid signature}
*/ private List<EndpointResolver> endpointResolvers;
public NamedServer addHandlers(Object o) throws IllegalArgumentException {
for (Class<?> c = o.getClass(); c != null; c = c.getSuperclass()) { private TrustManagerFactory trustManagerFactory;
for (Method m : c.getDeclaredMethods()) {
Context context = m.getAnnotation(Context.class); private KeyStore trustManagerKeyStore;
if (context != null) {
addHandler(context.value(), new MethodHandler(m, o), context.methods()); 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) {
this.trustManagerFactory = trustManagerFactory;
return this;
}
public Builder setTrustManagerKeyStore(KeyStore trustManagerKeyStore) {
this.trustManagerKeyStore = trustManagerKeyStore;
return this;
}
public Builder setSslContextProvider(Provider sslContextProvider) {
this.sslContextProvider = sslContextProvider;
return this;
}
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;
}
/**
* Adds an alias for this virtual server.
*
* @param alias the alias
* @return this builder
*/
public Builder addAlias(String alias) {
aliases.add(alias);
return this;
}
public Builder addEndpointResolver(EndpointResolver endpointResolver) {
this.endpointResolvers.add(endpointResolver);
return this;
}
public Builder singleEndpoint(String path, Service service) {
addEndpointResolver(EndpointResolver.builder()
.addEndpoint(Endpoint.builder().setPath(path).addFilter(service).build()).build());
return this;
}
public Builder singleEndpoint(String prefix, String path, Service service) {
addEndpointResolver(EndpointResolver.builder()
.addEndpoint(Endpoint.builder().setPrefix(prefix).setPath(path).addFilter(service).build()).build());
return this;
}
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);
} }
} }
return this;
}
/** private static ApplicationProtocolConfig newApplicationProtocolConfig() {
* Returns the endpoint for the given path. return new ApplicationProtocolConfig(ApplicationProtocolConfig.Protocol.ALPN,
* If an endpoint is not found for the given path, the search is repeated for ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE,
* its parent path, and so on until a base context is found. If neither the ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT,
* given path nor any of its parents has a context, an empty context is returned. ApplicationProtocolNames.HTTP_2,
* ApplicationProtocolNames.HTTP_1_1);
* @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);
} }
/**
* Returns the given string with all occurrences of the given character
* removed from its right side.
*
* @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);
}
/**
* Returns the parent of the given path.
*
* @param path the path whose parent is returned (must start with '/')
* @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);
}
} }

View file

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

View file

@ -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
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -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
}
}
} }
} }

View file

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

View file

@ -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 {

View file

@ -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 +
"]";
}
}

View file

@ -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,88 +104,48 @@ 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;
headers.add(HttpHeaderNames.CONTENT_TYPE, s); headers.add(HttpHeaderNames.CONTENT_TYPE, s);
}
if (!headers.contains(HttpHeaderNames.CONTENT_LENGTH) && !headers.contains(HttpHeaderNames.TRANSFER_ENCODING)) {
int length = byteBuf.readableBytes();
if (length < 0) {
headers.add(HttpHeaderNames.TRANSFER_ENCODING, "chunked");
} else {
headers.add(HttpHeaderNames.CONTENT_LENGTH, Long.toString(length));
}
}
if (serverRequest != null && "close".equalsIgnoreCase(serverRequest.getRequest().headers().get(HttpHeaderNames.CONNECTION)) &&
!headers.contains(HttpHeaderNames.CONNECTION)) {
headers.add(HttpHeaderNames.CONNECTION, "close");
}
if (!headers.contains(HttpHeaderNames.DATE)) {
headers.add(HttpHeaderNames.DATE, DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now(ZoneOffset.UTC)));
}
headers.add(HttpHeaderNames.SERVER, ServerName.getServerName());
} }
FullHttpResponse fullHttpResponse = byteBuf != null ? if (!headers.contains(HttpHeaderNames.CONTENT_LENGTH) && !headers.contains(HttpHeaderNames.TRANSFER_ENCODING)) {
new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, int length = byteBuf.readableBytes();
status, byteBuf, headers, trailingHeaders) : if (length < 0) {
new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, headers.add(HttpHeaderNames.TRANSFER_ENCODING, "chunked");
status, Unpooled.EMPTY_BUFFER, headers, trailingHeaders); } else {
headers.add(HttpHeaderNames.CONTENT_LENGTH, Long.toString(length));
}
}
if (serverRequest != null && "close".equalsIgnoreCase(serverRequest.getRequest().headers().get(HttpHeaderNames.CONNECTION)) &&
!headers.contains(HttpHeaderNames.CONNECTION)) {
headers.add(HttpHeaderNames.CONNECTION, "close");
}
if (!headers.contains(HttpHeaderNames.DATE)) {
headers.add(HttpHeaderNames.DATE, DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now(ZoneOffset.UTC)));
}
headers.add(HttpHeaderNames.SERVER, ServerName.getServerName());
FullHttpResponse fullHttpResponse =
new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, byteBuf, 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 '&amp;', '&gt;' and '&lt;' 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 = "&amp;";
break;
case '>':
ref = "&gt;";
break;
case '<':
ref = "&lt;";
break;
case '"':
ref = "&quot;";
break;
case '\'':
ref = "&#39;";
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();
}
} }

View file

@ -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 {

View file

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

View file

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

View file

@ -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 -> {
String response = fullHttpResponse.content().toString(StandardCharsets.UTF_8); if (fullHttpResponse.status().equals(HttpResponseStatus.OK)) {
//logger.log(Level.INFO, "status = " + fullHttpResponse.status() + " response body = " + response); String response = fullHttpResponse.content().toString(StandardCharsets.UTF_8);
counter.incrementAndGet(); //logger.log(Level.INFO, "status = " + fullHttpResponse.status() + " response body = " + response);
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 -> {
String response = fullHttpResponse.content().toString(StandardCharsets.UTF_8); if (fullHttpResponse.status().equals(HttpResponseStatus.OK)) {
//logger.log(Level.INFO, "status = " + fullHttpResponse.status() + " response body = " + response); String response = fullHttpResponse.content().toString(StandardCharsets.UTF_8);
counter.incrementAndGet(); //logger.log(Level.INFO, "status = " + fullHttpResponse.status() + " response body = " + response);
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 -> {
String response = fullHttpResponse.content().toString(StandardCharsets.UTF_8); if (fullHttpResponse.status().equals(HttpResponseStatus.OK)) {
//logger.log(Level.INFO, "status = " + fullHttpResponse.status() + String response = fullHttpResponse.content().toString(StandardCharsets.UTF_8);
// " response=" + response + " payload=" + payload); //logger.log(Level.INFO, "status = " + fullHttpResponse.status() +
counter.incrementAndGet(); // " response=" + response + " payload=" + payload);
counter.incrementAndGet();
}
}; };
try { try {
ExecutorService executorService = Executors.newFixedThreadPool(threads); ExecutorService executorService = Executors.newFixedThreadPool(threads);

View file

@ -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")

View file

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

View file

@ -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) ->
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(threads) .setPoolNodeConnectionLimit(threads)

View file

@ -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) ->
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(threads) .setPoolNodeConnectionLimit(threads)

View file

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

View file

@ -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 {

View file

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

View file

@ -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 {