diff --git a/gradle.properties b/gradle.properties index d3048fa..9435d7a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ group = org.xbib name = netty-http -version = 4.1.50.1 +version = 4.1.50.2 gradle.wrapper.version = 6.4.1 netty.version = 4.1.50.Final diff --git a/gradle/publishing/publication.gradle b/gradle/publishing/publication.gradle index c35fcb9..a0f826e 100644 --- a/gradle/publishing/publication.gradle +++ b/gradle/publishing/publication.gradle @@ -1,3 +1,4 @@ +import java.time.Duration apply plugin: "de.marcphilipp.nexus-publish" @@ -61,4 +62,5 @@ nexusPublishing { packageGroup = "org.xbib" } } + clientTimeout = Duration.ofSeconds(600) } diff --git a/gradle/publishing/sonatype.gradle b/gradle/publishing/sonatype.gradle index e1813f3..e85081d 100644 --- a/gradle/publishing/sonatype.gradle +++ b/gradle/publishing/sonatype.gradle @@ -1,3 +1,4 @@ +import java.time.Duration if (project.hasProperty('ossrhUsername') && project.hasProperty('ossrhPassword')) { diff --git a/netty-http-bouncycastle/build.gradle b/netty-http-bouncycastle/build.gradle index c9797b3..47ecbf5 100644 --- a/netty-http-bouncycastle/build.gradle +++ b/netty-http-bouncycastle/build.gradle @@ -1,4 +1,4 @@ dependencies { - api project(":netty-http-common") + api project(":netty-http-server-api") api "org.bouncycastle:bcpkix-jdk15on:${project.property('bouncycastle.version')}" } diff --git a/netty-http-bouncycastle/src/main/java/module-info.java b/netty-http-bouncycastle/src/main/java/module-info.java new file mode 100644 index 0000000..d509a26 --- /dev/null +++ b/netty-http-bouncycastle/src/main/java/module-info.java @@ -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; +} diff --git a/netty-http-bouncycastle/src/main/java/org/xbib/netty/http/bouncycastle/BouncyCastleSelfSignedCertificateProvider.java b/netty-http-bouncycastle/src/main/java/org/xbib/netty/http/bouncycastle/BouncyCastleSelfSignedCertificateProvider.java index 8fb1726..f6599ab 100644 --- a/netty-http-bouncycastle/src/main/java/org/xbib/netty/http/bouncycastle/BouncyCastleSelfSignedCertificateProvider.java +++ b/netty-http-bouncycastle/src/main/java/org/xbib/netty/http/bouncycastle/BouncyCastleSelfSignedCertificateProvider.java @@ -1,7 +1,7 @@ package org.xbib.netty.http.bouncycastle; 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.InputStream; import java.io.UncheckedIOException; diff --git a/netty-http-bouncycastle/src/main/java/org/xbib/netty/http/bouncycastle/SelfSignedCertificate.java b/netty-http-bouncycastle/src/main/java/org/xbib/netty/http/bouncycastle/SelfSignedCertificate.java index 36ee7fd..7ead4e1 100644 --- a/netty-http-bouncycastle/src/main/java/org/xbib/netty/http/bouncycastle/SelfSignedCertificate.java +++ b/netty-http-bouncycastle/src/main/java/org/xbib/netty/http/bouncycastle/SelfSignedCertificate.java @@ -28,8 +28,6 @@ import java.security.NoSuchProviderException; import java.security.PrivateKey; import java.security.SecureRandom; import java.util.Date; -import java.util.logging.Level; -import java.util.logging.Logger; /** * Generates a temporary self-signed certificate for testing purposes. @@ -133,11 +131,6 @@ public final class SelfSignedCertificate { 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 { byte[] buf = new byte[64]; byte[] base64 = Base64.encode(bytes); diff --git a/netty-http-bouncycastle/src/main/resources/META-INF/services/org.xbib.netty.http.common.ServerCertificateProvider b/netty-http-bouncycastle/src/main/resources/META-INF/services/org.xbib.netty.http.server.api.security.ServerCertificateProvider similarity index 100% rename from netty-http-bouncycastle/src/main/resources/META-INF/services/org.xbib.netty.http.common.ServerCertificateProvider rename to netty-http-bouncycastle/src/main/resources/META-INF/services/org.xbib.netty.http.server.api.security.ServerCertificateProvider diff --git a/netty-http-bouncycastle/src/test/java/org/xbib/netty/http/bouncycastle/SelfSignedCertificateTest.java b/netty-http-bouncycastle/src/test/java/org/xbib/netty/http/bouncycastle/SelfSignedCertificateTest.java index 618e673..21a747a 100644 --- a/netty-http-bouncycastle/src/test/java/org/xbib/netty/http/bouncycastle/SelfSignedCertificateTest.java +++ b/netty-http-bouncycastle/src/test/java/org/xbib/netty/http/bouncycastle/SelfSignedCertificateTest.java @@ -3,17 +3,23 @@ package org.xbib.netty.http.bouncycastle; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.junit.jupiter.api.Test; +import java.io.ByteArrayOutputStream; +import java.nio.charset.StandardCharsets; import java.security.SecureRandom; import java.security.Security; import java.util.logging.Logger; class SelfSignedCertificateTest { + private static final Logger logger = Logger.getLogger("test"); + @Test void testSelfSignedCertificate() throws Exception { Security.addProvider(new BouncyCastleProvider()); SelfSignedCertificate selfSignedCertificate = new SelfSignedCertificate(); 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)); } } diff --git a/netty-http-client-api/src/main/java/module-info.java b/netty-http-client-api/src/main/java/module-info.java index 7a28043..afc9e13 100644 --- a/netty-http-client-api/src/main/java/module-info.java +++ b/netty-http-client-api/src/main/java/module-info.java @@ -1,9 +1,4 @@ module org.xbib.netty.http.client.api { exports org.xbib.netty.http.client.api; 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; } diff --git a/netty-http-client-api/src/main/java/org/xbib/netty/http/client/api/ProtocolProvider.java b/netty-http-client-api/src/main/java/org/xbib/netty/http/client/api/ClientProtocolProvider.java similarity index 50% rename from netty-http-client-api/src/main/java/org/xbib/netty/http/client/api/ProtocolProvider.java rename to netty-http-client-api/src/main/java/org/xbib/netty/http/client/api/ClientProtocolProvider.java index 6ba410d..26ea1fa 100644 --- a/netty-http-client-api/src/main/java/org/xbib/netty/http/client/api/ProtocolProvider.java +++ b/netty-http-client-api/src/main/java/org/xbib/netty/http/client/api/ClientProtocolProvider.java @@ -1,6 +1,8 @@ package org.xbib.netty.http.client.api; -public interface ProtocolProvider { +import org.xbib.netty.http.common.HttpChannelInitializer; + +public interface ClientProtocolProvider { boolean supportsMajorVersion(int majorVersion); diff --git a/netty-http-client-api/src/main/java/org/xbib/netty/http/client/api/Transport.java b/netty-http-client-api/src/main/java/org/xbib/netty/http/client/api/ClientTransport.java similarity index 75% rename from netty-http-client-api/src/main/java/org/xbib/netty/http/client/api/Transport.java rename to netty-http-client-api/src/main/java/org/xbib/netty/http/client/api/ClientTransport.java index 1e33959..c203725 100644 --- a/netty-http-client-api/src/main/java/org/xbib/netty/http/client/api/Transport.java +++ b/netty-http-client-api/src/main/java/org/xbib/netty/http/client/api/ClientTransport.java @@ -7,21 +7,21 @@ import io.netty.handler.codec.http2.Http2Settings; import io.netty.util.AttributeKey; import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.common.HttpResponse; +import org.xbib.netty.http.common.Transport; import org.xbib.netty.http.common.cookie.CookieBox; - import javax.net.ssl.SSLSession; import java.io.IOException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.function.Function; -public interface Transport extends AutoCloseable { +public interface ClientTransport extends Transport { - AttributeKey TRANSPORT_ATTRIBUTE_KEY = AttributeKey.valueOf("transport"); + AttributeKey TRANSPORT_ATTRIBUTE_KEY = AttributeKey.valueOf("transport"); HttpAddress getHttpAddress(); - Transport execute(Request request) throws IOException; + ClientTransport execute(Request request) throws IOException; CompletableFuture execute(Request request, Function supplier) throws IOException; @@ -33,18 +33,20 @@ public interface Transport extends AutoCloseable { void pushPromiseReceived(Channel channel, Integer streamId, Integer promisedStreamId, Http2Headers headers); + void fail(Channel channel, Throwable throwable); + + void inactive(Channel channel); + void setCookieBox(CookieBox cookieBox); CookieBox getCookieBox(); - Transport get(); + ClientTransport get(); - Transport get(long value, TimeUnit timeUnit); + ClientTransport get(long value, TimeUnit timeUnit); void cancel(); - void fail(Throwable throwable); - boolean isFailed(); Throwable getFailure(); diff --git a/netty-http-client-rest/src/main/java/module-info.java b/netty-http-client-rest/src/main/java/module-info.java index 23bd97c..86e941e 100644 --- a/netty-http-client-rest/src/main/java/module-info.java +++ b/netty-http-client-rest/src/main/java/module-info.java @@ -1,7 +1,4 @@ module org.xbib.netty.http.client.rest { exports org.xbib.netty.http.client.rest; requires transitive org.xbib.netty.http.client; - requires org.xbib.net.url; - requires io.netty.buffer; - requires io.netty.codec.http; } diff --git a/netty-http-client/src/main/java/module-info.java b/netty-http-client/src/main/java/module-info.java index 1154c04..e4598fb 100644 --- a/netty-http-client/src/main/java/module-info.java +++ b/netty-http-client/src/main/java/module-info.java @@ -1,4 +1,9 @@ +import org.xbib.netty.http.client.Http1; +import org.xbib.netty.http.client.Http2; + 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.cookie; 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.transport; 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.transport; requires java.logging; - provides org.xbib.netty.http.client.api.ProtocolProvider with - org.xbib.netty.http.client.Http1Provider, - org.xbib.netty.http.client.Http2Provider; + provides org.xbib.netty.http.client.api.ClientProtocolProvider with Http1, Http2; } diff --git a/netty-http-client/src/main/java/org/xbib/netty/http/client/Client.java b/netty-http-client/src/main/java/org/xbib/netty/http/client/Client.java index dd17abe..04c1f80 100644 --- a/netty-http-client/src/main/java/org/xbib/netty/http/client/Client.java +++ b/netty-http-client/src/main/java/org/xbib/netty/http/client/Client.java @@ -22,17 +22,16 @@ import io.netty.handler.ssl.SslHandler; import io.netty.handler.ssl.SslProvider; import io.netty.handler.ssl.util.InsecureTrustManagerFactory; import io.netty.util.concurrent.Future; -import org.xbib.netty.http.client.api.HttpChannelInitializer; -import org.xbib.netty.http.client.api.ProtocolProvider; +import org.xbib.netty.http.client.api.ClientProtocolProvider; 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.api.Transport; 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.NetworkUtils; import org.xbib.netty.http.common.TransportProvider; import org.xbib.netty.http.common.security.SecurityUtil; - import javax.net.ssl.SNIHostName; import javax.net.ssl.SNIServerName; import javax.net.ssl.SSLEngine; @@ -89,9 +88,9 @@ public final class Client implements AutoCloseable { private final Bootstrap bootstrap; - private final Queue transports; + private final Queue transports; - private final List> protocolProviders; + private final List> protocolProviders; private final AtomicBoolean closed; @@ -118,7 +117,7 @@ public final class Client implements AutoCloseable { this.closed = new AtomicBoolean(false); this.clientConfig = clientConfig; this.protocolProviders = new ArrayList<>(); - for (ProtocolProvider provider : ServiceLoader.load(ProtocolProvider.class)) { + for (ClientProtocolProvider provider : ServiceLoader.load(ClientProtocolProvider.class)) { protocolProviders.add(provider); if (logger.isLoggable(Level.FINEST)) { logger.log(Level.FINEST, "protocol provider: " + provider.transportClass()); @@ -199,7 +198,7 @@ public final class Client implements AutoCloseable { return new Builder(); } - public List> getProtocolProviders() { + public List> getProtocolProviders() { return protocolProviders; } @@ -223,14 +222,14 @@ public final class Client implements AutoCloseable { return responseCounter; } - public Transport newTransport() { + public ClientTransport newTransport() { return newTransport(null); } - public Transport newTransport(HttpAddress httpAddress) { - Transport transport = null; + public ClientTransport newTransport(HttpAddress httpAddress) { + ClientTransport transport = null; if (httpAddress != null) { - for (ProtocolProvider protocolProvider : protocolProviders) { + for (ClientProtocolProvider protocolProvider : protocolProviders) { if (protocolProvider.supportsMajorVersion(httpAddress.getVersion().majorVersion())) { try { transport = protocolProvider.transportClass() @@ -245,7 +244,7 @@ public final class Client implements AutoCloseable { throw new UnsupportedOperationException("no protocol support for " + httpAddress); } } else if (hasPooledConnections()) { - for (ProtocolProvider protocolProvider : protocolProviders) { + for (ClientProtocolProvider protocolProvider : protocolProviders) { if (protocolProvider.supportsMajorVersion(pool.getVersion().majorVersion())) { try { 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())) .execute(request); } @@ -337,8 +336,8 @@ public final class Client implements AutoCloseable { * @param request the new request for continuing the request. * @throws IOException if continuation fails */ - public void continuation(Transport transport, Request request) throws IOException { - Transport nextTransport = newTransport(HttpAddress.of(request.url(), request.httpVersion())); + public void continuation(ClientTransport transport, Request request) throws IOException { + ClientTransport nextTransport = newTransport(HttpAddress.of(request.url(), request.httpVersion())); nextTransport.setCookieBox(transport.getCookieBox()); nextTransport.execute(request); nextTransport.get(); @@ -352,7 +351,7 @@ public final class Client implements AutoCloseable { * @param request the request to retry * @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.get(); closeAndRemove(transport); @@ -370,7 +369,7 @@ public final class Client implements AutoCloseable { public void shutdownGracefully(long amount, TimeUnit timeUnit) throws IOException { if (closed.compareAndSet(false, true)) { try { - for (Transport transport : transports) { + for (ClientTransport transport : transports) { transport.close(); } 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 { transport.close(); } catch (Exception e) { @@ -400,7 +399,7 @@ public final class Client implements AutoCloseable { HttpAddress httpAddress, SslHandlerFactory sslHandlerFactory, HttpChannelInitializer helper) { - for (ProtocolProvider protocolProvider : protocolProviders) { + for (ClientProtocolProvider protocolProvider : protocolProviders) { if (protocolProvider.supportsMajorVersion(majorVersion)) { try { return protocolProvider.initializerClass() diff --git a/netty-http-client/src/main/java/org/xbib/netty/http/client/Http1Provider.java b/netty-http-client/src/main/java/org/xbib/netty/http/client/Http1.java similarity index 76% rename from netty-http-client/src/main/java/org/xbib/netty/http/client/Http1Provider.java rename to netty-http-client/src/main/java/org/xbib/netty/http/client/Http1.java index f0ce2b4..91ce1c9 100644 --- a/netty-http-client/src/main/java/org/xbib/netty/http/client/Http1Provider.java +++ b/netty-http-client/src/main/java/org/xbib/netty/http/client/Http1.java @@ -1,10 +1,10 @@ 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.transport.Http1Transport; -public class Http1Provider implements ProtocolProvider { +public class Http1 implements ClientProtocolProvider { @Override public boolean supportsMajorVersion(int majorVersion) { diff --git a/netty-http-client/src/main/java/org/xbib/netty/http/client/Http2Provider.java b/netty-http-client/src/main/java/org/xbib/netty/http/client/Http2.java similarity index 76% rename from netty-http-client/src/main/java/org/xbib/netty/http/client/Http2Provider.java rename to netty-http-client/src/main/java/org/xbib/netty/http/client/Http2.java index 1e1f744..834a3bb 100644 --- a/netty-http-client/src/main/java/org/xbib/netty/http/client/Http2Provider.java +++ b/netty-http-client/src/main/java/org/xbib/netty/http/client/Http2.java @@ -1,10 +1,10 @@ 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.transport.Http2Transport; -public class Http2Provider implements ProtocolProvider { +public class Http2 implements ClientProtocolProvider { @Override public boolean supportsMajorVersion(int majorVersion) { diff --git a/netty-http-client/src/main/java/org/xbib/netty/http/client/handler/http/Http1ChannelInitializer.java b/netty-http-client/src/main/java/org/xbib/netty/http/client/handler/http/Http1ChannelInitializer.java index 6ad6032..1ce4946 100644 --- a/netty-http-client/src/main/java/org/xbib/netty/http/client/handler/http/Http1ChannelInitializer.java +++ b/netty-http-client/src/main/java/org/xbib/netty/http/client/handler/http/Http1ChannelInitializer.java @@ -14,10 +14,9 @@ import io.netty.handler.ssl.SslHandler; import io.netty.handler.stream.ChunkedWriteHandler; import org.xbib.netty.http.client.Client; 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.common.HttpAddress; - import java.util.logging.Level; import java.util.logging.Logger; diff --git a/netty-http-client/src/main/java/org/xbib/netty/http/client/handler/http/HttpResponseHandler.java b/netty-http-client/src/main/java/org/xbib/netty/http/client/handler/http/HttpResponseHandler.java index f533d2c..467228e 100644 --- a/netty-http-client/src/main/java/org/xbib/netty/http/client/handler/http/HttpResponseHandler.java +++ b/netty-http-client/src/main/java/org/xbib/netty/http/client/handler/http/HttpResponseHandler.java @@ -4,22 +4,25 @@ import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; 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 public class HttpResponseHandler extends SimpleChannelInboundHandler { @Override public void channelRead0(ChannelHandlerContext ctx, FullHttpResponse fullHttpResponse) throws Exception { - Transport transport = ctx.channel().attr(Transport.TRANSPORT_ATTRIBUTE_KEY).get(); - transport.responseReceived(ctx.channel(), null, fullHttpResponse); - // do not close ctx here + ClientTransport transport = ctx.channel().attr(ClientTransport.TRANSPORT_ATTRIBUTE_KEY).get(); + if (transport != null) { + transport.responseReceived(ctx.channel(), null, fullHttpResponse); + } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { - Transport transport = ctx.channel().attr(Transport.TRANSPORT_ATTRIBUTE_KEY).get(); - transport.fail(cause); - // do not close ctx here + ctx.fireExceptionCaught(cause); + ClientTransport transport = ctx.channel().attr(ClientTransport.TRANSPORT_ATTRIBUTE_KEY).get(); + if (transport != null) { + transport.fail(ctx.channel(), cause); + } } } diff --git a/netty-http-client/src/main/java/org/xbib/netty/http/client/handler/http2/Http2ChannelInitializer.java b/netty-http-client/src/main/java/org/xbib/netty/http/client/handler/http2/Http2ChannelInitializer.java index b9ed74f..d055679 100644 --- a/netty-http-client/src/main/java/org/xbib/netty/http/client/handler/http2/Http2ChannelInitializer.java +++ b/netty-http-client/src/main/java/org/xbib/netty/http/client/handler/http2/Http2ChannelInitializer.java @@ -14,11 +14,10 @@ import io.netty.handler.codec.http2.Http2MultiplexCodecBuilder; import io.netty.handler.logging.LogLevel; import org.xbib.netty.http.client.Client; 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.api.Transport; +import org.xbib.netty.http.client.api.ClientTransport; import org.xbib.netty.http.common.HttpAddress; - +import org.xbib.netty.http.common.HttpChannelInitializer; import java.util.logging.Level; import java.util.logging.Logger; @@ -86,7 +85,7 @@ public class Http2ChannelInitializer extends ChannelInitializer impleme public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof DefaultHttp2SettingsFrame) { 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) { transport.settingsReceived(settingsFrame.settings()); } @@ -100,7 +99,7 @@ public class Http2ChannelInitializer extends ChannelInitializer impleme if (evt instanceof Http2ConnectionPrefaceAndSettingsFrameWrittenEvent) { Http2ConnectionPrefaceAndSettingsFrameWrittenEvent event = (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) { transport.settingsReceived(null); } @@ -110,9 +109,9 @@ public class Http2ChannelInitializer extends ChannelInitializer impleme @Override 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) { - transport.fail(cause); + transport.fail(ctx.channel(), cause); } } } @@ -126,7 +125,7 @@ public class Http2ChannelInitializer extends ChannelInitializer impleme public void logPushPromise(Direction direction, ChannelHandlerContext ctx, int streamId, int promisedStreamId, Http2Headers headers, int 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) { transport.pushPromiseReceived(ctx.channel(), streamId, promisedStreamId, headers); } diff --git a/netty-http-client/src/main/java/org/xbib/netty/http/client/handler/http2/Http2ResponseHandler.java b/netty-http-client/src/main/java/org/xbib/netty/http/client/handler/http2/Http2ResponseHandler.java index 23dfa1f..19f72cd 100644 --- a/netty-http-client/src/main/java/org/xbib/netty/http/client/handler/http2/Http2ResponseHandler.java +++ b/netty-http-client/src/main/java/org/xbib/netty/http/client/handler/http2/Http2ResponseHandler.java @@ -5,23 +5,37 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.FullHttpResponse; 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 public class Http2ResponseHandler extends SimpleChannelInboundHandler { @Override 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()); - 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 } + @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 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { - Transport transport = ctx.channel().attr(Transport.TRANSPORT_ATTRIBUTE_KEY).get(); - transport.fail(cause); + ctx.fireExceptionCaught(cause); + ClientTransport transport = ctx.channel().attr(ClientTransport.TRANSPORT_ATTRIBUTE_KEY).get(); + if (transport != null) { + transport.fail(ctx.channel(), cause); + } // do not close ctx here } } diff --git a/netty-http-client/src/main/java/org/xbib/netty/http/client/transport/BaseTransport.java b/netty-http-client/src/main/java/org/xbib/netty/http/client/transport/BaseTransport.java index 1337b5f..c98fa37 100644 --- a/netty-http-client/src/main/java/org/xbib/netty/http/client/transport/BaseTransport.java +++ b/netty-http-client/src/main/java/org/xbib/netty/http/client/transport/BaseTransport.java @@ -10,14 +10,13 @@ import org.xbib.net.PercentDecoder; import org.xbib.net.URL; import org.xbib.net.URLSyntaxException; 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.client.api.Request; import org.xbib.netty.http.client.api.BackOff; import org.xbib.netty.http.common.HttpResponse; import org.xbib.netty.http.common.cookie.Cookie; import org.xbib.netty.http.common.cookie.CookieBox; - import javax.net.ssl.SSLSession; import java.io.IOException; import java.net.ConnectException; @@ -38,7 +37,7 @@ import java.util.logging.Level; import java.util.logging.Logger; 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()); @@ -54,15 +53,15 @@ public abstract class BaseTransport implements Transport { private SSLSession sslSession; - final Map flowMap; + public final Map flowMap; - final SortedMap requests; + protected final SortedMap requests; private CookieBox cookieBox; protected HttpDataFactory httpDataFactory; - BaseTransport(Client client, HttpAddress httpAddress) { + public BaseTransport(Client client, HttpAddress httpAddress) { this.client = client; this.httpAddress = httpAddress; this.channels = new ConcurrentHashMap<>(); @@ -103,9 +102,7 @@ public abstract class BaseTransport implements Transport { @Override public void close() { // channels are present, maybe forgot a get() to receive responses? - if (!channels.isEmpty()) { - get(); - } + get(); 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. * @param throwable the exception */ @Override - public void fail(Throwable throwable) { + public void fail(Channel channel, Throwable throwable) { // do not fail more than once if (this.throwable != null) { return; } - logger.log(Level.SEVERE, "failing: " + throwable.getMessage(), throwable); this.throwable = throwable; + logger.log(Level.SEVERE, "channel " + channel + " failing: " + throwable.getMessage(), throwable); for (Flow flow : flowMap.values()) { flow.fail(throwable); } } @Override - public Transport get() { + public void inactive(Channel channel) { + // do nothing + } + + @Override + public ClientTransport get() { return get(client.getClientConfig().getReadTimeoutMillis(), TimeUnit.MILLISECONDS); } @Override - public Transport get(long value, TimeUnit timeUnit) { - if (channels.isEmpty()) { - return this; - } - for (Map.Entry entry : flowMap.entrySet()) { - Flow flow = entry.getValue(); - if (!flow.isClosed()) { - for (Integer key : flow.keys()) { - String requestKey = getRequestKey(entry.getKey(), key); - try { - flow.get(key).get(value, timeUnit); - completeRequest(requestKey); - } catch (Exception e) { - completeRequestExceptionally(requestKey, e); - flow.fail(e); - } finally { - flow.remove(key); + public ClientTransport get(long value, TimeUnit timeUnit) { + if (!flowMap.isEmpty()) { + for (Map.Entry entry : flowMap.entrySet()) { + Flow flow = entry.getValue(); + if (!flow.isClosed()) { + for (Integer key : flow.keys()) { + String requestKey = getRequestKey(entry.getKey(), key); + try { + flow.get(key).get(value, timeUnit); + completeRequest(requestKey); + } catch (Exception e) { + completeRequestExceptionally(requestKey, e); + flow.fail(e); + } finally { + flow.remove(key); + } } + flow.close(); } - flow.close(); } + flowMap.clear(); } channels.values().forEach(channel -> { try { @@ -177,26 +179,21 @@ public abstract class BaseTransport implements Transport { @Override public void cancel() { - if (channels.isEmpty()) { - return; - } - for (Map.Entry entry : flowMap.entrySet()) { - Flow flow = entry.getValue(); - for (Integer key : flow.keys()) { - try { - flow.get(key).cancel(true); - } catch (Exception e) { - String requestKey = getRequestKey(entry.getKey(), key); - Request request = requests.get(requestKey); - if (request != null && request.getCompletableFuture() != null) { - request.getCompletableFuture().completeExceptionally(e); + if (!flowMap.isEmpty()) { + for (Map.Entry entry : flowMap.entrySet()) { + Flow flow = entry.getValue(); + for (Integer key : flow.keys()) { + try { + flow.get(key).cancel(true); + } catch (Exception e) { + completeRequestExceptionally(getRequestKey(entry.getKey(), key), e); + flow.fail(e); + } finally { + flow.remove(key); } - flow.fail(e); - } finally { - flow.remove(key); } + flow.close(); } - flow.close(); } channels.values().forEach(channel -> { try { diff --git a/netty-http-client/src/main/java/org/xbib/netty/http/client/transport/Flow.java b/netty-http-client/src/main/java/org/xbib/netty/http/client/transport/Flow.java index c8e88e4..5ad1a30 100644 --- a/netty-http-client/src/main/java/org/xbib/netty/http/client/transport/Flow.java +++ b/netty-http-client/src/main/java/org/xbib/netty/http/client/transport/Flow.java @@ -6,13 +6,13 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.atomic.AtomicInteger; -class Flow { +public class Flow { private final AtomicInteger counter; private final SortedMap> map; - Flow() { + public Flow() { this.counter = new AtomicInteger(3); this.map = new ConcurrentSkipListMap<>(); } diff --git a/netty-http-client/src/main/java/org/xbib/netty/http/client/transport/Http1Transport.java b/netty-http-client/src/main/java/org/xbib/netty/http/client/transport/Http1Transport.java index 00ee2a9..9e9638c 100644 --- a/netty-http-client/src/main/java/org/xbib/netty/http/client/transport/Http1Transport.java +++ b/netty-http-client/src/main/java/org/xbib/netty/http/client/transport/Http1Transport.java @@ -1,7 +1,6 @@ package org.xbib.netty.http.client.transport; import io.netty.channel.Channel; -import io.netty.channel.ChannelFuture; import io.netty.handler.codec.http.DefaultFullHttpRequest; import io.netty.handler.codec.http.FullHttpRequest; 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 org.xbib.net.URLSyntaxException; 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.ClientCookieEncoder; import org.xbib.netty.http.common.DefaultHttpResponse; import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.client.api.Request; import org.xbib.netty.http.common.cookie.Cookie; - import java.io.IOException; import java.util.ArrayList; import java.util.Collection; +import java.util.NoSuchElementException; import java.util.concurrent.CompletableFuture; import java.util.logging.Level; import java.util.logging.Logger; @@ -36,7 +35,7 @@ public class Http1Transport extends BaseTransport { } @Override - public Transport execute(Request request) throws IOException { + public ClientTransport execute(Request request) throws IOException { Channel channel = mapChannel(request); if (throwable != null) { return this; @@ -175,6 +174,11 @@ public class Http1Transport extends BaseTransport { @Override 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; + } } } diff --git a/netty-http-client/src/main/java/org/xbib/netty/http/client/transport/Http2Transport.java b/netty-http-client/src/main/java/org/xbib/netty/http/client/transport/Http2Transport.java index 06b8ff1..4176351 100644 --- a/netty-http-client/src/main/java/org/xbib/netty/http/client/transport/Http2Transport.java +++ b/netty-http-client/src/main/java/org/xbib/netty/http/client/transport/Http2Transport.java @@ -19,7 +19,7 @@ import io.netty.handler.codec.http2.HttpConversionUtil; import io.netty.util.AsciiString; import org.xbib.net.URLSyntaxException; import org.xbib.netty.http.client.Client; -import org.xbib.netty.http.client.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.ClientCookieEncoder; import org.xbib.netty.http.client.handler.http2.Http2ResponseHandler; @@ -49,7 +49,7 @@ public class Http2Transport extends BaseTransport { public Http2Transport(Client client, HttpAddress httpAddress) { super(client, httpAddress); this.settingsPromise = httpAddress != null ? new CompletableFuture<>() : null; - final Transport transport = this; + final ClientTransport transport = this; this.initializer = new ChannelInitializer<>() { @Override protected void initChannel(Channel ch) { @@ -68,7 +68,7 @@ public class Http2Transport extends BaseTransport { } @Override - public Transport execute(Request request) throws IOException { + public ClientTransport execute(Request request) throws IOException { Channel channel = mapChannel(request); if (throwable != null) { return this; diff --git a/netty-http-client/src/main/resources/META-INF/services/org.xbib.netty.http.client.api.ClientProtocolProvider b/netty-http-client/src/main/resources/META-INF/services/org.xbib.netty.http.client.api.ClientProtocolProvider new file mode 100644 index 0000000..c9ffe9f --- /dev/null +++ b/netty-http-client/src/main/resources/META-INF/services/org.xbib.netty.http.client.api.ClientProtocolProvider @@ -0,0 +1,2 @@ +org.xbib.netty.http.client.Http1 +org.xbib.netty.http.client.Http2 \ No newline at end of file diff --git a/netty-http-client/src/main/resources/META-INF/services/org.xbib.netty.http.client.api.ProtocolProvider b/netty-http-client/src/main/resources/META-INF/services/org.xbib.netty.http.client.api.ProtocolProvider deleted file mode 100644 index 2e1190b..0000000 --- a/netty-http-client/src/main/resources/META-INF/services/org.xbib.netty.http.client.api.ProtocolProvider +++ /dev/null @@ -1,2 +0,0 @@ -org.xbib.netty.http.client.Http1Provider -org.xbib.netty.http.client.Http2Provider \ No newline at end of file diff --git a/netty-http-common/src/main/java/module-info.java b/netty-http-common/src/main/java/module-info.java index 94399df..d380c85 100644 --- a/netty-http-common/src/main/java/module-info.java +++ b/netty-http-common/src/main/java/module-info.java @@ -1,15 +1,16 @@ module org.xbib.netty.http.common { exports org.xbib.netty.http.common; 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.security; exports org.xbib.netty.http.common.util; requires transitive org.xbib.net.url; - requires io.netty.buffer; - requires io.netty.common; - requires io.netty.transport; - requires io.netty.handler; - requires io.netty.codec.http; + requires transitive io.netty.buffer; + requires transitive io.netty.common; + requires transitive io.netty.transport; + requires transitive io.netty.handler; + requires transitive io.netty.codec; + requires transitive io.netty.codec.http; + requires transitive io.netty.codec.http2; requires java.logging; } diff --git a/netty-http-client-api/src/main/java/org/xbib/netty/http/client/api/HttpChannelInitializer.java b/netty-http-common/src/main/java/org/xbib/netty/http/common/HttpChannelInitializer.java similarity index 81% rename from netty-http-client-api/src/main/java/org/xbib/netty/http/client/api/HttpChannelInitializer.java rename to netty-http-common/src/main/java/org/xbib/netty/http/common/HttpChannelInitializer.java index 83f5057..778d8b4 100644 --- a/netty-http-client-api/src/main/java/org/xbib/netty/http/client/api/HttpChannelInitializer.java +++ b/netty-http-common/src/main/java/org/xbib/netty/http/common/HttpChannelInitializer.java @@ -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.ChannelHandler; @@ -6,5 +6,4 @@ import io.netty.channel.ChannelHandler; public interface HttpChannelInitializer extends ChannelHandler { void initChannel(Channel channel); - } diff --git a/netty-http-common/src/main/java/org/xbib/netty/http/common/Transport.java b/netty-http-common/src/main/java/org/xbib/netty/http/common/Transport.java new file mode 100644 index 0000000..0bda644 --- /dev/null +++ b/netty-http-common/src/main/java/org/xbib/netty/http/common/Transport.java @@ -0,0 +1,4 @@ +package org.xbib.netty.http.common; + +public interface Transport { +} diff --git a/netty-http-common/src/main/java/org/xbib/netty/http/common/mime/MalvaMimeMultipartParser.java b/netty-http-common/src/main/java/org/xbib/netty/http/common/mime/MalvaMimeMultipartParser.java deleted file mode 100644 index 03fbf36..0000000 --- a/netty-http-common/src/main/java/org/xbib/netty/http/common/mime/MalvaMimeMultipartParser.java +++ /dev/null @@ -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 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 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 m = new LinkedHashMap<>(); - for (Map.Entry 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 parseHeaderLine(String line) { - Map 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; - } -} - - diff --git a/netty-http-common/src/main/java/org/xbib/netty/http/common/mime/MimeMultipartParser.java b/netty-http-common/src/main/java/org/xbib/netty/http/common/mime/MimeMultipartParser.java index 027ec60..f68fc6a 100644 --- a/netty-http-common/src/main/java/org/xbib/netty/http/common/mime/MimeMultipartParser.java +++ b/netty-http-common/src/main/java/org/xbib/netty/http/common/mime/MimeMultipartParser.java @@ -1,12 +1,223 @@ 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; -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 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 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 m = new LinkedHashMap<>(); + for (Map.Entry 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 parseHeaderLine(String line) { + Map 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; + } } diff --git a/netty-http-common/src/main/java/org/xbib/netty/http/common/net/NetworkClass.java b/netty-http-common/src/main/java/org/xbib/netty/http/common/net/NetworkClass.java deleted file mode 100644 index f8e3786..0000000 --- a/netty-http-common/src/main/java/org/xbib/netty/http/common/net/NetworkClass.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.xbib.netty.http.common.net; - -/** - * The network classes. - */ -public enum NetworkClass { - - ANY, LOOPBACK, LOCAL, PUBLIC -} diff --git a/netty-http-common/src/main/java/org/xbib/netty/http/common/net/NetworkProtocolVersion.java b/netty-http-common/src/main/java/org/xbib/netty/http/common/net/NetworkProtocolVersion.java deleted file mode 100644 index b3cdc25..0000000 --- a/netty-http-common/src/main/java/org/xbib/netty/http/common/net/NetworkProtocolVersion.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.xbib.netty.http.common.net; - -/** - * The TCP/IP network protocol versions. - */ -public enum NetworkProtocolVersion { - - IPV4, IPV6, IPV46, NONE -} diff --git a/netty-http-common/src/main/java/org/xbib/netty/http/common/security/SecurityUtil.java b/netty-http-common/src/main/java/org/xbib/netty/http/common/security/SecurityUtil.java index c569e05..02fc4dd 100644 --- a/netty-http-common/src/main/java/org/xbib/netty/http/common/security/SecurityUtil.java +++ b/netty-http-common/src/main/java/org/xbib/netty/http/common/security/SecurityUtil.java @@ -51,5 +51,4 @@ public class SecurityUtil { CipherSuiteFilter DEFAULT_CIPHER_SUITE_FILTER = SupportedCipherSuiteFilter.INSTANCE; } - } diff --git a/netty-http-server-api/src/main/java/module-info.java b/netty-http-server-api/src/main/java/module-info.java index c349050..7acd0d6 100644 --- a/netty-http-server-api/src/main/java/module-info.java +++ b/netty-http-server-api/src/main/java/module-info.java @@ -1,12 +1,6 @@ module 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.security; 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; } diff --git a/netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/HttpChannelInitializer.java b/netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/HttpChannelInitializer.java deleted file mode 100644 index d4d78ea..0000000 --- a/netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/HttpChannelInitializer.java +++ /dev/null @@ -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); - -} diff --git a/netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/ProtocolProvider.java b/netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/ServerProtocolProvider.java similarity index 50% rename from netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/ProtocolProvider.java rename to netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/ServerProtocolProvider.java index fa08b64..aba3ddd 100644 --- a/netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/ProtocolProvider.java +++ b/netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/ServerProtocolProvider.java @@ -1,6 +1,8 @@ package org.xbib.netty.http.server.api; -public interface ProtocolProvider { +import org.xbib.netty.http.common.HttpChannelInitializer; + +public interface ServerProtocolProvider { boolean supportsMajorVersion(int majorVersion); diff --git a/netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/Transport.java b/netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/ServerTransport.java similarity index 75% rename from netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/Transport.java rename to netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/ServerTransport.java index e47cb52..574d045 100644 --- a/netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/Transport.java +++ b/netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/ServerTransport.java @@ -4,12 +4,12 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http2.Http2Settings; import io.netty.util.AttributeKey; - +import org.xbib.netty.http.common.Transport; import java.io.IOException; -public interface Transport { +public interface ServerTransport extends Transport { - AttributeKey TRANSPORT_ATTRIBUTE_KEY = AttributeKey.valueOf("transport"); + AttributeKey TRANSPORT_ATTRIBUTE_KEY = AttributeKey.valueOf("transport"); void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest, Integer sequenceId) throws IOException; diff --git a/netty-http-common/src/main/java/org/xbib/netty/http/common/ServerCertificateProvider.java b/netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/security/ServerCertificateProvider.java similarity index 93% rename from netty-http-common/src/main/java/org/xbib/netty/http/common/ServerCertificateProvider.java rename to netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/security/ServerCertificateProvider.java index 19071da..9515da3 100644 --- a/netty-http-common/src/main/java/org/xbib/netty/http/common/ServerCertificateProvider.java +++ b/netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/security/ServerCertificateProvider.java @@ -1,4 +1,4 @@ -package org.xbib.netty.http.common; +package org.xbib.netty.http.server.api.security; import java.io.InputStream; @@ -28,5 +28,4 @@ public interface ServerCertificateProvider { * @return key password */ String getKeyPassword(); - } diff --git a/netty-http-server-reactive/src/main/java/org/xbib/netty/http/server/reactive/HandlerPublisher.java b/netty-http-server-reactive/src/main/java/org/xbib/netty/http/server/reactive/HandlerPublisher.java index 03a3ec5..b6f6526 100644 --- a/netty-http-server-reactive/src/main/java/org/xbib/netty/http/server/reactive/HandlerPublisher.java +++ b/netty-http-server-reactive/src/main/java/org/xbib/netty/http/server/reactive/HandlerPublisher.java @@ -320,6 +320,7 @@ public class HandlerPublisher extends ChannelDuplexHandler implements Publish case DEMANDING: case IDLE: cancelled(); + // fall through case DRAINING: state = DONE; break; diff --git a/netty-http-server-rest/src/main/java/module-info.java b/netty-http-server-rest/src/main/java/module-info.java index c9b1810..94e44e4 100644 --- a/netty-http-server-rest/src/main/java/module-info.java +++ b/netty-http-server-rest/src/main/java/module-info.java @@ -1,6 +1,5 @@ module org.xbib.netty.http.server.rest { exports org.xbib.netty.http.server.rest; exports org.xbib.netty.http.server.rest.util; - requires org.xbib.netty.http.server; - requires io.netty.transport; + requires transitive org.xbib.netty.http.server; } diff --git a/netty-http-server/src/main/java/module-info.java b/netty-http-server/src/main/java/module-info.java index b26d8f3..80ab1f1 100644 --- a/netty-http-server/src/main/java/module-info.java +++ b/netty-http-server/src/main/java/module-info.java @@ -1,4 +1,10 @@ +import org.xbib.netty.http.server.Http1; +import org.xbib.netty.http.server.Http2; + 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.cookie; 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.util; 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; - provides org.xbib.netty.http.server.api.ProtocolProvider with - org.xbib.netty.http.server.Http1Provider, - org.xbib.netty.http.server.Http2Provider; + provides org.xbib.netty.http.server.api.ServerProtocolProvider with Http1, Http2; } diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/Domain.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/Domain.java index 1a8258f..2b1ae0b 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/Domain.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/Domain.java @@ -8,22 +8,31 @@ import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.SslProvider; import org.xbib.netty.http.common.HttpAddress; -import org.xbib.netty.http.common.ServerCertificateProvider; +import org.xbib.netty.http.server.api.security.ServerCertificateProvider; import org.xbib.netty.http.common.security.SecurityUtil; import org.xbib.netty.http.server.api.ServerRequest; import org.xbib.netty.http.server.api.ServerResponse; import org.xbib.netty.http.server.endpoint.HttpEndpoint; import org.xbib.netty.http.server.endpoint.HttpEndpointResolver; 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 java.io.IOException; import java.io.InputStream; +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyException; import java.security.KeyStore; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; 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.Arrays; -import java.util.Collections; +import java.util.Collection; import java.util.LinkedHashSet; import java.util.List; import java.util.Objects; @@ -49,6 +58,8 @@ public class Domain { private final List httpEndpointResolvers; + private final Collection certificates; + /** * Constructs a {@code NamedServer} with the given name. * @@ -58,29 +69,41 @@ public class Domain { * @param httpEndpointResolvers the endpoint resolvers * @param sslContext SSL context or null */ - protected Domain(String name, Set aliases, + private Domain(String name, + Set aliases, HttpAddress httpAddress, List httpEndpointResolvers, - SslContext sslContext) { + SslContext sslContext, + Collection certificates) { this.httpAddress = httpAddress; this.name = name; - this.sslContext = sslContext; - this.aliases = Collections.unmodifiableSet(aliases); + this.aliases = aliases; this.httpEndpointResolvers = httpEndpointResolvers; - } - - public static Builder builder() { - return builder(HttpAddress.http1("localhost", 8008)); + this.sslContext = sslContext; + this.certificates = certificates; + Objects.requireNonNull(httpEndpointResolvers); + if (httpEndpointResolvers.isEmpty()) { + throw new IllegalArgumentException("domain must have at least one endpoint resolver"); + } } public static Builder builder(HttpAddress httpAddress) { - return builder(httpAddress, "*"); + return builder(httpAddress, httpAddress.getInetSocketAddress().getHostString()); } 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() { return httpAddress; } @@ -94,10 +117,6 @@ public class Domain { return name; } - public SslContext getSslContext() { - return sslContext; - } - /** * Returns the aliases. * @@ -107,6 +126,22 @@ public class Domain { 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 getCertificateChain() { + return certificates; + } + /** * Handle server requests. * @param serverRequest the server request @@ -114,38 +149,38 @@ public class Domain { * @throws IOException if handling server request fails */ public void handle(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException { - if (httpEndpointResolvers != null) { - boolean found = false; - for (HttpEndpointResolver httpEndpointResolver : httpEndpointResolvers) { - List matchingEndpoints = httpEndpointResolver.matchingEndpointsFor(serverRequest); - if (matchingEndpoints != null && !matchingEndpoints.isEmpty()) { - httpEndpointResolver.handle(matchingEndpoints, serverRequest, serverResponse); - found = true; - break; - } + boolean found = false; + for (HttpEndpointResolver httpEndpointResolver : httpEndpointResolvers) { + List matchingEndpoints = httpEndpointResolver.matchingEndpointsFor(serverRequest); + if (matchingEndpoints != null && !matchingEndpoints.isEmpty()) { + httpEndpointResolver.handle(matchingEndpoints, serverRequest, serverResponse); + found = true; + break; } - if (!found) { - ServerResponse.write(serverResponse, HttpResponseStatus.NOT_IMPLEMENTED); - } - } else { - ServerResponse.write(serverResponse, HttpResponseStatus.NOT_IMPLEMENTED); + } + if (!found) { + ServerResponse.write(serverResponse, HttpResponseStatus.NOT_IMPLEMENTED, + "text/plain", "No endpoint match for request " + serverRequest + + " endpoints = " + httpEndpointResolvers); } } @Override public String toString() { - return name + " (" + httpAddress + ") " + aliases; + return name + " (" + httpAddress + ") aliases=" + aliases; } public static class Builder { - private HttpAddress httpAddress; + private final HttpAddress httpAddress; private String serverName; - private Set aliases; + private final Set aliases; - private List httpEndpointResolvers; + private final List httpEndpointResolvers; + + private SslContext sslContext; private TrustManagerFactory trustManagerFactory; @@ -159,17 +194,13 @@ public class Domain { private CipherSuiteFilter cipherSuiteFilter; - private InputStream keyCertChainInputStream; + private Collection keyCertChain; - private InputStream keyInputStream; + private PrivateKey privateKey; - private String keyPassword; - - Builder(HttpAddress httpAddress, String serverName) { + private Builder(HttpAddress httpAddress) { Objects.requireNonNull(httpAddress); - Objects.requireNonNull(serverName); this.httpAddress = httpAddress; - this.serverName = serverName; this.aliases = new LinkedHashSet<>(); this.httpEndpointResolvers = new ArrayList<>(); this.trustManagerFactory = SecurityUtil.Defaults.DEFAULT_TRUST_MANAGER_FACTORY; @@ -178,6 +209,26 @@ public class Domain { 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) { Objects.requireNonNull(trustManagerFactory); this.trustManagerFactory = trustManagerFactory; @@ -226,56 +277,36 @@ public class Domain { return this; } - public Builder setKeyCertChainInputStream(InputStream keyCertChainInputStream) { + public Builder setKeyCertChain(InputStream keyCertChainInputStream) + throws CertificateException { Objects.requireNonNull(keyCertChainInputStream); - this.keyCertChainInputStream = keyCertChainInputStream; + this.keyCertChain = CertificateUtils.toCertificate(keyCertChainInputStream); return this; } - public Builder setKeyInputStream(InputStream keyInputStream) { + public Builder setKey(InputStream keyInputStream, String keyPassword) + throws NoSuchPaddingException, NoSuchAlgorithmException, IOException, + KeyException, InvalidAlgorithmParameterException, InvalidKeySpecException { Objects.requireNonNull(keyInputStream); - this.keyInputStream = keyInputStream; + this.privateKey = PrivateKeyUtils.toPrivateKey(keyInputStream, keyPassword); return this; } - public Builder setKeyPassword(String keyPassword) { - // null in keyPassword allowed, it means no password - this.keyPassword = keyPassword; - return this; - } - - 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 serverCertificateProviders = ServiceLoader.load(ServerCertificateProvider.class); + public Builder setSelfCert() throws CertificateException, NoSuchPaddingException, + NoSuchAlgorithmException, IOException, KeyException, InvalidAlgorithmParameterException, + InvalidKeySpecException { + ServiceLoader serverCertificateProviders = + ServiceLoader.load(ServerCertificateProvider.class); for (ServerCertificateProvider serverCertificateProvider : serverCertificateProviders) { if ("org.xbib.netty.http.bouncycastle.BouncyCastleSelfSignedCertificateProvider".equals(serverCertificateProvider.getClass().getName())) { serverCertificateProvider.prepare(serverName); - setKeyCertChainInputStream(serverCertificateProvider.getCertificateChain()); - setKeyInputStream(serverCertificateProvider.getPrivateKey()); - setKeyPassword(serverCertificateProvider.getKeyPassword()); + setKeyCertChain(serverCertificateProvider.getCertificateChain()); + setKey(serverCertificateProvider.getPrivateKey(), serverCertificateProvider.getKeyPassword()); logger.log(Level.INFO, "self signed certificate installed"); } } - if (keyCertChainInputStream == null) { - logger.log(Level.WARNING, "unable to install self signed certificate. Is netty-http-bouncycastle present?"); + if (keyCertChain == null) { + throw new CertificateException("unable to set self certificate"); } return this; } @@ -301,7 +332,8 @@ public class Domain { public Builder singleEndpoint(String path, Filter filter) { Objects.requireNonNull(path); Objects.requireNonNull(filter); - addEndpointResolver(HttpEndpointResolver.builder() + this.httpEndpointResolvers.clear(); + this.httpEndpointResolvers.add(HttpEndpointResolver.builder() .addEndpoint(HttpEndpoint.builder() .setPath(path) .build()) @@ -341,26 +373,33 @@ public class Domain { } public Domain build() { - if (httpAddress.isSecure()) { + if (httpAddress.isSecure() ) { try { - trustManagerFactory.init(trustManagerKeyStore); - SslContextBuilder sslContextBuilder = SslContextBuilder - .forServer(keyCertChainInputStream, keyInputStream, keyPassword) - .trustManager(trustManagerFactory) - .sslProvider(sslProvider) - .ciphers(ciphers, cipherSuiteFilter); - if (sslContextProvider != null) { - sslContextBuilder.sslContextProvider(sslContextProvider); + if (sslContext == null && privateKey != null && keyCertChain != null) { + trustManagerFactory.init(trustManagerKeyStore); + SslContextBuilder sslContextBuilder = SslContextBuilder + .forServer(privateKey, keyCertChain) + .trustManager(trustManagerFactory) + .sslProvider(sslProvider) + .ciphers(ciphers, cipherSuiteFilter); + if (sslContextProvider != null) { + sslContextBuilder.sslContextProvider(sslContextProvider); + } + if (httpAddress.getVersion().majorVersion() == 2) { + sslContextBuilder.applicationProtocolConfig(newApplicationProtocolConfig()); + } + this.sslContext = sslContextBuilder.build(); } - if (httpAddress.getVersion().majorVersion() == 2) { - sslContextBuilder.applicationProtocolConfig(newApplicationProtocolConfig()); - } - return new Domain(serverName, aliases, httpAddress, httpEndpointResolvers, sslContextBuilder.build()); - } catch (Throwable t) { - throw new RuntimeException(t); + return new Domain(serverName, aliases, + httpAddress, httpEndpointResolvers, + sslContext, keyCertChain); + } catch (Exception e) { + throw new RuntimeException(e); } } else { - return new Domain(serverName, aliases, httpAddress, httpEndpointResolvers, null); + return new Domain(serverName, aliases, + httpAddress, httpEndpointResolvers, + null, null); } } diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/Http1Provider.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/Http1.java similarity index 76% rename from netty-http-server/src/main/java/org/xbib/netty/http/server/Http1Provider.java rename to netty-http-server/src/main/java/org/xbib/netty/http/server/Http1.java index 9f10daf..3735850 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/Http1Provider.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/Http1.java @@ -1,10 +1,10 @@ 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.transport.Http1Transport; -public class Http1Provider implements ProtocolProvider { +public class Http1 implements ServerProtocolProvider { @Override public boolean supportsMajorVersion(int majorVersion) { diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/Http2Provider.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/Http2.java similarity index 76% rename from netty-http-server/src/main/java/org/xbib/netty/http/server/Http2Provider.java rename to netty-http-server/src/main/java/org/xbib/netty/http/server/Http2.java index 2489a03..75821f7 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/Http2Provider.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/Http2.java @@ -1,10 +1,10 @@ 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.transport.Http2Transport; -public class Http2Provider implements ProtocolProvider { +public class Http2 implements ServerProtocolProvider { @Override public boolean supportsMajorVersion(int majorVersion) { diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/Server.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/Server.java index 6b3d8e8..1d67886 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/Server.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/Server.java @@ -14,18 +14,23 @@ import io.netty.handler.logging.LoggingHandler; import io.netty.handler.ssl.SslContext; import io.netty.util.DomainWildcardMappingBuilder; import io.netty.util.Mapping; +import org.xbib.net.URL; import org.xbib.netty.http.common.HttpAddress; 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.server.api.HttpChannelInitializer; -import org.xbib.netty.http.server.api.ProtocolProvider; +import org.xbib.netty.http.server.api.ServerProtocolProvider; +import org.xbib.netty.http.server.api.ServerRequest; 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.api.Transport; - import java.io.IOException; 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.List; import java.util.Objects; @@ -68,8 +73,6 @@ public final class Server implements AutoCloseable { private final ServerConfig serverConfig; - private final ByteBufAllocator byteBufAllocator; - private final EventLoopGroup parentEventLoopGroup; private final EventLoopGroup childEventLoopGroup; @@ -79,17 +82,14 @@ public final class Server implements AutoCloseable { */ private final BlockingThreadPoolExecutor executor; - private final Class socketChannelClass; - private final ServerBootstrap bootstrap; private ChannelFuture channelFuture; - private final List> protocolProviders; + private final List> protocolProviders; /** * Create a new HTTP server. - * Use {@link #builder(HttpAddress)} to build HTTP instance. * * @param serverConfig server configuration * @param byteBufAllocator byte buf allocator @@ -106,13 +106,13 @@ public final class Server implements AutoCloseable { BlockingThreadPoolExecutor executor) { Objects.requireNonNull(serverConfig); this.serverConfig = serverConfig; - this.byteBufAllocator = byteBufAllocator != null ? byteBufAllocator : ByteBufAllocator.DEFAULT; + ByteBufAllocator byteBufAllocator1 = byteBufAllocator != null ? byteBufAllocator : ByteBufAllocator.DEFAULT; this.parentEventLoopGroup = createParentEventLoopGroup(serverConfig, parentEventLoopGroup); this.childEventLoopGroup = createChildEventLoopGroup(serverConfig, childEventLoopGroup); - this.socketChannelClass = createSocketChannelClass(serverConfig, socketChannelClass); + Class socketChannelClass1 = createSocketChannelClass(serverConfig, socketChannelClass); this.executor = executor; this.protocolProviders =new ArrayList<>(); - for (ProtocolProvider provider : ServiceLoader.load(ProtocolProvider.class)) { + for (ServerProtocolProvider provider : ServiceLoader.load(ServerProtocolProvider.class)) { protocolProviders.add(provider); if (logger.isLoggable(Level.FINEST)) { logger.log(Level.FINEST, "protocol provider up: " + provider.transportClass()); @@ -120,13 +120,13 @@ public final class Server implements AutoCloseable { } this.bootstrap = new ServerBootstrap() .group(this.parentEventLoopGroup, this.childEventLoopGroup) - .channel(this.socketChannelClass) - .option(ChannelOption.ALLOCATOR, this.byteBufAllocator) + .channel(socketChannelClass1) + .option(ChannelOption.ALLOCATOR, byteBufAllocator1) .option(ChannelOption.SO_REUSEADDR, serverConfig.isReuseAddr()) .option(ChannelOption.SO_RCVBUF, serverConfig.getTcpReceiveBufferSize()) .option(ChannelOption.SO_BACKLOG, serverConfig.getBackLogSize()) .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, serverConfig.getConnectTimeoutMillis()) - .childOption(ChannelOption.ALLOCATOR, this.byteBufAllocator) + .childOption(ChannelOption.ALLOCATOR, byteBufAllocator1) .childOption(ChannelOption.SO_REUSEADDR, serverConfig.isReuseAddr()) .childOption(ChannelOption.TCP_NODELAY, serverConfig.isTcpNodelay()) .childOption(ChannelOption.SO_SNDBUF, serverConfig.getTcpSendBufferSize()) @@ -137,34 +137,39 @@ public final class Server implements AutoCloseable { bootstrap.handler(new LoggingHandler("bootstrap-server", serverConfig.getTrafficDebugLogLevel())); } 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 domainNameMapping = null; - if (serverConfig.getAddress().isSecure() && serverConfig.getDefaultDomain().getSslContext() != null) { + Domain defaultDomain = serverConfig.getDefaultDomain(); + if (serverConfig.getAddress().isSecure() && + defaultDomain != null && + defaultDomain.getSslContext() != null) { DomainWildcardMappingBuilder mappingBuilder = - new DomainWildcardMappingBuilder<>(serverConfig.getDefaultDomain().getSslContext()); + new DomainWildcardMappingBuilder<>(defaultDomain.getSslContext()); for (Domain domain : serverConfig.getDomains()) { - String name = domain.getName(); - if (!"*".equals(name)) { - mappingBuilder.add(name, domain.getSslContext()); + if (!domain.getName().equals(defaultDomain.getName())) { + mappingBuilder.add(domain.getName(), domain.getSslContext()); } } domainNameMapping = mappingBuilder.build(); + logger.log(Level.INFO, "domain name mapping: " + domainNameMapping); } bootstrap.childHandler(findChannelInitializer(serverConfig.getAddress().getVersion().majorVersion(), serverConfig.getAddress(), domainNameMapping)); } - public static Builder builder() { - return builder(HttpAddress.http1("localhost", 8008)); + @Override + public void close() { + try { + shutdownGracefully(); + } catch (IOException e) { + logger.log(Level.SEVERE, e.getMessage(), e); + } } - public static Builder builder(HttpAddress httpAddress) { - return new Builder(httpAddress); - } - - public static Builder builder(Domain domain) { - return new Builder(domain); + public static Builder builder(Domain defaultDomain) { + return new Builder(defaultDomain); } 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 * @return the virtual host with the given name or the default domain */ - public Domain getNamedServer(String name) { - Domain domain = serverConfig.getDomain(name); - if (domain == null) { - domain = serverConfig.getDefaultDomain(); - } - return domain; + public Domain getDomain(String dnsName) { + return serverConfig.getDomain(dnsName); } - public void handle(Domain domain, HttpServerRequest serverRequest, ServerResponse serverResponse) - throws IOException { + public Domain getDomain(URL url) { + 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) { executor.submit(() -> { try { @@ -224,10 +271,6 @@ public final class Server implements AutoCloseable { } } - public BlockingThreadPoolExecutor getExecutor() { - return executor; - } - public AtomicLong getRequestCounter() { return requestCounter; } @@ -236,8 +279,8 @@ public final class Server implements AutoCloseable { return responseCounter; } - public Transport newTransport(HttpVersion httpVersion) { - for (ProtocolProvider protocolProvider : protocolProviders) { + public ServerTransport newTransport(HttpVersion httpVersion) { + for (ServerProtocolProvider protocolProvider : protocolProviders) { if (protocolProvider.supportsMajorVersion(httpVersion.majorVersion())) { try { 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()); } - @Override - public void close() { - try { - shutdownGracefully(); - } catch (IOException e) { - logger.log(Level.SEVERE, e.getMessage(), e); - } - } - public void shutdownGracefully() throws IOException { 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, HttpAddress httpAddress, Mapping domainNameMapping) { - for (ProtocolProvider protocolProvider : protocolProviders) { + for (ServerProtocolProvider protocolProvider : protocolProviders) { if (protocolProvider.supportsMajorVersion(majorVersion)) { try { return protocolProvider.initializerClass() @@ -449,11 +499,7 @@ public final class Server implements AutoCloseable { private Class socketChannelClass; - private ServerConfig serverConfig; - - private Builder(HttpAddress httpAddress) { - this(Domain.builder(httpAddress, "*").build()); - } + private final ServerConfig serverConfig; private Builder(Domain defaultDomain) { this.serverConfig = new ServerConfig(); @@ -596,14 +642,13 @@ public final class Server implements AutoCloseable { return this; } - public Builder setTransportLayerSecurityProtocols(String[] protocols) { + public Builder setTransportLayerSecurityProtocols(String... protocols) { this.serverConfig.setProtocols(protocols); return this; } public Builder addDomain(Domain domain) { - this.serverConfig.putDomain(domain); - logger.log(Level.FINE, "adding named server: " + domain); + this.serverConfig.checkAndAddDomain(domain); return this; } @@ -616,6 +661,45 @@ public final class Server implements AutoCloseable { executor.setRejectedExecutionHandler((runnable, threadPoolExecutor) -> 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, parentEventLoopGroup, childEventLoopGroup, socketChannelClass, executor); diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/ServerConfig.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/ServerConfig.java index f19c476..0a69a7c 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/ServerConfig.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/ServerConfig.java @@ -262,6 +262,10 @@ public class ServerConfig { private KeyStore trustManagerKeyStore = null; + private boolean autoDomain = false; + + private boolean acceptInvalidCertificates = false; + public ServerConfig() { this.domains = new LinkedHashMap<>(); } @@ -596,7 +600,28 @@ public class ServerConfig { 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); for (String alias : domain.getAliases()) { domains.put(alias, domain); @@ -621,12 +646,14 @@ public class ServerConfig { return this; } - public Domain getDefaultDomain() { - return getDomain("*"); - } - public Domain getDomain(String name) { - return domains.get(name); + Domain domain = domains.get(name); + return domain != null ? domain : getDefaultDomain(); } -} \ No newline at end of file + public Domain getDefaultDomain() { + Domain defaultDomain = domains.get("*"); + return defaultDomain != null ? defaultDomain : + !domains.isEmpty() ? domains.values().iterator().next() : null; + } +} diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/HttpEndpointResolver.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/HttpEndpointResolver.java index 9460d0b..5535ea7 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/HttpEndpointResolver.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/HttpEndpointResolver.java @@ -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.annotation.Endpoint; import org.xbib.netty.http.server.endpoint.service.MethodService; - import java.io.IOException; import java.lang.reflect.Method; import java.util.ArrayList; @@ -78,7 +77,7 @@ public class HttpEndpointResolver { private String prefix; - private List endpoints; + private final List endpoints; private EndpointDispatcher endpointDispatcher; @@ -99,7 +98,7 @@ public class HttpEndpointResolver { } /** - * Add endpoint. + * Add endpoint under this endpoint. * * @param endpoint the endpoint * @return this builder diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/handler/http/Http1ChannelInitializer.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/handler/http/Http1ChannelInitializer.java index d64a1e8..ae1fcc6 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/handler/http/Http1ChannelInitializer.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/handler/http/Http1ChannelInitializer.java @@ -22,11 +22,11 @@ import io.netty.util.Mapping; import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.server.Server; 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.IdleTimeoutHandler; 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.util.logging.Level; @@ -56,8 +56,8 @@ public class Http1ChannelInitializer extends ChannelInitializer @Override public void initChannel(Channel channel) { - Transport transport = server.newTransport(httpAddress.getVersion()); - channel.attr(Transport.TRANSPORT_ATTRIBUTE_KEY).set(transport); + ServerTransport transport = server.newTransport(httpAddress.getVersion()); + channel.attr(ServerTransport.TRANSPORT_ATTRIBUTE_KEY).set(transport); if (serverConfig.isTrafficDebug()) { channel.pipeline().addLast(new TrafficLoggingHandler(LogLevel.DEBUG)); } @@ -125,7 +125,7 @@ public class Http1ChannelInitializer extends ChannelInitializer HttpResponseStatus.HTTP_VERSION_NOT_SUPPORTED); ctx.channel().writeAndFlush(response); } else { - Transport transport = server.newTransport(fullHttpRequest.protocolVersion()); + ServerTransport transport = server.newTransport(fullHttpRequest.protocolVersion()); transport.requestReceived(ctx, fullHttpRequest, httpPipelinedRequest.getSequenceId()); } fullHttpRequest.release(); diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/handler/http2/Http2ChannelInitializer.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/handler/http2/Http2ChannelInitializer.java index 0352231..df4b55d 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/handler/http2/Http2ChannelInitializer.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/handler/http2/Http2ChannelInitializer.java @@ -34,11 +34,11 @@ import io.netty.util.Mapping; import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.server.Server; 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.IdleTimeoutHandler; 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.util.logging.Level; @@ -68,8 +68,8 @@ public class Http2ChannelInitializer extends ChannelInitializer @Override public void initChannel(Channel channel) { - Transport transport = server.newTransport(httpAddress.getVersion()); - channel.attr(Transport.TRANSPORT_ATTRIBUTE_KEY).set(transport); + ServerTransport transport = server.newTransport(httpAddress.getVersion()); + channel.attr(ServerTransport.TRANSPORT_ATTRIBUTE_KEY).set(transport); if (serverConfig.isTrafficDebug()) { channel.pipeline().addLast(new TrafficLoggingHandler(LogLevel.DEBUG)); } @@ -94,8 +94,8 @@ public class Http2ChannelInitializer extends ChannelInitializer ChannelHandler channelHandler = new ChannelInitializer() { @Override protected void initChannel(Channel channel) { - Transport transport = server.newTransport(httpAddress.getVersion()); - channel.attr(Transport.TRANSPORT_ATTRIBUTE_KEY).set(transport); + ServerTransport transport = server.newTransport(httpAddress.getVersion()); + channel.attr(ServerTransport.TRANSPORT_ATTRIBUTE_KEY).set(transport); ChannelPipeline pipeline = channel.pipeline(); pipeline.addLast("server-frame-converter", new Http2StreamFrameToHttpObjectCodec(true)); @@ -137,7 +137,7 @@ public class Http2ChannelInitializer extends ChannelInitializer @Override 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); } } @@ -148,7 +148,7 @@ public class Http2ChannelInitializer extends ChannelInitializer public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof DefaultHttp2SettingsFrame) { 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()); } else if (msg instanceof DefaultHttpRequest) { DefaultHttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, @@ -159,13 +159,13 @@ public class Http2ChannelInitializer extends ChannelInitializer @Override 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); } @Override 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); } } diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/security/CertificateUtils.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/security/CertificateUtils.java new file mode 100644 index 0000000..1995c44 --- /dev/null +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/security/CertificateUtils.java @@ -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 toCertificate(InputStream keyCertChainInputStream) + throws CertificateException { + return (Collection) CertificateFactory.getInstance("X509") + .generateCertificates(keyCertChainInputStream); + } + + public static void processSubjectAlternativeNames(Collection certificates, + SubjectAlternativeNamesProcessor processor) throws CertificateParsingException { + if (certificates == null) { + return; + } + for (X509Certificate certificate : certificates) { + processor.setServerName(new DistinguishedNameParser(certificate.getSubjectX500Principal()) + .findMostSpecific("CN")); + Collection> 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); + } +} diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/security/DistinguishedNameParser.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/security/DistinguishedNameParser.java new file mode 100644 index 0000000..dc39ac9 --- /dev/null +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/security/DistinguishedNameParser.java @@ -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); + } + } + } +} diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/security/PrivateKeyUtils.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/security/PrivateKeyUtils.java new file mode 100644 index 0000000..7533335 --- /dev/null +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/security/PrivateKeyUtils.java @@ -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); + +} diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/BaseTransport.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/BaseTransport.java index 4f95b0d..856be73 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/BaseTransport.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/BaseTransport.java @@ -8,13 +8,12 @@ import io.netty.handler.codec.http.HttpVersion; import org.xbib.netty.http.server.Server; import org.xbib.netty.http.server.api.ServerRequest; import org.xbib.netty.http.server.api.ServerResponse; -import org.xbib.netty.http.server.Domain; -import org.xbib.netty.http.server.api.Transport; +import org.xbib.netty.http.server.api.ServerTransport; import java.util.logging.Level; 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()); @@ -34,14 +33,13 @@ abstract class BaseTransport implements Transport { * and required special header handling, possibly returning an * appropriate response. * - * @param domain the named server + * @param version the HTTP version of the server * @param serverRequest the request * @param serverResponse the response * @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(); - HttpVersion version = domain.getHttpAddress().getVersion(); if (version.majorVersion() == 1 || version.majorVersion() == 2) { if (!reqHeaders.contains(HttpHeaderNames.HOST)) { // RFC2616#14.23: missing Host header gets 400 diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/Http1Transport.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/Http1Transport.java index fa4c121..cecad0b 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/Http1Transport.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/Http1Transport.java @@ -2,13 +2,11 @@ package org.xbib.netty.http.server.transport; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.FullHttpRequest; -import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http2.Http2Settings; import io.netty.handler.ssl.SslHandler; import org.xbib.netty.http.server.Server; import org.xbib.netty.http.server.api.ServerResponse; -import org.xbib.netty.http.server.Domain; import java.io.IOException; public class Http1Transport extends BaseTransport { @@ -19,7 +17,6 @@ public class Http1Transport extends BaseTransport { @Override 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); serverRequest.setSequenceId(sequenceId); serverRequest.setRequestId(server.getRequestCounter().incrementAndGet()); @@ -28,9 +25,9 @@ public class Http1Transport extends BaseTransport { serverRequest.setSession(sslHandler.engine().getSession()); } HttpServerResponse serverResponse = new HttpServerResponse(server, serverRequest, ctx); - if (acceptRequest(domain, serverRequest, serverResponse)) { + if (acceptRequest(server.getServerConfig().getAddress().getVersion(), serverRequest, serverResponse)) { serverRequest.handleParameters(); - server.handle(domain, serverRequest, serverResponse); + server.handle(serverRequest, serverResponse); } else { ServerResponse.write(serverResponse, HttpResponseStatus.NOT_ACCEPTABLE); } diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/Http2Transport.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/Http2Transport.java index 3f1cfc1..2cca480 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/Http2Transport.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/Http2Transport.java @@ -2,13 +2,11 @@ package org.xbib.netty.http.server.transport; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.FullHttpRequest; -import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http2.Http2Settings; import io.netty.handler.codec.http2.HttpConversionUtil; import org.xbib.netty.http.server.Server; import org.xbib.netty.http.server.api.ServerResponse; -import org.xbib.netty.http.server.Domain; import java.io.IOException; import java.util.logging.Level; @@ -24,15 +22,14 @@ public class Http2Transport extends BaseTransport { @Override 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); serverRequest.setSequenceId(sequenceId); serverRequest.setRequestId(server.getRequestCounter().incrementAndGet()); serverRequest.setStreamId(fullHttpRequest.headers().getInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text())); ServerResponse serverResponse = new Http2ServerResponse(server, serverRequest, ctx); - if (acceptRequest(domain, serverRequest, serverResponse)) { + if (acceptRequest(server.getServerConfig().getAddress().getVersion(), serverRequest, serverResponse)) { serverRequest.handleParameters(); - server.handle(domain, serverRequest, serverResponse); + server.handle(serverRequest, serverResponse); } else { ServerResponse.write(serverResponse, HttpResponseStatus.NOT_ACCEPTABLE); } diff --git a/netty-http-server/src/main/resources/META-INF/services/org.xbib.netty.http.server.api.ProtocolProvider b/netty-http-server/src/main/resources/META-INF/services/org.xbib.netty.http.server.api.ProtocolProvider deleted file mode 100644 index 113af9e..0000000 --- a/netty-http-server/src/main/resources/META-INF/services/org.xbib.netty.http.server.api.ProtocolProvider +++ /dev/null @@ -1,2 +0,0 @@ -org.xbib.netty.http.server.Http1Provider -org.xbib.netty.http.server.Http2Provider diff --git a/netty-http-server/src/main/resources/META-INF/services/org.xbib.netty.http.server.api.ServerProtocolProvider b/netty-http-server/src/main/resources/META-INF/services/org.xbib.netty.http.server.api.ServerProtocolProvider new file mode 100644 index 0000000..6e83868 --- /dev/null +++ b/netty-http-server/src/main/resources/META-INF/services/org.xbib.netty.http.server.api.ServerProtocolProvider @@ -0,0 +1,2 @@ +org.xbib.netty.http.server.Http1 +org.xbib.netty.http.server.Http2 diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/BindExceptionTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/BindExceptionTest.java index 6f8693d..263823d 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/BindExceptionTest.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/BindExceptionTest.java @@ -12,12 +12,13 @@ import java.io.IOException; import java.net.BindException; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.fail; class BindExceptionTest { @Test 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")) .build(); Server server1 = Server.builder(domain).build(); @@ -29,6 +30,7 @@ class BindExceptionTest { assertNotNull(channelFuture1); ChannelFuture channelFuture2 = server2.accept(); // should crash with BindException + fail(); }); } finally { server1.shutdownGracefully(); diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/MultiDomainSecureServerTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/MultiDomainSecureServerTest.java new file mode 100644 index 0000000..9e95661 --- /dev/null +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/MultiDomainSecureServerTest.java @@ -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(); + } + } +} diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/MultiDomainServerTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/MultiDomainServerTest.java new file mode 100644 index 0000000..73c095c --- /dev/null +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/MultiDomainServerTest.java @@ -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(); + } + } +} diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/RunServerTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/RunServerTest.java deleted file mode 100644 index 9930821..0000000 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/RunServerTest.java +++ /dev/null @@ -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(); - } - } -} diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/ThreadLeakTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/ThreadLeakTest.java index 599455a..da4fd0a 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/ThreadLeakTest.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/ThreadLeakTest.java @@ -5,6 +5,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; 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.api.ServerResponse; import org.xbib.netty.http.server.Domain; @@ -22,7 +23,7 @@ class ThreadLeakTest { @Test void testForLeaks() throws IOException { - Domain domain = Domain.builder() + Domain domain = Domain.builder(HttpAddress.http1("localhost", 8008)) .singleEndpoint("/", (request, response) -> ServerResponse.write(response, "Hello World")) .build(); Server server = Server.builder(domain) diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/TransportLayerSecurityServerTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/TransportLayerSecurityServerTest.java index f03ad2b..4f45a1a 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/TransportLayerSecurityServerTest.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/TransportLayerSecurityServerTest.java @@ -8,7 +8,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.xbib.netty.http.client.Client; import org.xbib.netty.http.client.api.Request; 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.HttpResponse; import org.xbib.netty.http.server.Domain; @@ -33,7 +33,7 @@ class TransportLayerSecurityServerTest { .withContentType("text/plain") .write(request.getContent().retain())) .build()) - .setTransportLayerSecurityProtocols(new String[]{ "TLSv1.2"}) + .setTransportLayerSecurityProtocols("TLSv1.2") .build(); Client client = Client.builder() .trustInsecure() @@ -51,8 +51,8 @@ class TransportLayerSecurityServerTest { .content("Hello Jörg", "text/plain") .setResponseListener(responseListener) .build(); - Transport transport = client.execute(request).get(); - logger.log(Level.INFO, "HTTP 1.1 TLS protocol = " + transport.getSession().getProtocol()); + ClientTransport transport = client.execute(request).get(); + logger.log(Level.INFO, "TLS protocol = " + transport.getSession().getProtocol()); assertEquals("TLSv1.2", transport.getSession().getProtocol()); } finally { client.shutdownGracefully(); @@ -71,7 +71,7 @@ class TransportLayerSecurityServerTest { .withContentType("text/plain") .write(request.getContent().retain())) .build()) - .setTransportLayerSecurityProtocols(new String[]{ "TLSv1.3"}) + .setTransportLayerSecurityProtocols("TLSv1.3") .build(); Client client = Client.builder() .trustInsecure() @@ -90,8 +90,8 @@ class TransportLayerSecurityServerTest { .content("Hello Jörg", "text/plain") .setResponseListener(responseListener) .build(); - Transport transport = client.execute(request).get(); - logger.log(Level.INFO, "HTTP/2 TLS protocol = " + transport.getSession().getProtocol()); + ClientTransport transport = client.execute(request).get(); + logger.log(Level.INFO, "TLS protocol = " + transport.getSession().getProtocol()); assertEquals("TLSv1.3", transport.getSession().getProtocol()); } finally { client.shutdownGracefully(); diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http1/CleartextTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http1/CleartextTest.java index 8c23866..3998732 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http1/CleartextTest.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http1/CleartextTest.java @@ -7,7 +7,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.xbib.netty.http.client.Client; import org.xbib.netty.http.client.api.Request; 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.HttpResponse; import org.xbib.netty.http.server.Server; @@ -92,7 +92,7 @@ class CleartextTest { .content(Integer.toString(i), "text/plain") .setResponseListener(responseListener) .build(); - Transport transport = client.newTransport(); + ClientTransport transport = client.newTransport(); transport.execute(request); if (transport.isFailed()) { logger.log(Level.WARNING, transport.getFailure().getMessage(), transport.getFailure()); @@ -109,7 +109,7 @@ class CleartextTest { @Test void testMultithreadPooledClearTextHttp1() throws Exception { - int threads = 2; + int threads = 4; int loop = 1024; HttpAddress httpAddress = HttpAddress.http1("localhost", 8008); Domain domain = Domain.builder(httpAddress) @@ -144,7 +144,7 @@ class CleartextTest { .setResponseListener(responseListener) .build(); // note: in HTTP 1, a new transport is created per execution - Transport transport = client.newTransport(); + ClientTransport transport = client.newTransport(); transport.execute(request); if (transport.isFailed()) { logger.log(Level.WARNING, "transport failed: " + transport.getFailure().getMessage(), transport.getFailure()); diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http1/EncryptedTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http1/EncryptedTest.java index d232839..ba74bfe 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http1/EncryptedTest.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http1/EncryptedTest.java @@ -7,7 +7,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.xbib.netty.http.client.Client; import org.xbib.netty.http.client.api.Request; 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.HttpResponse; import org.xbib.netty.http.server.Server; @@ -86,7 +86,7 @@ class EncryptedTest { .content(Integer.toString(i), "text/plain") .setResponseListener(responseListener) .build(); - Transport transport = client.newTransport(); + ClientTransport transport = client.newTransport(); transport.execute(request); if (transport.isFailed()) { logger.log(Level.WARNING, transport.getFailure().getMessage(), transport.getFailure()); @@ -137,7 +137,7 @@ class EncryptedTest { .setResponseListener(responseListener) .build(); // note: a new transport is created per execution - final Transport transport = client.newTransport(); + final ClientTransport transport = client.newTransport(); transport.execute(request); if (transport.isFailed()) { logger.log(Level.WARNING, transport.getFailure().getMessage(), transport.getFailure()); diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/CleartextTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/CleartextTest.java index f450855..3147743 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/CleartextTest.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/CleartextTest.java @@ -6,7 +6,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.xbib.netty.http.client.Client; import org.xbib.netty.http.client.api.Request; 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.HttpResponse; import org.xbib.netty.http.server.Server; @@ -59,7 +59,7 @@ class CleartextTest { .content(payload, "text/plain") .setResponseListener(responseListener) .build(); - Transport transport = client.newTransport(httpAddress); + ClientTransport transport = client.newTransport(httpAddress); transport.execute(request); if (transport.isFailed()) { logger.log(Level.WARNING, transport.getFailure().getMessage(), transport.getFailure()); @@ -101,7 +101,7 @@ class CleartextTest { }; try { // single transport, single thread - Transport transport = client.newTransport(); + ClientTransport transport = client.newTransport(); for (int i = 0; i < loop; i++) { String payload = 0 + "/" + i; Request request = Request.get().setVersion("HTTP/2.0") @@ -115,7 +115,7 @@ class CleartextTest { break; } } - transport.get(60L, TimeUnit.SECONDS); + transport.get(10L, TimeUnit.SECONDS); } finally { server.shutdownGracefully(); client.shutdownGracefully(); @@ -126,7 +126,7 @@ class CleartextTest { @Test void testMultithreadPooledClearTextHttp2() throws Exception { - int threads = 2; + int threads = 4; int loop = 1024; HttpAddress httpAddress = HttpAddress.http2("localhost", 8008); Domain domain = Domain.builder(httpAddress) @@ -148,7 +148,7 @@ class CleartextTest { }; try { // 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); for (int n = 0; n < threads; n++) { final int t = n; @@ -174,19 +174,20 @@ class CleartextTest { }); } executorService.shutdown(); - boolean terminated = executorService.awaitTermination(20L, TimeUnit.SECONDS); + boolean terminated = executorService.awaitTermination(30L, TimeUnit.SECONDS); executorService.shutdownNow(); - logger.log(Level.INFO, "terminated = " + terminated + ", now waiting for transport to complete"); - transport.get(20L, TimeUnit.SECONDS); + logger.log(Level.INFO, "terminated = " + terminated + ", now waiting 30s for transport to complete"); + Thread.sleep(2000L); + transport.get(30L, TimeUnit.SECONDS); logger.log(Level.INFO, "transport complete"); } finally { - server.shutdownGracefully(20L, TimeUnit.SECONDS); - client.shutdownGracefully(20L, TimeUnit.SECONDS); + client.shutdownGracefully(); + server.shutdownGracefully(); } - logger.log(Level.INFO, "server requests = " + server.getRequestCounter() + - " server responses = " + server.getResponseCounter()); logger.log(Level.INFO, "client requests = " + client.getRequestCounter() + " 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()); assertEquals(threads * loop , counter.get()); } @@ -236,7 +237,7 @@ class CleartextTest { }; try { // 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); for (int n = 0; n < threads; n++) { final int t = n; @@ -264,12 +265,13 @@ class CleartextTest { executorService.shutdown(); boolean terminated = executorService.awaitTermination(10L, TimeUnit.SECONDS); logger.log(Level.INFO, "terminated = " + terminated + ", now waiting for transport to complete"); + Thread.sleep(2000L); transport.get(10L, TimeUnit.SECONDS); logger.log(Level.INFO, "transport complete"); } finally { - server1.shutdownGracefully(10L, TimeUnit.SECONDS); - server2.shutdownGracefully(10L, TimeUnit.SECONDS); - client.shutdownGracefully(10L, TimeUnit.SECONDS); + server1.shutdownGracefully(); + server2.shutdownGracefully(); + client.shutdownGracefully(); } logger.log(Level.INFO, "server1 requests = " + server1.getRequestCounter() + " server1 responses = " + server1.getResponseCounter()); diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/EncryptedTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/EncryptedTest.java index b4d25bb..17cd51c 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/EncryptedTest.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/EncryptedTest.java @@ -6,14 +6,13 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.xbib.netty.http.client.Client; import org.xbib.netty.http.client.api.Request; 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.HttpResponse; import org.xbib.netty.http.server.Server; import org.xbib.netty.http.server.Domain; import org.xbib.netty.http.server.test.NettyHttpTestExtension; -import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -52,7 +51,7 @@ class EncryptedTest { counter.incrementAndGet(); }; try { - Transport transport = client.newTransport(httpAddress); + ClientTransport transport = client.newTransport(httpAddress); String payload = 0 + "/" + 0; Request request = Request.get() .setVersion("HTTP/2.0") @@ -99,7 +98,7 @@ class EncryptedTest { }; try { // single transport, single thread - Transport transport = client.newTransport(); + ClientTransport transport = client.newTransport(); for (int i = 0; i < loop; i++) { String payload = 0 + "/" + i; Request request = Request.get().setVersion("HTTP/2.0") @@ -124,7 +123,7 @@ class EncryptedTest { @Test void testMultithreadPooledSecureHttp2() throws Exception { - int threads = 2; + int threads = 4; int loop = 1024; HttpAddress httpAddress = HttpAddress.secureHttp2("localhost", 8143); Server server = Server.builder(Domain.builder(httpAddress) @@ -147,7 +146,7 @@ class EncryptedTest { final ResponseListener responseListener = resp -> counter.incrementAndGet(); try { // 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); for (int n = 0; n < threads; n++) { final int t = n; @@ -166,8 +165,8 @@ class EncryptedTest { break; } } - } catch (IOException e) { - logger.log(Level.WARNING, e.getMessage(), e); + } catch (Exception e) { + logger.log(Level.SEVERE, e.getMessage(), e); } }); } @@ -175,6 +174,7 @@ class EncryptedTest { boolean terminated = executorService.awaitTermination(20, TimeUnit.SECONDS); executorService.shutdownNow(); logger.log(Level.INFO, "terminated = " + terminated + ", now waiting for transport to complete"); + Thread.sleep(2000L); transport.get(20, TimeUnit.SECONDS); } finally { client.shutdownGracefully(20, TimeUnit.SECONDS); diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/MixedProtocolTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/MixedProtocolTest.java index 6b26db9..4606421 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/MixedProtocolTest.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/MixedProtocolTest.java @@ -6,7 +6,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.xbib.netty.http.client.Client; import org.xbib.netty.http.client.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.server.Domain; import org.xbib.netty.http.server.Server; @@ -76,7 +76,7 @@ class MixedProtocolTest { .build(); for (int i = 0; i < max; i++) { // HTTP 2 breaks transport - Transport transport = client.execute(request).get(); + ClientTransport transport = client.execute(request).get(); if (transport.isFailed()) { count.incrementAndGet(); } @@ -100,7 +100,7 @@ class MixedProtocolTest { .build(); Server server = Server.builder(domain) //.enableDebug() - .setTransportLayerSecurityProtocols(new String[]{"TLSv1.2"}) + .setTransportLayerSecurityProtocols("TLSv1.2") .build(); Client client = Client.builder() //.enableDebug() @@ -119,7 +119,7 @@ class MixedProtocolTest { }) .build(); for (int i = 0; i < max; i++) { - Transport transport = client.execute(request).get(); + ClientTransport transport = client.execute(request).get(); if (transport.isFailed()) { count.incrementAndGet(); }