smarter domains, certificate classes, API cleanup

This commit is contained in:
Jörg Prante 2020-07-06 00:08:07 +02:00
parent aa82f681e3
commit 2c25413323
70 changed files with 1376 additions and 715 deletions

View file

@ -1,6 +1,6 @@
group = org.xbib group = org.xbib
name = netty-http name = netty-http
version = 4.1.50.1 version = 4.1.50.2
gradle.wrapper.version = 6.4.1 gradle.wrapper.version = 6.4.1
netty.version = 4.1.50.Final netty.version = 4.1.50.Final

View file

@ -1,3 +1,4 @@
import java.time.Duration
apply plugin: "de.marcphilipp.nexus-publish" apply plugin: "de.marcphilipp.nexus-publish"
@ -61,4 +62,5 @@ nexusPublishing {
packageGroup = "org.xbib" packageGroup = "org.xbib"
} }
} }
clientTimeout = Duration.ofSeconds(600)
} }

View file

@ -1,3 +1,4 @@
import java.time.Duration
if (project.hasProperty('ossrhUsername') && project.hasProperty('ossrhPassword')) { if (project.hasProperty('ossrhUsername') && project.hasProperty('ossrhPassword')) {

View file

@ -1,4 +1,4 @@
dependencies { dependencies {
api project(":netty-http-common") api project(":netty-http-server-api")
api "org.bouncycastle:bcpkix-jdk15on:${project.property('bouncycastle.version')}" api "org.bouncycastle:bcpkix-jdk15on:${project.property('bouncycastle.version')}"
} }

View file

@ -0,0 +1,8 @@
module org.xbib.netty.http.bouncycastle {
exports org.xbib.netty.http.bouncycastle;
requires org.xbib.netty.http.server.api;
requires org.bouncycastle.pkix;
requires org.bouncycastle.provider;
provides org.xbib.netty.http.server.api.security.ServerCertificateProvider with
org.xbib.netty.http.bouncycastle.BouncyCastleSelfSignedCertificateProvider;
}

View file

@ -1,7 +1,7 @@
package org.xbib.netty.http.bouncycastle; package org.xbib.netty.http.bouncycastle;
import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.operator.OperatorCreationException;
import org.xbib.netty.http.common.ServerCertificateProvider; import org.xbib.netty.http.server.api.security.ServerCertificateProvider;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.UncheckedIOException; import java.io.UncheckedIOException;

View file

@ -28,8 +28,6 @@ import java.security.NoSuchProviderException;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.Date; import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
/** /**
* Generates a temporary self-signed certificate for testing purposes. * Generates a temporary self-signed certificate for testing purposes.
@ -133,11 +131,6 @@ public final class SelfSignedCertificate {
outputStream.write(certBytes); outputStream.write(certBytes);
} }
public void exportPEM(Logger logger) {
logger.log(Level.INFO, new String(keyBytes, StandardCharsets.US_ASCII) +
new String(certBytes, StandardCharsets.US_ASCII));
}
private void writeEncoded(byte[] bytes, OutputStream outputStream) throws IOException { private void writeEncoded(byte[] bytes, OutputStream outputStream) throws IOException {
byte[] buf = new byte[64]; byte[] buf = new byte[64];
byte[] base64 = Base64.encode(bytes); byte[] base64 = Base64.encode(bytes);

View file

@ -3,17 +3,23 @@ package org.xbib.netty.http.bouncycastle;
import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.security.Security; import java.security.Security;
import java.util.logging.Logger; import java.util.logging.Logger;
class SelfSignedCertificateTest { class SelfSignedCertificateTest {
private static final Logger logger = Logger.getLogger("test");
@Test @Test
void testSelfSignedCertificate() throws Exception { void testSelfSignedCertificate() throws Exception {
Security.addProvider(new BouncyCastleProvider()); Security.addProvider(new BouncyCastleProvider());
SelfSignedCertificate selfSignedCertificate = new SelfSignedCertificate(); SelfSignedCertificate selfSignedCertificate = new SelfSignedCertificate();
selfSignedCertificate.generate("localhost", new SecureRandom(), 2048); selfSignedCertificate.generate("localhost", new SecureRandom(), 2048);
selfSignedCertificate.exportPEM(Logger.getLogger("test")); ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
selfSignedCertificate.exportPEM(outputStream);
logger.info(new String(outputStream.toByteArray(), StandardCharsets.US_ASCII));
} }
} }

View file

@ -1,9 +1,4 @@
module org.xbib.netty.http.client.api { module org.xbib.netty.http.client.api {
exports org.xbib.netty.http.client.api; exports org.xbib.netty.http.client.api;
requires transitive org.xbib.netty.http.common; requires transitive org.xbib.netty.http.common;
requires io.netty.buffer;
requires io.netty.common;
requires io.netty.codec.http;
requires io.netty.codec.http2;
requires io.netty.transport;
} }

View file

@ -1,6 +1,8 @@
package org.xbib.netty.http.client.api; package org.xbib.netty.http.client.api;
public interface ProtocolProvider<C extends HttpChannelInitializer, T extends Transport> { import org.xbib.netty.http.common.HttpChannelInitializer;
public interface ClientProtocolProvider<C extends HttpChannelInitializer, T extends ClientTransport> {
boolean supportsMajorVersion(int majorVersion); boolean supportsMajorVersion(int majorVersion);

View file

@ -7,21 +7,21 @@ import io.netty.handler.codec.http2.Http2Settings;
import io.netty.util.AttributeKey; import io.netty.util.AttributeKey;
import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.common.HttpResponse; import org.xbib.netty.http.common.HttpResponse;
import org.xbib.netty.http.common.Transport;
import org.xbib.netty.http.common.cookie.CookieBox; import org.xbib.netty.http.common.cookie.CookieBox;
import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSession;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Function; import java.util.function.Function;
public interface Transport extends AutoCloseable { public interface ClientTransport extends Transport {
AttributeKey<Transport> TRANSPORT_ATTRIBUTE_KEY = AttributeKey.valueOf("transport"); AttributeKey<ClientTransport> TRANSPORT_ATTRIBUTE_KEY = AttributeKey.valueOf("transport");
HttpAddress getHttpAddress(); HttpAddress getHttpAddress();
Transport execute(Request request) throws IOException; ClientTransport execute(Request request) throws IOException;
<T> CompletableFuture<T> execute(Request request, Function<HttpResponse, T> supplier) throws IOException; <T> CompletableFuture<T> execute(Request request, Function<HttpResponse, T> supplier) throws IOException;
@ -33,18 +33,20 @@ public interface Transport extends AutoCloseable {
void pushPromiseReceived(Channel channel, Integer streamId, Integer promisedStreamId, Http2Headers headers); void pushPromiseReceived(Channel channel, Integer streamId, Integer promisedStreamId, Http2Headers headers);
void fail(Channel channel, Throwable throwable);
void inactive(Channel channel);
void setCookieBox(CookieBox cookieBox); void setCookieBox(CookieBox cookieBox);
CookieBox getCookieBox(); CookieBox getCookieBox();
Transport get(); ClientTransport get();
Transport get(long value, TimeUnit timeUnit); ClientTransport get(long value, TimeUnit timeUnit);
void cancel(); void cancel();
void fail(Throwable throwable);
boolean isFailed(); boolean isFailed();
Throwable getFailure(); Throwable getFailure();

View file

@ -1,7 +1,4 @@
module org.xbib.netty.http.client.rest { module org.xbib.netty.http.client.rest {
exports org.xbib.netty.http.client.rest; exports org.xbib.netty.http.client.rest;
requires transitive org.xbib.netty.http.client; requires transitive org.xbib.netty.http.client;
requires org.xbib.net.url;
requires io.netty.buffer;
requires io.netty.codec.http;
} }

View file

@ -1,4 +1,9 @@
import org.xbib.netty.http.client.Http1;
import org.xbib.netty.http.client.Http2;
module org.xbib.netty.http.client { module org.xbib.netty.http.client {
uses org.xbib.netty.http.client.api.ClientProtocolProvider;
uses org.xbib.netty.http.common.TransportProvider;
exports org.xbib.netty.http.client; exports org.xbib.netty.http.client;
exports org.xbib.netty.http.client.cookie; exports org.xbib.netty.http.client.cookie;
exports org.xbib.netty.http.client.handler.http; exports org.xbib.netty.http.client.handler.http;
@ -7,15 +12,7 @@ module org.xbib.netty.http.client {
exports org.xbib.netty.http.client.retry; exports org.xbib.netty.http.client.retry;
exports org.xbib.netty.http.client.transport; exports org.xbib.netty.http.client.transport;
requires transitive org.xbib.netty.http.client.api; requires transitive org.xbib.netty.http.client.api;
requires io.netty.buffer;
requires io.netty.common;
requires io.netty.codec.http;
requires io.netty.codec.http2;
requires io.netty.handler;
requires io.netty.handler.proxy; requires io.netty.handler.proxy;
requires io.netty.transport;
requires java.logging; requires java.logging;
provides org.xbib.netty.http.client.api.ProtocolProvider with provides org.xbib.netty.http.client.api.ClientProtocolProvider with Http1, Http2;
org.xbib.netty.http.client.Http1Provider,
org.xbib.netty.http.client.Http2Provider;
} }

View file

@ -22,17 +22,16 @@ import io.netty.handler.ssl.SslHandler;
import io.netty.handler.ssl.SslProvider; import io.netty.handler.ssl.SslProvider;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory; import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import io.netty.util.concurrent.Future; import io.netty.util.concurrent.Future;
import org.xbib.netty.http.client.api.HttpChannelInitializer; import org.xbib.netty.http.client.api.ClientProtocolProvider;
import org.xbib.netty.http.client.api.ProtocolProvider;
import org.xbib.netty.http.client.api.Request; import org.xbib.netty.http.client.api.Request;
import org.xbib.netty.http.client.api.ClientTransport;
import org.xbib.netty.http.client.pool.BoundedChannelPool; import org.xbib.netty.http.client.pool.BoundedChannelPool;
import org.xbib.netty.http.client.api.Transport;
import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.common.HttpChannelInitializer;
import org.xbib.netty.http.common.HttpResponse; import org.xbib.netty.http.common.HttpResponse;
import org.xbib.netty.http.common.NetworkUtils; import org.xbib.netty.http.common.NetworkUtils;
import org.xbib.netty.http.common.TransportProvider; import org.xbib.netty.http.common.TransportProvider;
import org.xbib.netty.http.common.security.SecurityUtil; import org.xbib.netty.http.common.security.SecurityUtil;
import javax.net.ssl.SNIHostName; import javax.net.ssl.SNIHostName;
import javax.net.ssl.SNIServerName; import javax.net.ssl.SNIServerName;
import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngine;
@ -89,9 +88,9 @@ public final class Client implements AutoCloseable {
private final Bootstrap bootstrap; private final Bootstrap bootstrap;
private final Queue<Transport> transports; private final Queue<ClientTransport> transports;
private final List<ProtocolProvider<HttpChannelInitializer, Transport>> protocolProviders; private final List<ClientProtocolProvider<HttpChannelInitializer, ClientTransport>> protocolProviders;
private final AtomicBoolean closed; private final AtomicBoolean closed;
@ -118,7 +117,7 @@ public final class Client implements AutoCloseable {
this.closed = new AtomicBoolean(false); this.closed = new AtomicBoolean(false);
this.clientConfig = clientConfig; this.clientConfig = clientConfig;
this.protocolProviders = new ArrayList<>(); this.protocolProviders = new ArrayList<>();
for (ProtocolProvider<HttpChannelInitializer, Transport> provider : ServiceLoader.load(ProtocolProvider.class)) { for (ClientProtocolProvider<HttpChannelInitializer, ClientTransport> provider : ServiceLoader.load(ClientProtocolProvider.class)) {
protocolProviders.add(provider); protocolProviders.add(provider);
if (logger.isLoggable(Level.FINEST)) { if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "protocol provider: " + provider.transportClass()); logger.log(Level.FINEST, "protocol provider: " + provider.transportClass());
@ -199,7 +198,7 @@ public final class Client implements AutoCloseable {
return new Builder(); return new Builder();
} }
public List<ProtocolProvider<HttpChannelInitializer, Transport>> getProtocolProviders() { public List<ClientProtocolProvider<HttpChannelInitializer, ClientTransport>> getProtocolProviders() {
return protocolProviders; return protocolProviders;
} }
@ -223,14 +222,14 @@ public final class Client implements AutoCloseable {
return responseCounter; return responseCounter;
} }
public Transport newTransport() { public ClientTransport newTransport() {
return newTransport(null); return newTransport(null);
} }
public Transport newTransport(HttpAddress httpAddress) { public ClientTransport newTransport(HttpAddress httpAddress) {
Transport transport = null; ClientTransport transport = null;
if (httpAddress != null) { if (httpAddress != null) {
for (ProtocolProvider<HttpChannelInitializer, Transport> protocolProvider : protocolProviders) { for (ClientProtocolProvider<HttpChannelInitializer, ClientTransport> protocolProvider : protocolProviders) {
if (protocolProvider.supportsMajorVersion(httpAddress.getVersion().majorVersion())) { if (protocolProvider.supportsMajorVersion(httpAddress.getVersion().majorVersion())) {
try { try {
transport = protocolProvider.transportClass() transport = protocolProvider.transportClass()
@ -245,7 +244,7 @@ public final class Client implements AutoCloseable {
throw new UnsupportedOperationException("no protocol support for " + httpAddress); throw new UnsupportedOperationException("no protocol support for " + httpAddress);
} }
} else if (hasPooledConnections()) { } else if (hasPooledConnections()) {
for (ProtocolProvider<HttpChannelInitializer, Transport> protocolProvider : protocolProviders) { for (ClientProtocolProvider<HttpChannelInitializer, ClientTransport> protocolProvider : protocolProviders) {
if (protocolProvider.supportsMajorVersion(pool.getVersion().majorVersion())) { if (protocolProvider.supportsMajorVersion(pool.getVersion().majorVersion())) {
try { try {
transport = protocolProvider.transportClass() transport = protocolProvider.transportClass()
@ -311,7 +310,7 @@ public final class Client implements AutoCloseable {
} }
} }
public Transport execute(Request request) throws IOException { public ClientTransport execute(Request request) throws IOException {
return newTransport(HttpAddress.of(request.url(), request.httpVersion())) return newTransport(HttpAddress.of(request.url(), request.httpVersion()))
.execute(request); .execute(request);
} }
@ -337,8 +336,8 @@ public final class Client implements AutoCloseable {
* @param request the new request for continuing the request. * @param request the new request for continuing the request.
* @throws IOException if continuation fails * @throws IOException if continuation fails
*/ */
public void continuation(Transport transport, Request request) throws IOException { public void continuation(ClientTransport transport, Request request) throws IOException {
Transport nextTransport = newTransport(HttpAddress.of(request.url(), request.httpVersion())); ClientTransport nextTransport = newTransport(HttpAddress.of(request.url(), request.httpVersion()));
nextTransport.setCookieBox(transport.getCookieBox()); nextTransport.setCookieBox(transport.getCookieBox());
nextTransport.execute(request); nextTransport.execute(request);
nextTransport.get(); nextTransport.get();
@ -352,7 +351,7 @@ public final class Client implements AutoCloseable {
* @param request the request to retry * @param request the request to retry
* @throws IOException if retry failed * @throws IOException if retry failed
*/ */
public void retry(Transport transport, Request request) throws IOException { public void retry(ClientTransport transport, Request request) throws IOException {
transport.execute(request); transport.execute(request);
transport.get(); transport.get();
closeAndRemove(transport); closeAndRemove(transport);
@ -370,7 +369,7 @@ public final class Client implements AutoCloseable {
public void shutdownGracefully(long amount, TimeUnit timeUnit) throws IOException { public void shutdownGracefully(long amount, TimeUnit timeUnit) throws IOException {
if (closed.compareAndSet(false, true)) { if (closed.compareAndSet(false, true)) {
try { try {
for (Transport transport : transports) { for (ClientTransport transport : transports) {
transport.close(); transport.close();
} }
transports.clear(); transports.clear();
@ -386,7 +385,7 @@ public final class Client implements AutoCloseable {
} }
} }
private void closeAndRemove(Transport transport) throws IOException { private void closeAndRemove(ClientTransport transport) throws IOException {
try { try {
transport.close(); transport.close();
} catch (Exception e) { } catch (Exception e) {
@ -400,7 +399,7 @@ public final class Client implements AutoCloseable {
HttpAddress httpAddress, HttpAddress httpAddress,
SslHandlerFactory sslHandlerFactory, SslHandlerFactory sslHandlerFactory,
HttpChannelInitializer helper) { HttpChannelInitializer helper) {
for (ProtocolProvider<HttpChannelInitializer, Transport> protocolProvider : protocolProviders) { for (ClientProtocolProvider<HttpChannelInitializer, ClientTransport> protocolProvider : protocolProviders) {
if (protocolProvider.supportsMajorVersion(majorVersion)) { if (protocolProvider.supportsMajorVersion(majorVersion)) {
try { try {
return protocolProvider.initializerClass() return protocolProvider.initializerClass()

View file

@ -1,10 +1,10 @@
package org.xbib.netty.http.client; package org.xbib.netty.http.client;
import org.xbib.netty.http.client.api.ProtocolProvider; import org.xbib.netty.http.client.api.ClientProtocolProvider;
import org.xbib.netty.http.client.handler.http.Http1ChannelInitializer; import org.xbib.netty.http.client.handler.http.Http1ChannelInitializer;
import org.xbib.netty.http.client.transport.Http1Transport; import org.xbib.netty.http.client.transport.Http1Transport;
public class Http1Provider implements ProtocolProvider<Http1ChannelInitializer, Http1Transport> { public class Http1 implements ClientProtocolProvider<Http1ChannelInitializer, Http1Transport> {
@Override @Override
public boolean supportsMajorVersion(int majorVersion) { public boolean supportsMajorVersion(int majorVersion) {

View file

@ -1,10 +1,10 @@
package org.xbib.netty.http.client; package org.xbib.netty.http.client;
import org.xbib.netty.http.client.api.ProtocolProvider; import org.xbib.netty.http.client.api.ClientProtocolProvider;
import org.xbib.netty.http.client.handler.http2.Http2ChannelInitializer; import org.xbib.netty.http.client.handler.http2.Http2ChannelInitializer;
import org.xbib.netty.http.client.transport.Http2Transport; import org.xbib.netty.http.client.transport.Http2Transport;
public class Http2Provider implements ProtocolProvider<Http2ChannelInitializer, Http2Transport> { public class Http2 implements ClientProtocolProvider<Http2ChannelInitializer, Http2Transport> {
@Override @Override
public boolean supportsMajorVersion(int majorVersion) { public boolean supportsMajorVersion(int majorVersion) {

View file

@ -14,10 +14,9 @@ import io.netty.handler.ssl.SslHandler;
import io.netty.handler.stream.ChunkedWriteHandler; import io.netty.handler.stream.ChunkedWriteHandler;
import org.xbib.netty.http.client.Client; import org.xbib.netty.http.client.Client;
import org.xbib.netty.http.client.ClientConfig; import org.xbib.netty.http.client.ClientConfig;
import org.xbib.netty.http.client.api.HttpChannelInitializer; import org.xbib.netty.http.common.HttpChannelInitializer;
import org.xbib.netty.http.client.handler.http2.Http2ChannelInitializer; import org.xbib.netty.http.client.handler.http2.Http2ChannelInitializer;
import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.common.HttpAddress;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;

View file

@ -4,22 +4,25 @@ import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.FullHttpResponse;
import org.xbib.netty.http.client.api.Transport; import org.xbib.netty.http.client.api.ClientTransport;
@ChannelHandler.Sharable @ChannelHandler.Sharable
public class HttpResponseHandler extends SimpleChannelInboundHandler<FullHttpResponse> { public class HttpResponseHandler extends SimpleChannelInboundHandler<FullHttpResponse> {
@Override @Override
public void channelRead0(ChannelHandlerContext ctx, FullHttpResponse fullHttpResponse) throws Exception { public void channelRead0(ChannelHandlerContext ctx, FullHttpResponse fullHttpResponse) throws Exception {
Transport transport = ctx.channel().attr(Transport.TRANSPORT_ATTRIBUTE_KEY).get(); ClientTransport transport = ctx.channel().attr(ClientTransport.TRANSPORT_ATTRIBUTE_KEY).get();
transport.responseReceived(ctx.channel(), null, fullHttpResponse); if (transport != null) {
// do not close ctx here transport.responseReceived(ctx.channel(), null, fullHttpResponse);
}
} }
@Override @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
Transport transport = ctx.channel().attr(Transport.TRANSPORT_ATTRIBUTE_KEY).get(); ctx.fireExceptionCaught(cause);
transport.fail(cause); ClientTransport transport = ctx.channel().attr(ClientTransport.TRANSPORT_ATTRIBUTE_KEY).get();
// do not close ctx here if (transport != null) {
transport.fail(ctx.channel(), cause);
}
} }
} }

View file

@ -14,11 +14,10 @@ import io.netty.handler.codec.http2.Http2MultiplexCodecBuilder;
import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LogLevel;
import org.xbib.netty.http.client.Client; import org.xbib.netty.http.client.Client;
import org.xbib.netty.http.client.ClientConfig; import org.xbib.netty.http.client.ClientConfig;
import org.xbib.netty.http.client.api.HttpChannelInitializer;
import org.xbib.netty.http.client.handler.http.TrafficLoggingHandler; import org.xbib.netty.http.client.handler.http.TrafficLoggingHandler;
import org.xbib.netty.http.client.api.Transport; import org.xbib.netty.http.client.api.ClientTransport;
import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.common.HttpChannelInitializer;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -86,7 +85,7 @@ public class Http2ChannelInitializer extends ChannelInitializer<Channel> impleme
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof DefaultHttp2SettingsFrame) { if (msg instanceof DefaultHttp2SettingsFrame) {
DefaultHttp2SettingsFrame settingsFrame = (DefaultHttp2SettingsFrame) msg; DefaultHttp2SettingsFrame settingsFrame = (DefaultHttp2SettingsFrame) msg;
Transport transport = ctx.channel().attr(Transport.TRANSPORT_ATTRIBUTE_KEY).get(); ClientTransport transport = ctx.channel().attr(ClientTransport.TRANSPORT_ATTRIBUTE_KEY).get();
if (transport != null) { if (transport != null) {
transport.settingsReceived(settingsFrame.settings()); transport.settingsReceived(settingsFrame.settings());
} }
@ -100,7 +99,7 @@ public class Http2ChannelInitializer extends ChannelInitializer<Channel> impleme
if (evt instanceof Http2ConnectionPrefaceAndSettingsFrameWrittenEvent) { if (evt instanceof Http2ConnectionPrefaceAndSettingsFrameWrittenEvent) {
Http2ConnectionPrefaceAndSettingsFrameWrittenEvent event = Http2ConnectionPrefaceAndSettingsFrameWrittenEvent event =
(Http2ConnectionPrefaceAndSettingsFrameWrittenEvent)evt; (Http2ConnectionPrefaceAndSettingsFrameWrittenEvent)evt;
Transport transport = ctx.channel().attr(Transport.TRANSPORT_ATTRIBUTE_KEY).get(); ClientTransport transport = ctx.channel().attr(ClientTransport.TRANSPORT_ATTRIBUTE_KEY).get();
if (transport != null) { if (transport != null) {
transport.settingsReceived(null); transport.settingsReceived(null);
} }
@ -110,9 +109,9 @@ public class Http2ChannelInitializer extends ChannelInitializer<Channel> impleme
@Override @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
Transport transport = ctx.channel().attr(Transport.TRANSPORT_ATTRIBUTE_KEY).get(); ClientTransport transport = ctx.channel().attr(ClientTransport.TRANSPORT_ATTRIBUTE_KEY).get();
if (transport != null) { if (transport != null) {
transport.fail(cause); transport.fail(ctx.channel(), cause);
} }
} }
} }
@ -126,7 +125,7 @@ public class Http2ChannelInitializer extends ChannelInitializer<Channel> impleme
public void logPushPromise(Direction direction, ChannelHandlerContext ctx, int streamId, int promisedStreamId, public void logPushPromise(Direction direction, ChannelHandlerContext ctx, int streamId, int promisedStreamId,
Http2Headers headers, int padding) { Http2Headers headers, int padding) {
super.logPushPromise(direction, ctx, streamId, promisedStreamId, headers, padding); super.logPushPromise(direction, ctx, streamId, promisedStreamId, headers, padding);
Transport transport = ctx.channel().attr(Transport.TRANSPORT_ATTRIBUTE_KEY).get(); ClientTransport transport = ctx.channel().attr(ClientTransport.TRANSPORT_ATTRIBUTE_KEY).get();
if (transport != null) { if (transport != null) {
transport.pushPromiseReceived(ctx.channel(), streamId, promisedStreamId, headers); transport.pushPromiseReceived(ctx.channel(), streamId, promisedStreamId, headers);
} }

View file

@ -5,23 +5,37 @@ import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http2.HttpConversionUtil; import io.netty.handler.codec.http2.HttpConversionUtil;
import org.xbib.netty.http.client.api.Transport; import org.xbib.netty.http.client.api.ClientTransport;
@ChannelHandler.Sharable @ChannelHandler.Sharable
public class Http2ResponseHandler extends SimpleChannelInboundHandler<FullHttpResponse> { public class Http2ResponseHandler extends SimpleChannelInboundHandler<FullHttpResponse> {
@Override @Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpResponse httpResponse) throws Exception { protected void channelRead0(ChannelHandlerContext ctx, FullHttpResponse httpResponse) throws Exception {
Transport transport = ctx.channel().attr(Transport.TRANSPORT_ATTRIBUTE_KEY).get();
Integer streamId = httpResponse.headers().getInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text()); Integer streamId = httpResponse.headers().getInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text());
transport.responseReceived(ctx.channel(), streamId, httpResponse); ClientTransport transport = ctx.channel().attr(ClientTransport.TRANSPORT_ATTRIBUTE_KEY).get();
if (transport != null) {
transport.responseReceived(ctx.channel(), streamId, httpResponse);
}
// do not close ctx here // do not close ctx here
} }
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelInactive();
ClientTransport transport = ctx.channel().attr(ClientTransport.TRANSPORT_ATTRIBUTE_KEY).get();
if (transport != null) {
transport.inactive(ctx.channel());
}
}
@Override @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
Transport transport = ctx.channel().attr(Transport.TRANSPORT_ATTRIBUTE_KEY).get(); ctx.fireExceptionCaught(cause);
transport.fail(cause); ClientTransport transport = ctx.channel().attr(ClientTransport.TRANSPORT_ATTRIBUTE_KEY).get();
if (transport != null) {
transport.fail(ctx.channel(), cause);
}
// do not close ctx here // do not close ctx here
} }
} }

View file

@ -10,14 +10,13 @@ import org.xbib.net.PercentDecoder;
import org.xbib.net.URL; import org.xbib.net.URL;
import org.xbib.net.URLSyntaxException; import org.xbib.net.URLSyntaxException;
import org.xbib.netty.http.client.Client; import org.xbib.netty.http.client.Client;
import org.xbib.netty.http.client.api.Transport; import org.xbib.netty.http.client.api.ClientTransport;
import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.client.api.Request; import org.xbib.netty.http.client.api.Request;
import org.xbib.netty.http.client.api.BackOff; import org.xbib.netty.http.client.api.BackOff;
import org.xbib.netty.http.common.HttpResponse; import org.xbib.netty.http.common.HttpResponse;
import org.xbib.netty.http.common.cookie.Cookie; import org.xbib.netty.http.common.cookie.Cookie;
import org.xbib.netty.http.common.cookie.CookieBox; import org.xbib.netty.http.common.cookie.CookieBox;
import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSession;
import java.io.IOException; import java.io.IOException;
import java.net.ConnectException; import java.net.ConnectException;
@ -38,7 +37,7 @@ import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public abstract class BaseTransport implements Transport { public abstract class BaseTransport implements ClientTransport {
private static final Logger logger = Logger.getLogger(BaseTransport.class.getName()); private static final Logger logger = Logger.getLogger(BaseTransport.class.getName());
@ -54,15 +53,15 @@ public abstract class BaseTransport implements Transport {
private SSLSession sslSession; private SSLSession sslSession;
final Map<String, Flow> flowMap; public final Map<String, Flow> flowMap;
final SortedMap<String, Request> requests; protected final SortedMap<String, Request> requests;
private CookieBox cookieBox; private CookieBox cookieBox;
protected HttpDataFactory httpDataFactory; protected HttpDataFactory httpDataFactory;
BaseTransport(Client client, HttpAddress httpAddress) { public BaseTransport(Client client, HttpAddress httpAddress) {
this.client = client; this.client = client;
this.httpAddress = httpAddress; this.httpAddress = httpAddress;
this.channels = new ConcurrentHashMap<>(); this.channels = new ConcurrentHashMap<>();
@ -103,9 +102,7 @@ public abstract class BaseTransport implements Transport {
@Override @Override
public void close() { public void close() {
// channels are present, maybe forgot a get() to receive responses? // channels are present, maybe forgot a get() to receive responses?
if (!channels.isEmpty()) { get();
get();
}
cancel(); cancel();
} }
@ -120,50 +117,55 @@ public abstract class BaseTransport implements Transport {
} }
/** /**
* The underlying network layer failed, not possible to know the request. * The underlying network layer failed.
* So we fail all (open) promises. * So we fail all (open) promises.
* @param throwable the exception * @param throwable the exception
*/ */
@Override @Override
public void fail(Throwable throwable) { public void fail(Channel channel, Throwable throwable) {
// do not fail more than once // do not fail more than once
if (this.throwable != null) { if (this.throwable != null) {
return; return;
} }
logger.log(Level.SEVERE, "failing: " + throwable.getMessage(), throwable);
this.throwable = throwable; this.throwable = throwable;
logger.log(Level.SEVERE, "channel " + channel + " failing: " + throwable.getMessage(), throwable);
for (Flow flow : flowMap.values()) { for (Flow flow : flowMap.values()) {
flow.fail(throwable); flow.fail(throwable);
} }
} }
@Override @Override
public Transport get() { public void inactive(Channel channel) {
// do nothing
}
@Override
public ClientTransport get() {
return get(client.getClientConfig().getReadTimeoutMillis(), TimeUnit.MILLISECONDS); return get(client.getClientConfig().getReadTimeoutMillis(), TimeUnit.MILLISECONDS);
} }
@Override @Override
public Transport get(long value, TimeUnit timeUnit) { public ClientTransport get(long value, TimeUnit timeUnit) {
if (channels.isEmpty()) { if (!flowMap.isEmpty()) {
return this; for (Map.Entry<String, Flow> entry : flowMap.entrySet()) {
} Flow flow = entry.getValue();
for (Map.Entry<String, Flow> entry : flowMap.entrySet()) { if (!flow.isClosed()) {
Flow flow = entry.getValue(); for (Integer key : flow.keys()) {
if (!flow.isClosed()) { String requestKey = getRequestKey(entry.getKey(), key);
for (Integer key : flow.keys()) { try {
String requestKey = getRequestKey(entry.getKey(), key); flow.get(key).get(value, timeUnit);
try { completeRequest(requestKey);
flow.get(key).get(value, timeUnit); } catch (Exception e) {
completeRequest(requestKey); completeRequestExceptionally(requestKey, e);
} catch (Exception e) { flow.fail(e);
completeRequestExceptionally(requestKey, e); } finally {
flow.fail(e); flow.remove(key);
} finally { }
flow.remove(key);
} }
flow.close();
} }
flow.close();
} }
flowMap.clear();
} }
channels.values().forEach(channel -> { channels.values().forEach(channel -> {
try { try {
@ -177,26 +179,21 @@ public abstract class BaseTransport implements Transport {
@Override @Override
public void cancel() { public void cancel() {
if (channels.isEmpty()) { if (!flowMap.isEmpty()) {
return; for (Map.Entry<String, Flow> entry : flowMap.entrySet()) {
} Flow flow = entry.getValue();
for (Map.Entry<String, Flow> entry : flowMap.entrySet()) { for (Integer key : flow.keys()) {
Flow flow = entry.getValue(); try {
for (Integer key : flow.keys()) { flow.get(key).cancel(true);
try { } catch (Exception e) {
flow.get(key).cancel(true); completeRequestExceptionally(getRequestKey(entry.getKey(), key), e);
} catch (Exception e) { flow.fail(e);
String requestKey = getRequestKey(entry.getKey(), key); } finally {
Request request = requests.get(requestKey); flow.remove(key);
if (request != null && request.getCompletableFuture() != null) {
request.getCompletableFuture().completeExceptionally(e);
} }
flow.fail(e);
} finally {
flow.remove(key);
} }
flow.close();
} }
flow.close();
} }
channels.values().forEach(channel -> { channels.values().forEach(channel -> {
try { try {

View file

@ -6,13 +6,13 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
class Flow { public class Flow {
private final AtomicInteger counter; private final AtomicInteger counter;
private final SortedMap<Integer, CompletableFuture<Boolean>> map; private final SortedMap<Integer, CompletableFuture<Boolean>> map;
Flow() { public Flow() {
this.counter = new AtomicInteger(3); this.counter = new AtomicInteger(3);
this.map = new ConcurrentSkipListMap<>(); this.map = new ConcurrentSkipListMap<>();
} }

View file

@ -1,7 +1,6 @@
package org.xbib.netty.http.client.transport; package org.xbib.netty.http.client.transport;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.handler.codec.http.DefaultFullHttpRequest; import io.netty.handler.codec.http.DefaultFullHttpRequest;
import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.FullHttpResponse;
@ -12,17 +11,17 @@ import io.netty.handler.codec.http2.Http2Settings;
import io.netty.handler.codec.http2.HttpConversionUtil; import io.netty.handler.codec.http2.HttpConversionUtil;
import org.xbib.net.URLSyntaxException; import org.xbib.net.URLSyntaxException;
import org.xbib.netty.http.client.Client; import org.xbib.netty.http.client.Client;
import org.xbib.netty.http.client.api.Transport; import org.xbib.netty.http.client.api.ClientTransport;
import org.xbib.netty.http.client.cookie.ClientCookieDecoder; import org.xbib.netty.http.client.cookie.ClientCookieDecoder;
import org.xbib.netty.http.client.cookie.ClientCookieEncoder; import org.xbib.netty.http.client.cookie.ClientCookieEncoder;
import org.xbib.netty.http.common.DefaultHttpResponse; import org.xbib.netty.http.common.DefaultHttpResponse;
import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.client.api.Request; import org.xbib.netty.http.client.api.Request;
import org.xbib.netty.http.common.cookie.Cookie; import org.xbib.netty.http.common.cookie.Cookie;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.NoSuchElementException;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -36,7 +35,7 @@ public class Http1Transport extends BaseTransport {
} }
@Override @Override
public Transport execute(Request request) throws IOException { public ClientTransport execute(Request request) throws IOException {
Channel channel = mapChannel(request); Channel channel = mapChannel(request);
if (throwable != null) { if (throwable != null) {
return this; return this;
@ -175,6 +174,11 @@ public class Http1Transport extends BaseTransport {
@Override @Override
protected String getRequestKey(String channelId, Integer streamId) { protected String getRequestKey(String channelId, Integer streamId) {
return requests.isEmpty() ? null : requests.lastKey(); try {
return requests.isEmpty() ? null : requests.lastKey();
} catch (NoSuchElementException e) {
// ConcurrentSkipListMap is not thread-safe, can be emptied before lastKey() is called
return null;
}
} }
} }

View file

@ -19,7 +19,7 @@ import io.netty.handler.codec.http2.HttpConversionUtil;
import io.netty.util.AsciiString; import io.netty.util.AsciiString;
import org.xbib.net.URLSyntaxException; import org.xbib.net.URLSyntaxException;
import org.xbib.netty.http.client.Client; import org.xbib.netty.http.client.Client;
import org.xbib.netty.http.client.api.Transport; import org.xbib.netty.http.client.api.ClientTransport;
import org.xbib.netty.http.client.cookie.ClientCookieDecoder; import org.xbib.netty.http.client.cookie.ClientCookieDecoder;
import org.xbib.netty.http.client.cookie.ClientCookieEncoder; import org.xbib.netty.http.client.cookie.ClientCookieEncoder;
import org.xbib.netty.http.client.handler.http2.Http2ResponseHandler; import org.xbib.netty.http.client.handler.http2.Http2ResponseHandler;
@ -49,7 +49,7 @@ public class Http2Transport extends BaseTransport {
public Http2Transport(Client client, HttpAddress httpAddress) { public Http2Transport(Client client, HttpAddress httpAddress) {
super(client, httpAddress); super(client, httpAddress);
this.settingsPromise = httpAddress != null ? new CompletableFuture<>() : null; this.settingsPromise = httpAddress != null ? new CompletableFuture<>() : null;
final Transport transport = this; final ClientTransport transport = this;
this.initializer = new ChannelInitializer<>() { this.initializer = new ChannelInitializer<>() {
@Override @Override
protected void initChannel(Channel ch) { protected void initChannel(Channel ch) {
@ -68,7 +68,7 @@ public class Http2Transport extends BaseTransport {
} }
@Override @Override
public Transport execute(Request request) throws IOException { public ClientTransport execute(Request request) throws IOException {
Channel channel = mapChannel(request); Channel channel = mapChannel(request);
if (throwable != null) { if (throwable != null) {
return this; return this;

View file

@ -0,0 +1,2 @@
org.xbib.netty.http.client.Http1
org.xbib.netty.http.client.Http2

View file

@ -1,2 +0,0 @@
org.xbib.netty.http.client.Http1Provider
org.xbib.netty.http.client.Http2Provider

View file

@ -1,15 +1,16 @@
module org.xbib.netty.http.common { module org.xbib.netty.http.common {
exports org.xbib.netty.http.common; exports org.xbib.netty.http.common;
exports org.xbib.netty.http.common.cookie; exports org.xbib.netty.http.common.cookie;
exports org.xbib.netty.http.common.net;
exports org.xbib.netty.http.common.mime; exports org.xbib.netty.http.common.mime;
exports org.xbib.netty.http.common.security; exports org.xbib.netty.http.common.security;
exports org.xbib.netty.http.common.util; exports org.xbib.netty.http.common.util;
requires transitive org.xbib.net.url; requires transitive org.xbib.net.url;
requires io.netty.buffer; requires transitive io.netty.buffer;
requires io.netty.common; requires transitive io.netty.common;
requires io.netty.transport; requires transitive io.netty.transport;
requires io.netty.handler; requires transitive io.netty.handler;
requires io.netty.codec.http; requires transitive io.netty.codec;
requires transitive io.netty.codec.http;
requires transitive io.netty.codec.http2;
requires java.logging; requires java.logging;
} }

View file

@ -1,4 +1,4 @@
package org.xbib.netty.http.client.api; package org.xbib.netty.http.common;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandler;
@ -6,5 +6,4 @@ import io.netty.channel.ChannelHandler;
public interface HttpChannelInitializer extends ChannelHandler { public interface HttpChannelInitializer extends ChannelHandler {
void initChannel(Channel channel); void initChannel(Channel channel);
} }

View file

@ -0,0 +1,4 @@
package org.xbib.netty.http.common;
public interface Transport {
}

View file

@ -1,229 +0,0 @@
package org.xbib.netty.http.common.mime;
import io.netty.buffer.ByteBuf;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
/**
* A MIME multi part message parser (RFC 2046).
*/
public class MalvaMimeMultipartParser implements MimeMultipartParser {
private String contentType;
private byte[] boundary;
private ByteBuf payload;
private String type;
private String subType;
public MalvaMimeMultipartParser(String contentType, ByteBuf payload) {
this.contentType = contentType;
this.payload = payload;
if (contentType != null) {
int pos = contentType.indexOf(';');
this.type = pos >= 0 ? contentType.substring(0, pos) : contentType;
this.type = type.trim().toLowerCase();
this.subType = type.startsWith("multipart") ? type.substring(10).trim() : null;
Map<String, String> m = parseHeaderLine(contentType);
this.boundary = m.containsKey("boundary") ? m.get("boundary").toString().getBytes(StandardCharsets.US_ASCII) : null;
}
}
@Override
public String type() {
return type;
}
@Override
public String subType() {
return subType;
}
@Override
public void parse(MimeMultipartListener listener) throws IOException {
if (boundary == null) {
return;
}
// Assumption: header is in 8 bytes (ISO-8859-1). Convert to Unicode.
StringBuilder sb = new StringBuilder();
boolean inHeader = true;
boolean inBody = false;
Integer start = null;
Map<String, String> headers = new LinkedHashMap<>();
int eol = 0;
byte[] payloadBytes = payload.array();
for (int i = 0; i < payloadBytes.length; i++) {
byte b = payloadBytes[i];
if (inHeader) {
switch (b) {
case '\r':
break;
case '\n':
if (sb.length() > 0) {
String[] s = sb.toString().split(":");
String k = s[0];
String v = s[1];
if (!k.startsWith("--")) {
headers.put(k.toLowerCase(Locale.ROOT), v.trim());
}
eol = 0;
sb.setLength(0);
} else {
eol++;
if (eol >= 1) {
eol = 0;
sb.setLength(0);
inHeader = false;
inBody = true;
}
}
break;
default:
eol = 0;
sb.append(b);
break;
}
}
if (inBody) {
int len = headers.containsKey("content-length") ?
Integer.parseInt(headers.get("content-length")) : -1;
if (len > 0) {
inBody = false;
inHeader = true;
} else {
if (start == null) {
if (b != '\r' && b != '\n') {
start = i;
}
}
if (start != null) {
i = indexOf(payloadBytes, boundary, start, payloadBytes.length);
if (i == -1) {
throw new IOException("boundary not found");
}
int l = i - start;
if (l > 4) {
l = l - 4;
}
//BytesReference body = new BytesArray(payloadBytes, start, l)
ByteBuf body = payload.retainedSlice(start, l);
Map<String, String> m = new LinkedHashMap<>();
for (Map.Entry<String, String> entry : headers.entrySet()) {
m.putAll(parseHeaderLine(entry.getValue()));
}
headers.putAll(m);
if (listener != null) {
listener.handle(type, subType, new MimePart(headers, body));
}
inBody = false;
inHeader = true;
headers = new LinkedHashMap<>();
start = null;
eol = -1;
}
}
}
}
}
private Map<String, String> parseHeaderLine(String line) {
Map<String, String> params = new LinkedHashMap<>();
int pos = line.indexOf(";");
String spec = line.substring(pos + 1);
if (pos < 0) {
return params;
}
String key = "";
String value;
boolean inKey = true;
boolean inString = false;
int start = 0;
int i;
for (i = 0; i < spec.length(); i++) {
switch (spec.charAt(i)) {
case '=':
if (inKey) {
key = spec.substring(start, i).trim().toLowerCase();
start = i + 1;
inKey = false;
} else if (!inString) {
throw new IllegalArgumentException(contentType + " value has illegal character '=' at " + i + ": " + spec);
}
break;
case ';':
if (inKey) {
if (spec.substring(start, i).trim().length() > 0) {
throw new IllegalArgumentException(contentType + " parameter missing value at " + i + ": " + spec);
} else {
throw new IllegalArgumentException(contentType + " parameter key has illegal character ';' at " + i + ": " + spec);
}
} else if (!inString) {
value = spec.substring(start, i).trim();
params.put(key, value);
key = null;
start = i + 1;
inKey = true;
}
break;
case '"':
if (inKey) {
throw new IllegalArgumentException(contentType + " key has illegal character '\"' at " + i + ": " + spec);
} else if (inString) {
value = spec.substring(start, i).trim();
params.put(key, value);
key = null;
for (i++; i < spec.length() && spec.charAt(i) != ';'; i++) {
if (!Character.isWhitespace(spec.charAt(i))) {
throw new IllegalArgumentException(contentType + " value has garbage after quoted string at " + i + ": " + spec);
}
}
start = i + 1;
inString = false;
inKey = true;
} else {
if (spec.substring(start, i).trim().length() > 0) {
throw new IllegalArgumentException(contentType + " value has garbage before quoted string at " + i + ": " + spec);
}
start = i + 1;
inString = true;
}
break;
}
}
if (inKey) {
if (pos > start && spec.substring(start, i).trim().length() > 0) {
throw new IllegalArgumentException(contentType + " missing value at " + i + ": " + spec);
}
} else if (!inString) {
value = spec.substring(start, i).trim();
params.put(key, value);
} else {
throw new IllegalArgumentException(contentType + " has an unterminated quoted string: " + spec);
}
return params;
}
private static int indexOf(byte[] array, byte[] target, int start, int end) {
if (target.length == 0) {
return 0;
}
outer:
for (int i = start; i < end - target.length + 1; i++) {
for (int j = 0; j < target.length; j++) {
if (array[i + j] != target[j]) {
continue outer;
}
}
return i;
}
return -1;
}
}

View file

@ -1,12 +1,223 @@
package org.xbib.netty.http.common.mime; package org.xbib.netty.http.common.mime;
import io.netty.buffer.ByteBuf;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
public interface MimeMultipartParser { /**
* A MIME multi part message parser (RFC 2046).
*/
public class MimeMultipartParser {
String type(); private final String contentType;
String subType(); private final ByteBuf payload;
void parse(MimeMultipartListener listener) throws IOException; private byte[] boundary;
private String type;
private String subType;
public MimeMultipartParser(String contentType, ByteBuf payload) {
this.contentType = contentType;
this.payload = payload;
if (contentType != null) {
int pos = contentType.indexOf(';');
this.type = pos >= 0 ? contentType.substring(0, pos) : contentType;
this.type = type.trim().toLowerCase();
this.subType = type.startsWith("multipart") ? type.substring(10).trim() : null;
Map<String, String> m = parseHeaderLine(contentType);
this.boundary = m.containsKey("boundary") ?
m.get("boundary").getBytes(StandardCharsets.US_ASCII) : null;
}
}
public String type() {
return type;
}
public String subType() {
return subType;
}
public void parse(MimeMultipartListener listener) throws IOException {
if (boundary == null) {
return;
}
// Assumption: header is in 8 bytes (ISO-8859-1). Convert to Unicode.
StringBuilder sb = new StringBuilder();
boolean inHeader = true;
boolean inBody = false;
Integer start = null;
Map<String, String> headers = new LinkedHashMap<>();
int eol = 0;
byte[] payloadBytes = payload.array();
for (int i = 0; i < payloadBytes.length; i++) {
byte b = payloadBytes[i];
if (inHeader) {
switch (b) {
case '\r':
break;
case '\n':
if (sb.length() > 0) {
String[] s = sb.toString().split(":");
String k = s[0];
String v = s[1];
if (!k.startsWith("--")) {
headers.put(k.toLowerCase(Locale.ROOT), v.trim());
}
eol = 0;
sb.setLength(0);
} else {
eol++;
if (eol >= 1) {
eol = 0;
sb.setLength(0);
inHeader = false;
inBody = true;
}
}
break;
default:
eol = 0;
sb.append(b);
break;
}
}
if (inBody) {
int len = headers.containsKey("content-length") ?
Integer.parseInt(headers.get("content-length")) : -1;
if (len > 0) {
inBody = false;
inHeader = true;
} else {
if (b != '\r' && b != '\n') {
start = i;
}
if (start != null) {
i = indexOf(payloadBytes, boundary, start, payloadBytes.length);
if (i == -1) {
throw new IOException("boundary not found");
}
int l = i - start;
if (l > 4) {
l = l - 4;
}
//BytesReference body = new BytesArray(payloadBytes, start, l)
ByteBuf body = payload.retainedSlice(start, l);
Map<String, String> m = new LinkedHashMap<>();
for (Map.Entry<String, String> entry : headers.entrySet()) {
m.putAll(parseHeaderLine(entry.getValue()));
}
headers.putAll(m);
if (listener != null) {
listener.handle(type, subType, new MimePart(headers, body));
}
inBody = false;
inHeader = true;
headers = new LinkedHashMap<>();
start = null;
eol = -1;
}
}
}
}
}
private Map<String, String> parseHeaderLine(String line) {
Map<String, String> params = new LinkedHashMap<>();
int pos = line.indexOf(";");
String spec = line.substring(pos + 1);
if (pos < 0) {
return params;
}
String key = "";
String value;
boolean inKey = true;
boolean inString = false;
int start = 0;
int i;
for (i = 0; i < spec.length(); i++) {
switch (spec.charAt(i)) {
case '=':
if (inKey) {
key = spec.substring(start, i).trim().toLowerCase();
start = i + 1;
inKey = false;
} else if (!inString) {
throw new IllegalArgumentException(contentType + " value has illegal character '=' at " + i + ": " + spec);
}
break;
case ';':
if (inKey) {
if (spec.substring(start, i).trim().length() > 0) {
throw new IllegalArgumentException(contentType + " parameter missing value at " + i + ": " + spec);
} else {
throw new IllegalArgumentException(contentType + " parameter key has illegal character ';' at " + i + ": " + spec);
}
} else if (!inString) {
value = spec.substring(start, i).trim();
params.put(key, value);
key = null;
start = i + 1;
inKey = true;
}
break;
case '"':
if (inKey) {
throw new IllegalArgumentException(contentType + " key has illegal character '\"' at " + i + ": " + spec);
} else if (inString) {
value = spec.substring(start, i).trim();
params.put(key, value);
key = null;
for (i++; i < spec.length() && spec.charAt(i) != ';'; i++) {
if (!Character.isWhitespace(spec.charAt(i))) {
throw new IllegalArgumentException(contentType + " value has garbage after quoted string at " + i + ": " + spec);
}
}
start = i + 1;
inString = false;
inKey = true;
} else {
if (spec.substring(start, i).trim().length() > 0) {
throw new IllegalArgumentException(contentType + " value has garbage before quoted string at " + i + ": " + spec);
}
start = i + 1;
inString = true;
}
break;
}
}
if (inKey) {
if (pos > start && spec.substring(start, i).trim().length() > 0) {
throw new IllegalArgumentException(contentType + " missing value at " + i + ": " + spec);
}
} else if (!inString) {
value = spec.substring(start, i).trim();
params.put(key, value);
} else {
throw new IllegalArgumentException(contentType + " has an unterminated quoted string: " + spec);
}
return params;
}
private static int indexOf(byte[] array, byte[] target, int start, int end) {
if (target.length == 0) {
return 0;
}
outer:
for (int i = start; i < end - target.length + 1; i++) {
for (int j = 0; j < target.length; j++) {
if (array[i + j] != target[j]) {
continue outer;
}
}
return i;
}
return -1;
}
} }

View file

@ -1,9 +0,0 @@
package org.xbib.netty.http.common.net;
/**
* The network classes.
*/
public enum NetworkClass {
ANY, LOOPBACK, LOCAL, PUBLIC
}

View file

@ -1,9 +0,0 @@
package org.xbib.netty.http.common.net;
/**
* The TCP/IP network protocol versions.
*/
public enum NetworkProtocolVersion {
IPV4, IPV6, IPV46, NONE
}

View file

@ -51,5 +51,4 @@ public class SecurityUtil {
CipherSuiteFilter DEFAULT_CIPHER_SUITE_FILTER = SupportedCipherSuiteFilter.INSTANCE; CipherSuiteFilter DEFAULT_CIPHER_SUITE_FILTER = SupportedCipherSuiteFilter.INSTANCE;
} }
} }

View file

@ -1,12 +1,6 @@
module org.xbib.netty.http.server.api { module org.xbib.netty.http.server.api {
exports org.xbib.netty.http.server.api; exports org.xbib.netty.http.server.api;
exports org.xbib.netty.http.server.api.annotation; exports org.xbib.netty.http.server.api.annotation;
exports org.xbib.netty.http.server.api.security;
requires transitive org.xbib.netty.http.common; requires transitive org.xbib.netty.http.common;
requires org.xbib.net.url;
requires io.netty.buffer;
requires io.netty.common;
requires io.netty.handler;
requires io.netty.transport;
requires io.netty.codec.http;
requires io.netty.codec.http2;
} }

View file

@ -1,10 +0,0 @@
package org.xbib.netty.http.server.api;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
public interface HttpChannelInitializer extends ChannelHandler {
void initChannel(Channel channel);
}

View file

@ -1,6 +1,8 @@
package org.xbib.netty.http.server.api; package org.xbib.netty.http.server.api;
public interface ProtocolProvider<C extends HttpChannelInitializer, T extends Transport> { import org.xbib.netty.http.common.HttpChannelInitializer;
public interface ServerProtocolProvider<C extends HttpChannelInitializer, T extends ServerTransport> {
boolean supportsMajorVersion(int majorVersion); boolean supportsMajorVersion(int majorVersion);

View file

@ -4,12 +4,12 @@ import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http2.Http2Settings; import io.netty.handler.codec.http2.Http2Settings;
import io.netty.util.AttributeKey; import io.netty.util.AttributeKey;
import org.xbib.netty.http.common.Transport;
import java.io.IOException; import java.io.IOException;
public interface Transport { public interface ServerTransport extends Transport {
AttributeKey<Transport> TRANSPORT_ATTRIBUTE_KEY = AttributeKey.valueOf("transport"); AttributeKey<ServerTransport> TRANSPORT_ATTRIBUTE_KEY = AttributeKey.valueOf("transport");
void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest, Integer sequenceId) throws IOException; void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest, Integer sequenceId) throws IOException;

View file

@ -1,4 +1,4 @@
package org.xbib.netty.http.common; package org.xbib.netty.http.server.api.security;
import java.io.InputStream; import java.io.InputStream;
@ -28,5 +28,4 @@ public interface ServerCertificateProvider {
* @return key password * @return key password
*/ */
String getKeyPassword(); String getKeyPassword();
} }

View file

@ -320,6 +320,7 @@ public class HandlerPublisher<T> extends ChannelDuplexHandler implements Publish
case DEMANDING: case DEMANDING:
case IDLE: case IDLE:
cancelled(); cancelled();
// fall through
case DRAINING: case DRAINING:
state = DONE; state = DONE;
break; break;

View file

@ -1,6 +1,5 @@
module org.xbib.netty.http.server.rest { module org.xbib.netty.http.server.rest {
exports org.xbib.netty.http.server.rest; exports org.xbib.netty.http.server.rest;
exports org.xbib.netty.http.server.rest.util; exports org.xbib.netty.http.server.rest.util;
requires org.xbib.netty.http.server; requires transitive org.xbib.netty.http.server;
requires io.netty.transport;
} }

View file

@ -1,4 +1,10 @@
import org.xbib.netty.http.server.Http1;
import org.xbib.netty.http.server.Http2;
module org.xbib.netty.http.server { module org.xbib.netty.http.server {
uses org.xbib.netty.http.server.api.security.ServerCertificateProvider;
uses org.xbib.netty.http.server.api.ServerProtocolProvider;
uses org.xbib.netty.http.common.TransportProvider;
exports org.xbib.netty.http.server; exports org.xbib.netty.http.server;
exports org.xbib.netty.http.server.cookie; exports org.xbib.netty.http.server.cookie;
exports org.xbib.netty.http.server.endpoint; exports org.xbib.netty.http.server.endpoint;
@ -10,15 +16,6 @@ module org.xbib.netty.http.server {
exports org.xbib.netty.http.server.transport; exports org.xbib.netty.http.server.transport;
exports org.xbib.netty.http.server.util; exports org.xbib.netty.http.server.util;
requires transitive org.xbib.netty.http.server.api; requires transitive org.xbib.netty.http.server.api;
requires org.xbib.net.url;
requires io.netty.buffer;
requires io.netty.common;
requires io.netty.handler;
requires io.netty.transport;
requires io.netty.codec.http;
requires io.netty.codec.http2;
requires java.logging; requires java.logging;
provides org.xbib.netty.http.server.api.ProtocolProvider with provides org.xbib.netty.http.server.api.ServerProtocolProvider with Http1, Http2;
org.xbib.netty.http.server.Http1Provider,
org.xbib.netty.http.server.Http2Provider;
} }

View file

@ -8,22 +8,31 @@ import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SslProvider; import io.netty.handler.ssl.SslProvider;
import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.common.ServerCertificateProvider; import org.xbib.netty.http.server.api.security.ServerCertificateProvider;
import org.xbib.netty.http.common.security.SecurityUtil; import org.xbib.netty.http.common.security.SecurityUtil;
import org.xbib.netty.http.server.api.ServerRequest; import org.xbib.netty.http.server.api.ServerRequest;
import org.xbib.netty.http.server.api.ServerResponse; import org.xbib.netty.http.server.api.ServerResponse;
import org.xbib.netty.http.server.endpoint.HttpEndpoint; import org.xbib.netty.http.server.endpoint.HttpEndpoint;
import org.xbib.netty.http.server.endpoint.HttpEndpointResolver; import org.xbib.netty.http.server.endpoint.HttpEndpointResolver;
import org.xbib.netty.http.server.api.Filter; import org.xbib.netty.http.server.api.Filter;
import org.xbib.netty.http.server.security.CertificateUtils;
import org.xbib.netty.http.server.security.PrivateKeyUtils;
import javax.crypto.NoSuchPaddingException;
import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.TrustManagerFactory;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyException;
import java.security.KeyStore; import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Provider; import java.security.Provider;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collection;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@ -49,6 +58,8 @@ public class Domain {
private final List<HttpEndpointResolver> httpEndpointResolvers; private final List<HttpEndpointResolver> httpEndpointResolvers;
private final Collection<? extends X509Certificate> certificates;
/** /**
* Constructs a {@code NamedServer} with the given name. * Constructs a {@code NamedServer} with the given name.
* *
@ -58,29 +69,41 @@ public class Domain {
* @param httpEndpointResolvers the endpoint resolvers * @param httpEndpointResolvers the endpoint resolvers
* @param sslContext SSL context or null * @param sslContext SSL context or null
*/ */
protected Domain(String name, Set<String> aliases, private Domain(String name,
Set<String> aliases,
HttpAddress httpAddress, HttpAddress httpAddress,
List<HttpEndpointResolver> httpEndpointResolvers, List<HttpEndpointResolver> httpEndpointResolvers,
SslContext sslContext) { SslContext sslContext,
Collection<? extends X509Certificate> certificates) {
this.httpAddress = httpAddress; this.httpAddress = httpAddress;
this.name = name; this.name = name;
this.sslContext = sslContext; this.aliases = aliases;
this.aliases = Collections.unmodifiableSet(aliases);
this.httpEndpointResolvers = httpEndpointResolvers; this.httpEndpointResolvers = httpEndpointResolvers;
} this.sslContext = sslContext;
this.certificates = certificates;
public static Builder builder() { Objects.requireNonNull(httpEndpointResolvers);
return builder(HttpAddress.http1("localhost", 8008)); if (httpEndpointResolvers.isEmpty()) {
throw new IllegalArgumentException("domain must have at least one endpoint resolver");
}
} }
public static Builder builder(HttpAddress httpAddress) { public static Builder builder(HttpAddress httpAddress) {
return builder(httpAddress, "*"); return builder(httpAddress, httpAddress.getInetSocketAddress().getHostString());
} }
public static Builder builder(HttpAddress httpAddress, String serverName) { public static Builder builder(HttpAddress httpAddress, String serverName) {
return new Builder(httpAddress, serverName); return new Builder(httpAddress).setServerName(serverName);
} }
public static Builder builder(Domain domain) {
return new Builder(domain);
}
/**
* The address this domain binds to.
*
* @return the HTTP address
*/
public HttpAddress getHttpAddress() { public HttpAddress getHttpAddress() {
return httpAddress; return httpAddress;
} }
@ -94,10 +117,6 @@ public class Domain {
return name; return name;
} }
public SslContext getSslContext() {
return sslContext;
}
/** /**
* Returns the aliases. * Returns the aliases.
* *
@ -107,6 +126,22 @@ public class Domain {
return aliases; return aliases;
} }
/**
* Returns SSL context.
* @return the SSL context
*/
public SslContext getSslContext() {
return sslContext;
}
/**
* Get certificate chain.
* @return the certificate chain or null if not secure
*/
public Collection<? extends X509Certificate> getCertificateChain() {
return certificates;
}
/** /**
* Handle server requests. * Handle server requests.
* @param serverRequest the server request * @param serverRequest the server request
@ -114,38 +149,38 @@ public class Domain {
* @throws IOException if handling server request fails * @throws IOException if handling server request fails
*/ */
public void handle(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException { public void handle(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
if (httpEndpointResolvers != null) { boolean found = false;
boolean found = false; for (HttpEndpointResolver httpEndpointResolver : httpEndpointResolvers) {
for (HttpEndpointResolver httpEndpointResolver : httpEndpointResolvers) { List<HttpEndpoint> matchingEndpoints = httpEndpointResolver.matchingEndpointsFor(serverRequest);
List<HttpEndpoint> matchingEndpoints = httpEndpointResolver.matchingEndpointsFor(serverRequest); if (matchingEndpoints != null && !matchingEndpoints.isEmpty()) {
if (matchingEndpoints != null && !matchingEndpoints.isEmpty()) { httpEndpointResolver.handle(matchingEndpoints, serverRequest, serverResponse);
httpEndpointResolver.handle(matchingEndpoints, serverRequest, serverResponse); found = true;
found = true; break;
break;
}
} }
if (!found) { }
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_IMPLEMENTED); if (!found) {
} ServerResponse.write(serverResponse, HttpResponseStatus.NOT_IMPLEMENTED,
} else { "text/plain", "No endpoint match for request " + serverRequest +
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_IMPLEMENTED); " endpoints = " + httpEndpointResolvers);
} }
} }
@Override @Override
public String toString() { public String toString() {
return name + " (" + httpAddress + ") " + aliases; return name + " (" + httpAddress + ") aliases=" + aliases;
} }
public static class Builder { public static class Builder {
private HttpAddress httpAddress; private final HttpAddress httpAddress;
private String serverName; private String serverName;
private Set<String> aliases; private final Set<String> aliases;
private List<HttpEndpointResolver> httpEndpointResolvers; private final List<HttpEndpointResolver> httpEndpointResolvers;
private SslContext sslContext;
private TrustManagerFactory trustManagerFactory; private TrustManagerFactory trustManagerFactory;
@ -159,17 +194,13 @@ public class Domain {
private CipherSuiteFilter cipherSuiteFilter; private CipherSuiteFilter cipherSuiteFilter;
private InputStream keyCertChainInputStream; private Collection<? extends X509Certificate> keyCertChain;
private InputStream keyInputStream; private PrivateKey privateKey;
private String keyPassword; private Builder(HttpAddress httpAddress) {
Builder(HttpAddress httpAddress, String serverName) {
Objects.requireNonNull(httpAddress); Objects.requireNonNull(httpAddress);
Objects.requireNonNull(serverName);
this.httpAddress = httpAddress; this.httpAddress = httpAddress;
this.serverName = serverName;
this.aliases = new LinkedHashSet<>(); this.aliases = new LinkedHashSet<>();
this.httpEndpointResolvers = new ArrayList<>(); this.httpEndpointResolvers = new ArrayList<>();
this.trustManagerFactory = SecurityUtil.Defaults.DEFAULT_TRUST_MANAGER_FACTORY; this.trustManagerFactory = SecurityUtil.Defaults.DEFAULT_TRUST_MANAGER_FACTORY;
@ -178,6 +209,26 @@ public class Domain {
this.cipherSuiteFilter = SecurityUtil.Defaults.DEFAULT_CIPHER_SUITE_FILTER; this.cipherSuiteFilter = SecurityUtil.Defaults.DEFAULT_CIPHER_SUITE_FILTER;
} }
private Builder(Domain domain) {
this.httpAddress = domain.httpAddress;
this.aliases = new LinkedHashSet<>();
this.httpEndpointResolvers = new ArrayList<>(domain.httpEndpointResolvers);
this.sslContext = domain.sslContext;
this.keyCertChain = domain.certificates;
}
public Builder setServerName(String serverName) {
if (this.serverName == null) {
this.serverName = serverName;
}
return this;
}
public Builder setSslContext(SslContext sslContext) {
this.sslContext = sslContext;
return this;
}
public Builder setTrustManagerFactory(TrustManagerFactory trustManagerFactory) { public Builder setTrustManagerFactory(TrustManagerFactory trustManagerFactory) {
Objects.requireNonNull(trustManagerFactory); Objects.requireNonNull(trustManagerFactory);
this.trustManagerFactory = trustManagerFactory; this.trustManagerFactory = trustManagerFactory;
@ -226,56 +277,36 @@ public class Domain {
return this; return this;
} }
public Builder setKeyCertChainInputStream(InputStream keyCertChainInputStream) { public Builder setKeyCertChain(InputStream keyCertChainInputStream)
throws CertificateException {
Objects.requireNonNull(keyCertChainInputStream); Objects.requireNonNull(keyCertChainInputStream);
this.keyCertChainInputStream = keyCertChainInputStream; this.keyCertChain = CertificateUtils.toCertificate(keyCertChainInputStream);
return this; return this;
} }
public Builder setKeyInputStream(InputStream keyInputStream) { public Builder setKey(InputStream keyInputStream, String keyPassword)
throws NoSuchPaddingException, NoSuchAlgorithmException, IOException,
KeyException, InvalidAlgorithmParameterException, InvalidKeySpecException {
Objects.requireNonNull(keyInputStream); Objects.requireNonNull(keyInputStream);
this.keyInputStream = keyInputStream; this.privateKey = PrivateKeyUtils.toPrivateKey(keyInputStream, keyPassword);
return this; return this;
} }
public Builder setKeyPassword(String keyPassword) { public Builder setSelfCert() throws CertificateException, NoSuchPaddingException,
// null in keyPassword allowed, it means no password NoSuchAlgorithmException, IOException, KeyException, InvalidAlgorithmParameterException,
this.keyPassword = keyPassword; InvalidKeySpecException {
return this; ServiceLoader<ServerCertificateProvider> serverCertificateProviders =
} ServiceLoader.load(ServerCertificateProvider.class);
public Builder setKeyCert(InputStream keyCertChainInputStream, InputStream keyInputStream) {
Objects.requireNonNull(keyCertChainInputStream);
Objects.requireNonNull(keyInputStream);
setKeyCertChainInputStream(keyCertChainInputStream);
setKeyInputStream(keyInputStream);
return this;
}
public Builder setKeyCert(InputStream keyCertChainInputStream, InputStream keyInputStream,
String keyPassword) {
Objects.requireNonNull(keyCertChainInputStream);
Objects.requireNonNull(keyInputStream);
Objects.requireNonNull(keyPassword);
setKeyCertChainInputStream(keyCertChainInputStream);
setKeyInputStream(keyInputStream);
setKeyPassword(keyPassword);
return this;
}
public Builder setSelfCert() {
ServiceLoader<ServerCertificateProvider> serverCertificateProviders = ServiceLoader.load(ServerCertificateProvider.class);
for (ServerCertificateProvider serverCertificateProvider : serverCertificateProviders) { for (ServerCertificateProvider serverCertificateProvider : serverCertificateProviders) {
if ("org.xbib.netty.http.bouncycastle.BouncyCastleSelfSignedCertificateProvider".equals(serverCertificateProvider.getClass().getName())) { if ("org.xbib.netty.http.bouncycastle.BouncyCastleSelfSignedCertificateProvider".equals(serverCertificateProvider.getClass().getName())) {
serverCertificateProvider.prepare(serverName); serverCertificateProvider.prepare(serverName);
setKeyCertChainInputStream(serverCertificateProvider.getCertificateChain()); setKeyCertChain(serverCertificateProvider.getCertificateChain());
setKeyInputStream(serverCertificateProvider.getPrivateKey()); setKey(serverCertificateProvider.getPrivateKey(), serverCertificateProvider.getKeyPassword());
setKeyPassword(serverCertificateProvider.getKeyPassword());
logger.log(Level.INFO, "self signed certificate installed"); logger.log(Level.INFO, "self signed certificate installed");
} }
} }
if (keyCertChainInputStream == null) { if (keyCertChain == null) {
logger.log(Level.WARNING, "unable to install self signed certificate. Is netty-http-bouncycastle present?"); throw new CertificateException("unable to set self certificate");
} }
return this; return this;
} }
@ -301,7 +332,8 @@ public class Domain {
public Builder singleEndpoint(String path, Filter filter) { public Builder singleEndpoint(String path, Filter filter) {
Objects.requireNonNull(path); Objects.requireNonNull(path);
Objects.requireNonNull(filter); Objects.requireNonNull(filter);
addEndpointResolver(HttpEndpointResolver.builder() this.httpEndpointResolvers.clear();
this.httpEndpointResolvers.add(HttpEndpointResolver.builder()
.addEndpoint(HttpEndpoint.builder() .addEndpoint(HttpEndpoint.builder()
.setPath(path) .setPath(path)
.build()) .build())
@ -341,26 +373,33 @@ public class Domain {
} }
public Domain build() { public Domain build() {
if (httpAddress.isSecure()) { if (httpAddress.isSecure() ) {
try { try {
trustManagerFactory.init(trustManagerKeyStore); if (sslContext == null && privateKey != null && keyCertChain != null) {
SslContextBuilder sslContextBuilder = SslContextBuilder trustManagerFactory.init(trustManagerKeyStore);
.forServer(keyCertChainInputStream, keyInputStream, keyPassword) SslContextBuilder sslContextBuilder = SslContextBuilder
.trustManager(trustManagerFactory) .forServer(privateKey, keyCertChain)
.sslProvider(sslProvider) .trustManager(trustManagerFactory)
.ciphers(ciphers, cipherSuiteFilter); .sslProvider(sslProvider)
if (sslContextProvider != null) { .ciphers(ciphers, cipherSuiteFilter);
sslContextBuilder.sslContextProvider(sslContextProvider); if (sslContextProvider != null) {
sslContextBuilder.sslContextProvider(sslContextProvider);
}
if (httpAddress.getVersion().majorVersion() == 2) {
sslContextBuilder.applicationProtocolConfig(newApplicationProtocolConfig());
}
this.sslContext = sslContextBuilder.build();
} }
if (httpAddress.getVersion().majorVersion() == 2) { return new Domain(serverName, aliases,
sslContextBuilder.applicationProtocolConfig(newApplicationProtocolConfig()); httpAddress, httpEndpointResolvers,
} sslContext, keyCertChain);
return new Domain(serverName, aliases, httpAddress, httpEndpointResolvers, sslContextBuilder.build()); } catch (Exception e) {
} catch (Throwable t) { throw new RuntimeException(e);
throw new RuntimeException(t);
} }
} else { } else {
return new Domain(serverName, aliases, httpAddress, httpEndpointResolvers, null); return new Domain(serverName, aliases,
httpAddress, httpEndpointResolvers,
null, null);
} }
} }

View file

@ -1,10 +1,10 @@
package org.xbib.netty.http.server; package org.xbib.netty.http.server;
import org.xbib.netty.http.server.api.ProtocolProvider; import org.xbib.netty.http.server.api.ServerProtocolProvider;
import org.xbib.netty.http.server.handler.http.Http1ChannelInitializer; import org.xbib.netty.http.server.handler.http.Http1ChannelInitializer;
import org.xbib.netty.http.server.transport.Http1Transport; import org.xbib.netty.http.server.transport.Http1Transport;
public class Http1Provider implements ProtocolProvider<Http1ChannelInitializer, Http1Transport> { public class Http1 implements ServerProtocolProvider<Http1ChannelInitializer, Http1Transport> {
@Override @Override
public boolean supportsMajorVersion(int majorVersion) { public boolean supportsMajorVersion(int majorVersion) {

View file

@ -1,10 +1,10 @@
package org.xbib.netty.http.server; package org.xbib.netty.http.server;
import org.xbib.netty.http.server.api.ProtocolProvider; import org.xbib.netty.http.server.api.ServerProtocolProvider;
import org.xbib.netty.http.server.handler.http2.Http2ChannelInitializer; import org.xbib.netty.http.server.handler.http2.Http2ChannelInitializer;
import org.xbib.netty.http.server.transport.Http2Transport; import org.xbib.netty.http.server.transport.Http2Transport;
public class Http2Provider implements ProtocolProvider<Http2ChannelInitializer, Http2Transport> { public class Http2 implements ServerProtocolProvider<Http2ChannelInitializer, Http2Transport> {
@Override @Override
public boolean supportsMajorVersion(int majorVersion) { public boolean supportsMajorVersion(int majorVersion) {

View file

@ -14,18 +14,23 @@ import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContext;
import io.netty.util.DomainWildcardMappingBuilder; import io.netty.util.DomainWildcardMappingBuilder;
import io.netty.util.Mapping; import io.netty.util.Mapping;
import org.xbib.net.URL;
import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.common.NetworkUtils; import org.xbib.netty.http.common.NetworkUtils;
import org.xbib.netty.http.common.HttpChannelInitializer;
import org.xbib.netty.http.common.TransportProvider; import org.xbib.netty.http.common.TransportProvider;
import org.xbib.netty.http.server.api.HttpChannelInitializer; import org.xbib.netty.http.server.api.ServerProtocolProvider;
import org.xbib.netty.http.server.api.ProtocolProvider; import org.xbib.netty.http.server.api.ServerRequest;
import org.xbib.netty.http.server.api.ServerResponse; import org.xbib.netty.http.server.api.ServerResponse;
import org.xbib.netty.http.common.security.SecurityUtil; import org.xbib.netty.http.server.api.ServerTransport;
import org.xbib.netty.http.server.security.CertificateUtils;
import org.xbib.netty.http.server.transport.HttpServerRequest; import org.xbib.netty.http.server.transport.HttpServerRequest;
import org.xbib.netty.http.server.api.Transport;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@ -68,8 +73,6 @@ public final class Server implements AutoCloseable {
private final ServerConfig serverConfig; private final ServerConfig serverConfig;
private final ByteBufAllocator byteBufAllocator;
private final EventLoopGroup parentEventLoopGroup; private final EventLoopGroup parentEventLoopGroup;
private final EventLoopGroup childEventLoopGroup; private final EventLoopGroup childEventLoopGroup;
@ -79,17 +82,14 @@ public final class Server implements AutoCloseable {
*/ */
private final BlockingThreadPoolExecutor executor; private final BlockingThreadPoolExecutor executor;
private final Class<? extends ServerSocketChannel> socketChannelClass;
private final ServerBootstrap bootstrap; private final ServerBootstrap bootstrap;
private ChannelFuture channelFuture; private ChannelFuture channelFuture;
private final List<ProtocolProvider<HttpChannelInitializer, Transport>> protocolProviders; private final List<ServerProtocolProvider<HttpChannelInitializer, ServerTransport>> protocolProviders;
/** /**
* Create a new HTTP server. * Create a new HTTP server.
* Use {@link #builder(HttpAddress)} to build HTTP instance.
* *
* @param serverConfig server configuration * @param serverConfig server configuration
* @param byteBufAllocator byte buf allocator * @param byteBufAllocator byte buf allocator
@ -106,13 +106,13 @@ public final class Server implements AutoCloseable {
BlockingThreadPoolExecutor executor) { BlockingThreadPoolExecutor executor) {
Objects.requireNonNull(serverConfig); Objects.requireNonNull(serverConfig);
this.serverConfig = serverConfig; this.serverConfig = serverConfig;
this.byteBufAllocator = byteBufAllocator != null ? byteBufAllocator : ByteBufAllocator.DEFAULT; ByteBufAllocator byteBufAllocator1 = byteBufAllocator != null ? byteBufAllocator : ByteBufAllocator.DEFAULT;
this.parentEventLoopGroup = createParentEventLoopGroup(serverConfig, parentEventLoopGroup); this.parentEventLoopGroup = createParentEventLoopGroup(serverConfig, parentEventLoopGroup);
this.childEventLoopGroup = createChildEventLoopGroup(serverConfig, childEventLoopGroup); this.childEventLoopGroup = createChildEventLoopGroup(serverConfig, childEventLoopGroup);
this.socketChannelClass = createSocketChannelClass(serverConfig, socketChannelClass); Class<? extends ServerSocketChannel> socketChannelClass1 = createSocketChannelClass(serverConfig, socketChannelClass);
this.executor = executor; this.executor = executor;
this.protocolProviders =new ArrayList<>(); this.protocolProviders =new ArrayList<>();
for (ProtocolProvider<HttpChannelInitializer, Transport> provider : ServiceLoader.load(ProtocolProvider.class)) { for (ServerProtocolProvider<HttpChannelInitializer, ServerTransport> provider : ServiceLoader.load(ServerProtocolProvider.class)) {
protocolProviders.add(provider); protocolProviders.add(provider);
if (logger.isLoggable(Level.FINEST)) { if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "protocol provider up: " + provider.transportClass()); logger.log(Level.FINEST, "protocol provider up: " + provider.transportClass());
@ -120,13 +120,13 @@ public final class Server implements AutoCloseable {
} }
this.bootstrap = new ServerBootstrap() this.bootstrap = new ServerBootstrap()
.group(this.parentEventLoopGroup, this.childEventLoopGroup) .group(this.parentEventLoopGroup, this.childEventLoopGroup)
.channel(this.socketChannelClass) .channel(socketChannelClass1)
.option(ChannelOption.ALLOCATOR, this.byteBufAllocator) .option(ChannelOption.ALLOCATOR, byteBufAllocator1)
.option(ChannelOption.SO_REUSEADDR, serverConfig.isReuseAddr()) .option(ChannelOption.SO_REUSEADDR, serverConfig.isReuseAddr())
.option(ChannelOption.SO_RCVBUF, serverConfig.getTcpReceiveBufferSize()) .option(ChannelOption.SO_RCVBUF, serverConfig.getTcpReceiveBufferSize())
.option(ChannelOption.SO_BACKLOG, serverConfig.getBackLogSize()) .option(ChannelOption.SO_BACKLOG, serverConfig.getBackLogSize())
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, serverConfig.getConnectTimeoutMillis()) .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, serverConfig.getConnectTimeoutMillis())
.childOption(ChannelOption.ALLOCATOR, this.byteBufAllocator) .childOption(ChannelOption.ALLOCATOR, byteBufAllocator1)
.childOption(ChannelOption.SO_REUSEADDR, serverConfig.isReuseAddr()) .childOption(ChannelOption.SO_REUSEADDR, serverConfig.isReuseAddr())
.childOption(ChannelOption.TCP_NODELAY, serverConfig.isTcpNodelay()) .childOption(ChannelOption.TCP_NODELAY, serverConfig.isTcpNodelay())
.childOption(ChannelOption.SO_SNDBUF, serverConfig.getTcpSendBufferSize()) .childOption(ChannelOption.SO_SNDBUF, serverConfig.getTcpSendBufferSize())
@ -137,34 +137,39 @@ public final class Server implements AutoCloseable {
bootstrap.handler(new LoggingHandler("bootstrap-server", serverConfig.getTrafficDebugLogLevel())); bootstrap.handler(new LoggingHandler("bootstrap-server", serverConfig.getTrafficDebugLogLevel()));
} }
if (serverConfig.getDefaultDomain() == null) { if (serverConfig.getDefaultDomain() == null) {
throw new IllegalStateException("no default named server (with name '*') configured, unable to continue"); throw new IllegalStateException("no default domain configured, unable to continue");
} }
// translate domains into Netty mapping
Mapping<String, SslContext> domainNameMapping = null; Mapping<String, SslContext> domainNameMapping = null;
if (serverConfig.getAddress().isSecure() && serverConfig.getDefaultDomain().getSslContext() != null) { Domain defaultDomain = serverConfig.getDefaultDomain();
if (serverConfig.getAddress().isSecure() &&
defaultDomain != null &&
defaultDomain.getSslContext() != null) {
DomainWildcardMappingBuilder<SslContext> mappingBuilder = DomainWildcardMappingBuilder<SslContext> mappingBuilder =
new DomainWildcardMappingBuilder<>(serverConfig.getDefaultDomain().getSslContext()); new DomainWildcardMappingBuilder<>(defaultDomain.getSslContext());
for (Domain domain : serverConfig.getDomains()) { for (Domain domain : serverConfig.getDomains()) {
String name = domain.getName(); if (!domain.getName().equals(defaultDomain.getName())) {
if (!"*".equals(name)) { mappingBuilder.add(domain.getName(), domain.getSslContext());
mappingBuilder.add(name, domain.getSslContext());
} }
} }
domainNameMapping = mappingBuilder.build(); domainNameMapping = mappingBuilder.build();
logger.log(Level.INFO, "domain name mapping: " + domainNameMapping);
} }
bootstrap.childHandler(findChannelInitializer(serverConfig.getAddress().getVersion().majorVersion(), bootstrap.childHandler(findChannelInitializer(serverConfig.getAddress().getVersion().majorVersion(),
serverConfig.getAddress(), domainNameMapping)); serverConfig.getAddress(), domainNameMapping));
} }
public static Builder builder() { @Override
return builder(HttpAddress.http1("localhost", 8008)); public void close() {
try {
shutdownGracefully();
} catch (IOException e) {
logger.log(Level.SEVERE, e.getMessage(), e);
}
} }
public static Builder builder(HttpAddress httpAddress) { public static Builder builder(Domain defaultDomain) {
return new Builder(httpAddress); return new Builder(defaultDomain);
}
public static Builder builder(Domain domain) {
return new Builder(domain);
} }
public ServerConfig getServerConfig() { public ServerConfig getServerConfig() {
@ -189,22 +194,64 @@ public final class Server implements AutoCloseable {
} }
/** /**
* Returns the named server with the given name. * Returns the domain with the given host name.
* *
* @param name the name of the virtual host to return or null for the * @param dnsName the name of the virtual host with optional port to return or null for the
* default domain * default domain
* @return the virtual host with the given name or the default domain * @return the virtual host with the given name or the default domain
*/ */
public Domain getNamedServer(String name) { public Domain getDomain(String dnsName) {
Domain domain = serverConfig.getDomain(name); return serverConfig.getDomain(dnsName);
if (domain == null) {
domain = serverConfig.getDefaultDomain();
}
return domain;
} }
public void handle(Domain domain, HttpServerRequest serverRequest, ServerResponse serverResponse) public Domain getDomain(URL url) {
throws IOException { return getDomain(url.getHost());
}
public URL getPublishURL() {
return getPublishURL(null);
}
public URL getPublishURL(ServerRequest serverRequest) {
Domain domain = serverRequest != null ? getDomain(serverRequest.getURL()) : serverConfig.getDefaultDomain();
URL bindURL = domain.getHttpAddress().base();
String scheme = serverRequest != null ? serverRequest.getHeaders().get("x-forwarded-proto") : null;
if (scheme == null) {
scheme = bindURL.getScheme();
}
String host = serverRequest != null ? serverRequest.getHeaders().get("x-forwarded-host") : null;
if (host == null) {
host = serverRequest != null ? serverRequest.getHeaders().get("host") : null;
if (host == null) {
host = bindURL.getHost();
}
}
String port = null;
if (host != null) {
host = stripPort(host);
port = extractPort(host);
if (port == null) {
port = bindURL.getPort() != null ? Integer.toString(bindURL.getPort()) : null;
}
}
String path = serverRequest != null ? serverRequest.getHeaders().get("x-forwarded-path") : null;
URL.Builder builder = URL.builder().scheme(scheme).host(host);
if (port != null) {
if (path != null) {
return builder.port(Integer.parseInt(port)).path(path).build();
} else {
return builder.port(Integer.parseInt(port)).build();
}
}
if (path != null) {
return builder.path(path).build();
} else {
return builder.build();
}
}
public void handle(HttpServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
Domain domain = getDomain(serverRequest.getURL());
if (executor != null) { if (executor != null) {
executor.submit(() -> { executor.submit(() -> {
try { try {
@ -224,10 +271,6 @@ public final class Server implements AutoCloseable {
} }
} }
public BlockingThreadPoolExecutor getExecutor() {
return executor;
}
public AtomicLong getRequestCounter() { public AtomicLong getRequestCounter() {
return requestCounter; return requestCounter;
} }
@ -236,8 +279,8 @@ public final class Server implements AutoCloseable {
return responseCounter; return responseCounter;
} }
public Transport newTransport(HttpVersion httpVersion) { public ServerTransport newTransport(HttpVersion httpVersion) {
for (ProtocolProvider<HttpChannelInitializer, Transport> protocolProvider : protocolProviders) { for (ServerProtocolProvider<HttpChannelInitializer, ServerTransport> protocolProvider : protocolProviders) {
if (protocolProvider.supportsMajorVersion(httpVersion.majorVersion())) { if (protocolProvider.supportsMajorVersion(httpVersion.majorVersion())) {
try { try {
return protocolProvider.transportClass() return protocolProvider.transportClass()
@ -251,15 +294,6 @@ public final class Server implements AutoCloseable {
throw new IllegalStateException("no channel initializer found for major version " + httpVersion.majorVersion()); throw new IllegalStateException("no channel initializer found for major version " + httpVersion.majorVersion());
} }
@Override
public void close() {
try {
shutdownGracefully();
} catch (IOException e) {
logger.log(Level.SEVERE, e.getMessage(), e);
}
}
public void shutdownGracefully() throws IOException { public void shutdownGracefully() throws IOException {
shutdownGracefully(30L, TimeUnit.SECONDS); shutdownGracefully(30L, TimeUnit.SECONDS);
} }
@ -289,10 +323,26 @@ public final class Server implements AutoCloseable {
} }
} }
private static String stripPort(String hostMaybePort) {
if (hostMaybePort == null) {
return null;
}
int i = hostMaybePort.lastIndexOf(':');
return i >= 0 ? hostMaybePort.substring(0, i) : hostMaybePort;
}
private static String extractPort(String hostMaybePort) {
if (hostMaybePort == null) {
return null;
}
int i = hostMaybePort.lastIndexOf(':');
return i >= 0 ? hostMaybePort.substring(i + 1) : null;
}
private HttpChannelInitializer findChannelInitializer(int majorVersion, private HttpChannelInitializer findChannelInitializer(int majorVersion,
HttpAddress httpAddress, HttpAddress httpAddress,
Mapping<String, SslContext> domainNameMapping) { Mapping<String, SslContext> domainNameMapping) {
for (ProtocolProvider<HttpChannelInitializer, Transport> protocolProvider : protocolProviders) { for (ServerProtocolProvider<HttpChannelInitializer, ServerTransport> protocolProvider : protocolProviders) {
if (protocolProvider.supportsMajorVersion(majorVersion)) { if (protocolProvider.supportsMajorVersion(majorVersion)) {
try { try {
return protocolProvider.initializerClass() return protocolProvider.initializerClass()
@ -449,11 +499,7 @@ public final class Server implements AutoCloseable {
private Class<? extends ServerSocketChannel> socketChannelClass; private Class<? extends ServerSocketChannel> socketChannelClass;
private ServerConfig serverConfig; private final ServerConfig serverConfig;
private Builder(HttpAddress httpAddress) {
this(Domain.builder(httpAddress, "*").build());
}
private Builder(Domain defaultDomain) { private Builder(Domain defaultDomain) {
this.serverConfig = new ServerConfig(); this.serverConfig = new ServerConfig();
@ -596,14 +642,13 @@ public final class Server implements AutoCloseable {
return this; return this;
} }
public Builder setTransportLayerSecurityProtocols(String[] protocols) { public Builder setTransportLayerSecurityProtocols(String... protocols) {
this.serverConfig.setProtocols(protocols); this.serverConfig.setProtocols(protocols);
return this; return this;
} }
public Builder addDomain(Domain domain) { public Builder addDomain(Domain domain) {
this.serverConfig.putDomain(domain); this.serverConfig.checkAndAddDomain(domain);
logger.log(Level.FINE, "adding named server: " + domain);
return this; return this;
} }
@ -616,6 +661,45 @@ public final class Server implements AutoCloseable {
executor.setRejectedExecutionHandler((runnable, threadPoolExecutor) -> executor.setRejectedExecutionHandler((runnable, threadPoolExecutor) ->
logger.log(Level.SEVERE, "rejected: " + runnable)); logger.log(Level.SEVERE, "rejected: " + runnable));
} }
if (serverConfig.isAutoDomain()) {
// unpack subject alternative names into separate domains
for (Domain domain : serverConfig.getDomains()) {
try {
CertificateUtils.processSubjectAlternativeNames(domain.getCertificateChain(),
new CertificateUtils.SubjectAlternativeNamesProcessor() {
@Override
public void setServerName(String serverName) {
}
@Override
public void setSubjectAlternativeName(String subjectAlternativeName) {
Domain alternativeDomain = Domain.builder(domain)
.setServerName(subjectAlternativeName)
.build();
addDomain(alternativeDomain);
}
});
} catch (CertificateParsingException e) {
logger.log(Level.SEVERE, "domain " + domain + ": unable to parse certificate: " + e.getMessage(), e);
}
}
}
for (Domain domain : serverConfig.getDomains()) {
if (domain.getCertificateChain() != null) {
for (X509Certificate certificate : domain.getCertificateChain()) {
try {
certificate.checkValidity();
logger.log(Level.INFO, "certificate " + certificate.getSubjectDN().getName() + " for " + domain + " is valid");
} catch (CertificateNotYetValidException | CertificateExpiredException e) {
logger.log(Level.SEVERE, "certificate " + certificate.getSubjectDN().getName() + " for " + domain + " is not valid: " + e.getMessage(), e);
if (!serverConfig.isAcceptInvalidCertificates()) {
throw new IllegalArgumentException(e);
}
}
}
}
}
logger.log(Level.INFO, "configured domains: " + serverConfig.getDomains());
return new Server(serverConfig, byteBufAllocator, return new Server(serverConfig, byteBufAllocator,
parentEventLoopGroup, childEventLoopGroup, socketChannelClass, parentEventLoopGroup, childEventLoopGroup, socketChannelClass,
executor); executor);

View file

@ -262,6 +262,10 @@ public class ServerConfig {
private KeyStore trustManagerKeyStore = null; private KeyStore trustManagerKeyStore = null;
private boolean autoDomain = false;
private boolean acceptInvalidCertificates = false;
public ServerConfig() { public ServerConfig() {
this.domains = new LinkedHashMap<>(); this.domains = new LinkedHashMap<>();
} }
@ -596,7 +600,28 @@ public class ServerConfig {
return cipherSuiteFilter; return cipherSuiteFilter;
} }
public ServerConfig putDomain(Domain domain) { public ServerConfig setAutoDomain(boolean autoDomain) {
this.autoDomain = autoDomain;
return this;
}
public boolean isAutoDomain() {
return autoDomain;
}
public ServerConfig setAcceptInvalidCertificates(boolean acceptInvalidCertificates) {
this.acceptInvalidCertificates = acceptInvalidCertificates;
return this;
}
public boolean isAcceptInvalidCertificates() {
return acceptInvalidCertificates;
}
public ServerConfig checkAndAddDomain(Domain domain) {
if (domains.containsKey(domain.getName())) {
return this;
}
domains.put(domain.getName(), domain); domains.put(domain.getName(), domain);
for (String alias : domain.getAliases()) { for (String alias : domain.getAliases()) {
domains.put(alias, domain); domains.put(alias, domain);
@ -621,12 +646,14 @@ public class ServerConfig {
return this; return this;
} }
public Domain getDefaultDomain() {
return getDomain("*");
}
public Domain getDomain(String name) { public Domain getDomain(String name) {
return domains.get(name); Domain domain = domains.get(name);
return domain != null ? domain : getDefaultDomain();
} }
} public Domain getDefaultDomain() {
Domain defaultDomain = domains.get("*");
return defaultDomain != null ? defaultDomain :
!domains.isEmpty() ? domains.values().iterator().next() : null;
}
}

View file

@ -6,7 +6,6 @@ import org.xbib.netty.http.server.api.ServerRequest;
import org.xbib.netty.http.server.api.ServerResponse; import org.xbib.netty.http.server.api.ServerResponse;
import org.xbib.netty.http.server.api.annotation.Endpoint; import org.xbib.netty.http.server.api.annotation.Endpoint;
import org.xbib.netty.http.server.endpoint.service.MethodService; import org.xbib.netty.http.server.endpoint.service.MethodService;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
@ -78,7 +77,7 @@ public class HttpEndpointResolver {
private String prefix; private String prefix;
private List<HttpEndpoint> endpoints; private final List<HttpEndpoint> endpoints;
private EndpointDispatcher<HttpEndpoint> endpointDispatcher; private EndpointDispatcher<HttpEndpoint> endpointDispatcher;
@ -99,7 +98,7 @@ public class HttpEndpointResolver {
} }
/** /**
* Add endpoint. * Add endpoint under this endpoint.
* *
* @param endpoint the endpoint * @param endpoint the endpoint
* @return this builder * @return this builder

View file

@ -22,11 +22,11 @@ import io.netty.util.Mapping;
import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.server.Server; import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.ServerConfig; import org.xbib.netty.http.server.ServerConfig;
import org.xbib.netty.http.server.api.HttpChannelInitializer; import org.xbib.netty.http.common.HttpChannelInitializer;
import org.xbib.netty.http.server.handler.ExtendedSNIHandler; import org.xbib.netty.http.server.handler.ExtendedSNIHandler;
import org.xbib.netty.http.server.handler.IdleTimeoutHandler; import org.xbib.netty.http.server.handler.IdleTimeoutHandler;
import org.xbib.netty.http.server.handler.TrafficLoggingHandler; import org.xbib.netty.http.server.handler.TrafficLoggingHandler;
import org.xbib.netty.http.server.api.Transport; import org.xbib.netty.http.server.api.ServerTransport;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.logging.Level; import java.util.logging.Level;
@ -56,8 +56,8 @@ public class Http1ChannelInitializer extends ChannelInitializer<Channel>
@Override @Override
public void initChannel(Channel channel) { public void initChannel(Channel channel) {
Transport transport = server.newTransport(httpAddress.getVersion()); ServerTransport transport = server.newTransport(httpAddress.getVersion());
channel.attr(Transport.TRANSPORT_ATTRIBUTE_KEY).set(transport); channel.attr(ServerTransport.TRANSPORT_ATTRIBUTE_KEY).set(transport);
if (serverConfig.isTrafficDebug()) { if (serverConfig.isTrafficDebug()) {
channel.pipeline().addLast(new TrafficLoggingHandler(LogLevel.DEBUG)); channel.pipeline().addLast(new TrafficLoggingHandler(LogLevel.DEBUG));
} }
@ -125,7 +125,7 @@ public class Http1ChannelInitializer extends ChannelInitializer<Channel>
HttpResponseStatus.HTTP_VERSION_NOT_SUPPORTED); HttpResponseStatus.HTTP_VERSION_NOT_SUPPORTED);
ctx.channel().writeAndFlush(response); ctx.channel().writeAndFlush(response);
} else { } else {
Transport transport = server.newTransport(fullHttpRequest.protocolVersion()); ServerTransport transport = server.newTransport(fullHttpRequest.protocolVersion());
transport.requestReceived(ctx, fullHttpRequest, httpPipelinedRequest.getSequenceId()); transport.requestReceived(ctx, fullHttpRequest, httpPipelinedRequest.getSequenceId());
} }
fullHttpRequest.release(); fullHttpRequest.release();

View file

@ -34,11 +34,11 @@ import io.netty.util.Mapping;
import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.server.Server; import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.ServerConfig; import org.xbib.netty.http.server.ServerConfig;
import org.xbib.netty.http.server.api.HttpChannelInitializer; import org.xbib.netty.http.common.HttpChannelInitializer;
import org.xbib.netty.http.server.handler.ExtendedSNIHandler; import org.xbib.netty.http.server.handler.ExtendedSNIHandler;
import org.xbib.netty.http.server.handler.IdleTimeoutHandler; import org.xbib.netty.http.server.handler.IdleTimeoutHandler;
import org.xbib.netty.http.server.handler.TrafficLoggingHandler; import org.xbib.netty.http.server.handler.TrafficLoggingHandler;
import org.xbib.netty.http.server.api.Transport; import org.xbib.netty.http.server.api.ServerTransport;
import java.io.IOException; import java.io.IOException;
import java.util.logging.Level; import java.util.logging.Level;
@ -68,8 +68,8 @@ public class Http2ChannelInitializer extends ChannelInitializer<Channel>
@Override @Override
public void initChannel(Channel channel) { public void initChannel(Channel channel) {
Transport transport = server.newTransport(httpAddress.getVersion()); ServerTransport transport = server.newTransport(httpAddress.getVersion());
channel.attr(Transport.TRANSPORT_ATTRIBUTE_KEY).set(transport); channel.attr(ServerTransport.TRANSPORT_ATTRIBUTE_KEY).set(transport);
if (serverConfig.isTrafficDebug()) { if (serverConfig.isTrafficDebug()) {
channel.pipeline().addLast(new TrafficLoggingHandler(LogLevel.DEBUG)); channel.pipeline().addLast(new TrafficLoggingHandler(LogLevel.DEBUG));
} }
@ -94,8 +94,8 @@ public class Http2ChannelInitializer extends ChannelInitializer<Channel>
ChannelHandler channelHandler = new ChannelInitializer<Channel>() { ChannelHandler channelHandler = new ChannelInitializer<Channel>() {
@Override @Override
protected void initChannel(Channel channel) { protected void initChannel(Channel channel) {
Transport transport = server.newTransport(httpAddress.getVersion()); ServerTransport transport = server.newTransport(httpAddress.getVersion());
channel.attr(Transport.TRANSPORT_ATTRIBUTE_KEY).set(transport); channel.attr(ServerTransport.TRANSPORT_ATTRIBUTE_KEY).set(transport);
ChannelPipeline pipeline = channel.pipeline(); ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast("server-frame-converter", pipeline.addLast("server-frame-converter",
new Http2StreamFrameToHttpObjectCodec(true)); new Http2StreamFrameToHttpObjectCodec(true));
@ -137,7 +137,7 @@ public class Http2ChannelInitializer extends ChannelInitializer<Channel>
@Override @Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest) throws IOException { protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest) throws IOException {
Transport transport = ctx.channel().attr(Transport.TRANSPORT_ATTRIBUTE_KEY).get(); ServerTransport transport = ctx.channel().attr(ServerTransport.TRANSPORT_ATTRIBUTE_KEY).get();
transport.requestReceived(ctx, fullHttpRequest, null); transport.requestReceived(ctx, fullHttpRequest, null);
} }
} }
@ -148,7 +148,7 @@ public class Http2ChannelInitializer extends ChannelInitializer<Channel>
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof DefaultHttp2SettingsFrame) { if (msg instanceof DefaultHttp2SettingsFrame) {
DefaultHttp2SettingsFrame http2SettingsFrame = (DefaultHttp2SettingsFrame) msg; DefaultHttp2SettingsFrame http2SettingsFrame = (DefaultHttp2SettingsFrame) msg;
Transport transport = ctx.channel().attr(Transport.TRANSPORT_ATTRIBUTE_KEY).get(); ServerTransport transport = ctx.channel().attr(ServerTransport.TRANSPORT_ATTRIBUTE_KEY).get();
transport.settingsReceived(ctx, http2SettingsFrame.settings()); transport.settingsReceived(ctx, http2SettingsFrame.settings());
} else if (msg instanceof DefaultHttpRequest) { } else if (msg instanceof DefaultHttpRequest) {
DefaultHttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, DefaultHttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1,
@ -159,13 +159,13 @@ public class Http2ChannelInitializer extends ChannelInitializer<Channel>
@Override @Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
Transport transport = ctx.channel().attr(Transport.TRANSPORT_ATTRIBUTE_KEY).get(); ServerTransport transport = ctx.channel().attr(ServerTransport.TRANSPORT_ATTRIBUTE_KEY).get();
ctx.fireUserEventTriggered(evt); ctx.fireUserEventTriggered(evt);
} }
@Override @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws IOException { public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws IOException {
Transport transport = ctx.channel().attr(Transport.TRANSPORT_ATTRIBUTE_KEY).get(); ServerTransport transport = ctx.channel().attr(ServerTransport.TRANSPORT_ATTRIBUTE_KEY).get();
transport.exceptionReceived(ctx, cause); transport.exceptionReceived(ctx, cause);
} }
} }

View file

@ -0,0 +1,47 @@
package org.xbib.netty.http.server.security;
import java.io.InputStream;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.List;
public class CertificateUtils {
@SuppressWarnings("unchecked")
public static Collection<? extends X509Certificate> toCertificate(InputStream keyCertChainInputStream)
throws CertificateException {
return (Collection<? extends X509Certificate>) CertificateFactory.getInstance("X509")
.generateCertificates(keyCertChainInputStream);
}
public static void processSubjectAlternativeNames(Collection<? extends X509Certificate> certificates,
SubjectAlternativeNamesProcessor processor) throws CertificateParsingException {
if (certificates == null) {
return;
}
for (X509Certificate certificate : certificates) {
processor.setServerName(new DistinguishedNameParser(certificate.getSubjectX500Principal())
.findMostSpecific("CN"));
Collection<List<?>> altNames = certificate.getSubjectAlternativeNames();
if (altNames != null) {
for (List<?> altName : altNames) {
Integer type = (Integer) altName.get(0);
if (type == 2) { // Type DNS
String string = altName.get(1).toString();
processor.setSubjectAlternativeName(string);
}
}
}
}
}
public interface SubjectAlternativeNamesProcessor {
void setServerName(String serverName);
void setSubjectAlternativeName(String subjectAlternativeName);
}
}

View file

@ -0,0 +1,300 @@
package org.xbib.netty.http.server.security;
import javax.security.auth.x500.X500Principal;
/**
* A distinguished name (DN) parser.
* This parser only supports extracting a string value from a DN.
* It doesn't support values in the hex-string style.
*
* Taken from okhttp
*/
public final class DistinguishedNameParser {
private final String dn;
private final int length;
private int pos;
private int beg;
private int end;
private int cur;
private char[] chars;
public DistinguishedNameParser(X500Principal principal) {
this.dn = principal.getName(X500Principal.RFC2253);
this.length = this.dn.length();
}
private String nextAT() {
while (pos < length && chars[pos] == ' ') {
pos++;
}
if (pos == length) {
return null;
}
beg = pos;
pos++;
while (pos < length && chars[pos] != '=' && chars[pos] != ' ') {
pos++;
}
if (pos >= length) {
throw new IllegalStateException("Unexpected end of DN: " + dn);
}
end = pos;
if (chars[pos] == ' ') {
while (pos < length && chars[pos] != '=' && chars[pos] == ' ') {
pos++;
}
if (chars[pos] != '=' || pos == length) {
throw new IllegalStateException("Unexpected end of DN: " + dn);
}
}
pos++;
while (pos < length && chars[pos] == ' ') {
pos++;
}
if ((end - beg > 4) && (chars[beg + 3] == '.') &&
(chars[beg] == 'O' || chars[beg] == 'o') &&
(chars[beg + 1] == 'I' || chars[beg + 1] == 'i') &&
(chars[beg + 2] == 'D' || chars[beg + 2] == 'd')) {
beg += 4;
}
return new String(chars, beg, end - beg);
}
private String quotedAV() {
pos++;
beg = pos;
end = beg;
while (true) {
if (pos == length) {
throw new IllegalStateException("Unexpected end of DN: " + dn);
}
if (chars[pos] == '"') {
pos++;
break;
} else if (chars[pos] == '\\') {
chars[end] = getEscaped();
} else {
chars[end] = chars[pos];
}
pos++;
end++;
}
while (pos < length && chars[pos] == ' ') {
pos++;
}
return new String(chars, beg, end - beg);
}
private String hexAV() {
if (pos + 4 >= length) {
throw new IllegalStateException("Unexpected end of DN: " + dn);
}
beg = pos;
pos++;
while (true) {
if (pos == length || chars[pos] == '+' || chars[pos] == ','
|| chars[pos] == ';') {
end = pos;
break;
}
if (chars[pos] == ' ') {
end = pos;
pos++;
while (pos < length && chars[pos] == ' ') {
pos++;
}
break;
} else if (chars[pos] >= 'A' && chars[pos] <= 'F') {
chars[pos] += 32;
}
pos++;
}
int hexLen = end - beg;
if (hexLen < 5 || (hexLen & 1) == 0) {
throw new IllegalStateException("Unexpected end of DN: " + dn);
}
return new String(chars, beg, hexLen);
}
private String escapedAV() {
beg = pos;
end = pos;
while (true) {
if (pos >= length) {
return new String(chars, beg, end - beg);
}
switch (chars[pos]) {
case '+':
case ',':
case ';':
return new String(chars, beg, end - beg);
case '\\':
chars[end++] = getEscaped();
pos++;
break;
case ' ':
cur = end;
pos++;
chars[end++] = ' ';
for (; pos < length && chars[pos] == ' '; pos++) {
chars[end++] = ' ';
}
if (pos == length ||
chars[pos] == ',' ||
chars[pos] == '+' ||
chars[pos] == ';') {
return new String(chars, beg, cur - beg);
}
break;
default:
chars[end++] = chars[pos];
pos++;
}
}
}
private char getEscaped() {
pos++;
if (pos == length) {
throw new IllegalStateException("Unexpected end of DN: " + dn);
}
switch (chars[pos]) {
case '"':
case '\\':
case ',':
case '=':
case '+':
case '<':
case '>':
case '#':
case ';':
case ' ':
case '*':
case '%':
case '_':
return chars[pos];
default:
return getUTF8();
}
}
private char getUTF8() {
int res = getByte(pos);
pos++;
if (res < 128) {
return (char) res;
} else if (res >= 192 && res <= 247) {
int count;
if (res <= 223) {
count = 1;
res = res & 0x1F;
} else if (res <= 239) {
count = 2;
res = res & 0x0F;
} else {
count = 3;
res = res & 0x07;
}
int b;
for (int i = 0; i < count; i++) {
pos++;
if (pos == length || chars[pos] != '\\') {
return 0x3F;
}
pos++;
b = getByte(pos);
pos++;
if ((b & 0xC0) != 0x80) {
return 0x3F;
}
res = (res << 6) + (b & 0x3F);
}
return (char) res;
} else {
return 0x3F;
}
}
private int getByte(int position) {
if (position + 1 >= length) {
throw new IllegalStateException("Malformed DN: " + dn);
}
int b1, b2;
b1 = chars[position];
if (b1 >= '0' && b1 <= '9') {
b1 = b1 - '0';
} else if (b1 >= 'a' && b1 <= 'f') {
b1 = b1 - 87;
} else if (b1 >= 'A' && b1 <= 'F') {
b1 = b1 - 55;
} else {
throw new IllegalStateException("Malformed DN: " + dn);
}
b2 = chars[position + 1];
if (b2 >= '0' && b2 <= '9') {
b2 = b2 - '0';
} else if (b2 >= 'a' && b2 <= 'f') {
b2 = b2 - 87;
} else if (b2 >= 'A' && b2 <= 'F') {
b2 = b2 - 55;
} else {
throw new IllegalStateException("Malformed DN: " + dn);
}
return (b1 << 4) + b2;
}
public String findMostSpecific(String attributeType) {
pos = 0;
beg = 0;
end = 0;
cur = 0;
chars = dn.toCharArray();
String attType = nextAT();
if (attType == null) {
return null;
}
while (true) {
String attValue = "";
if (pos == length) {
return null;
}
switch (chars[pos]) {
case '"':
attValue = quotedAV();
break;
case '#':
attValue = hexAV();
break;
case '+':
case ',':
case ';':
break;
default:
attValue = escapedAV();
}
if (attributeType.equalsIgnoreCase(attType)) {
return attValue;
}
if (pos >= length) {
return null;
}
if (chars[pos] != ',' && chars[pos] != ';') {
if (chars[pos] != '+') {
throw new IllegalStateException("Malformed DN: " + dn);
}
}
pos++;
attType = nextAT();
if (attType == null) {
throw new IllegalStateException("Malformed DN: " + dn);
}
}
}
}

View file

@ -0,0 +1,90 @@
package org.xbib.netty.http.server.security;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.base64.Base64;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.crypto.Cipher;
import javax.crypto.EncryptedPrivateKeyInfo;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
public class PrivateKeyUtils {
public static PrivateKey toPrivateKey(InputStream keyInputStream, String keyPassword)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeySpecException,
InvalidAlgorithmParameterException, KeyException, IOException {
if (keyInputStream == null) {
return null;
}
return getPrivateKeyFromByteBuffer(readPrivateKey(keyInputStream), keyPassword);
}
private static final String[] KEY_TYPES = { "RSA", "DSA", "EC" };
private static PrivateKey getPrivateKeyFromByteBuffer(ByteBuf encodedKeyBuf, String keyPassword)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeySpecException,
InvalidAlgorithmParameterException, KeyException, IOException {
byte[] encodedKey = new byte[encodedKeyBuf.readableBytes()];
encodedKeyBuf.readBytes(encodedKey).release();
PKCS8EncodedKeySpec encodedKeySpec =
generateKeySpec(keyPassword == null ? null : keyPassword.toCharArray(), encodedKey);
for (String keyType : KEY_TYPES) {
try {
return KeyFactory.getInstance(keyType)
.generatePrivate(encodedKeySpec);
} catch (InvalidKeySpecException ignore) {
// ignore
}
}
throw new InvalidKeySpecException("Neither RSA, DSA nor EC worked");
}
private static PKCS8EncodedKeySpec generateKeySpec(char[] password, byte[] key)
throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeySpecException,
InvalidKeyException, InvalidAlgorithmParameterException {
if (password == null) {
return new PKCS8EncodedKeySpec(key);
}
EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo(key);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(encryptedPrivateKeyInfo.getAlgName());
PBEKeySpec pbeKeySpec = new PBEKeySpec(password);
SecretKey pbeKey = keyFactory.generateSecret(pbeKeySpec);
Cipher cipher = Cipher.getInstance(encryptedPrivateKeyInfo.getAlgName());
cipher.init(Cipher.DECRYPT_MODE, pbeKey, encryptedPrivateKeyInfo.getAlgParameters());
return encryptedPrivateKeyInfo.getKeySpec(cipher);
}
private static ByteBuf readPrivateKey(InputStream in) throws KeyException, IOException {
byte[] content = in.readAllBytes();
Matcher m = KEY_PATTERN.matcher(new String(content, StandardCharsets.US_ASCII));
if (!m.find()) {
throw new KeyException("could not find a PKCS #8 private key in input stream");
}
ByteBuf base64 = Unpooled.copiedBuffer(m.group(1), StandardCharsets.US_ASCII);
ByteBuf der = Base64.decode(base64);
base64.release();
return der;
}
private static final Pattern KEY_PATTERN = Pattern.compile(
"-+BEGIN\\s+.*PRIVATE\\s+KEY[^-]*-+(?:\\s|\\r|\\n)+" +
"([a-z0-9+/=\\r\\n]+)" +
"-+END\\s+.*PRIVATE\\s+KEY[^-]*-+",
Pattern.CASE_INSENSITIVE);
}

View file

@ -8,13 +8,12 @@ import io.netty.handler.codec.http.HttpVersion;
import org.xbib.netty.http.server.Server; import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.api.ServerRequest; import org.xbib.netty.http.server.api.ServerRequest;
import org.xbib.netty.http.server.api.ServerResponse; import org.xbib.netty.http.server.api.ServerResponse;
import org.xbib.netty.http.server.Domain; import org.xbib.netty.http.server.api.ServerTransport;
import org.xbib.netty.http.server.api.Transport;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
abstract class BaseTransport implements Transport { abstract class BaseTransport implements ServerTransport {
private static final Logger logger = Logger.getLogger(BaseTransport.class.getName()); private static final Logger logger = Logger.getLogger(BaseTransport.class.getName());
@ -34,14 +33,13 @@ abstract class BaseTransport implements Transport {
* and required special header handling, possibly returning an * and required special header handling, possibly returning an
* appropriate response. * appropriate response.
* *
* @param domain the named server * @param version the HTTP version of the server
* @param serverRequest the request * @param serverRequest the request
* @param serverResponse the response * @param serverResponse the response
* @return whether further processing should be performed * @return whether further processing should be performed
*/ */
static boolean acceptRequest(Domain domain, ServerRequest serverRequest, ServerResponse serverResponse) { static boolean acceptRequest(HttpVersion version, ServerRequest serverRequest, ServerResponse serverResponse) {
HttpHeaders reqHeaders = serverRequest.getHeaders(); HttpHeaders reqHeaders = serverRequest.getHeaders();
HttpVersion version = domain.getHttpAddress().getVersion();
if (version.majorVersion() == 1 || version.majorVersion() == 2) { if (version.majorVersion() == 1 || version.majorVersion() == 2) {
if (!reqHeaders.contains(HttpHeaderNames.HOST)) { if (!reqHeaders.contains(HttpHeaderNames.HOST)) {
// RFC2616#14.23: missing Host header gets 400 // RFC2616#14.23: missing Host header gets 400

View file

@ -2,13 +2,11 @@ package org.xbib.netty.http.server.transport;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.FullHttpRequest; 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.http.HttpResponseStatus;
import io.netty.handler.codec.http2.Http2Settings; import io.netty.handler.codec.http2.Http2Settings;
import io.netty.handler.ssl.SslHandler; import io.netty.handler.ssl.SslHandler;
import org.xbib.netty.http.server.Server; import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.api.ServerResponse; import org.xbib.netty.http.server.api.ServerResponse;
import org.xbib.netty.http.server.Domain;
import java.io.IOException; import java.io.IOException;
public class Http1Transport extends BaseTransport { public class Http1Transport extends BaseTransport {
@ -19,7 +17,6 @@ public class Http1Transport extends BaseTransport {
@Override @Override
public void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest, Integer sequenceId) throws IOException { public void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest, Integer sequenceId) throws IOException {
Domain domain = server.getNamedServer(fullHttpRequest.headers().get(HttpHeaderNames.HOST));
HttpServerRequest serverRequest = new HttpServerRequest(server, fullHttpRequest, ctx); HttpServerRequest serverRequest = new HttpServerRequest(server, fullHttpRequest, ctx);
serverRequest.setSequenceId(sequenceId); serverRequest.setSequenceId(sequenceId);
serverRequest.setRequestId(server.getRequestCounter().incrementAndGet()); serverRequest.setRequestId(server.getRequestCounter().incrementAndGet());
@ -28,9 +25,9 @@ public class Http1Transport extends BaseTransport {
serverRequest.setSession(sslHandler.engine().getSession()); serverRequest.setSession(sslHandler.engine().getSession());
} }
HttpServerResponse serverResponse = new HttpServerResponse(server, serverRequest, ctx); HttpServerResponse serverResponse = new HttpServerResponse(server, serverRequest, ctx);
if (acceptRequest(domain, serverRequest, serverResponse)) { if (acceptRequest(server.getServerConfig().getAddress().getVersion(), serverRequest, serverResponse)) {
serverRequest.handleParameters(); serverRequest.handleParameters();
server.handle(domain, serverRequest, serverResponse); server.handle(serverRequest, serverResponse);
} else { } else {
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_ACCEPTABLE); ServerResponse.write(serverResponse, HttpResponseStatus.NOT_ACCEPTABLE);
} }

View file

@ -2,13 +2,11 @@ package org.xbib.netty.http.server.transport;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.FullHttpRequest; 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.http.HttpResponseStatus;
import io.netty.handler.codec.http2.Http2Settings; import io.netty.handler.codec.http2.Http2Settings;
import io.netty.handler.codec.http2.HttpConversionUtil; import io.netty.handler.codec.http2.HttpConversionUtil;
import org.xbib.netty.http.server.Server; import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.api.ServerResponse; import org.xbib.netty.http.server.api.ServerResponse;
import org.xbib.netty.http.server.Domain;
import java.io.IOException; import java.io.IOException;
import java.util.logging.Level; import java.util.logging.Level;
@ -24,15 +22,14 @@ public class Http2Transport extends BaseTransport {
@Override @Override
public void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest, Integer sequenceId) throws IOException { public void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest, Integer sequenceId) throws IOException {
Domain domain = server.getNamedServer(fullHttpRequest.headers().get(HttpHeaderNames.HOST));
HttpServerRequest serverRequest = new HttpServerRequest(server, fullHttpRequest, ctx); HttpServerRequest serverRequest = new HttpServerRequest(server, fullHttpRequest, ctx);
serverRequest.setSequenceId(sequenceId); serverRequest.setSequenceId(sequenceId);
serverRequest.setRequestId(server.getRequestCounter().incrementAndGet()); serverRequest.setRequestId(server.getRequestCounter().incrementAndGet());
serverRequest.setStreamId(fullHttpRequest.headers().getInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text())); serverRequest.setStreamId(fullHttpRequest.headers().getInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text()));
ServerResponse serverResponse = new Http2ServerResponse(server, serverRequest, ctx); ServerResponse serverResponse = new Http2ServerResponse(server, serverRequest, ctx);
if (acceptRequest(domain, serverRequest, serverResponse)) { if (acceptRequest(server.getServerConfig().getAddress().getVersion(), serverRequest, serverResponse)) {
serverRequest.handleParameters(); serverRequest.handleParameters();
server.handle(domain, serverRequest, serverResponse); server.handle(serverRequest, serverResponse);
} else { } else {
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_ACCEPTABLE); ServerResponse.write(serverResponse, HttpResponseStatus.NOT_ACCEPTABLE);
} }

View file

@ -1,2 +0,0 @@
org.xbib.netty.http.server.Http1Provider
org.xbib.netty.http.server.Http2Provider

View file

@ -0,0 +1,2 @@
org.xbib.netty.http.server.Http1
org.xbib.netty.http.server.Http2

View file

@ -12,12 +12,13 @@ import java.io.IOException;
import java.net.BindException; import java.net.BindException;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail;
class BindExceptionTest { class BindExceptionTest {
@Test @Test
void testDoubleServer() throws IOException { void testDoubleServer() throws IOException {
Domain domain = Domain.builder(HttpAddress.http1("localhost", 8008), "*") Domain domain = Domain.builder(HttpAddress.http1("localhost", 8008))
.singleEndpoint("/", (request, response) -> ServerResponse.write(response, "Hello World")) .singleEndpoint("/", (request, response) -> ServerResponse.write(response, "Hello World"))
.build(); .build();
Server server1 = Server.builder(domain).build(); Server server1 = Server.builder(domain).build();
@ -29,6 +30,7 @@ class BindExceptionTest {
assertNotNull(channelFuture1); assertNotNull(channelFuture1);
ChannelFuture channelFuture2 = server2.accept(); ChannelFuture channelFuture2 = server2.accept();
// should crash with BindException // should crash with BindException
fail();
}); });
} finally { } finally {
server1.shutdownGracefully(); server1.shutdownGracefully();

View file

@ -0,0 +1,75 @@
package org.xbib.netty.http.server.test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.xbib.netty.http.client.Client;
import org.xbib.netty.http.client.api.Request;
import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.server.Domain;
import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.api.ServerResponse;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.logging.Level;
import java.util.logging.Logger;
@Disabled
class MultiDomainSecureServerTest {
private static final Logger logger = Logger.getLogger(MultiDomainSecureServerTest.class.getName());
@Test
void testSecureServer() throws Exception {
InputStream certInputStream = getClass().getResourceAsStream("/fl-20210906.crt");
if (certInputStream == null) {
return;
}
InputStream keyInputStream = getClass().getResourceAsStream("/fl-20210906.pkcs8");
if (keyInputStream == null) {
return;
}
HttpAddress httpAddress = HttpAddress.secureHttp2("localhost", 8443);
Domain fl = Domain.builder(httpAddress, "fl.hbz-nrw.de")
.setKeyCertChain(certInputStream)
.setKey(keyInputStream, null)
.singleEndpoint("/", (request, response) -> ServerResponse.write(response, "Hello fl.hbz-nrw.de"))
.build();
Domain zfl2 = Domain.builder(fl)
.setServerName("zfl2.hbz-nrw.de")
.singleEndpoint("/", (request, response) -> ServerResponse.write(response, "Hello zfl2.hbz-nrw.de"))
.build();
Server server = Server.builder(fl)
.addDomain(zfl2)
.setTransportLayerSecurityProtocols("TLSv1.3")
.build();
Client client = Client.builder()
.build();
try {
server.accept();
Request request = Request.get()
.setVersion("HTTP/2.0")
.url("https://fl.hbz-nrw.de:8443")
.setResponseListener(resp -> {
String response = resp.getBodyAsString(StandardCharsets.UTF_8);
logger.log(Level.INFO, "got response: " + response + " status=" + resp.getStatus());
assertEquals("Hello fl.hbz-nrw.de", response);
})
.build();
client.execute(request).get();
request = Request.get()
.setVersion("HTTP/2.0")
.url("https://zfl2.hbz-nrw.de:8443")
.setResponseListener(resp -> {
String response = resp.getBodyAsString(StandardCharsets.UTF_8);
logger.log(Level.INFO, "got response: " + response + " status=" + resp.getStatus());
assertEquals("Hello zfl2.hbz-nrw.de", response);
})
.build();
client.execute(request).get();
} finally {
client.shutdownGracefully();
server.shutdownGracefully();
}
}
}

View file

@ -0,0 +1,61 @@
package org.xbib.netty.http.server.test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.xbib.netty.http.client.Client;
import org.xbib.netty.http.client.api.Request;
import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.api.ServerResponse;
import org.xbib.netty.http.server.Domain;
import java.nio.charset.StandardCharsets;
import java.util.logging.Level;
import java.util.logging.Logger;
@Disabled
class MultiDomainServerTest {
private static final Logger logger = Logger.getLogger(MultiDomainServerTest.class.getName());
@Test
void testServer() throws Exception {
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
Domain fl = Domain.builder(httpAddress, "fl.hbz-nrw.de")
.singleEndpoint("/", (request, response) -> ServerResponse.write(response, "Hello fl.hbz-nrw.de"))
.build();
Domain zfl2 = Domain.builder(fl)
.setServerName("zfl2.hbz-nrw.de")
.singleEndpoint("/", (request, response) -> ServerResponse.write(response, "Hello zfl2.hbz-nrw.de"))
.build();
Server server = Server.builder(fl)
.addDomain(zfl2)
.build();
Client client = Client.builder()
.build();
try {
server.accept();
Request request = Request.get()
.url("http://fl.hbz-nrw.de:8008")
.setResponseListener(resp -> {
String response = resp.getBodyAsString(StandardCharsets.UTF_8);
logger.log(Level.INFO, "got response: " + response + " status=" + resp.getStatus());
assertEquals("Hello fl.hbz-nrw.de", response);
})
.build();
client.execute(request).get();
request = Request.get()
.url("http://zfl2.hbz-nrw.de:8008")
.setResponseListener(resp -> {
String response = resp.getBodyAsString(StandardCharsets.UTF_8);
logger.log(Level.INFO, "got response: " + response + " status=" + resp.getStatus());
assertEquals("Hello zfl2.hbz-nrw.de", response);
})
.build();
client.execute(request).get();
} finally {
client.shutdownGracefully();
server.shutdownGracefully();
}
}
}

View file

@ -1,25 +0,0 @@
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.api.ServerResponse;
import org.xbib.netty.http.server.Domain;
@Disabled
class RunServerTest {
@Test
void testServer() throws Exception {
Domain domain = Domain.builder(HttpAddress.http1("localhost", 8008), "*")
.singleEndpoint("/", (request, response) -> ServerResponse.write(response, "Hello World"))
.build();
Server server = Server.builder(domain).build();
try {
server.accept().channel().closeFuture().sync();
} finally {
server.shutdownGracefully();
}
}
}

View file

@ -5,6 +5,7 @@ import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.server.Server; import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.api.ServerResponse; import org.xbib.netty.http.server.api.ServerResponse;
import org.xbib.netty.http.server.Domain; import org.xbib.netty.http.server.Domain;
@ -22,7 +23,7 @@ class ThreadLeakTest {
@Test @Test
void testForLeaks() throws IOException { void testForLeaks() throws IOException {
Domain domain = Domain.builder() Domain domain = Domain.builder(HttpAddress.http1("localhost", 8008))
.singleEndpoint("/", (request, response) -> ServerResponse.write(response, "Hello World")) .singleEndpoint("/", (request, response) -> ServerResponse.write(response, "Hello World"))
.build(); .build();
Server server = Server.builder(domain) Server server = Server.builder(domain)

View file

@ -8,7 +8,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.xbib.netty.http.client.Client; import org.xbib.netty.http.client.Client;
import org.xbib.netty.http.client.api.Request; import org.xbib.netty.http.client.api.Request;
import org.xbib.netty.http.client.api.ResponseListener; import org.xbib.netty.http.client.api.ResponseListener;
import org.xbib.netty.http.client.api.Transport; import org.xbib.netty.http.client.api.ClientTransport;
import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.common.HttpResponse; import org.xbib.netty.http.common.HttpResponse;
import org.xbib.netty.http.server.Domain; import org.xbib.netty.http.server.Domain;
@ -33,7 +33,7 @@ class TransportLayerSecurityServerTest {
.withContentType("text/plain") .withContentType("text/plain")
.write(request.getContent().retain())) .write(request.getContent().retain()))
.build()) .build())
.setTransportLayerSecurityProtocols(new String[]{ "TLSv1.2"}) .setTransportLayerSecurityProtocols("TLSv1.2")
.build(); .build();
Client client = Client.builder() Client client = Client.builder()
.trustInsecure() .trustInsecure()
@ -51,8 +51,8 @@ class TransportLayerSecurityServerTest {
.content("Hello Jörg", "text/plain") .content("Hello Jörg", "text/plain")
.setResponseListener(responseListener) .setResponseListener(responseListener)
.build(); .build();
Transport transport = client.execute(request).get(); ClientTransport transport = client.execute(request).get();
logger.log(Level.INFO, "HTTP 1.1 TLS protocol = " + transport.getSession().getProtocol()); logger.log(Level.INFO, "TLS protocol = " + transport.getSession().getProtocol());
assertEquals("TLSv1.2", transport.getSession().getProtocol()); assertEquals("TLSv1.2", transport.getSession().getProtocol());
} finally { } finally {
client.shutdownGracefully(); client.shutdownGracefully();
@ -71,7 +71,7 @@ class TransportLayerSecurityServerTest {
.withContentType("text/plain") .withContentType("text/plain")
.write(request.getContent().retain())) .write(request.getContent().retain()))
.build()) .build())
.setTransportLayerSecurityProtocols(new String[]{ "TLSv1.3"}) .setTransportLayerSecurityProtocols("TLSv1.3")
.build(); .build();
Client client = Client.builder() Client client = Client.builder()
.trustInsecure() .trustInsecure()
@ -90,8 +90,8 @@ class TransportLayerSecurityServerTest {
.content("Hello Jörg", "text/plain") .content("Hello Jörg", "text/plain")
.setResponseListener(responseListener) .setResponseListener(responseListener)
.build(); .build();
Transport transport = client.execute(request).get(); ClientTransport transport = client.execute(request).get();
logger.log(Level.INFO, "HTTP/2 TLS protocol = " + transport.getSession().getProtocol()); logger.log(Level.INFO, "TLS protocol = " + transport.getSession().getProtocol());
assertEquals("TLSv1.3", transport.getSession().getProtocol()); assertEquals("TLSv1.3", transport.getSession().getProtocol());
} finally { } finally {
client.shutdownGracefully(); client.shutdownGracefully();

View file

@ -7,7 +7,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.xbib.netty.http.client.Client; import org.xbib.netty.http.client.Client;
import org.xbib.netty.http.client.api.Request; import org.xbib.netty.http.client.api.Request;
import org.xbib.netty.http.client.api.ResponseListener; import org.xbib.netty.http.client.api.ResponseListener;
import org.xbib.netty.http.client.api.Transport; import org.xbib.netty.http.client.api.ClientTransport;
import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.common.HttpResponse; import org.xbib.netty.http.common.HttpResponse;
import org.xbib.netty.http.server.Server; import org.xbib.netty.http.server.Server;
@ -92,7 +92,7 @@ class CleartextTest {
.content(Integer.toString(i), "text/plain") .content(Integer.toString(i), "text/plain")
.setResponseListener(responseListener) .setResponseListener(responseListener)
.build(); .build();
Transport transport = client.newTransport(); ClientTransport transport = client.newTransport();
transport.execute(request); transport.execute(request);
if (transport.isFailed()) { if (transport.isFailed()) {
logger.log(Level.WARNING, transport.getFailure().getMessage(), transport.getFailure()); logger.log(Level.WARNING, transport.getFailure().getMessage(), transport.getFailure());
@ -109,7 +109,7 @@ class CleartextTest {
@Test @Test
void testMultithreadPooledClearTextHttp1() throws Exception { void testMultithreadPooledClearTextHttp1() throws Exception {
int threads = 2; int threads = 4;
int loop = 1024; int loop = 1024;
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008); HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
Domain domain = Domain.builder(httpAddress) Domain domain = Domain.builder(httpAddress)
@ -144,7 +144,7 @@ class CleartextTest {
.setResponseListener(responseListener) .setResponseListener(responseListener)
.build(); .build();
// note: in HTTP 1, a new transport is created per execution // note: in HTTP 1, a new transport is created per execution
Transport transport = client.newTransport(); ClientTransport transport = client.newTransport();
transport.execute(request); transport.execute(request);
if (transport.isFailed()) { if (transport.isFailed()) {
logger.log(Level.WARNING, "transport failed: " + transport.getFailure().getMessage(), transport.getFailure()); logger.log(Level.WARNING, "transport failed: " + transport.getFailure().getMessage(), transport.getFailure());

View file

@ -7,7 +7,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.xbib.netty.http.client.Client; import org.xbib.netty.http.client.Client;
import org.xbib.netty.http.client.api.Request; import org.xbib.netty.http.client.api.Request;
import org.xbib.netty.http.client.api.ResponseListener; import org.xbib.netty.http.client.api.ResponseListener;
import org.xbib.netty.http.client.api.Transport; import org.xbib.netty.http.client.api.ClientTransport;
import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.common.HttpResponse; import org.xbib.netty.http.common.HttpResponse;
import org.xbib.netty.http.server.Server; import org.xbib.netty.http.server.Server;
@ -86,7 +86,7 @@ class EncryptedTest {
.content(Integer.toString(i), "text/plain") .content(Integer.toString(i), "text/plain")
.setResponseListener(responseListener) .setResponseListener(responseListener)
.build(); .build();
Transport transport = client.newTransport(); ClientTransport transport = client.newTransport();
transport.execute(request); transport.execute(request);
if (transport.isFailed()) { if (transport.isFailed()) {
logger.log(Level.WARNING, transport.getFailure().getMessage(), transport.getFailure()); logger.log(Level.WARNING, transport.getFailure().getMessage(), transport.getFailure());
@ -137,7 +137,7 @@ class EncryptedTest {
.setResponseListener(responseListener) .setResponseListener(responseListener)
.build(); .build();
// note: a new transport is created per execution // note: a new transport is created per execution
final Transport transport = client.newTransport(); final ClientTransport transport = client.newTransport();
transport.execute(request); transport.execute(request);
if (transport.isFailed()) { if (transport.isFailed()) {
logger.log(Level.WARNING, transport.getFailure().getMessage(), transport.getFailure()); logger.log(Level.WARNING, transport.getFailure().getMessage(), transport.getFailure());

View file

@ -6,7 +6,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.xbib.netty.http.client.Client; import org.xbib.netty.http.client.Client;
import org.xbib.netty.http.client.api.Request; import org.xbib.netty.http.client.api.Request;
import org.xbib.netty.http.client.api.ResponseListener; import org.xbib.netty.http.client.api.ResponseListener;
import org.xbib.netty.http.client.api.Transport; import org.xbib.netty.http.client.api.ClientTransport;
import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.common.HttpResponse; import org.xbib.netty.http.common.HttpResponse;
import org.xbib.netty.http.server.Server; import org.xbib.netty.http.server.Server;
@ -59,7 +59,7 @@ class CleartextTest {
.content(payload, "text/plain") .content(payload, "text/plain")
.setResponseListener(responseListener) .setResponseListener(responseListener)
.build(); .build();
Transport transport = client.newTransport(httpAddress); ClientTransport transport = client.newTransport(httpAddress);
transport.execute(request); transport.execute(request);
if (transport.isFailed()) { if (transport.isFailed()) {
logger.log(Level.WARNING, transport.getFailure().getMessage(), transport.getFailure()); logger.log(Level.WARNING, transport.getFailure().getMessage(), transport.getFailure());
@ -101,7 +101,7 @@ class CleartextTest {
}; };
try { try {
// single transport, single thread // single transport, single thread
Transport transport = client.newTransport(); ClientTransport transport = client.newTransport();
for (int i = 0; i < loop; i++) { for (int i = 0; i < loop; i++) {
String payload = 0 + "/" + i; String payload = 0 + "/" + i;
Request request = Request.get().setVersion("HTTP/2.0") Request request = Request.get().setVersion("HTTP/2.0")
@ -115,7 +115,7 @@ class CleartextTest {
break; break;
} }
} }
transport.get(60L, TimeUnit.SECONDS); transport.get(10L, TimeUnit.SECONDS);
} finally { } finally {
server.shutdownGracefully(); server.shutdownGracefully();
client.shutdownGracefully(); client.shutdownGracefully();
@ -126,7 +126,7 @@ class CleartextTest {
@Test @Test
void testMultithreadPooledClearTextHttp2() throws Exception { void testMultithreadPooledClearTextHttp2() throws Exception {
int threads = 2; int threads = 4;
int loop = 1024; int loop = 1024;
HttpAddress httpAddress = HttpAddress.http2("localhost", 8008); HttpAddress httpAddress = HttpAddress.http2("localhost", 8008);
Domain domain = Domain.builder(httpAddress) Domain domain = Domain.builder(httpAddress)
@ -148,7 +148,7 @@ class CleartextTest {
}; };
try { try {
// note: for HTTP/2 only, we can use a single shared transport // note: for HTTP/2 only, we can use a single shared transport
final Transport transport = client.newTransport(); final ClientTransport transport = client.newTransport();
ExecutorService executorService = Executors.newFixedThreadPool(threads); ExecutorService executorService = Executors.newFixedThreadPool(threads);
for (int n = 0; n < threads; n++) { for (int n = 0; n < threads; n++) {
final int t = n; final int t = n;
@ -174,19 +174,20 @@ class CleartextTest {
}); });
} }
executorService.shutdown(); executorService.shutdown();
boolean terminated = executorService.awaitTermination(20L, TimeUnit.SECONDS); boolean terminated = executorService.awaitTermination(30L, TimeUnit.SECONDS);
executorService.shutdownNow(); executorService.shutdownNow();
logger.log(Level.INFO, "terminated = " + terminated + ", now waiting for transport to complete"); logger.log(Level.INFO, "terminated = " + terminated + ", now waiting 30s for transport to complete");
transport.get(20L, TimeUnit.SECONDS); Thread.sleep(2000L);
transport.get(30L, TimeUnit.SECONDS);
logger.log(Level.INFO, "transport complete"); logger.log(Level.INFO, "transport complete");
} finally { } finally {
server.shutdownGracefully(20L, TimeUnit.SECONDS); client.shutdownGracefully();
client.shutdownGracefully(20L, TimeUnit.SECONDS); server.shutdownGracefully();
} }
logger.log(Level.INFO, "server requests = " + server.getRequestCounter() +
" server responses = " + server.getResponseCounter());
logger.log(Level.INFO, "client requests = " + client.getRequestCounter() + logger.log(Level.INFO, "client requests = " + client.getRequestCounter() +
" client responses = " + client.getResponseCounter()); " client responses = " + client.getResponseCounter());
logger.log(Level.INFO, "server requests = " + server.getRequestCounter() +
" server responses = " + server.getResponseCounter());
logger.log(Level.INFO, "expected=" + (threads * loop) + " counter=" + counter.get()); logger.log(Level.INFO, "expected=" + (threads * loop) + " counter=" + counter.get());
assertEquals(threads * loop , counter.get()); assertEquals(threads * loop , counter.get());
} }
@ -236,7 +237,7 @@ class CleartextTest {
}; };
try { try {
// note: for HTTP/2 only, we can use a single shared transport // note: for HTTP/2 only, we can use a single shared transport
final Transport transport = client.newTransport(); final ClientTransport transport = client.newTransport();
ExecutorService executorService = Executors.newFixedThreadPool(threads); ExecutorService executorService = Executors.newFixedThreadPool(threads);
for (int n = 0; n < threads; n++) { for (int n = 0; n < threads; n++) {
final int t = n; final int t = n;
@ -264,12 +265,13 @@ class CleartextTest {
executorService.shutdown(); executorService.shutdown();
boolean terminated = executorService.awaitTermination(10L, TimeUnit.SECONDS); boolean terminated = executorService.awaitTermination(10L, TimeUnit.SECONDS);
logger.log(Level.INFO, "terminated = " + terminated + ", now waiting for transport to complete"); logger.log(Level.INFO, "terminated = " + terminated + ", now waiting for transport to complete");
Thread.sleep(2000L);
transport.get(10L, TimeUnit.SECONDS); transport.get(10L, TimeUnit.SECONDS);
logger.log(Level.INFO, "transport complete"); logger.log(Level.INFO, "transport complete");
} finally { } finally {
server1.shutdownGracefully(10L, TimeUnit.SECONDS); server1.shutdownGracefully();
server2.shutdownGracefully(10L, TimeUnit.SECONDS); server2.shutdownGracefully();
client.shutdownGracefully(10L, TimeUnit.SECONDS); client.shutdownGracefully();
} }
logger.log(Level.INFO, "server1 requests = " + server1.getRequestCounter() + logger.log(Level.INFO, "server1 requests = " + server1.getRequestCounter() +
" server1 responses = " + server1.getResponseCounter()); " server1 responses = " + server1.getResponseCounter());

View file

@ -6,14 +6,13 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.xbib.netty.http.client.Client; import org.xbib.netty.http.client.Client;
import org.xbib.netty.http.client.api.Request; import org.xbib.netty.http.client.api.Request;
import org.xbib.netty.http.client.api.ResponseListener; import org.xbib.netty.http.client.api.ResponseListener;
import org.xbib.netty.http.client.api.Transport; import org.xbib.netty.http.client.api.ClientTransport;
import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.common.HttpResponse; import org.xbib.netty.http.common.HttpResponse;
import org.xbib.netty.http.server.Server; import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.Domain; import org.xbib.netty.http.server.Domain;
import org.xbib.netty.http.server.test.NettyHttpTestExtension; import org.xbib.netty.http.server.test.NettyHttpTestExtension;
import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
@ -52,7 +51,7 @@ class EncryptedTest {
counter.incrementAndGet(); counter.incrementAndGet();
}; };
try { try {
Transport transport = client.newTransport(httpAddress); ClientTransport transport = client.newTransport(httpAddress);
String payload = 0 + "/" + 0; String payload = 0 + "/" + 0;
Request request = Request.get() Request request = Request.get()
.setVersion("HTTP/2.0") .setVersion("HTTP/2.0")
@ -99,7 +98,7 @@ class EncryptedTest {
}; };
try { try {
// single transport, single thread // single transport, single thread
Transport transport = client.newTransport(); ClientTransport transport = client.newTransport();
for (int i = 0; i < loop; i++) { for (int i = 0; i < loop; i++) {
String payload = 0 + "/" + i; String payload = 0 + "/" + i;
Request request = Request.get().setVersion("HTTP/2.0") Request request = Request.get().setVersion("HTTP/2.0")
@ -124,7 +123,7 @@ class EncryptedTest {
@Test @Test
void testMultithreadPooledSecureHttp2() throws Exception { void testMultithreadPooledSecureHttp2() throws Exception {
int threads = 2; int threads = 4;
int loop = 1024; int loop = 1024;
HttpAddress httpAddress = HttpAddress.secureHttp2("localhost", 8143); HttpAddress httpAddress = HttpAddress.secureHttp2("localhost", 8143);
Server server = Server.builder(Domain.builder(httpAddress) Server server = Server.builder(Domain.builder(httpAddress)
@ -147,7 +146,7 @@ class EncryptedTest {
final ResponseListener<HttpResponse> responseListener = resp -> counter.incrementAndGet(); final ResponseListener<HttpResponse> responseListener = resp -> counter.incrementAndGet();
try { try {
// note: for HTTP/2 only, we can use a single shared transport // note: for HTTP/2 only, we can use a single shared transport
final Transport transport = client.newTransport(); final ClientTransport transport = client.newTransport();
ExecutorService executorService = Executors.newFixedThreadPool(threads); ExecutorService executorService = Executors.newFixedThreadPool(threads);
for (int n = 0; n < threads; n++) { for (int n = 0; n < threads; n++) {
final int t = n; final int t = n;
@ -166,8 +165,8 @@ class EncryptedTest {
break; break;
} }
} }
} catch (IOException e) { } catch (Exception e) {
logger.log(Level.WARNING, e.getMessage(), e); logger.log(Level.SEVERE, e.getMessage(), e);
} }
}); });
} }
@ -175,6 +174,7 @@ class EncryptedTest {
boolean terminated = executorService.awaitTermination(20, TimeUnit.SECONDS); boolean terminated = executorService.awaitTermination(20, TimeUnit.SECONDS);
executorService.shutdownNow(); executorService.shutdownNow();
logger.log(Level.INFO, "terminated = " + terminated + ", now waiting for transport to complete"); logger.log(Level.INFO, "terminated = " + terminated + ", now waiting for transport to complete");
Thread.sleep(2000L);
transport.get(20, TimeUnit.SECONDS); transport.get(20, TimeUnit.SECONDS);
} finally { } finally {
client.shutdownGracefully(20, TimeUnit.SECONDS); client.shutdownGracefully(20, TimeUnit.SECONDS);

View file

@ -6,7 +6,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.xbib.netty.http.client.Client; import org.xbib.netty.http.client.Client;
import org.xbib.netty.http.client.api.Request; import org.xbib.netty.http.client.api.Request;
import org.xbib.netty.http.client.api.Transport; import org.xbib.netty.http.client.api.ClientTransport;
import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.server.Domain; import org.xbib.netty.http.server.Domain;
import org.xbib.netty.http.server.Server; import org.xbib.netty.http.server.Server;
@ -76,7 +76,7 @@ class MixedProtocolTest {
.build(); .build();
for (int i = 0; i < max; i++) { for (int i = 0; i < max; i++) {
// HTTP 2 breaks transport // HTTP 2 breaks transport
Transport transport = client.execute(request).get(); ClientTransport transport = client.execute(request).get();
if (transport.isFailed()) { if (transport.isFailed()) {
count.incrementAndGet(); count.incrementAndGet();
} }
@ -100,7 +100,7 @@ class MixedProtocolTest {
.build(); .build();
Server server = Server.builder(domain) Server server = Server.builder(domain)
//.enableDebug() //.enableDebug()
.setTransportLayerSecurityProtocols(new String[]{"TLSv1.2"}) .setTransportLayerSecurityProtocols("TLSv1.2")
.build(); .build();
Client client = Client.builder() Client client = Client.builder()
//.enableDebug() //.enableDebug()
@ -119,7 +119,7 @@ class MixedProtocolTest {
}) })
.build(); .build();
for (int i = 0; i < max; i++) { for (int i = 0; i < max; i++) {
Transport transport = client.execute(request).get(); ClientTransport transport = client.execute(request).get();
if (transport.isFailed()) { if (transport.isFailed()) {
count.incrementAndGet(); count.incrementAndGet();
} }