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