Refactor test HttpServer implementations

Due to the static nature of JUnit parameterized test inputs, an
HttpServer instance is re-used for all tests per test class.

This commit adds lifecycle handling to AbstractHttpServer with a
lifecycle monitor to ensure test server fields are properly
initialized and reset after each test .
This commit is contained in:
Rossen Stoyanchev 2017-01-27 10:31:08 -05:00
parent 33f0995b42
commit cb0d992303
6 changed files with 195 additions and 178 deletions

View File

@ -20,12 +20,12 @@ import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import org.springframework.http.server.reactive.HttpHandler; import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.util.SocketUtils; import org.springframework.util.Assert;
/** /**
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
*/ */
public class HttpServerSupport { public abstract class AbstractHttpServer implements HttpServer {
private String host = "0.0.0.0"; private String host = "0.0.0.0";
@ -35,6 +35,10 @@ public class HttpServerSupport {
private Map<String, HttpHandler> handlerMap; private Map<String, HttpHandler> handlerMap;
private boolean running;
private final Object lifecycleMonitor = new Object();
public void setHost(String host) { public void setHost(String host) {
this.host = host; this.host = host;
@ -49,9 +53,6 @@ public class HttpServerSupport {
} }
public int getPort() { public int getPort() {
if(this.port == -1) {
this.port = SocketUtils.findAvailableTcpPort(8080);
}
return this.port; return this.port;
} }
@ -74,4 +75,76 @@ public class HttpServerSupport {
return this.handlerMap; return this.handlerMap;
} }
// InitializingBean
@Override
public final void afterPropertiesSet() throws Exception {
synchronized (this.lifecycleMonitor) {
Assert.isTrue(this.host != null);
Assert.isTrue(this.port != -1);
Assert.isTrue(this.httpHandler != null || this.handlerMap != null);
Assert.isTrue(!this.running);
initServer();
}
}
protected abstract void initServer() throws Exception;
// Lifecycle
@Override
public boolean isRunning() {
synchronized (this.lifecycleMonitor) {
return this.running;
}
}
@Override
public final void start() {
synchronized (this.lifecycleMonitor) {
if (!isRunning()) {
this.running = true;
try {
startInternal();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
}
}
protected abstract void startInternal() throws Exception;
@Override
public final void stop() {
synchronized (this.lifecycleMonitor) {
if (isRunning()) {
this.running = false;
try {
stopInternal();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
finally {
reset();
}
}
}
}
protected void reset() {
this.host = "0.0.0.0";
this.port = -1;
this.httpHandler = null;
this.handlerMap = null;
}
protected abstract void stopInternal() throws Exception;
} }

View File

@ -21,28 +21,25 @@ import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.servlet.ServletHolder;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.http.server.reactive.JettyHttpHandlerAdapter; import org.springframework.http.server.reactive.JettyHttpHandlerAdapter;
import org.springframework.http.server.reactive.ServletHttpHandlerAdapter; import org.springframework.http.server.reactive.ServletHttpHandlerAdapter;
import org.springframework.util.Assert;
/** /**
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
*/ */
public class JettyHttpServer extends HttpServerSupport implements HttpServer, InitializingBean { public class JettyHttpServer extends AbstractHttpServer {
private Server jettyServer; private Server jettyServer;
private ServletContextHandler contextHandler; private ServletContextHandler contextHandler;
private boolean running;
@Override @Override
public void afterPropertiesSet() throws Exception { protected void initServer() throws Exception {
this.jettyServer = new Server(); this.jettyServer = new Server();
ServletHttpHandlerAdapter servlet = initServletHttpHandlerAdapter(); ServletHttpHandlerAdapter servlet = createServletAdapter();
ServletHolder servletHolder = new ServletHolder(servlet); ServletHolder servletHolder = new ServletHolder(servlet);
this.contextHandler = new ServletContextHandler(this.jettyServer, "", false, false); this.contextHandler = new ServletContextHandler(this.jettyServer, "", false, false);
@ -55,59 +52,54 @@ public class JettyHttpServer extends HttpServerSupport implements HttpServer, In
this.jettyServer.addConnector(connector); this.jettyServer.addConnector(connector);
} }
private ServletHttpHandlerAdapter initServletHttpHandlerAdapter() { private ServletHttpHandlerAdapter createServletAdapter() {
if (getHttpHandlerMap() != null) { return getHttpHandlerMap() != null ? new JettyHttpHandlerAdapter(getHttpHandlerMap()) :
return new JettyHttpHandlerAdapter(getHttpHandlerMap()); new JettyHttpHandlerAdapter(getHttpHandler());
}
else {
Assert.notNull(getHttpHandler());
return new JettyHttpHandlerAdapter(getHttpHandler());
}
} }
@Override @Override
public void start() { protected void startInternal() throws Exception {
if (!this.running) { this.jettyServer.start();
try {
this.running = true;
this.jettyServer.start();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
} }
@Override @Override
public void stop() { protected void stopInternal() throws Exception {
if (this.running) { try {
if (this.contextHandler.isRunning()) {
this.contextHandler.stop();
}
}
finally {
try { try {
this.running = false; if (this.jettyServer.isRunning()) {
if (this.contextHandler.isRunning()) { this.jettyServer.setStopTimeout(5000);
this.contextHandler.stop(); this.jettyServer.stop();
this.jettyServer.destroy();
} }
} }
catch (Exception ex) { catch (Exception ex) {
throw new IllegalStateException(ex); // ignore
}
finally {
try {
if (this.jettyServer.isRunning()) {
this.jettyServer.setStopTimeout(5000);
this.jettyServer.stop();
this.jettyServer.destroy();
}
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
} }
} }
} }
@Override @Override
public boolean isRunning() { protected void reset() {
return this.running; super.reset();
try {
if (this.jettyServer.isRunning()) {
this.jettyServer.setStopTimeout(5000);
this.jettyServer.stop();
this.jettyServer.destroy();
}
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
finally {
this.jettyServer = null;
this.contextHandler = null;
}
} }
} }

View File

@ -18,16 +18,16 @@ package org.springframework.http.server.reactive.bootstrap;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import org.jetbrains.annotations.NotNull;
import reactor.core.Loopback; import reactor.core.Loopback;
import reactor.ipc.netty.NettyContext; import reactor.ipc.netty.NettyContext;
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter; import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
import org.springframework.util.Assert;
/** /**
* @author Stephane Maldini * @author Stephane Maldini
*/ */
public class ReactorHttpServer extends HttpServerSupport implements HttpServer, Loopback { public class ReactorHttpServer extends AbstractHttpServer implements Loopback {
private ReactorHttpHandlerAdapter reactorHandler; private ReactorHttpHandlerAdapter reactorHandler;
@ -37,25 +37,19 @@ public class ReactorHttpServer extends HttpServerSupport implements HttpServer,
@Override @Override
public void afterPropertiesSet() throws Exception { protected void initServer() throws Exception {
if (getHttpHandlerMap() != null) { this.reactorHandler = createHttpHandlerAdapter();
this.reactorHandler = new ReactorHttpHandlerAdapter(getHttpHandlerMap()); this.reactorServer = reactor.ipc.netty.http.server.HttpServer.create(getHost(), getPort());
}
else {
Assert.notNull(getHttpHandler());
this.reactorHandler = new ReactorHttpHandlerAdapter(getHttpHandler());
}
this.reactorServer = reactor.ipc.netty.http.server.HttpServer
.create(getHost(), getPort());
} }
@NotNull
@Override private ReactorHttpHandlerAdapter createHttpHandlerAdapter() {
public boolean isRunning() { return getHttpHandlerMap() != null ?
NettyContext context = this.nettyContext.get(); new ReactorHttpHandlerAdapter(getHttpHandlerMap()) :
return (context != null && context.channel().isActive()); new ReactorHttpHandlerAdapter(getHttpHandler());
} }
@Override @Override
public Object connectedInput() { public Object connectedInput() {
return this.reactorServer; return this.reactorServer;
@ -67,17 +61,22 @@ public class ReactorHttpServer extends HttpServerSupport implements HttpServer,
} }
@Override @Override
public void start() { protected void startInternal() {
if (this.nettyContext.get() == null) { NettyContext nettyContext = this.reactorServer.newHandler(this.reactorHandler).block();
this.nettyContext.set(this.reactorServer.newHandler(reactorHandler).block()); this.nettyContext.set(nettyContext);
}
} }
@Override @Override
public void stop() { protected void stopInternal() {
NettyContext context = this.nettyContext.getAndSet(null); this.nettyContext.get().dispose();
if (context != null) {
context.dispose();
}
} }
@Override
protected void reset() {
super.reset();
this.reactorServer = null;
this.reactorHandler = null;
this.nettyContext.set(null);
}
} }

View File

@ -19,58 +19,51 @@ package org.springframework.http.server.reactive.bootstrap;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import org.jetbrains.annotations.NotNull;
import org.springframework.http.server.reactive.RxNettyHttpHandlerAdapter; import org.springframework.http.server.reactive.RxNettyHttpHandlerAdapter;
import org.springframework.util.Assert;
/** /**
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
*/ */
public class RxNettyHttpServer extends HttpServerSupport implements HttpServer { public class RxNettyHttpServer extends AbstractHttpServer {
private RxNettyHttpHandlerAdapter rxNettyHandler; private RxNettyHttpHandlerAdapter rxNettyHandler;
private io.reactivex.netty.protocol.http.server.HttpServer<ByteBuf, ByteBuf> rxNettyServer; private io.reactivex.netty.protocol.http.server.HttpServer<ByteBuf, ByteBuf> rxNettyServer;
private boolean running;
@Override @Override
public void afterPropertiesSet() throws Exception { protected void initServer() throws Exception {
this.rxNettyHandler = createHttpHandlerAdapter();
if (getHttpHandlerMap() != null) {
this.rxNettyHandler = new RxNettyHttpHandlerAdapter(getHttpHandlerMap());
}
else {
Assert.notNull(getHttpHandler());
this.rxNettyHandler = new RxNettyHttpHandlerAdapter(getHttpHandler());
}
this.rxNettyServer = io.reactivex.netty.protocol.http.server.HttpServer this.rxNettyServer = io.reactivex.netty.protocol.http.server.HttpServer
.newServer(new InetSocketAddress(getHost(), getPort())); .newServer(new InetSocketAddress(getHost(), getPort()));
} }
@NotNull
@Override private RxNettyHttpHandlerAdapter createHttpHandlerAdapter() {
public boolean isRunning() { return getHttpHandlerMap() != null ?
return this.running; new RxNettyHttpHandlerAdapter(getHttpHandlerMap()) :
new RxNettyHttpHandlerAdapter(getHttpHandler());
} }
@Override @Override
public void start() { protected void startInternal() {
if (!this.running) { this.rxNettyServer.start(this.rxNettyHandler);
this.running = true;
this.rxNettyServer.start(this.rxNettyHandler);
}
} }
@Override @Override
public void stop() { protected void stopInternal() {
if (this.running) { this.rxNettyServer.shutdown();
this.running = false; }
this.rxNettyServer.shutdown();
} @Override
protected void reset() {
super.reset();
this.rxNettyServer = null;
this.rxNettyHandler = null;
} }
} }

View File

@ -22,7 +22,6 @@ import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException; import org.apache.catalina.LifecycleException;
import org.apache.catalina.startup.Tomcat; import org.apache.catalina.startup.Tomcat;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.http.server.reactive.ServletHttpHandlerAdapter; import org.springframework.http.server.reactive.ServletHttpHandlerAdapter;
import org.springframework.http.server.reactive.TomcatHttpHandlerAdapter; import org.springframework.http.server.reactive.TomcatHttpHandlerAdapter;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -30,40 +29,34 @@ import org.springframework.util.Assert;
/** /**
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
*/ */
public class TomcatHttpServer extends HttpServerSupport implements HttpServer, InitializingBean { public class TomcatHttpServer extends AbstractHttpServer {
private final String baseDir;
private final Class<?> wsListener;
private Tomcat tomcatServer; private Tomcat tomcatServer;
private boolean running;
private String baseDir;
private Class<?> wsListener;
public TomcatHttpServer() {
}
public TomcatHttpServer(String baseDir) { public TomcatHttpServer(String baseDir) {
this.baseDir = baseDir; this(baseDir, null);
} }
public TomcatHttpServer(String baseDir, Class<?> wsListener) { public TomcatHttpServer(String baseDir, Class<?> wsListener) {
Assert.notNull(baseDir);
this.baseDir = baseDir; this.baseDir = baseDir;
this.wsListener = wsListener; this.wsListener = wsListener;
} }
@Override @Override
public void afterPropertiesSet() throws Exception { protected void initServer() throws Exception {
this.tomcatServer = new Tomcat(); this.tomcatServer = new Tomcat();
if (this.baseDir != null) { this.tomcatServer.setBaseDir(baseDir);
this.tomcatServer.setBaseDir(baseDir);
}
this.tomcatServer.setHostname(getHost()); this.tomcatServer.setHostname(getHost());
this.tomcatServer.setPort(getPort()); this.tomcatServer.setPort(getPort());
ServletHttpHandlerAdapter servlet = initServletHttpHandlerAdapter(); ServletHttpHandlerAdapter servlet = initServletAdapter();
File base = new File(System.getProperty("java.io.tmpdir")); File base = new File(System.getProperty("java.io.tmpdir"));
Context rootContext = tomcatServer.addContext("", base.getAbsolutePath()); Context rootContext = tomcatServer.addContext("", base.getAbsolutePath());
@ -74,47 +67,27 @@ public class TomcatHttpServer extends HttpServerSupport implements HttpServer, I
} }
} }
private ServletHttpHandlerAdapter initServletHttpHandlerAdapter() { private ServletHttpHandlerAdapter initServletAdapter() {
if (getHttpHandlerMap() != null) { return getHttpHandlerMap() != null ?
return new TomcatHttpHandlerAdapter(getHttpHandlerMap()); new TomcatHttpHandlerAdapter(getHttpHandlerMap()) :
} new TomcatHttpHandlerAdapter(getHttpHandler());
else {
Assert.notNull(getHttpHandler());
return new TomcatHttpHandlerAdapter(getHttpHandler());
}
} }
@Override @Override
public void start() { protected void startInternal() throws LifecycleException {
if (!this.running) { this.tomcatServer.start();
try {
this.running = true;
this.tomcatServer.start();
}
catch (LifecycleException ex) {
throw new IllegalStateException(ex);
}
}
} }
@Override @Override
public void stop() { protected void stopInternal() throws Exception {
if (this.running) { this.tomcatServer.stop();
try { this.tomcatServer.destroy();
this.running = false;
this.tomcatServer.stop();
this.tomcatServer.destroy();
}
catch (LifecycleException ex) {
throw new IllegalStateException(ex);
}
}
} }
@Override @Override
public boolean isRunning() { protected void reset() {
return this.running; this.tomcatServer = null;
} }
} }

View File

@ -19,55 +19,42 @@ package org.springframework.http.server.reactive.bootstrap;
import io.undertow.Undertow; import io.undertow.Undertow;
import org.springframework.http.server.reactive.UndertowHttpHandlerAdapter; import org.springframework.http.server.reactive.UndertowHttpHandlerAdapter;
import org.springframework.util.Assert;
/** /**
* @author Marek Hawrylczak * @author Marek Hawrylczak
*/ */
public class UndertowHttpServer extends HttpServerSupport implements HttpServer { public class UndertowHttpServer extends AbstractHttpServer {
private Undertow server; private Undertow server;
private boolean running;
@Override @Override
public void afterPropertiesSet() throws Exception { protected void initServer() throws Exception {
this.server = Undertow.builder().addHttpListener(getPort(), getHost()) this.server = Undertow.builder().addHttpListener(getPort(), getHost())
.setHandler(initUndertowHttpHandlerAdapter()) .setHandler(initHttpHandlerAdapter())
.build(); .build();
} }
private UndertowHttpHandlerAdapter initUndertowHttpHandlerAdapter() { private UndertowHttpHandlerAdapter initHttpHandlerAdapter() {
if (getHttpHandlerMap() != null) { return getHttpHandlerMap() != null ?
return new UndertowHttpHandlerAdapter(getHttpHandlerMap()); new UndertowHttpHandlerAdapter(getHttpHandlerMap()) :
} new UndertowHttpHandlerAdapter(getHttpHandler());
else {
Assert.notNull(getHttpHandler());
return new UndertowHttpHandlerAdapter(getHttpHandler());
}
} }
@Override @Override
public void start() { protected void startInternal() {
if (!this.running) { this.server.start();
this.server.start();
this.running = true;
}
} }
@Override @Override
public void stop() { protected void stopInternal() {
if (this.running) { this.server.stop();
this.server.stop();
this.running = false;
}
} }
@Override @Override
public boolean isRunning() { protected void reset() {
return this.running; super.reset();
this.server = null;
} }
} }