package org.jooby.internal.netty; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.isA; import java.io.File; import java.io.IOException; import java.util.List; import java.util.concurrent.ThreadFactory; import org.jooby.spi.HttpHandler; import org.jooby.spi.Server; import org.jooby.test.MockUnit; import org.jooby.test.MockUnit.Block; import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; import com.typesafe.config.ConfigValueFactory; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.ServerChannel; import io.netty.channel.epoll.Epoll; import io.netty.channel.epoll.EpollChannelOption; import io.netty.channel.epoll.EpollEventLoopGroup; import io.netty.channel.epoll.EpollServerSocketChannel; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; import io.netty.util.concurrent.DefaultEventExecutorGroup; import io.netty.util.concurrent.DefaultThreadFactory; import io.netty.util.concurrent.EventExecutorGroup; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; @RunWith(PowerMockRunner.class) @PrepareForTest({NettyServer.class, NioEventLoopGroup.class, DefaultThreadFactory.class, DefaultEventExecutorGroup.class, ServerBootstrap.class, SslContextBuilder.class, File.class, Epoll.class, EpollEventLoopGroup.class, NettySslContext.class, GenericFutureListener.class }) public class NettyServerTest { Config config = ConfigFactory.empty() .withValue("server.http2.enabled", ConfigValueFactory.fromAnyRef(false)) .withValue("netty.threads.Boss", ConfigValueFactory.fromAnyRef(1)) .withValue("netty.threads.Worker", ConfigValueFactory.fromAnyRef(0)) .withValue("netty.threads.Max", ConfigValueFactory.fromAnyRef(2)) .withValue("netty.threads.Name", ConfigValueFactory.fromAnyRef("netty task")) .withValue("netty.options.SO_BACKLOG", ConfigValueFactory.fromAnyRef(1024)) .withValue("netty.worker.options.SO_REUSEADDR", ConfigValueFactory.fromAnyRef(true)) .withValue("netty.http.MaxContentLength", ConfigValueFactory.fromAnyRef("200k")) .withValue("netty.http.MaxInitialLineLength", ConfigValueFactory.fromAnyRef("4k")) .withValue("netty.http.MaxHeaderSize", ConfigValueFactory.fromAnyRef("8k")) .withValue("netty.http.MaxChunkSize", ConfigValueFactory.fromAnyRef("8k")) .withValue("netty.http.IdleTimeout", ConfigValueFactory.fromAnyRef("30s")) .withValue("netty.options.CONNECT_TIMEOUT_MILLIS", ConfigValueFactory.fromAnyRef(1000)) .withValue("application.port", ConfigValueFactory.fromAnyRef(6789)) .withValue("application.host", ConfigValueFactory.fromAnyRef("0.0.0.0")); @SuppressWarnings({"rawtypes", "unchecked" }) private MockUnit.Block parentEventLoop = unit -> { NioEventLoopGroup eventLoop = unit.constructor(NioEventLoopGroup.class) .args(int.class, ThreadFactory.class) .build(1, unit.get(ThreadFactory.class)); unit.registerMock(EventLoopGroup.class, eventLoop); unit.registerMock(NioEventLoopGroup.class, eventLoop); Future future = unit.mock(Future.class); expect(future.addListener(unit.capture(GenericFutureListener.class))).andReturn(future); expect(eventLoop.shutdownGracefully()).andReturn(future); expect(eventLoop.isShuttingDown()).andReturn(false); }; private Block taskThreadFactory = unit -> { DefaultThreadFactory factory = unit.constructor(DefaultThreadFactory.class) .args(String.class) .build("netty task"); unit.registerMock(DefaultThreadFactory.class, factory); }; private Block taskExecutor = unit -> { DefaultEventExecutorGroup executor = unit.constructor(DefaultEventExecutorGroup.class) .args(int.class, ThreadFactory.class) .build(2, unit.get(DefaultThreadFactory.class)); unit.registerMock(EventExecutorGroup.class, executor); }; private Block channel = unit -> { ChannelFuture cfuture = unit.mock(ChannelFuture.class); expect(cfuture.sync()).andReturn(cfuture); Channel channel = unit.mock(Channel.class); expect(channel.closeFuture()).andReturn(cfuture); unit.registerMock(Channel.class, channel); }; private Block noepoll = unit -> { unit.mockStatic(Epoll.class); expect(Epoll.isAvailable()).andReturn(false).times(1, 2); }; @Test public void defaultServer() throws Exception { new MockUnit(HttpHandler.class) .expect(parentThreadFactory("nio-boss")) .expect(noepoll) .expect(parentEventLoop) .expect(taskThreadFactory) .expect(taskExecutor) .expect(channel) .expect(bootstrap(6789)) .run(unit -> { NettyServer server = new NettyServer(unit.get(HttpHandler.class), config); try { assertNotNull(server.executor()); server.start(); assertTrue(server.executor().isPresent()); server.join(); } finally { server.stop(); } }); } @Test public void eventLoopIsShuttingDown() throws Exception { new MockUnit(HttpHandler.class) .expect(parentThreadFactory("nio-boss")) .expect(noepoll) .expect(unit -> { NioEventLoopGroup eventLoop = unit.constructor(NioEventLoopGroup.class) .args(int.class, ThreadFactory.class) .build(1, unit.get(ThreadFactory.class)); unit.registerMock(EventLoopGroup.class, eventLoop); unit.registerMock(NioEventLoopGroup.class, eventLoop); expect(eventLoop.isShuttingDown()).andReturn(true); }) .expect(taskThreadFactory) .expect(taskExecutor) .expect(channel) .expect(bootstrap(6789)) .run(unit -> { NettyServer server = new NettyServer(unit.get(HttpHandler.class), config); try { server.start(); server.join(); } finally { server.stop(); } }); } private Block parentThreadFactory(final String name) { return unit -> { DefaultThreadFactory factory = unit.constructor(DefaultThreadFactory.class) .args(String.class, int.class) .build(name, false); unit.registerMock(ThreadFactory.class, factory); }; } @SuppressWarnings({"unchecked", "rawtypes" }) @Test public void epollServer() throws Exception { new MockUnit(HttpHandler.class) .expect(parentThreadFactory("epoll-boss")) .expect(unit -> { unit.mockStatic(Epoll.class); expect(Epoll.isAvailable()).andReturn(true); EpollEventLoopGroup eventLoop = unit.constructor(EpollEventLoopGroup.class) .args(int.class, ThreadFactory.class) .build(1, unit.get(ThreadFactory.class)); unit.registerMock(EventLoopGroup.class, eventLoop); unit.registerMock(EpollEventLoopGroup.class, eventLoop); Future future = unit.mock(Future.class); expect(future.addListener(unit.capture(GenericFutureListener.class))).andReturn(future); expect(eventLoop.shutdownGracefully()).andReturn(future); expect(eventLoop.isShuttingDown()).andReturn(false); }) .expect(taskThreadFactory) .expect(taskExecutor) .expect(channel) .expect(bootstrap(6789, EpollEventLoopGroup.class, EpollServerSocketChannel.class)) .expect(unit -> { ServerBootstrap bootstrap = unit.get(ServerBootstrap.class); ChannelOption option = EpollChannelOption.TCP_NOTSENT_LOWAT; expect(bootstrap.option(option, 1000L)).andReturn(bootstrap); }) .run(unit -> { NettyServer server = new NettyServer(unit.get(HttpHandler.class), config .withValue("netty.options.TCP_NOTSENT_LOWAT", ConfigValueFactory.fromAnyRef(1000))); try { server.start(); server.join(); } finally { server.stop(); } }); } @Test public void unknownOption() throws Exception { Config config = this.config.withValue("netty.options.x", ConfigValueFactory.fromAnyRef(1)); new MockUnit(HttpHandler.class) .expect(parentThreadFactory("nio-boss")) .expect(noepoll) .expect(parentEventLoop) .expect(taskThreadFactory) .expect(taskExecutor) .expect(channel) .expect(bootstrap(6789)) .run(unit -> { NettyServer server = new NettyServer(unit.get(HttpHandler.class), config); try { server.start(); server.join(); } finally { server.stop(); } }); } @SuppressWarnings({"unchecked", "rawtypes" }) @Test public void serverWithWorkerEventLoop() throws Exception { Config config = this.config.withValue("netty.threads.Worker", ConfigValueFactory.fromAnyRef(1)); new MockUnit(HttpHandler.class) .expect(parentThreadFactory("nio-boss")) .expect(noepoll) .expect(unit -> { NioEventLoopGroup eventLoop = unit.constructor(NioEventLoopGroup.class) .args(int.class, ThreadFactory.class) .build(1, unit.get(ThreadFactory.class)); unit.registerMock(EventLoopGroup.class, eventLoop); Future future = unit.mock(Future.class); expect(future.addListener(unit.capture(GenericFutureListener.class))).andReturn(future); expect(eventLoop.shutdownGracefully()).andReturn(future); expect(eventLoop.isShuttingDown()).andReturn(false); }) .expect(unit -> { DefaultThreadFactory factory = unit.constructor(DefaultThreadFactory.class) .args(String.class, int.class) .build("nio-worker", false); unit.registerMock(DefaultThreadFactory.class, factory); }) .expect(unit -> { NioEventLoopGroup eventLoop = unit.constructor(NioEventLoopGroup.class) .args(int.class, ThreadFactory.class) .build(1, unit.get(DefaultThreadFactory.class)); unit.registerMock(NioEventLoopGroup.class, eventLoop); Future future = unit.mock(Future.class); expect(future.isSuccess()).andReturn(true); expect(future.addListener(unit.capture(GenericFutureListener.class))).andReturn(future); expect(eventLoop.shutdownGracefully()).andReturn(future); expect(eventLoop.isShuttingDown()).andReturn(false); unit.registerMock(Future.class, future); }) .expect(taskThreadFactory) .expect(taskExecutor) .expect(channel) .expect(bootstrap(6789)) .run(unit -> { NettyServer server = new NettyServer(unit.get(HttpHandler.class), config); try { server.start(); server.join(); } finally { server.stop(); } }, unit -> { List<GenericFutureListener> captured = unit.captured(GenericFutureListener.class); captured.get(0) .operationComplete(unit.get(Future.class)); }); } @SuppressWarnings({"unchecked", "rawtypes" }) @Test public void eventLoopError() throws Exception { Config config = this.config.withValue("netty.threads.Worker", ConfigValueFactory.fromAnyRef(1)); new MockUnit(HttpHandler.class) .expect(parentThreadFactory("nio-boss")) .expect(noepoll) .expect(unit -> { NioEventLoopGroup eventLoop = unit.constructor(NioEventLoopGroup.class) .args(int.class, ThreadFactory.class) .build(1, unit.get(ThreadFactory.class)); unit.registerMock(EventLoopGroup.class, eventLoop); Future future = unit.mock(Future.class); expect(future.addListener(unit.capture(GenericFutureListener.class))).andReturn(future); expect(eventLoop.shutdownGracefully()).andReturn(future); expect(eventLoop.isShuttingDown()).andReturn(false); }) .expect(unit -> { DefaultThreadFactory factory = unit.constructor(DefaultThreadFactory.class) .args(String.class, int.class) .build("nio-worker", false); unit.registerMock(DefaultThreadFactory.class, factory); }) .expect(unit -> { NioEventLoopGroup eventLoop = unit.constructor(NioEventLoopGroup.class) .args(int.class, ThreadFactory.class) .build(1, unit.get(DefaultThreadFactory.class)); unit.registerMock(NioEventLoopGroup.class, eventLoop); Future future = unit.mock(Future.class); expect(future.isSuccess()).andReturn(false); expect(future.cause()).andReturn(new IOException("intentional err")); expect(future.addListener(unit.capture(GenericFutureListener.class))).andReturn(future); expect(eventLoop.shutdownGracefully()).andReturn(future); expect(eventLoop.isShuttingDown()).andReturn(false); unit.registerMock(Future.class, future); }) .expect(taskThreadFactory) .expect(taskExecutor) .expect(channel) .expect(bootstrap(6789)) .run(unit -> { NettyServer server = new NettyServer(unit.get(HttpHandler.class), config); try { server.start(); server.join(); } finally { server.stop(); } }, unit -> { List<GenericFutureListener> captured = unit.captured(GenericFutureListener.class); captured.get(0) .operationComplete(unit.get(Future.class)); }); } @Test public void httpsServer() throws Exception { Config config = this.config .withValue("application.tmpdir", ConfigValueFactory.fromAnyRef("target")) .withValue("application.securePort", ConfigValueFactory.fromAnyRef(8443)) .withValue("ssl.keystore.cert", ConfigValueFactory.fromAnyRef("org/jooby/unsecure.crt")) .withValue("ssl.keystore.key", ConfigValueFactory.fromAnyRef("org/jooby/unsecure.key")); new MockUnit(HttpHandler.class) .expect(parentThreadFactory("nio-boss")) .expect(noepoll) .expect(parentEventLoop) .expect(taskThreadFactory) .expect(taskExecutor) .expect(channel) .expect(bootstrap(6789)) .expect(bootstrap(8443)) .expect(unit -> { unit.mockStatic(NettySslContext.class); SslContext sslCtx = unit.mock(SslContext.class); expect(NettySslContext.build(config)).andReturn(sslCtx); }) .run(unit -> { NettyServer server = new NettyServer(unit.get(HttpHandler.class), config); try { server.start(); server.join(); } finally { server.stop(); } }); } private Block bootstrap(final int port) { return bootstrap(port, NioEventLoopGroup.class, NioServerSocketChannel.class); } private Block bootstrap(final int port, final Class<? extends EventLoopGroup> eventLoop, final Class<? extends ServerChannel> channelClass) { return unit -> { ServerBootstrap bootstrap = unit.mockConstructor(ServerBootstrap.class); expect(bootstrap.group( unit.get(EventLoopGroup.class), unit.get(eventLoop))) .andReturn(bootstrap); LoggingHandler handler = unit.constructor(LoggingHandler.class) .args(Class.class, LogLevel.class) .build(Server.class, LogLevel.DEBUG); expect(bootstrap.channel(channelClass)).andReturn(bootstrap); expect(bootstrap.handler(handler)).andReturn(bootstrap); expect(bootstrap.childHandler(isA(NettyPipeline.class))).andReturn(bootstrap); expect(bootstrap.option(ChannelOption.SO_BACKLOG, 1024)).andReturn(bootstrap); expect(bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 1000)).andReturn(bootstrap); expect(bootstrap.childOption(ChannelOption.SO_REUSEADDR, true)).andReturn(bootstrap); ChannelFuture future = unit.mock(ChannelFuture.class); expect(future.sync()).andReturn(future); expect(future.channel()).andReturn(unit.get(Channel.class)); expect(bootstrap.bind("0.0.0.0", port)).andReturn(future); unit.registerMock(ServerBootstrap.class, bootstrap); }; } }