diff --git a/build.gradle b/build.gradle index e72b5aa..34c9265 100644 --- a/build.gradle +++ b/build.gradle @@ -38,7 +38,9 @@ subprojects { jar { manifest { + attributes('Implementation-Title': project.name) attributes('Implementation-Version': project.version) + attributes('Implementation-Vendor': 'Jörg Prante') } } diff --git a/gradle.properties b/gradle.properties index d430f52..947b941 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,10 +1,11 @@ group = org.xbib name = netty-http -version = 4.1.43.1 +version = 4.1.44.0 # netty -netty.version = 4.1.43.Final -tcnative.version = 2.0.25.Final +netty.version = 4.1.44.Final +tcnative.version = 2.0.28.Final +tcnative-legacy-macosx.version = 2.0.26.Final # for netty-http-common xbib-net-url.version = 2.0.3 @@ -21,7 +22,7 @@ xbib-guice.version = 4.0.4 # for rx reactivex.version = 1.2.10 -# test +# for test junit.version = 5.5.2 junit4.version = 4.12 conscrypt.version = 2.2.1 diff --git a/netty-http-bouncycastle/build.gradle b/netty-http-bouncycastle/build.gradle new file mode 100644 index 0000000..c984729 --- /dev/null +++ b/netty-http-bouncycastle/build.gradle @@ -0,0 +1,4 @@ +dependencies { + compile project(":netty-http-common") + compile "org.bouncycastle:bcpkix-jdk15on:${project.property('bouncycastle.version')}" +} 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 new file mode 100644 index 0000000..b5cb02d --- /dev/null +++ b/netty-http-bouncycastle/src/main/java/org/xbib/netty/http/bouncycastle/BouncyCastleSelfSignedCertificateProvider.java @@ -0,0 +1,34 @@ +package org.xbib.netty.http.bouncycastle; + +import org.xbib.netty.http.common.ServerCertificateProvider; +import java.io.InputStream; +import java.security.SecureRandom; + +public class BouncyCastleSelfSignedCertificateProvider implements ServerCertificateProvider { + + private final SelfSignedCertificate selfSignedCertificate; + + public BouncyCastleSelfSignedCertificateProvider() { + this.selfSignedCertificate = new SelfSignedCertificate(); + } + + @Override + public void prepare(String fqdn) throws Exception { + selfSignedCertificate.generate(fqdn, new SecureRandom(), 2048); + } + + @Override + public InputStream getPrivateKey() { + return selfSignedCertificate.privateKey(); + } + + @Override + public InputStream getCertificateChain() { + return selfSignedCertificate.certificate(); + } + + @Override + public String getKeyPassword() { + return null; + } +} diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/security/tls/SelfSignedCertificate.java b/netty-http-bouncycastle/src/main/java/org/xbib/netty/http/bouncycastle/SelfSignedCertificate.java similarity index 93% rename from netty-http-server/src/main/java/org/xbib/netty/http/server/security/tls/SelfSignedCertificate.java rename to netty-http-bouncycastle/src/main/java/org/xbib/netty/http/bouncycastle/SelfSignedCertificate.java index 91f0104..36ee7fd 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/security/tls/SelfSignedCertificate.java +++ b/netty-http-bouncycastle/src/main/java/org/xbib/netty/http/bouncycastle/SelfSignedCertificate.java @@ -1,4 +1,4 @@ -package org.xbib.netty.http.server.security.tls; +package org.xbib.netty.http.bouncycastle; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x509.AlgorithmIdentifier; @@ -58,16 +58,6 @@ public final class SelfSignedCertificate { private PrivateKey key; - public SelfSignedCertificate() - throws IOException, NoSuchProviderException, NoSuchAlgorithmException, OperatorCreationException { - this("localhost"); - } - - public SelfSignedCertificate(String fqdn) - throws IOException, NoSuchProviderException, NoSuchAlgorithmException, OperatorCreationException { - generate(fqdn, new SecureRandom(), 2048); - } - /** * Creates a new instance. * 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.common.ServerCertificateProvider new file mode 100644 index 0000000..6302c7a --- /dev/null +++ b/netty-http-bouncycastle/src/main/resources/META-INF/services/org.xbib.netty.http.common.ServerCertificateProvider @@ -0,0 +1 @@ +org.xbib.netty.http.bouncycastle.BouncyCastleSelfSignedCertificateProvider \ No newline at end of file diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/SelfSignedCertificateTest.java b/netty-http-bouncycastle/src/test/java/org/xbib/netty/http/bouncycastle/SelfSignedCertificateTest.java similarity index 72% rename from netty-http-server/src/test/java/org/xbib/netty/http/server/test/SelfSignedCertificateTest.java rename to netty-http-bouncycastle/src/test/java/org/xbib/netty/http/bouncycastle/SelfSignedCertificateTest.java index 64e1e15..618e673 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/SelfSignedCertificateTest.java +++ b/netty-http-bouncycastle/src/test/java/org/xbib/netty/http/bouncycastle/SelfSignedCertificateTest.java @@ -1,9 +1,9 @@ -package org.xbib.netty.http.server.test; +package org.xbib.netty.http.bouncycastle; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.junit.jupiter.api.Test; -import org.xbib.netty.http.server.security.tls.SelfSignedCertificate; +import java.security.SecureRandom; import java.security.Security; import java.util.logging.Logger; @@ -12,7 +12,8 @@ class SelfSignedCertificateTest { @Test void testSelfSignedCertificate() throws Exception { Security.addProvider(new BouncyCastleProvider()); - SelfSignedCertificate selfSignedCertificate = new SelfSignedCertificate("localhost"); + SelfSignedCertificate selfSignedCertificate = new SelfSignedCertificate(); + selfSignedCertificate.generate("localhost", new SecureRandom(), 2048); selfSignedCertificate.exportPEM(Logger.getLogger("test")); } } diff --git a/netty-http-client/build.gradle b/netty-http-client/build.gradle index 3878976..ec0361b 100644 --- a/netty-http-client/build.gradle +++ b/netty-http-client/build.gradle @@ -1,10 +1,17 @@ +import org.apache.tools.ant.taskdefs.condition.Os dependencies { compile project(":netty-http-client-api") compile "io.netty:netty-handler-proxy:${project.property('netty.version')}" - compile "io.netty:netty-transport-native-epoll:${project.property('netty.version')}" - testCompile "io.netty:netty-tcnative-boringssl-static:${project.property('tcnative.version')}" - testCompile "org.bouncycastle:bcpkix-jdk15on:${project.property('bouncycastle.version')}" + runtime "org.bouncycastle:bcpkix-jdk15on:${project.property('bouncycastle.version')}" + // we are an Mac OS X 10.12, but tcnative > 2.0.26 is for 10.13+ + if (Os.isFamily(Os.FAMILY_MAC)) { + runtime "io.netty:netty-tcnative-boringssl-static:${project.property('tcnative-legacy-macosx.version')}" + runtime project(':netty-http-kqueue') + } else { + runtime "io.netty:netty-tcnative-boringssl-static:${project.property('tcnative.version')}" + runtime project(':netty-http-epoll') + } testCompile "org.conscrypt:conscrypt-openjdk-uber:${project.property('conscrypt.version')}" testCompile "com.fasterxml.jackson.core:jackson-databind:${project.property('jackson.version')}" } 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 6662031..bdf0033 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 @@ -6,8 +6,6 @@ import io.netty.channel.Channel; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.WriteBufferWaterMark; -import io.netty.channel.epoll.EpollEventLoopGroup; -import io.netty.channel.epoll.EpollSocketChannel; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.pool.ChannelPoolHandler; import io.netty.channel.socket.SocketChannel; @@ -32,6 +30,7 @@ import org.xbib.netty.http.client.api.Transport; import org.xbib.netty.http.common.HttpAddress; 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; @@ -87,10 +86,6 @@ public final class Client implements AutoCloseable { private final ByteBufAllocator byteBufAllocator; - private final EventLoopGroup eventLoopGroup; - - private final Class socketChannelClass; - private final Bootstrap bootstrap; private final Queue transports; @@ -99,6 +94,10 @@ public final class Client implements AutoCloseable { private final AtomicBoolean closed; + private EventLoopGroup eventLoopGroup; + + private Class socketChannelClass; + private BoundedChannelPool pool; public Client() { @@ -125,13 +124,39 @@ public final class Client implements AutoCloseable { } } initializeTrustManagerFactory(clientConfig); - this.byteBufAllocator = byteBufAllocator != null ? - byteBufAllocator : ByteBufAllocator.DEFAULT; - this.eventLoopGroup = eventLoopGroup != null ? eventLoopGroup : clientConfig.isEpoll() ? - new EpollEventLoopGroup(clientConfig.getThreadCount(), new HttpClientThreadFactory()) : - new NioEventLoopGroup(clientConfig.getThreadCount(), new HttpClientThreadFactory()); - this.socketChannelClass = socketChannelClass != null ? socketChannelClass : clientConfig.isEpoll() ? - EpollSocketChannel.class : NioSocketChannel.class; + this.byteBufAllocator = byteBufAllocator != null ? byteBufAllocator : ByteBufAllocator.DEFAULT; + if (eventLoopGroup != null) { + this.eventLoopGroup = eventLoopGroup; + } else { + ServiceLoader transportProviders = ServiceLoader.load(TransportProvider.class); + for (TransportProvider transportProvider : transportProviders) { + if (clientConfig.getTransportProviderName() == null || clientConfig.getTransportProviderName().equals(transportProvider.getClass().getName())) { + this.eventLoopGroup = transportProvider.createEventLoopGroup(clientConfig.getThreadCount(), new HttpClientThreadFactory()); + if (logger.isLoggable(Level.FINEST)) { + logger.log(Level.FINEST, "transport provider event loop group: " + this.eventLoopGroup.getClass().getName()); + } + } + } + } + if (this.eventLoopGroup == null) { + this.eventLoopGroup = new NioEventLoopGroup(clientConfig.getThreadCount(), new HttpClientThreadFactory()); + } + if (socketChannelClass != null) { + this.socketChannelClass = socketChannelClass; + } else { + ServiceLoader transportProviders = ServiceLoader.load(TransportProvider.class); + for (TransportProvider transportProvider : transportProviders) { + if (clientConfig.getTransportProviderName() == null || clientConfig.getTransportProviderName().equals(transportProvider.getClass().getName())) { + this.socketChannelClass = transportProvider.createSocketChannelClass(); + if (logger.isLoggable(Level.FINEST)) { + logger.log(Level.FINEST, "transport provider channel: " + this.socketChannelClass.getName()); + } + } + } + } + if (this.socketChannelClass == null) { + this.socketChannelClass = NioSocketChannel.class; + } this.bootstrap = new Bootstrap() .group(this.eventLoopGroup) .channel(this.socketChannelClass) @@ -567,6 +592,11 @@ public final class Client implements AutoCloseable { return this; } + public Builder setTransportProviderName(String transportProviderName) { + clientConfig.setTransportProviderName(transportProviderName); + return this; + } + public Builder setEventLoop(EventLoopGroup eventLoopGroup) { this.eventLoopGroup = eventLoopGroup; return this; diff --git a/netty-http-client/src/main/java/org/xbib/netty/http/client/ClientConfig.java b/netty-http-client/src/main/java/org/xbib/netty/http/client/ClientConfig.java index 47fcea9..8e82824 100644 --- a/netty-http-client/src/main/java/org/xbib/netty/http/client/ClientConfig.java +++ b/netty-http-client/src/main/java/org/xbib/netty/http/client/ClientConfig.java @@ -1,7 +1,6 @@ package org.xbib.netty.http.client; import io.netty.channel.WriteBufferWaterMark; -import io.netty.channel.epoll.Epoll; import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.codec.http2.Http2Settings; import io.netty.handler.logging.LogLevel; @@ -35,9 +34,9 @@ public class ClientConfig { LogLevel DEFAULT_DEBUG_LOG_LEVEL = LogLevel.DEBUG; /** - * The default for selecting epoll. If available, select epoll. + * The transport provider */ - boolean EPOLL = Epoll.isAvailable(); + String DEFAULT_TRANSPORT_PROVIDER = null; /** * If set to 0, then Netty will decide about thread count. @@ -178,7 +177,7 @@ public class ClientConfig { private LogLevel debugLogLevel = Defaults.DEFAULT_DEBUG_LOG_LEVEL; - private boolean epoll = Defaults.EPOLL; + private String transportProviderName = Defaults.DEFAULT_TRANSPORT_PROVIDER; private int threadCount = Defaults.THREAD_COUNT; @@ -282,18 +281,13 @@ public class ClientConfig { return debugLogLevel; } - public ClientConfig enableEpoll() { - this.epoll = true; + public ClientConfig setTransportProviderName(String transportProviderName) { + this.transportProviderName = transportProviderName; return this; } - public ClientConfig disableEpoll() { - this.epoll = false; - return this; - } - - public boolean isEpoll() { - return epoll; + public String getTransportProviderName() { + return transportProviderName; } public ClientConfig setThreadCount(int threadCount) { diff --git a/netty-http-client/src/test/java/org/xbib/netty/http/client/test/NettyHttpTestExtension.java b/netty-http-client/src/test/java/org/xbib/netty/http/client/test/NettyHttpTestExtension.java index bb3379a..a5ef433 100644 --- a/netty-http-client/src/test/java/org/xbib/netty/http/client/test/NettyHttpTestExtension.java +++ b/netty-http-client/src/test/java/org/xbib/netty/http/client/test/NettyHttpTestExtension.java @@ -1,10 +1,8 @@ package org.xbib.netty.http.client.test; -import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.ExtensionContext; -import java.security.Security; import java.util.logging.ConsoleHandler; import java.util.logging.Handler; import java.util.logging.Level; @@ -16,9 +14,9 @@ public class NettyHttpTestExtension implements BeforeAllCallback { @Override public void beforeAll(ExtensionContext context) { - if (Security.getProvider("BC") == null) { - Security.addProvider(new BouncyCastleProvider()); - } + //if (Security.getProvider("BC") == null) { + // Security.addProvider(new BouncyCastleProvider()); + //} System.setProperty("io.netty.noUnsafe", Boolean.toString(true)); // System.setProperty("io.netty.leakDetection.level", "paranoid"); Level level = Level.INFO; diff --git a/netty-http-client/src/test/java/org/xbib/netty/http/client/test/pool/DisableTestCondition.java b/netty-http-client/src/test/java/org/xbib/netty/http/client/test/pool/DisableTestCondition.java deleted file mode 100644 index 9ea45f8..0000000 --- a/netty-http-client/src/test/java/org/xbib/netty/http/client/test/pool/DisableTestCondition.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.xbib.netty.http.client.test.pool; - -import io.netty.channel.epoll.Epoll; -import org.junit.jupiter.api.extension.ConditionEvaluationResult; -import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.jupiter.api.extension.ExtensionContext; - -public class DisableTestCondition implements ExecutionCondition { - @Override - public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { - if (Epoll.isAvailable()) { - return ConditionEvaluationResult.enabled("Test enabled"); - } else { - return ConditionEvaluationResult.disabled("Test disabled"); - } - } -} diff --git a/netty-http-client/src/test/java/org/xbib/netty/http/client/test/pool/DisabledIfEpolllNotAvailable.java b/netty-http-client/src/test/java/org/xbib/netty/http/client/test/pool/DisabledIfEpolllNotAvailable.java deleted file mode 100644 index b9b6d74..0000000 --- a/netty-http-client/src/test/java/org/xbib/netty/http/client/test/pool/DisabledIfEpolllNotAvailable.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.xbib.netty.http.client.test.pool; - -import org.junit.jupiter.api.extension.ExtendWith; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target({ElementType.TYPE, ElementType.METHOD}) -@Retention(RetentionPolicy.RUNTIME) -@ExtendWith(DisableTestCondition.class) -public @interface DisabledIfEpolllNotAvailable { -} diff --git a/netty-http-client/src/test/java/org/xbib/netty/http/client/test/pool/EpollTest.java b/netty-http-client/src/test/java/org/xbib/netty/http/client/test/pool/EpollTest.java deleted file mode 100644 index a60e36d..0000000 --- a/netty-http-client/src/test/java/org/xbib/netty/http/client/test/pool/EpollTest.java +++ /dev/null @@ -1,184 +0,0 @@ -package org.xbib.netty.http.client.test.pool; - -import io.netty.bootstrap.Bootstrap; -import io.netty.bootstrap.ServerBootstrap; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; -import io.netty.channel.Channel; -import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInitializer; -import io.netty.channel.ChannelOption; -import io.netty.channel.EventLoopGroup; -import io.netty.channel.SimpleChannelInboundHandler; -import io.netty.channel.epoll.EpollEventLoopGroup; -import io.netty.channel.epoll.EpollServerSocketChannel; -import io.netty.channel.epoll.EpollSocketChannel; -import io.netty.channel.socket.SocketChannel; - -import io.netty.handler.codec.http.HttpVersion; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.xbib.netty.http.common.HttpAddress; -import org.xbib.netty.http.client.api.Pool; -import org.xbib.netty.http.client.pool.BoundedChannelPool; - -import java.io.Closeable; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.LongAdder; -import java.util.logging.Level; -import java.util.logging.Logger; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -@DisabledIfEpolllNotAvailable -class EpollTest { - - private static final Logger logger = Logger.getLogger(EpollTest.class.getName()); - - private static final int CONCURRENCY = 4; - - private static final List NODES = - Collections.singletonList(HttpAddress.http1("localhost", 12345)); - - private static final long TEST_TIME_SECONDS = 100; - - private static final int ATTEMPTS = 1_000; - - private static final int FAIL_EVERY_ATTEMPT = 10; - - private static final ByteBuf PAYLOAD = Unpooled.directBuffer(0x1000).writeZero(0x1000); - - private MockEpollServer mockEpollServer; - - private Pool channelPool; - - private EventLoopGroup eventLoopGroup; - - @BeforeAll - void setUp() throws Exception { - mockEpollServer = new MockEpollServer(12345, FAIL_EVERY_ATTEMPT); - Semaphore semaphore = new Semaphore(CONCURRENCY); - eventLoopGroup = new EpollEventLoopGroup(); - Bootstrap bootstrap = new Bootstrap() - .group(eventLoopGroup) - .channel(EpollSocketChannel.class) - .handler(new ChannelInitializer() { - @Override - protected void initChannel(SocketChannel socketChannel) { - socketChannel.pipeline().addLast(new DummyClientChannelHandler()); - } - }) - .option(ChannelOption.SO_KEEPALIVE, true) - .option(ChannelOption.SO_REUSEADDR, true) - .option(ChannelOption.TCP_NODELAY, true); - channelPool = new BoundedChannelPool<>(semaphore, HttpVersion.HTTP_1_1, - NODES, bootstrap, null, 0, BoundedChannelPool.PoolKeySelectorType.ROUNDROBIN); - channelPool.prepare(CONCURRENCY); - } - - @AfterAll - void tearDown() throws Exception { - channelPool.close(); - eventLoopGroup.shutdownGracefully(); - mockEpollServer.close(); - } - - @Test - void testPoolEpoll() throws Exception { - LongAdder longAdder = new LongAdder(); - ExecutorService executor = Executors.newFixedThreadPool(CONCURRENCY); - for(int i = 0; i < CONCURRENCY; i ++) { - executor.submit(() -> { - Channel channel; - for(int j = 0; j < ATTEMPTS; j ++) { - try { - while ((channel = channelPool.acquire()) == null) { - Thread.sleep(1); // very short? - } - channel.writeAndFlush(PAYLOAD.retain()).sync(); - channelPool.release(channel, false); - longAdder.increment(); - } catch (InterruptedException e) { - break; - } catch (Throwable cause) { - logger.log(Level.WARNING, cause.getMessage(), cause); - } - } - }); - } - executor.shutdown(); - executor.awaitTermination(TEST_TIME_SECONDS, TimeUnit.SECONDS); - assertTrue(executor.isTerminated()); - assertEquals(CONCURRENCY * ATTEMPTS, longAdder.sum(), - 2 * CONCURRENCY * ATTEMPTS / FAIL_EVERY_ATTEMPT); - } - - class DummyClientChannelHandler extends SimpleChannelInboundHandler { - - @Override - protected void channelRead0(ChannelHandlerContext ctx, Object msg) { - } - - @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { - super.exceptionCaught(ctx, cause); - logger.log(Level.WARNING, cause.getMessage(), cause); - } - } - - class MockEpollServer implements Closeable { - - private final EventLoopGroup dispatchGroup; - - private final EventLoopGroup workerGroup; - - private final ChannelFuture bindFuture; - - private final AtomicLong reqCounter; - - MockEpollServer(int port, int dropEveryRequest) throws InterruptedException { - dispatchGroup = new EpollEventLoopGroup(); - workerGroup = new EpollEventLoopGroup(); - reqCounter = new AtomicLong(0); - ServerBootstrap bootstrap = new ServerBootstrap() - .group(dispatchGroup, workerGroup) - .channel(EpollServerSocketChannel.class) - .childHandler(new ChannelInitializer() { - @Override - public void initChannel(SocketChannel ch) { - if (dropEveryRequest > 0) { - ch.pipeline().addLast(new SimpleChannelInboundHandler() { - @Override - protected void channelRead0(ChannelHandlerContext ctx, Object msg) { - if (reqCounter.incrementAndGet() % dropEveryRequest == 0) { - Channel channel = ctx.channel(); - logger.log(Level.INFO,"dropping the connection " + channel); - channel.close(); - } - } - }); - } - } - }); - bindFuture = bootstrap.bind(port).sync(); - } - - @Override - public void close() { - bindFuture.channel().close(); - workerGroup.shutdownGracefully(); - dispatchGroup.shutdownGracefully(); - } - } -} diff --git a/netty-http-common/src/main/java/org/xbib/netty/http/common/ServerCertificateProvider.java b/netty-http-common/src/main/java/org/xbib/netty/http/common/ServerCertificateProvider.java new file mode 100644 index 0000000..1a33603 --- /dev/null +++ b/netty-http-common/src/main/java/org/xbib/netty/http/common/ServerCertificateProvider.java @@ -0,0 +1,32 @@ +package org.xbib.netty.http.common; + +import java.io.InputStream; + +public interface ServerCertificateProvider { + + /** + * Prepare the server certificate, if appropriate. + * + * @param fqdn the full qualified domain name. + */ + void prepare(String fqdn) throws Exception; + + /** + * Returns the generated RSA private key file in PEM format. + * @return input stream of private key + */ + InputStream getPrivateKey(); + + /** + * Returns the generated X.509 certificate file in PEM format. + * @return input stream of certificate + */ + InputStream getCertificateChain(); + + /** + * A key password or null if key password is not required. + * @return key password + */ + String getKeyPassword(); + +} diff --git a/netty-http-common/src/main/java/org/xbib/netty/http/common/TransportProvider.java b/netty-http-common/src/main/java/org/xbib/netty/http/common/TransportProvider.java new file mode 100644 index 0000000..6becb21 --- /dev/null +++ b/netty-http-common/src/main/java/org/xbib/netty/http/common/TransportProvider.java @@ -0,0 +1,16 @@ +package org.xbib.netty.http.common; + +import io.netty.channel.EventLoopGroup; +import io.netty.channel.socket.ServerSocketChannel; +import io.netty.channel.socket.SocketChannel; +import java.util.concurrent.ThreadFactory; + +public interface TransportProvider { + + EventLoopGroup createEventLoopGroup(int nThreads, ThreadFactory threadFactory); + + Class createSocketChannelClass(); + + Class createServerSocketChannelClass(); + +} diff --git a/netty-http-epoll/build.gradle b/netty-http-epoll/build.gradle new file mode 100644 index 0000000..0bd2900 --- /dev/null +++ b/netty-http-epoll/build.gradle @@ -0,0 +1,4 @@ +dependencies { + compile project(":netty-http-common") + compile "io.netty:netty-transport-native-epoll:${project.property('netty.version')}" +} diff --git a/netty-http-epoll/src/main/java/org/xbib/netty/http/epoll/EpollTransportProvider.java b/netty-http-epoll/src/main/java/org/xbib/netty/http/epoll/EpollTransportProvider.java new file mode 100644 index 0000000..9516c4b --- /dev/null +++ b/netty-http-epoll/src/main/java/org/xbib/netty/http/epoll/EpollTransportProvider.java @@ -0,0 +1,29 @@ +package org.xbib.netty.http.epoll; + +import io.netty.channel.EventLoopGroup; +import io.netty.channel.epoll.Epoll; +import io.netty.channel.epoll.EpollEventLoopGroup; +import io.netty.channel.epoll.EpollServerSocketChannel; +import io.netty.channel.epoll.EpollSocketChannel; +import io.netty.channel.socket.ServerSocketChannel; +import io.netty.channel.socket.SocketChannel; +import org.xbib.netty.http.common.TransportProvider; +import java.util.concurrent.ThreadFactory; + +public class EpollTransportProvider implements TransportProvider { + + @Override + public EventLoopGroup createEventLoopGroup(int nThreads, ThreadFactory threadFactory) { + return Epoll.isAvailable() ? new EpollEventLoopGroup(nThreads, threadFactory) : null; + } + + @Override + public Class createSocketChannelClass() { + return Epoll.isAvailable() ? EpollSocketChannel.class : null; + } + + @Override + public Class createServerSocketChannelClass() { + return Epoll.isAvailable() ? EpollServerSocketChannel.class : null; + } +} diff --git a/netty-http-epoll/src/main/resources/META-INF/services/org.xbib.netty.http.common.TransportProvider b/netty-http-epoll/src/main/resources/META-INF/services/org.xbib.netty.http.common.TransportProvider new file mode 100644 index 0000000..8ccf661 --- /dev/null +++ b/netty-http-epoll/src/main/resources/META-INF/services/org.xbib.netty.http.common.TransportProvider @@ -0,0 +1 @@ +org.xbib.netty.http.epoll.EpollTransportProvider \ No newline at end of file diff --git a/netty-http-kqueue/build.gradle b/netty-http-kqueue/build.gradle new file mode 100644 index 0000000..3780875 --- /dev/null +++ b/netty-http-kqueue/build.gradle @@ -0,0 +1,4 @@ +dependencies { + compile project(":netty-http-common") + compile "io.netty:netty-transport-native-kqueue:${project.property('netty.version')}" +} diff --git a/netty-http-kqueue/src/main/java/org/xbib/netty/http/kqueue/KqueueTransportProvider.java b/netty-http-kqueue/src/main/java/org/xbib/netty/http/kqueue/KqueueTransportProvider.java new file mode 100644 index 0000000..e919b86 --- /dev/null +++ b/netty-http-kqueue/src/main/java/org/xbib/netty/http/kqueue/KqueueTransportProvider.java @@ -0,0 +1,29 @@ +package org.xbib.netty.http.kqueue; + +import io.netty.channel.EventLoopGroup; +import io.netty.channel.kqueue.KQueue; +import io.netty.channel.kqueue.KQueueEventLoopGroup; +import io.netty.channel.kqueue.KQueueServerSocketChannel; +import io.netty.channel.kqueue.KQueueSocketChannel; +import io.netty.channel.socket.ServerSocketChannel; +import io.netty.channel.socket.SocketChannel; +import org.xbib.netty.http.common.TransportProvider; +import java.util.concurrent.ThreadFactory; + +public class KqueueTransportProvider implements TransportProvider { + + @Override + public EventLoopGroup createEventLoopGroup(int nThreads, ThreadFactory threadFactory) { + return KQueue.isAvailable() ? new KQueueEventLoopGroup(nThreads, threadFactory) : null; + } + + @Override + public Class createSocketChannelClass() { + return KQueue.isAvailable() ? KQueueSocketChannel.class : null; + } + + @Override + public Class createServerSocketChannelClass() { + return KQueue.isAvailable() ? KQueueServerSocketChannel.class : null; + } +} diff --git a/netty-http-kqueue/src/main/resources/META-INF/services/org.xbib.netty.http.common.TransportProvider b/netty-http-kqueue/src/main/resources/META-INF/services/org.xbib.netty.http.common.TransportProvider new file mode 100644 index 0000000..6714ec5 --- /dev/null +++ b/netty-http-kqueue/src/main/resources/META-INF/services/org.xbib.netty.http.common.TransportProvider @@ -0,0 +1 @@ +org.xbib.netty.http.kqueue.KqueueTransportProvider \ No newline at end of file diff --git a/netty-http-server/build.gradle b/netty-http-server/build.gradle index 79c328f..870f433 100644 --- a/netty-http-server/build.gradle +++ b/netty-http-server/build.gradle @@ -1,8 +1,16 @@ +import org.apache.tools.ant.taskdefs.condition.Os + dependencies { compile project(":netty-http-common") compile project(":netty-http-server-api") - compile "io.netty:netty-transport-native-epoll:${project.property('netty.version')}" - compile "io.netty:netty-tcnative-boringssl-static:${project.property('tcnative.version')}" - compile "org.bouncycastle:bcpkix-jdk15on:${project.property('bouncycastle.version')}" + // we are an Mac OS X 10.12, but tcnative > 2.0.26 is for 10.13+ + if (Os.isFamily(Os.FAMILY_MAC)) { + runtime "io.netty:netty-tcnative-boringssl-static:${project.property('tcnative-legacy-macosx.version')}" + runtime project(':netty-http-kqueue') + } else { + runtime "io.netty:netty-tcnative-boringssl-static:${project.property('tcnative.version')}" + runtime project(':netty-http-epoll') + } testCompile project(":netty-http-client") + testRuntime project(":netty-http-bouncycastle") } 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 df0cf01..0f51c34 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,13 +8,13 @@ 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.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.tls.SelfSignedCertificate; import javax.net.ssl.TrustManagerFactory; import java.io.IOException; @@ -27,13 +27,18 @@ import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Objects; +import java.util.ServiceLoader; import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; /** * The {@code Domain} class represents a virtual server with a name, with or without SSL. */ public class Domain { + private static final Logger logger = Logger.getLogger(Domain.class.getName()); + private final String name; private final Set aliases; @@ -259,10 +264,19 @@ public class Domain { } public Builder setSelfCert() throws Exception { - SelfSignedCertificate selfSignedCertificate = new SelfSignedCertificate(serverName); - setKeyCertChainInputStream(selfSignedCertificate.certificate()); - setKeyInputStream(selfSignedCertificate.privateKey()); - setKeyPassword(null); + 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()); + 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?"); + } return this; } 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 6155c65..5cb31cb 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 @@ -6,9 +6,6 @@ import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.WriteBufferWaterMark; -import io.netty.channel.epoll.Epoll; -import io.netty.channel.epoll.EpollEventLoopGroup; -import io.netty.channel.epoll.EpollServerSocketChannel; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.ServerSocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; @@ -20,6 +17,7 @@ import io.netty.util.DomainNameMapping; import io.netty.util.DomainNameMappingBuilder; import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.common.NetworkUtils; +import org.xbib.netty.http.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.ServerResponse; @@ -328,9 +326,15 @@ public final class Server implements AutoCloseable { EventLoopGroup parentEventLoopGroup ) { EventLoopGroup eventLoopGroup = parentEventLoopGroup; if (eventLoopGroup == null) { - eventLoopGroup = serverConfig.isEpoll() ? - new EpollEventLoopGroup(serverConfig.getParentThreadCount(), new HttpServerParentThreadFactory()) : - new NioEventLoopGroup(serverConfig.getParentThreadCount(), new HttpServerParentThreadFactory()); + ServiceLoader transportProviders = ServiceLoader.load(TransportProvider.class); + for (TransportProvider transportProvider : transportProviders) { + if (serverConfig.getTransportProviderName() == null || serverConfig.getTransportProviderName().equals(transportProvider.getClass().getName())) { + eventLoopGroup = transportProvider.createEventLoopGroup(serverConfig.getParentThreadCount(), new HttpServerParentThreadFactory()); + } + } + } + if (eventLoopGroup == null) { + eventLoopGroup = new NioEventLoopGroup(serverConfig.getParentThreadCount(), new HttpServerParentThreadFactory()); } return eventLoopGroup; } @@ -339,9 +343,15 @@ public final class Server implements AutoCloseable { EventLoopGroup childEventLoopGroup ) { EventLoopGroup eventLoopGroup = childEventLoopGroup; if (eventLoopGroup == null) { - eventLoopGroup = serverConfig.isEpoll() ? - new EpollEventLoopGroup(serverConfig.getChildThreadCount(), new HttpServerChildThreadFactory()) : - new NioEventLoopGroup(serverConfig.getChildThreadCount(), new HttpServerChildThreadFactory()); + ServiceLoader transportProviders = ServiceLoader.load(TransportProvider.class); + for (TransportProvider transportProvider : transportProviders) { + if (serverConfig.getTransportProviderName() == null || serverConfig.getTransportProviderName().equals(transportProvider.getClass().getName())) { + eventLoopGroup = transportProvider.createEventLoopGroup(serverConfig.getChildThreadCount(), new HttpServerChildThreadFactory()); + } + } + } + if (eventLoopGroup == null) { + eventLoopGroup = new NioEventLoopGroup(serverConfig.getChildThreadCount(), new HttpServerChildThreadFactory()); } return eventLoopGroup; } @@ -350,12 +360,16 @@ public final class Server implements AutoCloseable { Class socketChannelClass) { Class channelClass = socketChannelClass; if (channelClass == null) { - if (serverConfig.isEpoll() && Epoll.isAvailable()) { - channelClass = EpollServerSocketChannel.class; - } else { - channelClass = NioServerSocketChannel.class; + ServiceLoader transportProviders = ServiceLoader.load(TransportProvider.class); + for (TransportProvider transportProvider : transportProviders) { + if (serverConfig.getTransportProviderName() == null || serverConfig.getTransportProviderName().equals(transportProvider.getClass().getName())) { + channelClass = transportProvider.createServerSocketChannelClass(); + } } } + if (channelClass == null) { + channelClass = NioServerSocketChannel.class; + } return channelClass; } @@ -470,6 +484,11 @@ public final class Server implements AutoCloseable { return this; } + public Builder setTransportProviderName(String transportProviderName) { + this.serverConfig.setTransportProviderName(transportProviderName); + return this; + } + public Builder setParentEventLoopGroup(EventLoopGroup parentEventLoopGroup) { this.parentEventLoopGroup = parentEventLoopGroup; return this; @@ -485,11 +504,6 @@ public final class Server implements AutoCloseable { return this; } - public Builder setUseEpoll(boolean useEpoll) { - this.serverConfig.setEpoll(useEpoll); - return this; - } - public Builder setConnectTimeoutMillis(int connectTimeoutMillis) { this.serverConfig.setConnectTimeoutMillis(connectTimeoutMillis); return this; 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 a41d715..f19c476 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 @@ -1,7 +1,6 @@ package org.xbib.netty.http.server; import io.netty.channel.WriteBufferWaterMark; -import io.netty.channel.epoll.Epoll; import io.netty.handler.codec.http2.Http2Settings; import io.netty.handler.logging.LogLevel; import io.netty.handler.ssl.CipherSuiteFilter; @@ -35,10 +34,7 @@ public class ServerConfig { */ LogLevel DEBUG_LOG_LEVEL = LogLevel.DEBUG; - /** - * The default for selecting epoll. If available, select epoll. - */ - boolean EPOLL = Epoll.isAvailable(); + String TRANSPORT_PROVIDER_NAME = null; /** * Let Netty decide about parent thread count. @@ -200,7 +196,7 @@ public class ServerConfig { private LogLevel debugLogLevel = Defaults.DEBUG_LOG_LEVEL; - private boolean epoll = Defaults.EPOLL; + private String transportProviderName = Defaults.TRANSPORT_PROVIDER_NAME; private int parentThreadCount = Defaults.PARENT_THREAD_COUNT; @@ -293,23 +289,13 @@ public class ServerConfig { return debugLogLevel; } - public ServerConfig enableEpoll() { - this.epoll = true; + public ServerConfig setTransportProviderName(String transportProviderName) { + this.transportProviderName = transportProviderName; return this; } - public ServerConfig disableEpoll() { - this.epoll = false; - return this; - } - - public ServerConfig setEpoll(boolean epoll) { - this.epoll = epoll; - return this; - } - - public boolean isEpoll() { - return epoll; + public String getTransportProviderName() { + return transportProviderName; } public ServerConfig setParentThreadCount(int parentThreadCount) { 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 d458566..6fbb726 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 @@ -128,7 +128,7 @@ class CleartextTest { @Test void testMultithreadPooledClearTextHttp2() throws Exception { int threads = 2; - int loop = 1000; + int loop = 1024; HttpAddress httpAddress = HttpAddress.http2("localhost", 8008); Domain domain = Domain.builder(httpAddress) .singleEndpoint("/", (request, response) -> @@ -179,13 +179,13 @@ class CleartextTest { }); } executorService.shutdown(); - boolean terminated = executorService.awaitTermination(10L, TimeUnit.SECONDS); + boolean terminated = executorService.awaitTermination(20L, TimeUnit.SECONDS); executorService.shutdownNow(); logger.log(Level.INFO, "terminated = " + terminated + ", now waiting for transport to complete"); - transport.get(10L, TimeUnit.SECONDS); + transport.get(20L, TimeUnit.SECONDS); } finally { - server.shutdownGracefully(10L, TimeUnit.SECONDS); - client.shutdownGracefully(10L, TimeUnit.SECONDS); + server.shutdownGracefully(20L, TimeUnit.SECONDS); + client.shutdownGracefully(20L, TimeUnit.SECONDS); } logger.log(Level.INFO, "server requests = " + server.getRequestCounter() + " server responses = " + server.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 8a4690d..eeb1d0f 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 @@ -71,7 +71,7 @@ class EncryptedTest { @Test void testPooledSecureHttp2() throws Exception { - int loop = 4096; + int loop = 1024; HttpAddress httpAddress = HttpAddress.secureHttp2("localhost", 8143); Server server = Server.builder(Domain.builder(httpAddress) .setSelfCert() @@ -116,8 +116,8 @@ class EncryptedTest { @Test void testMultithreadPooledSecureHttp2() throws Exception { - int threads = 4; - int loop = 4 * 1024; + int threads = 2; + int loop = 1024; HttpAddress httpAddress = HttpAddress.secureHttp2("localhost", 8143); Server server = Server.builder(Domain.builder(httpAddress) .setSelfCert() @@ -164,12 +164,13 @@ class EncryptedTest { }); } executorService.shutdown(); - boolean terminated = executorService.awaitTermination(60, TimeUnit.SECONDS); + boolean terminated = executorService.awaitTermination(20, TimeUnit.SECONDS); + executorService.shutdownNow(); logger.log(Level.INFO, "terminated = " + terminated + ", now waiting for transport to complete"); - transport.get(60, TimeUnit.SECONDS); + transport.get(20, TimeUnit.SECONDS); } finally { - client.shutdownGracefully(); - server.shutdownGracefully(); + client.shutdownGracefully(20, TimeUnit.SECONDS); + server.shutdownGracefully(20, TimeUnit.SECONDS); } assertEquals(threads * loop , counter.get()); } diff --git a/settings.gradle b/settings.gradle index decb4d7..e8a0fe4 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,4 +1,7 @@ include 'netty-http-common' +include 'netty-http-epoll' +include 'netty-http-kqueue' +include 'netty-http-bouncycastle' include 'netty-http-client-api' include 'netty-http-client' include 'netty-http-client-rest'