Merge branch '2.0.x'
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.boot.web.embedded.undertow;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
@@ -28,7 +29,6 @@ import io.undertow.Undertow;
|
||||
import io.undertow.UndertowOptions;
|
||||
import io.undertow.server.HttpHandler;
|
||||
import io.undertow.server.handlers.accesslog.AccessLogHandler;
|
||||
import io.undertow.server.handlers.accesslog.AccessLogReceiver;
|
||||
import io.undertow.server.handlers.accesslog.DefaultAccessLogReceiver;
|
||||
import io.undertow.servlet.api.DeploymentInfo;
|
||||
import org.xnio.OptionMap;
|
||||
@@ -96,9 +96,8 @@ public class UndertowReactiveWebServerFactory extends AbstractReactiveWebServerF
|
||||
public WebServer getWebServer(
|
||||
org.springframework.http.server.reactive.HttpHandler httpHandler) {
|
||||
Undertow.Builder builder = createBuilder(getPort());
|
||||
HttpHandler handler = createUndertowHandler(httpHandler);
|
||||
builder.setHandler(handler);
|
||||
return new UndertowWebServer(builder, getPort() >= 0);
|
||||
Closeable closeable = configureHandler(builder, httpHandler);
|
||||
return new UndertowWebServer(builder, getPort() >= 0, closeable);
|
||||
}
|
||||
|
||||
private Undertow.Builder createBuilder(int port) {
|
||||
@@ -127,7 +126,7 @@ public class UndertowReactiveWebServerFactory extends AbstractReactiveWebServerF
|
||||
return builder;
|
||||
}
|
||||
|
||||
private HttpHandler createUndertowHandler(
|
||||
private Closeable configureHandler(Undertow.Builder builder,
|
||||
org.springframework.http.server.reactive.HttpHandler httpHandler) {
|
||||
HttpHandler handler = new UndertowHttpHandlerAdapter(httpHandler);
|
||||
if (this.useForwardHeaders) {
|
||||
@@ -135,25 +134,39 @@ public class UndertowReactiveWebServerFactory extends AbstractReactiveWebServerF
|
||||
}
|
||||
handler = UndertowCompressionConfigurer.configureCompression(getCompression(),
|
||||
handler);
|
||||
Closeable closeable = null;
|
||||
if (isAccessLogEnabled()) {
|
||||
handler = createAccessLogHandler(handler);
|
||||
closeable = configureAccessLogHandler(builder, handler);
|
||||
}
|
||||
return handler;
|
||||
else {
|
||||
builder.setHandler(handler);
|
||||
}
|
||||
return closeable;
|
||||
}
|
||||
|
||||
private AccessLogHandler createAccessLogHandler(
|
||||
io.undertow.server.HttpHandler handler) {
|
||||
private Closeable configureAccessLogHandler(Undertow.Builder builder,
|
||||
HttpHandler handler) {
|
||||
try {
|
||||
createAccessLogDirectoryIfNecessary();
|
||||
XnioWorker worker = createWorker();
|
||||
String prefix = (this.accessLogPrefix != null ? this.accessLogPrefix
|
||||
: "access_log.");
|
||||
AccessLogReceiver accessLogReceiver = new DefaultAccessLogReceiver(
|
||||
createWorker(), this.accessLogDirectory, prefix, this.accessLogSuffix,
|
||||
DefaultAccessLogReceiver accessLogReceiver = new DefaultAccessLogReceiver(
|
||||
worker, this.accessLogDirectory, prefix, this.accessLogSuffix,
|
||||
this.accessLogRotate);
|
||||
String formatString = (this.accessLogPattern != null ? this.accessLogPattern
|
||||
String formatString = ((this.accessLogPattern != null) ? this.accessLogPattern
|
||||
: "common");
|
||||
return new AccessLogHandler(handler, accessLogReceiver, formatString,
|
||||
Undertow.class.getClassLoader());
|
||||
builder.setHandler(new AccessLogHandler(handler, accessLogReceiver,
|
||||
formatString, Undertow.class.getClassLoader()));
|
||||
return () -> {
|
||||
try {
|
||||
accessLogReceiver.close();
|
||||
worker.shutdown();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
};
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException("Failed to create AccessLogHandler", ex);
|
||||
|
||||
@@ -25,11 +25,14 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.EventListener;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.ServletContainerInitializer;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletContextEvent;
|
||||
import javax.servlet.ServletContextListener;
|
||||
import javax.servlet.ServletException;
|
||||
|
||||
import io.undertow.Undertow;
|
||||
@@ -48,6 +51,7 @@ import io.undertow.server.session.SessionManager;
|
||||
import io.undertow.servlet.Servlets;
|
||||
import io.undertow.servlet.api.DeploymentInfo;
|
||||
import io.undertow.servlet.api.DeploymentManager;
|
||||
import io.undertow.servlet.api.ListenerInfo;
|
||||
import io.undertow.servlet.api.MimeMapping;
|
||||
import io.undertow.servlet.api.ServletContainerInitializerInfo;
|
||||
import io.undertow.servlet.api.ServletStackTraces;
|
||||
@@ -292,27 +296,35 @@ public class UndertowServletWebServerFactory extends AbstractServletWebServerFac
|
||||
}
|
||||
|
||||
private void configureAccessLog(DeploymentInfo deploymentInfo) {
|
||||
deploymentInfo.addInitialHandlerChainWrapper(this::createAccessLogHandler);
|
||||
}
|
||||
|
||||
private AccessLogHandler createAccessLogHandler(HttpHandler handler) {
|
||||
try {
|
||||
createAccessLogDirectoryIfNecessary();
|
||||
XnioWorker worker = createWorker();
|
||||
String prefix = (this.accessLogPrefix != null ? this.accessLogPrefix
|
||||
: "access_log.");
|
||||
AccessLogReceiver accessLogReceiver = new DefaultAccessLogReceiver(
|
||||
createWorker(), this.accessLogDirectory, prefix, this.accessLogSuffix,
|
||||
DefaultAccessLogReceiver accessLogReceiver = new DefaultAccessLogReceiver(
|
||||
worker, this.accessLogDirectory, prefix, this.accessLogSuffix,
|
||||
this.accessLogRotate);
|
||||
String formatString = (this.accessLogPattern != null ? this.accessLogPattern
|
||||
: "common");
|
||||
return new AccessLogHandler(handler, accessLogReceiver, formatString,
|
||||
Undertow.class.getClassLoader());
|
||||
EventListener listener = new AccessLogShutdownListener(worker,
|
||||
accessLogReceiver);
|
||||
deploymentInfo.addListener(new ListenerInfo(AccessLogShutdownListener.class,
|
||||
new ImmediateInstanceFactory<EventListener>(listener)));
|
||||
deploymentInfo.addInitialHandlerChainWrapper(
|
||||
(handler) -> createAccessLogHandler(handler, accessLogReceiver));
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException("Failed to create AccessLogHandler", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private AccessLogHandler createAccessLogHandler(HttpHandler handler,
|
||||
AccessLogReceiver accessLogReceiver) {
|
||||
createAccessLogDirectoryIfNecessary();
|
||||
String formatString = ((this.accessLogPattern != null) ? this.accessLogPattern
|
||||
: "common");
|
||||
return new AccessLogHandler(handler, accessLogReceiver, formatString,
|
||||
Undertow.class.getClassLoader());
|
||||
}
|
||||
|
||||
private void createAccessLogDirectoryIfNecessary() {
|
||||
Assert.state(this.accessLogDirectory != null, "Access log directory is not set");
|
||||
if (!this.accessLogDirectory.isDirectory() && !this.accessLogDirectory.mkdirs()) {
|
||||
@@ -645,4 +657,33 @@ public class UndertowServletWebServerFactory extends AbstractServletWebServerFac
|
||||
|
||||
}
|
||||
|
||||
private static class AccessLogShutdownListener implements ServletContextListener {
|
||||
|
||||
private final XnioWorker worker;
|
||||
|
||||
private final DefaultAccessLogReceiver accessLogReceiver;
|
||||
|
||||
AccessLogShutdownListener(XnioWorker worker,
|
||||
DefaultAccessLogReceiver accessLogReceiver) {
|
||||
this.worker = worker;
|
||||
this.accessLogReceiver = accessLogReceiver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contextInitialized(ServletContextEvent sce) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contextDestroyed(ServletContextEvent sce) {
|
||||
try {
|
||||
this.accessLogReceiver.close();
|
||||
this.worker.shutdown();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.boot.web.embedded.undertow;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.BindException;
|
||||
import java.net.InetSocketAddress;
|
||||
@@ -56,6 +57,8 @@ public class UndertowWebServer implements WebServer {
|
||||
|
||||
private final boolean autoStart;
|
||||
|
||||
private final Closeable closeable;
|
||||
|
||||
private Undertow undertow;
|
||||
|
||||
private volatile boolean started = false;
|
||||
@@ -66,8 +69,21 @@ public class UndertowWebServer implements WebServer {
|
||||
* @param autoStart if the server should be started
|
||||
*/
|
||||
public UndertowWebServer(Undertow.Builder builder, boolean autoStart) {
|
||||
this(builder, autoStart, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link UndertowWebServer} instance.
|
||||
* @param builder the builder
|
||||
* @param autoStart if the server should be started
|
||||
* @param closeable called when the server is stopped
|
||||
* @since 2.0.4
|
||||
*/
|
||||
public UndertowWebServer(Undertow.Builder builder, boolean autoStart,
|
||||
Closeable closeable) {
|
||||
this.builder = builder;
|
||||
this.autoStart = autoStart;
|
||||
this.closeable = closeable;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -112,6 +128,7 @@ public class UndertowWebServer implements WebServer {
|
||||
try {
|
||||
if (this.undertow != null) {
|
||||
this.undertow.stop();
|
||||
this.closeable.close();
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
@@ -214,6 +231,9 @@ public class UndertowWebServer implements WebServer {
|
||||
this.started = false;
|
||||
try {
|
||||
this.undertow.stop();
|
||||
if (this.closeable != null) {
|
||||
this.closeable.close();
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new WebServerException("Unable to stop undertow", ex);
|
||||
|
||||
@@ -16,15 +16,25 @@
|
||||
|
||||
package org.springframework.boot.web.embedded.undertow;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import io.undertow.Undertow;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.mockito.InOrder;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.boot.web.reactive.server.AbstractReactiveWebServerFactoryTests;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.server.reactive.HttpHandler;
|
||||
import org.springframework.web.reactive.function.BodyInserters;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
@@ -38,6 +48,9 @@ import static org.mockito.Mockito.mock;
|
||||
public class UndertowReactiveWebServerFactoryTests
|
||||
extends AbstractReactiveWebServerFactoryTests {
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
@Override
|
||||
protected UndertowReactiveWebServerFactory getFactory() {
|
||||
return new UndertowReactiveWebServerFactory(0);
|
||||
@@ -83,4 +96,44 @@ public class UndertowReactiveWebServerFactoryTests
|
||||
assertForwardHeaderIsUsed(factory);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void accessLogCanBeEnabled()
|
||||
throws IOException, URISyntaxException, InterruptedException {
|
||||
testAccessLog(null, null, "access_log.log");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void accessLogCanBeCustomized()
|
||||
throws IOException, URISyntaxException, InterruptedException {
|
||||
testAccessLog("my_access.", "logz", "my_access.logz");
|
||||
}
|
||||
|
||||
private void testAccessLog(String prefix, String suffix, String expectedFile)
|
||||
throws IOException, URISyntaxException, InterruptedException {
|
||||
UndertowReactiveWebServerFactory factory = getFactory();
|
||||
factory.setAccessLogEnabled(true);
|
||||
factory.setAccessLogPrefix(prefix);
|
||||
factory.setAccessLogSuffix(suffix);
|
||||
File accessLogDirectory = this.temporaryFolder.getRoot();
|
||||
factory.setAccessLogDirectory(accessLogDirectory);
|
||||
assertThat(accessLogDirectory.listFiles()).isEmpty();
|
||||
this.webServer = factory.getWebServer(new EchoHandler());
|
||||
this.webServer.start();
|
||||
WebClient client = getWebClient().build();
|
||||
Mono<String> result = client.post().uri("/test").contentType(MediaType.TEXT_PLAIN)
|
||||
.body(BodyInserters.fromObject("Hello World")).exchange()
|
||||
.flatMap((response) -> response.bodyToMono(String.class));
|
||||
assertThat(result.block()).isEqualTo("Hello World");
|
||||
File accessLog = new File(accessLogDirectory, expectedFile);
|
||||
awaitFile(accessLog);
|
||||
assertThat(accessLogDirectory.listFiles()).contains(accessLog);
|
||||
}
|
||||
|
||||
private void awaitFile(File file) throws InterruptedException {
|
||||
long end = System.currentTimeMillis() + 10000;
|
||||
while (!file.exists() && System.currentTimeMillis() < end) {
|
||||
Thread.sleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user