Commit eb4fc16e authored by Madhura Bhave's avatar Madhura Bhave

Add customizers for Reactive Web Servers

Closes gh-9572
parent 1a0f2198
...@@ -17,6 +17,10 @@ ...@@ -17,6 +17,10 @@
package org.springframework.boot.web.embedded.jetty; package org.springframework.boot.web.embedded.jetty;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
...@@ -34,6 +38,7 @@ import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory; ...@@ -34,6 +38,7 @@ import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory;
import org.springframework.boot.web.server.WebServer; import org.springframework.boot.web.server.WebServer;
import org.springframework.http.server.reactive.HttpHandler; import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.JettyHttpHandlerAdapter; import org.springframework.http.server.reactive.JettyHttpHandlerAdapter;
import org.springframework.util.Assert;
/** /**
* {@link ReactiveWebServerFactory} that can be used to create {@link JettyWebServer}s. * {@link ReactiveWebServerFactory} that can be used to create {@link JettyWebServer}s.
...@@ -56,6 +61,8 @@ public class JettyReactiveWebServerFactory extends AbstractReactiveWebServerFact ...@@ -56,6 +61,8 @@ public class JettyReactiveWebServerFactory extends AbstractReactiveWebServerFact
*/ */
private int selectors = -1; private int selectors = -1;
private List<JettyServerCustomizer> jettyServerCustomizers = new ArrayList<>();
private ThreadPool threadPool; private ThreadPool threadPool;
/** /**
...@@ -90,6 +97,9 @@ public class JettyReactiveWebServerFactory extends AbstractReactiveWebServerFact ...@@ -90,6 +97,9 @@ public class JettyReactiveWebServerFactory extends AbstractReactiveWebServerFact
ServletContextHandler contextHandler = new ServletContextHandler(server, "", ServletContextHandler contextHandler = new ServletContextHandler(server, "",
false, false); false, false);
contextHandler.addServlet(servletHolder, "/"); contextHandler.addServlet(servletHolder, "/");
for (JettyServerCustomizer customizer : getServerCustomizers()) {
customizer.customize(server);
}
JettyReactiveWebServerFactory.logger JettyReactiveWebServerFactory.logger
.info("Server initialized with port: " + port); .info("Server initialized with port: " + port);
return server; return server;
...@@ -134,6 +144,36 @@ public class JettyReactiveWebServerFactory extends AbstractReactiveWebServerFact ...@@ -134,6 +144,36 @@ public class JettyReactiveWebServerFactory extends AbstractReactiveWebServerFact
this.acceptors = acceptors; this.acceptors = acceptors;
} }
/**
* Sets {@link JettyServerCustomizer}s that will be applied to the {@link Server}
* before it is started. Calling this method will replace any existing customizers.
* @param customizers the Jetty customizers to apply
*/
public void setServerCustomizers(
Collection<? extends JettyServerCustomizer> customizers) {
Assert.notNull(customizers, "Customizers must not be null");
this.jettyServerCustomizers = new ArrayList<>(customizers);
}
/**
* Returns a mutable collection of Jetty {@link JettyServerCustomizer}s that will be applied
* to the {@link Server} before it is created.
* @return the Jetty customizers
*/
public Collection<JettyServerCustomizer> getServerCustomizers() {
return this.jettyServerCustomizers;
}
/**
* Add {@link JettyServerCustomizer}s that will be applied to the {@link Server}
* before it is started.
* @param customizers the customizers to add
*/
public void addServerCustomizers(JettyServerCustomizer... customizers) {
Assert.notNull(customizers, "Customizers must not be null");
this.jettyServerCustomizers.addAll(Arrays.asList(customizers));
}
/** /**
* Set the number of selector threads to use. * Set the number of selector threads to use.
* @param selectors the number of selector threads to use * @param selectors the number of selector threads to use
......
...@@ -55,6 +55,8 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac ...@@ -55,6 +55,8 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac
private List<TomcatContextCustomizer> tomcatContextCustomizers = new ArrayList<>(); private List<TomcatContextCustomizer> tomcatContextCustomizers = new ArrayList<>();
private List<TomcatConnectorCustomizer> tomcatConnectorCustomizers = new ArrayList<>();
/** /**
* Create a new {@link TomcatServletWebServerFactory} instance. * Create a new {@link TomcatServletWebServerFactory} instance.
*/ */
...@@ -132,6 +134,9 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac ...@@ -132,6 +134,9 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac
// If ApplicationContext is slow to start we want Tomcat not to bind to the socket // If ApplicationContext is slow to start we want Tomcat not to bind to the socket
// prematurely... // prematurely...
connector.setProperty("bindOnInit", "false"); connector.setProperty("bindOnInit", "false");
for (TomcatConnectorCustomizer customizer : this.tomcatConnectorCustomizers) {
customizer.customize(connector);
}
} }
private void customizeProtocol(AbstractProtocol<?> protocol) { private void customizeProtocol(AbstractProtocol<?> protocol) {
...@@ -173,6 +178,39 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac ...@@ -173,6 +178,39 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac
this.tomcatContextCustomizers.addAll(Arrays.asList(tomcatContextCustomizers)); this.tomcatContextCustomizers.addAll(Arrays.asList(tomcatContextCustomizers));
} }
/**
* Set {@link TomcatConnectorCustomizer}s that should be applied to the Tomcat
* {@link Connector} . Calling this method will replace any existing customizers.
* @param tomcatConnectorCustomizers the customizers to set
*/
public void setTomcatConnectorCustomizers(
Collection<? extends TomcatConnectorCustomizer> tomcatConnectorCustomizers) {
Assert.notNull(tomcatConnectorCustomizers,
"TomcatConnectorCustomizers must not be null");
this.tomcatConnectorCustomizers = new ArrayList<>(tomcatConnectorCustomizers);
}
/**
* Add {@link TomcatConnectorCustomizer}s that should be added to the Tomcat
* {@link Connector}.
* @param tomcatConnectorCustomizers the customizers to add
*/
public void addConnectorCustomizers(
TomcatConnectorCustomizer... tomcatConnectorCustomizers) {
Assert.notNull(tomcatConnectorCustomizers,
"TomcatConnectorCustomizers must not be null");
this.tomcatConnectorCustomizers.addAll(Arrays.asList(tomcatConnectorCustomizers));
}
/**
* Returns a mutable collection of the {@link TomcatConnectorCustomizer}s that will be
* applied to the Tomcat {@link Connector} .
* @return the customizers that will be applied
*/
public Collection<TomcatConnectorCustomizer> getTomcatConnectorCustomizers() {
return this.tomcatConnectorCustomizers;
}
/** /**
* Factory method called to create the {@link TomcatWebServer}. Subclasses can * Factory method called to create the {@link TomcatWebServer}. Subclasses can
* override this method to return a different {@link TomcatWebServer} or apply * override this method to return a different {@link TomcatWebServer} or apply
......
...@@ -16,6 +16,11 @@ ...@@ -16,6 +16,11 @@
package org.springframework.boot.web.embedded.undertow; package org.springframework.boot.web.embedded.undertow;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import io.undertow.Undertow; import io.undertow.Undertow;
import org.springframework.boot.web.reactive.server.AbstractReactiveWebServerFactory; import org.springframework.boot.web.reactive.server.AbstractReactiveWebServerFactory;
...@@ -23,6 +28,7 @@ import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory; ...@@ -23,6 +28,7 @@ import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory;
import org.springframework.boot.web.server.WebServer; import org.springframework.boot.web.server.WebServer;
import org.springframework.http.server.reactive.HttpHandler; import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.UndertowHttpHandlerAdapter; import org.springframework.http.server.reactive.UndertowHttpHandlerAdapter;
import org.springframework.util.Assert;
/** /**
* {@link ReactiveWebServerFactory} that can be used to create {@link UndertowWebServer}s. * {@link ReactiveWebServerFactory} that can be used to create {@link UndertowWebServer}s.
...@@ -40,6 +46,8 @@ public class UndertowReactiveWebServerFactory extends AbstractReactiveWebServerF ...@@ -40,6 +46,8 @@ public class UndertowReactiveWebServerFactory extends AbstractReactiveWebServerF
private Boolean directBuffers; private Boolean directBuffers;
private List<UndertowBuilderCustomizer> builderCustomizers = new ArrayList<>();
/** /**
* Create a new {@link UndertowReactiveWebServerFactory} instance. * Create a new {@link UndertowReactiveWebServerFactory} instance.
*/ */
...@@ -78,6 +86,9 @@ public class UndertowReactiveWebServerFactory extends AbstractReactiveWebServerF ...@@ -78,6 +86,9 @@ public class UndertowReactiveWebServerFactory extends AbstractReactiveWebServerF
builder.setDirectBuffers(this.directBuffers); builder.setDirectBuffers(this.directBuffers);
} }
builder.addHttpListener(port, getListenAddress()); builder.addHttpListener(port, getListenAddress());
for (UndertowBuilderCustomizer customizer : this.builderCustomizers) {
customizer.customize(builder);
}
return builder; return builder;
} }
...@@ -104,4 +115,34 @@ public class UndertowReactiveWebServerFactory extends AbstractReactiveWebServerF ...@@ -104,4 +115,34 @@ public class UndertowReactiveWebServerFactory extends AbstractReactiveWebServerF
this.directBuffers = directBuffers; this.directBuffers = directBuffers;
} }
/**
* Set {@link UndertowBuilderCustomizer}s that should be applied to the Undertow
* {@link Undertow.Builder}. Calling this method will replace any existing customizers.
* @param customizers the customizers to set
*/
public void setBuilderCustomizers(
Collection<? extends UndertowBuilderCustomizer> customizers) {
Assert.notNull(customizers, "Customizers must not be null");
this.builderCustomizers = new ArrayList<>(customizers);
}
/**
* Returns a mutable collection of the {@link UndertowBuilderCustomizer}s that will be
* applied to the Undertow {@link Undertow.Builder} .
* @return the customizers that will be applied
*/
public Collection<UndertowBuilderCustomizer> getBuilderCustomizers() {
return this.builderCustomizers;
}
/**
* Add {@link UndertowBuilderCustomizer}s that should be used to customize the
* Undertow {@link Undertow.Builder}.
* @param customizers the customizers to add
*/
public void addBuilderCustomizers(UndertowBuilderCustomizer... customizers) {
Assert.notNull(customizers, "Customizers must not be null");
this.builderCustomizers.addAll(Arrays.asList(customizers));
}
} }
...@@ -16,20 +16,64 @@ ...@@ -16,20 +16,64 @@
package org.springframework.boot.web.embedded.jetty; package org.springframework.boot.web.embedded.jetty;
import org.springframework.boot.web.reactive.server.AbstractReactiveWebServerFactory; import java.util.Arrays;
import org.eclipse.jetty.server.Server;
import org.junit.Test;
import org.mockito.InOrder;
import org.springframework.boot.web.reactive.server.AbstractReactiveWebServerFactoryTests; import org.springframework.boot.web.reactive.server.AbstractReactiveWebServerFactoryTests;
import org.springframework.http.server.reactive.HttpHandler;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
/** /**
* Tests for {@link JettyReactiveWebServerFactory} and {@link JettyWebServer}. * Tests for {@link JettyReactiveWebServerFactory} and {@link JettyWebServer}.
* *
* @author Brian Clozel * @author Brian Clozel
* @author Madhura Bhave
*/ */
public class JettyReactiveWebServerFactoryTests public class JettyReactiveWebServerFactoryTests
extends AbstractReactiveWebServerFactoryTests { extends AbstractReactiveWebServerFactoryTests {
@Override @Override
protected AbstractReactiveWebServerFactory getFactory() { protected JettyReactiveWebServerFactory getFactory() {
return new JettyReactiveWebServerFactory(0); return new JettyReactiveWebServerFactory(0);
} }
@Test
public void setNullServerCustomizersShouldThrowException() {
JettyReactiveWebServerFactory factory = getFactory();
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Customizers must not be null");
factory.setServerCustomizers(null);
}
@Test
public void addNullServerCustomizersShouldThrowException() {
JettyReactiveWebServerFactory factory = getFactory();
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Customizers must not be null");
factory.addServerCustomizers((JettyServerCustomizer[]) null);
}
@Test
public void jettyCustomizersShouldBeInvoked() throws Exception {
HttpHandler handler = mock(HttpHandler.class);
JettyReactiveWebServerFactory factory = getFactory();
JettyServerCustomizer[] configurations = new JettyServerCustomizer[4];
for (int i = 0; i < configurations.length; i++) {
configurations[i] = mock(JettyServerCustomizer.class);
}
factory.setServerCustomizers(Arrays.asList(configurations[0], configurations[1]));
factory.addServerCustomizers(configurations[2], configurations[3]);
this.webServer = factory.getWebServer(handler);
InOrder ordered = inOrder((Object[]) configurations);
for (JettyServerCustomizer configuration : configurations) {
ordered.verify(configuration).customize(any(Server.class));
}
}
} }
...@@ -19,6 +19,7 @@ package org.springframework.boot.web.embedded.tomcat; ...@@ -19,6 +19,7 @@ package org.springframework.boot.web.embedded.tomcat;
import java.util.Arrays; import java.util.Arrays;
import org.apache.catalina.Context; import org.apache.catalina.Context;
import org.apache.catalina.connector.Connector;
import org.junit.Test; import org.junit.Test;
import org.mockito.InOrder; import org.mockito.InOrder;
...@@ -33,6 +34,7 @@ import static org.mockito.Mockito.mock; ...@@ -33,6 +34,7 @@ import static org.mockito.Mockito.mock;
* Tests for {@link TomcatReactiveWebServerFactory}. * Tests for {@link TomcatReactiveWebServerFactory}.
* *
* @author Brian Clozel * @author Brian Clozel
* @author Madhura Bhave
*/ */
public class TomcatReactiveWebServerFactoryTests public class TomcatReactiveWebServerFactoryTests
extends AbstractReactiveWebServerFactoryTests { extends AbstractReactiveWebServerFactoryTests {
...@@ -58,4 +60,37 @@ public class TomcatReactiveWebServerFactoryTests ...@@ -58,4 +60,37 @@ public class TomcatReactiveWebServerFactoryTests
} }
} }
@Test
public void setNullConnectorCustomizersShouldThrowException() {
TomcatReactiveWebServerFactory factory = getFactory();
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Customizers must not be null");
factory.setTomcatConnectorCustomizers(null);
}
@Test
public void addNullAddConnectorCustomizersShouldThrowException() {
TomcatReactiveWebServerFactory factory = getFactory();
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Customizers must not be null");
factory.addConnectorCustomizers((TomcatConnectorCustomizer[]) null);
}
@Test
public void tomcatConnectorCustomizersShouldBeInvoked() throws Exception {
TomcatReactiveWebServerFactory factory = getFactory();
HttpHandler handler = mock(HttpHandler.class);
TomcatConnectorCustomizer[] listeners = new TomcatConnectorCustomizer[4];
for (int i = 0; i < listeners.length; i++) {
listeners[i] = mock(TomcatConnectorCustomizer.class);
}
factory.setTomcatConnectorCustomizers(Arrays.asList(listeners[0], listeners[1]));
factory.addConnectorCustomizers(listeners[2], listeners[3]);
this.webServer = factory.getWebServer(handler);
InOrder ordered = inOrder((Object[]) listeners);
for (TomcatConnectorCustomizer listener : listeners) {
ordered.verify(listener).customize(any(Connector.class));
}
}
} }
...@@ -16,12 +16,24 @@ ...@@ -16,12 +16,24 @@
package org.springframework.boot.web.embedded.undertow; package org.springframework.boot.web.embedded.undertow;
import java.util.Arrays;
import io.undertow.Undertow;
import org.junit.Test;
import org.mockito.InOrder;
import org.springframework.boot.web.reactive.server.AbstractReactiveWebServerFactoryTests; import org.springframework.boot.web.reactive.server.AbstractReactiveWebServerFactoryTests;
import org.springframework.http.server.reactive.HttpHandler;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
/** /**
* Tests for {@link UndertowReactiveWebServerFactory} and {@link UndertowWebServer}. * Tests for {@link UndertowReactiveWebServerFactory} and {@link UndertowWebServer}.
* *
* @author Brian Clozel * @author Brian Clozel
* @author Madhura Bhave
*/ */
public class UndertowReactiveWebServerFactoryTests public class UndertowReactiveWebServerFactoryTests
extends AbstractReactiveWebServerFactoryTests { extends AbstractReactiveWebServerFactoryTests {
...@@ -31,4 +43,37 @@ public class UndertowReactiveWebServerFactoryTests ...@@ -31,4 +43,37 @@ public class UndertowReactiveWebServerFactoryTests
return new UndertowReactiveWebServerFactory(0); return new UndertowReactiveWebServerFactory(0);
} }
@Test
public void setNullBuilderCustomizersShouldThrowException() {
UndertowReactiveWebServerFactory factory = getFactory();
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Customizers must not be null");
factory.setBuilderCustomizers(null);
}
@Test
public void addNullBuilderCustomizersShouldThrowException() {
UndertowReactiveWebServerFactory factory = getFactory();
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Customizers must not be null");
factory.addBuilderCustomizers((UndertowBuilderCustomizer[]) null);
}
@Test
public void builderCustomizersShouldBeInvoked() throws Exception {
UndertowReactiveWebServerFactory factory = getFactory();
HttpHandler handler = mock(HttpHandler.class);
UndertowBuilderCustomizer[] customizers = new UndertowBuilderCustomizer[4];
for (int i = 0; i < customizers.length; i++) {
customizers[i] = mock(UndertowBuilderCustomizer.class);
}
factory.setBuilderCustomizers(Arrays.asList(customizers[0], customizers[1]));
factory.addBuilderCustomizers(customizers[2], customizers[3]);
this.webServer = factory.getWebServer(handler);
InOrder ordered = inOrder((Object[]) customizers);
for (UndertowBuilderCustomizer customizer : customizers) {
ordered.verify(customizer).customize(any(Undertow.Builder.class));
}
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment