Skip to content
Commits on Source (9)
......@@ -4,14 +4,14 @@ Undertow
Undertow is a Java web server based on non-blocking IO. It consists of a few different parts:
* A core HTTP server that supports both blocking and non-blocking IO
* A Servlet 3.1 implementation
* A Servlet 4.0 implementation
* A JSR-356 compliant web socket implementation
Website: http://undertow.io
Issues: https://issues.jboss.org/browse/UNDERTOW
Project Lead: Stuart Douglas <sdouglas@redhat.com>
Project Lead: Flavia Rainone <frainone@redhat.com>
Mailing List: undertow-dev@lists.jboss.org
http://lists.jboss.org/mailman/listinfo/undertow-dev
......
# Undertow Benchmarks
## JMH
Benchmarks use the [JMH harness](https://openjdk.java.net/projects/code-tools/jmh/).
## Running
```bash
mvn install
java -jar benchmarks/target/undertow-benchmarks.jar
```
Alternatively benchmarks may be run from the IDE using a JMH plugin like
[this one for idea](https://plugins.jetbrains.com/plugin/7529-jmh-plugin).
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ JBoss, Home of Professional Open Source.
~ Copyright 2019 Red Hat, Inc., and individual contributors
~ as indicated by the @author tags.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.undertow</groupId>
<artifactId>undertow-parent</artifactId>
<version>2.0.23.Final</version>
</parent>
<groupId>io.undertow</groupId>
<artifactId>undertow-benchmarks</artifactId>
<version>2.0.23.Final</version>
<name>Undertow Benchmarks</name>
<dependencies>
<dependency>
<groupId>io.undertow</groupId>
<artifactId>undertow-core</artifactId>
</dependency>
<dependency>
<groupId>io.undertow</groupId>
<artifactId>undertow-servlet</artifactId>
</dependency>
<dependency>
<groupId>io.undertow</groupId>
<artifactId>undertow-websockets-jsr</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging-processor</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.xnio</groupId>
<artifactId>xnio-nio</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.logmanager</groupId>
<artifactId>jboss-logmanager</artifactId>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
</dependency>
</dependencies>
<build>
<finalName>undertow-benchmarks</finalName>
<resources>
<resource>
<directory>src/main/java</directory>
<excludes>
<exclude>**/*.java</exclude>
</excludes>
</resource>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>org.openjdk.jmh.Main</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
/*
* JBoss, Home of Professional Open Source.
* Copyright 2019 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.undertow.benchmarks;
import java.io.IOException;
import java.io.InputStream;
/**
* Utility functionality used by benchmarks.
*
* @author Carter Kozak
*/
final class BenchmarkUtils {
private static final byte[] GARBAGE_BUFFER = new byte[16 * 1024];
/** Consumes the {@link InputStream}, returning the number of bytes read. */
static long length(InputStream stream) throws IOException {
long total = 0;
while (true) {
int read = stream.read(GARBAGE_BUFFER);
if (read == -1) {
return total;
}
total += read;
}
}
private BenchmarkUtils() {}
}
/*
* JBoss, Home of Professional Open Source.
* Copyright 2019 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.undertow.benchmarks;
import io.undertow.Handlers;
import io.undertow.Undertow;
import io.undertow.UndertowOptions;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import io.undertow.server.handlers.BlockingHandler;
import io.undertow.util.Headers;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.TearDown;
import org.xnio.Options;
import org.xnio.SslClientAuthMode;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* @author Carter Kozak
*/
@State(Scope.Benchmark)
public class SimpleBenchmarkState {
private static final int PORT = 4433;
@SuppressWarnings("unused") // Set by JMH
@Param({"HTTP", "HTTPS"})
private ListenerType listenerType;
private Undertow undertow;
private CloseableHttpClient client;
private String baseUri;
@Setup
public final void before() {
Undertow.Builder builder = Undertow.builder()
.setIoThreads(4)
.setWorkerThreads(64)
.setServerOption(UndertowOptions.SHUTDOWN_TIMEOUT, 10000)
.setSocketOption(Options.SSL_CLIENT_AUTH_MODE, SslClientAuthMode.NOT_REQUESTED)
.setHandler(Handlers.routing()
/* Responds with N bytes where N is the value of the "size" query parameter. */
.get("/blocking", new BlockingHandler(new HttpHandler() {
@Override
public void handleRequest(HttpServerExchange exchange) throws Exception {
String value = exchange.getQueryParameters().get("size").getFirst();
int bytes = Integer.parseInt(value);
exchange.getResponseHeaders()
.put(Headers.CONTENT_TYPE, "application/octet-stream")
.put(Headers.CONTENT_LENGTH, value);
OutputStream out = exchange.getOutputStream();
for (int i = 0; i < bytes; i++) {
out.write(1);
}
}
}))
/* Responds with the the string value of the number of bytes received. */
.post("/blocking", new BlockingHandler(new HttpHandler() {
@Override
public void handleRequest(HttpServerExchange exchange) throws Exception {
InputStream stream = exchange.getInputStream();
long length = BenchmarkUtils.length(stream);
String stringValue = Long.toString(length);
exchange.getResponseHeaders()
.put(Headers.CONTENT_TYPE, "text/plain")
.put(Headers.CONTENT_LENGTH, stringValue.length());
exchange.getResponseSender().send(stringValue);
}
})));
switch (listenerType) {
case HTTP:
builder.addHttpListener(PORT, "0.0.0.0");
break;
case HTTPS:
builder.addHttpsListener(PORT, "0.0.0.0", TLSUtils.newServerContext());
break;
default:
throw new IllegalStateException("Unknown protocol: " + listenerType);
}
undertow = builder.build();
undertow.start();
client = HttpClients.custom()
.disableConnectionState()
.disableAutomaticRetries()
.setSSLContext(TLSUtils.newClientContext())
.setMaxConnPerRoute(100)
.setMaxConnTotal(100)
.build();
baseUri = (listenerType == ListenerType.HTTP ? "http" : "https") + "://localhost:" + PORT;
}
@TearDown
public final void after() throws IOException {
if (undertow != null) {
undertow.stop();
undertow = null;
}
if (client != null) {
client.close();
client = null;
}
}
public CloseableHttpClient client() {
return client;
}
public String getBaseUri() {
return baseUri;
}
public enum ListenerType {HTTP, HTTPS}
}
/*
* JBoss, Home of Professional Open Source.
* Copyright 2019 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.undertow.benchmarks;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.InputStreamEntity;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Threads;
import org.openjdk.jmh.annotations.Warmup;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.TimeUnit;
/**
* @author Carter Kozak
*/
@Measurement(iterations = 3, time = 3)
@Warmup(iterations = 3, time = 3)
@Fork(1)
@Threads(32)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public class SimpleBenchmarks {
@Benchmark
public void benchmarkBlockingEmptyGet(SimpleBenchmarkState state) throws IOException {
try (CloseableHttpResponse response = state.client()
.execute(new HttpGet(state.getBaseUri() + "/blocking?size=0"))) {
validateLength(response, 0L);
}
}
@Benchmark
public void benchmarkBlockingLargeGet(SimpleBenchmarkState state) throws IOException {
try (CloseableHttpResponse response = state.client()
.execute(new HttpGet(state.getBaseUri() + "/blocking?size=256000"))) {
validateLength(response, 256000L);
}
}
@Benchmark
public void benchmarkBlockingEmptyPost(SimpleBenchmarkState state) throws IOException {
try (CloseableHttpResponse response = state.client()
.execute(new HttpPost(state.getBaseUri() + "/blocking"))) {
String result = asString(validate(response).getEntity());
if (!"0".equals(result)) {
throw new IllegalStateException("expected 0, was " + result);
}
}
}
@Benchmark
public void benchmarkBlockingLargePost(SimpleBenchmarkState state) throws IOException {
HttpPost post = new HttpPost(state.getBaseUri() + "/blocking");
post.setEntity(new InputStreamEntity(new StubInputStream(256000)));
try (CloseableHttpResponse response = state.client().execute(post)) {
String result = asString(validate(response).getEntity());
if (!"256000".equals(result)) {
throw new IllegalStateException("expected 256000, was " + result);
}
}
}
private String asString(HttpEntity entity) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
entity.writeTo(baos);
return baos.toString("UTF-8");
}
private void validateLength(HttpResponse response, long expectedLength) throws IOException {
long length = BenchmarkUtils.length(validate(response).getEntity().getContent());
if (length != expectedLength) {
throw new IllegalStateException("Unexpected length " + length);
}
}
private <T extends HttpResponse> T validate(T response) {
int status = response.getStatusLine().getStatusCode();
if (status != 200) {
throw new IllegalStateException("Unexpected status code " + status);
}
return response;
}
private static final class StubInputStream extends InputStream {
private int bytes;
StubInputStream(int bytes) {
this.bytes = bytes;
}
@Override
public int read() {
if (bytes <= 0) {
return -1;
}
bytes--;
return 1;
}
@Override
public int available() {
return bytes;
}
}
}
/*
* JBoss, Home of Professional Open Source.
* Copyright 2019 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.undertow.benchmarks;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
/**
* Helper utility to create {@link SSLContext} instances.
*
* @author Carter Kozak
*/
final class TLSUtils {
private static final String SERVER_KEY_STORE = "server.keystore";
private static final String SERVER_TRUST_STORE = "server.truststore";
private static final String CLIENT_KEY_STORE = "client.keystore";
private static final String CLIENT_TRUST_STORE = "client.truststore";
private static final char[] STORE_PASSWORD = "password".toCharArray();
static SSLContext newServerContext() {
try {
KeyStore keyStore = loadKeyStore(SERVER_KEY_STORE);
KeyStore trustStore = loadKeyStore(SERVER_TRUST_STORE);
return createSSLContext(keyStore, trustStore);
} catch (IOException e) {
throw new RuntimeException("Failed to create server SSLContext", e);
}
}
static SSLContext newClientContext() {
try {
KeyStore keyStore = loadKeyStore(CLIENT_KEY_STORE);
KeyStore trustStore = loadKeyStore(CLIENT_TRUST_STORE);
return createSSLContext(keyStore, trustStore);
} catch (IOException e) {
throw new RuntimeException("Failed to create client SSLContext", e);
}
}
private static KeyStore loadKeyStore(final String name) throws IOException {
try (InputStream stream = TLSUtils.class.getClassLoader().getResourceAsStream(name)) {
if (stream == null) {
throw new RuntimeException("Could not load keystore");
}
try {
KeyStore loadedKeystore = KeyStore.getInstance("JKS");
loadedKeystore.load(stream, STORE_PASSWORD);
return loadedKeystore;
} catch (KeyStoreException | NoSuchAlgorithmException | CertificateException e) {
throw new IOException("Unable to load KeyStore " + name, e);
}
}
}
private static SSLContext createSSLContext(final KeyStore keyStore, final KeyStore trustStore) throws IOException {
KeyManager[] keyManagers;
try {
KeyManagerFactory keyManagerFactory =
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, STORE_PASSWORD);
keyManagers = keyManagerFactory.getKeyManagers();
} catch (NoSuchAlgorithmException | UnrecoverableKeyException | KeyStoreException e) {
throw new IOException("Unable to initialise KeyManager[]", e);
}
TrustManager[] trustManagers;
try {
TrustManagerFactory trustManagerFactory =
TrustManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(trustStore);
trustManagers = trustManagerFactory.getTrustManagers();
} catch (NoSuchAlgorithmException | KeyStoreException e) {
throw new IOException("Unable to initialise TrustManager[]", e);
}
try {
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(keyManagers, trustManagers, null);
return sslContext;
} catch (NoSuchAlgorithmException | KeyManagementException e) {
throw new IOException("Unable to create and initialise the SSLContext", e);
}
}
private TLSUtils() {}
}
#
# JBoss, Home of Professional Open Source.
# Copyright 2012 Red Hat, Inc., and individual contributors
# as indicated by the @author tags.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Additional logger names to configure (root logger is always configured)
loggers=org.xnio.listener,org.xnio.ssl,org.apache,io.undertow.util.TestHttpClient
# Root logger configuration
logger.level=${test.level:ERROR}
logger.handlers=CONSOLE
# Console handler configuration
handler.CONSOLE=org.jboss.logmanager.handlers.ConsoleHandler
handler.CONSOLE.properties=autoFlush,target
handler.CONSOLE.target=SYSTEM_ERR
handler.CONSOLE.level=ALL
handler.CONSOLE.autoFlush=true
handler.CONSOLE.formatter=PATTERN
# The log format pattern
formatter.PATTERN=org.jboss.logmanager.formatters.PatternFormatter
formatter.PATTERN.properties=pattern
formatter.PATTERN.pattern=%d{HH:mm:ss,SSS} %-5p (%t) [%c] <%F:%L> %m%n
logger.org.xnio.listener.level=DEBUG
logger.org.xnio.ssl.level=DEBUG
logger.org.apache.level=WARN
logger.org.apache.useParentHandlers=false
logger.io.undertow.util.TestHttpClient.level=WARN
......@@ -25,12 +25,12 @@
<parent>
<groupId>io.undertow</groupId>
<artifactId>undertow-parent</artifactId>
<version>1.4.25.Final</version>
<version>2.0.23.Final</version>
</parent>
<groupId>io.undertow</groupId>
<artifactId>undertow-core</artifactId>
<version>1.4.25.Final</version>
<version>2.0.23.Final</version>
<name>Undertow Core</name>
......@@ -474,5 +474,100 @@
</plugins>
</build>
</profile>
<profile>
<id>jdk9</id>
<activation>
<jdk>9</jdk>
</activation>
<properties>
<java9.sourceDirectory>${project.basedir}/src/main/java9</java9.sourceDirectory>
<java9.build.outputDirectory>${project.build.directory}/classes-java9</java9.build.outputDirectory>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestEntries>
<Multi-Release>true</Multi-Release>
</manifestEntries>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<id>compile-java9</id>
<phase>compile</phase>
<configuration>
<tasks>
<mkdir dir="${java9.build.outputDirectory}" />
<javac srcdir="${java9.sourceDirectory}"
destdir="${java9.build.outputDirectory}"
classpath="${project.build.outputDirectory}"
includeantruntime="false"
/>
</tasks>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<executions>
<execution>
<id>check-style</id>
<phase>compile</phase>
<goals>
<goal>checkstyle</goal>
</goals>
</execution>
<execution>
<id>check-style-java9</id>
<phase>compile</phase>
<goals>
<goal>checkstyle</goal>
</goals>
<configuration>
<sourceDirectories>
<sourceDirectory>${java9.sourceDirectory}</sourceDirectory>
</sourceDirectories>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-resources</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.outputDirectory}/META-INF/versions/9</outputDirectory>
<resources>
<resource>
<directory>${java9.build.outputDirectory}</directory>
<filtering>false</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
......@@ -53,6 +53,8 @@ import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* Convenience class used to build an Undertow server.
......@@ -154,6 +156,7 @@ public final class Undertow {
for (ListenerConfig listener : listeners) {
UndertowLogger.ROOT_LOGGER.debugf("Configuring listener with protocol %s for interface %s and port %s", listener.type, listener.host, listener.port);
final HttpHandler rootHandler = listener.rootHandler != null ? listener.rootHandler : this.rootHandler;
OptionMap socketOptionsWithOverrides = OptionMap.builder().addAll(socketOptions).addAll(listener.overrideSocketOptions).getMap();
if (listener.type == ListenerType.AJP) {
AjpOpenListener openListener = new AjpOpenListener(buffers, serverOptions);
openListener.setRootHandler(rootHandler);
......@@ -165,7 +168,6 @@ public final class Undertow {
finalListener = openListener;
}
ChannelListener<AcceptingChannel<StreamConnection>> acceptListener = ChannelListeners.openListenerAdapter(finalListener);
OptionMap socketOptionsWithOverrides = OptionMap.builder().addAll(socketOptions).addAll(listener.overrideSocketOptions).getMap();
AcceptingChannel<? extends StreamConnection> server = worker.createStreamConnectionServer(new InetSocketAddress(Inet4Address.getByName(listener.host), listener.port), acceptListener, socketOptionsWithOverrides);
server.resumeAccepts();
channels.add(server);
......@@ -188,7 +190,6 @@ public final class Undertow {
}
ChannelListener<AcceptingChannel<StreamConnection>> acceptListener = ChannelListeners.openListenerAdapter(finalListener);
OptionMap socketOptionsWithOverrides = OptionMap.builder().addAll(socketOptions).addAll(listener.overrideSocketOptions).getMap();
AcceptingChannel<? extends StreamConnection> server = worker.createStreamConnectionServer(new InetSocketAddress(Inet4Address.getByName(listener.host), listener.port), acceptListener, socketOptionsWithOverrides);
server.resumeAccepts();
channels.add(server);
......@@ -201,12 +202,10 @@ public final class Undertow {
if (http2) {
AlpnOpenListener alpn = new AlpnOpenListener(buffers, undertowOptions, httpOpenListener);
if (http2) {
Http2OpenListener http2Listener = new Http2OpenListener(buffers, undertowOptions);
http2Listener.setRootHandler(rootHandler);
alpn.addProtocol(Http2OpenListener.HTTP2, http2Listener, 10);
alpn.addProtocol(Http2OpenListener.HTTP2_14, http2Listener, 7);
}
openListener = alpn;
} else {
openListener = httpOpenListener;
......@@ -216,15 +215,14 @@ public final class Undertow {
if (listener.sslContext != null) {
xnioSsl = new UndertowXnioSsl(xnio, OptionMap.create(Options.USE_DIRECT_BUFFERS, true), listener.sslContext);
} else {
OptionMap.Builder builder = OptionMap.builder();
builder.addAll(listener.overrideSocketOptions);
if (!listener.overrideSocketOptions.contains(Options.SSL_PROTOCOL)) {
OptionMap.Builder builder = OptionMap.builder()
.addAll(socketOptionsWithOverrides);
if (!socketOptionsWithOverrides.contains(Options.SSL_PROTOCOL)) {
builder.set(Options.SSL_PROTOCOL, "TLSv1.2");
}
xnioSsl = new UndertowXnioSsl(xnio, OptionMap.create(Options.USE_DIRECT_BUFFERS, true), JsseSslUtils.createSSLContext(listener.keyManagers, listener.trustManagers, new SecureRandom(), builder.getMap()));
}
OptionMap socketOptionsWithOverrides = OptionMap.builder().addAll(socketOptions).addAll(listener.overrideSocketOptions).getMap();
AcceptingChannel<? extends StreamConnection> sslServer;
if (listener.useProxyProtocol) {
ChannelListener<AcceptingChannel<StreamConnection>> acceptListener = ChannelListeners.openListenerAdapter(new ProxyProtocolOpenListener(openListener, xnioSsl, buffers, socketOptionsWithOverrides));
......@@ -263,10 +261,18 @@ public final class Undertow {
* Only shutdown the worker if it was created during start()
*/
if (internalWorker && worker != null) {
Integer shutdownTimeoutMillis = serverOptions.get(UndertowOptions.SHUTDOWN_TIMEOUT);
worker.shutdown();
try {
if (shutdownTimeoutMillis == null) {
worker.awaitTermination();
} else {
if (!worker.awaitTermination(shutdownTimeoutMillis, TimeUnit.MILLISECONDS)) {
worker.shutdownNow();
}
}
} catch (InterruptedException e) {
worker.shutdownNow();
throw new RuntimeException(e);
}
worker = null;
......@@ -552,7 +558,7 @@ public final class Undertow {
* to the various worker-related configuration (ioThreads, workerThreads, workerOptions)
* when {@link Undertow#start()} is called.
* Additionally, this newly created worker will be shutdown when {@link Undertow#stop()} is called.
* <br/>
* <br>
* <p>
* When non-null, the provided {@link XnioWorker} will be reused instead of creating a new {@link XnioWorker}
* when {@link Undertow#start()} is called.
......@@ -577,6 +583,7 @@ public final class Undertow {
private final OpenListener openListener;
private final UndertowXnioSsl ssl;
private final AcceptingChannel<? extends StreamConnection> channel;
private volatile boolean suspended = false;
public ListenerInfo(String protcol, SocketAddress address, OpenListener openListener, UndertowXnioSsl ssl, AcceptingChannel<? extends StreamConnection> channel) {
this.protcol = protcol;
......@@ -608,6 +615,37 @@ public final class Undertow {
}
}
public synchronized void suspend() {
suspended = true;
channel.suspendAccepts();
CountDownLatch latch = new CountDownLatch(1);
//the channel may be in the middle of an accept, we need to close from the IO thread
channel.getIoThread().execute(new Runnable() {
@Override
public void run() {
try {
openListener.closeConnections();
} finally {
latch.countDown();
}
}
});
try {
latch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
public synchronized void resume() {
suspended = false;
channel.resumeAccepts();
}
public boolean isSuspended() {
return suspended;
}
public ConnectorStatistics getConnectorStatistics() {
return openListener.getConnectorStatistics();
}
......
......@@ -68,6 +68,7 @@ public interface UndertowLogger extends BasicLogger {
* attacker to fill up the logs by intentionally causing IO exceptions.
*/
UndertowLogger REQUEST_IO_LOGGER = Logger.getMessageLogger(UndertowLogger.class, UndertowLogger.class.getPackage().getName() + ".request.io");
UndertowLogger ERROR_RESPONSE = Logger.getMessageLogger(UndertowLogger.class, UndertowLogger.class.getPackage().getName() + ".request.error-response");
@LogMessage(level = ERROR)
@Message(id = 5001, value = "An exception occurred processing the request")
......
......@@ -20,6 +20,8 @@ package io.undertow;
import java.io.IOException;
import java.nio.channels.ClosedChannelException;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLPeerUnverifiedException;
......@@ -87,7 +89,7 @@ public interface UndertowMessages {
// @Message(id = 16, value = "Could not add cookie as cookie handler was not present in the handler chain")
// IllegalStateException cookieHandlerNotPresent();
@Message(id = 17, value = "Form value is a file, use getFile() instead")
@Message(id = 17, value = "Form value is a file, use getFileItem() instead")
IllegalStateException formValueIsAFile();
@Message(id = 18, value = "Form value is a String, use getValue() instead")
......@@ -527,6 +529,9 @@ public interface UndertowMessages {
@Message(id = 165, value = "Invalid character %s in request-target")
String invalidCharacterInRequestTarget(char next);
@Message(id = 166, value = "Pooled object is closed")
IllegalStateException objectIsClosed();
@Message(id = 167, value = "More than one host header in request")
IOException moreThanOneHostHeader();
......@@ -560,9 +565,36 @@ public interface UndertowMessages {
@Message(id = 181, value = "HTTP/2 trailers too large for single buffer")
RuntimeException http2TrailerToLargeForSingleBuffer();
@Message(id = 182, value = "Ping not supported")
IOException pingNotSupported();
@Message(id = 183, value = "Ping timed out")
IOException pingTimeout();
@Message(id = 184, value = "Stream limit exceeded")
IOException streamLimitExceeded();
@Message(id = 185, value = "Invalid IP address %s")
IOException invalidIpAddress(String addressString);
@Message(id = 186, value = "Invalid TLS extension")
SSLException invalidTlsExt();
@Message(id = 187, value = "Not enough data")
SSLException notEnoughData();
@Message(id = 188, value = "Empty host name in SNI extension")
SSLException emptyHostNameSni();
@Message(id = 189, value = "Duplicated host name of type %s")
SSLException duplicatedSniServerName(int type);
@Message(id = 190, value = "No context for SSL connection")
SSLException noContextForSslConnection();
@Message(id = 191, value = "Default context cannot be null")
IllegalStateException defaultContextCannotBeNull();
@Message(id = 192, value = "Form value is a in-memory file, use getFileItem() instead")
IllegalStateException formValueIsInMemoryFile();
}
......@@ -276,7 +276,7 @@ public class UndertowOptions {
/**
* The maximum number of concurrent requests that will be processed at a time. This differs from max concurrent streams in that it is not sent to the remote client.
*
* If the number of pending requests exceeds this number then requests will be queued, the difference between this and max concurrent streams determins
* If the number of pending requests exceeds this number then requests will be queued, the difference between this and max concurrent streams determines
* the maximum number of requests that will be queued.
*
* Queued requests are processed by a priority queue, rather than a FIFO based queue, using HTTP2 stream priority.
......@@ -324,6 +324,21 @@ public class UndertowOptions {
public static final Option<Boolean> ALLOW_UNESCAPED_CHARACTERS_IN_URL = Option.simple(UndertowOptions.class,"ALLOW_UNESCAPED_CHARACTERS_IN_URL", Boolean.class);
/**
* The server shutdown timeout in milliseconds after which the executor will be forcefully shut down interrupting
* tasks which are still executing.
*
* There is no timeout by default.
*/
public static final Option<Integer> SHUTDOWN_TIMEOUT = Option.simple(UndertowOptions.class, "SHUTDOWN_TIMEOUT", Integer.class);
/**
* The endpoint identification algorithm.
*
* @see javax.net.ssl.SSLParameters#setEndpointIdentificationAlgorithm(String)
*/
public static final Option<String> ENDPOINT_IDENTIFICATION_ALGORITHM = Option.simple(UndertowOptions.class, "ENDPOINT_IDENTIFICATION_ALGORITHM", String.class);
private UndertowOptions() {
}
......
......@@ -75,6 +75,10 @@ public class ExchangeAttributes {
return RemoteIPAttribute.INSTANCE;
}
public static ExchangeAttribute remoteObfuscatedIp() {
return RemoteObfuscatedIPAttribute.INSTANCE;
}
public static ExchangeAttribute remoteUser() {
return RemoteUserAttribute.INSTANCE;
}
......
/*
* JBoss, Home of Professional Open Source.
* Copyright 2019 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.undertow.attribute;
import io.undertow.server.HttpServerExchange;
import io.undertow.util.NetworkUtils;
import java.net.InetAddress;
import java.net.InetSocketAddress;
/**
* The remote IP address
*
* @author Stuart Douglas
*/
public class RemoteObfuscatedIPAttribute implements ExchangeAttribute {
public static final String REMOTE_OBFUSCATED_IP_SHORT = "%o";
public static final String REMOTE_OBFUSCATED_IP = "%{REMOTE_OBFUSCATED_IP}";
public static final ExchangeAttribute INSTANCE = new RemoteObfuscatedIPAttribute();
private RemoteObfuscatedIPAttribute() {
}
@Override
public String readAttribute(final HttpServerExchange exchange) {
final InetSocketAddress sourceAddress = exchange.getSourceAddress();
InetAddress address = sourceAddress.getAddress();
if (address == null) {
//this can happen when we have an unresolved X-forwarded-for address
//in this case we just return the IP of the balancer
address = ((InetSocketAddress) exchange.getConnection().getPeerAddress()).getAddress();
}
if(address == null) {
return null;
}
return NetworkUtils.toObfuscatedString(address);
}
@Override
public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException {
throw new ReadOnlyAttributeException("Remote Obfuscated IP", newValue);
}
public static final class Builder implements ExchangeAttributeBuilder {
@Override
public String name() {
return "Remote Obfuscated IP";
}
@Override
public ExchangeAttribute build(final String token) {
if (token.equals(REMOTE_OBFUSCATED_IP) || token.equals(REMOTE_OBFUSCATED_IP_SHORT)) {
return RemoteObfuscatedIPAttribute.INSTANCE;
}
return null;
}
@Override
public int priority() {
return 0;
}
}
}
/*
* JBoss, Home of Professional Open Source.
* Copyright 2014 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.undertow.attribute;
import io.undertow.server.HttpServerExchange;
import io.undertow.server.handlers.Cookie;
import io.undertow.server.handlers.CookieImpl;
/**
* A request cookie
*/
public class RequestCookieAttribute implements ExchangeAttribute {
private static final String TOKEN_PREFIX = "%{req-cookie,";
private final String cookieName;
public RequestCookieAttribute(final String cookieName) {
this.cookieName = cookieName;
}
@Override
public String readAttribute(final HttpServerExchange exchange) {
Cookie cookie = exchange.getRequestCookies().get(cookieName);
if (cookie == null) {
return null;
}
return cookie.getValue();
}
@Override
public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException {
exchange.getRequestCookies().put(cookieName, new CookieImpl(cookieName, newValue));
}
public static final class Builder implements ExchangeAttributeBuilder {
@Override
public String name() {
return "Request cookie";
}
@Override
public ExchangeAttribute build(final String token) {
if (token.startsWith(TOKEN_PREFIX) && token.endsWith("}")) {
final String cookieName = token.substring(TOKEN_PREFIX.length(), token.length() - 1);
return new RequestCookieAttribute(cookieName);
}
return null;
}
@Override
public int priority() {
return 0;
}
}
}