[Git][java-team/undertow][upstream] New upstream version 2.1.3
Markus Koschany
gitlab at salsa.debian.org
Thu Jun 4 21:57:53 BST 2020
Markus Koschany pushed to branch upstream at Debian Java Maintainers / undertow
Commits:
19a11c12 by Markus Koschany at 2020-06-04T22:48:35+02:00
New upstream version 2.1.3
- - - - -
24 changed files:
- benchmarks/pom.xml
- core/pom.xml
- core/src/main/java/io/undertow/io/AsyncSenderImpl.java
- core/src/main/java/io/undertow/io/BlockingSenderImpl.java
- core/src/main/java/io/undertow/protocols/alpn/JDK9AlpnProvider.java
- core/src/main/java/io/undertow/server/protocol/http/HttpRequestParser.java
- core/src/main/java/io/undertow/util/ByteRange.java
- core/src/main/java/io/undertow/util/Cookies.java
- core/src/test/java/io/undertow/server/handlers/RangeRequestTestCase.java
- core/src/test/java/io/undertow/server/handlers/session/InMemorySessionTestCase.java
- core/src/test/java/io/undertow/server/protocol/http/SimpleParserTestCase.java
- core/src/test/java/io/undertow/util/ByteRangeTestCase.java
- core/src/test/java/io/undertow/util/CookiesTestCase.java
- coverage-report/pom.xml
- dist/pom.xml
- examples/pom.xml
- parser-generator/pom.xml
- pom.xml
- servlet/pom.xml
- servlet/src/main/java/io/undertow/servlet/core/BlockingWriterSenderImpl.java
- servlet/src/main/java/io/undertow/servlet/spec/HttpServletRequestImpl.java
- servlet/src/main/java/io/undertow/servlet/spec/ServletInputStreamImpl.java
- servlet/src/main/java/io/undertow/servlet/spec/ServletOutputStreamImpl.java
- websockets-jsr/pom.xml
Changes:
=====================================
benchmarks/pom.xml
=====================================
@@ -25,11 +25,11 @@
<parent>
<groupId>io.undertow</groupId>
<artifactId>undertow-parent</artifactId>
- <version>2.1.1.Final</version>
+ <version>2.1.3.Final</version>
</parent>
<artifactId>undertow-benchmarks</artifactId>
- <version>2.1.1.Final</version>
+ <version>2.1.3.Final</version>
<name>Undertow Benchmarks</name>
=====================================
core/pom.xml
=====================================
@@ -25,12 +25,12 @@
<parent>
<groupId>io.undertow</groupId>
<artifactId>undertow-parent</artifactId>
- <version>2.1.1.Final</version>
+ <version>2.1.3.Final</version>
</parent>
<groupId>io.undertow</groupId>
<artifactId>undertow-core</artifactId>
- <version>2.1.1.Final</version>
+ <version>2.1.3.Final</version>
<name>Undertow Core</name>
=====================================
core/src/main/java/io/undertow/io/AsyncSenderImpl.java
=====================================
@@ -25,18 +25,19 @@ import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
-import io.undertow.UndertowLogger;
-import io.undertow.UndertowMessages;
-import io.undertow.server.HttpServerExchange;
-import io.undertow.util.Headers;
import org.xnio.Buffers;
import org.xnio.ChannelExceptionHandler;
import org.xnio.ChannelListener;
import org.xnio.ChannelListeners;
import org.xnio.IoUtils;
-import io.undertow.connector.PooledByteBuffer;
import org.xnio.channels.StreamSinkChannel;
+import io.undertow.UndertowLogger;
+import io.undertow.UndertowMessages;
+import io.undertow.connector.PooledByteBuffer;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.util.Headers;
+
/**
* @author Stuart Douglas
*/
@@ -44,11 +45,28 @@ public class AsyncSenderImpl implements Sender {
private StreamSinkChannel channel;
private final HttpServerExchange exchange;
- private ByteBuffer[] buffer;
private PooledByteBuffer[] pooledBuffers = null;
private FileChannel fileChannel;
private IoCallback callback;
- private boolean inCallback;
+
+ private ByteBuffer[] buffer;
+
+ /**
+ * This object is not intended to be used in a multi threaded manner
+ * however as we run code after the callback it is possible that another
+ * thread may call send while we are still running
+ * we use the 'writeThread' state guard to protect against this happening.
+ *
+ * During a send() call the 'writeThread' object is set first, followed by the
+ * buffer. The inCallback variable is used to determine if the current thread
+ * is in the process of running a callback.
+ *
+ * After the callback has been invoked the thread that initiated the callback
+ * will only continue to process if it is the writeThread.
+ *
+ */
+ private volatile Thread writeThread;
+ private volatile Thread inCallback;
private ChannelListener<StreamSinkChannel> writeListener;
@@ -116,6 +134,7 @@ public class AsyncSenderImpl implements Sender {
@Override
public void send(final ByteBuffer buffer, final IoCallback callback) {
+ writeThread = Thread.currentThread();
if (callback == null) {
throw UndertowMessages.MESSAGES.argumentCannotBeNull("callback");
}
@@ -147,7 +166,7 @@ public class AsyncSenderImpl implements Sender {
}
}
this.callback = callback;
- if (inCallback) {
+ if (inCallback == Thread.currentThread()) {
this.buffer = new ByteBuffer[]{buffer};
return;
}
@@ -180,6 +199,7 @@ public class AsyncSenderImpl implements Sender {
@Override
public void send(final ByteBuffer[] buffer, final IoCallback callback) {
+ writeThread = Thread.currentThread();
if (callback == null) {
throw UndertowMessages.MESSAGES.argumentCannotBeNull("callback");
}
@@ -195,7 +215,7 @@ public class AsyncSenderImpl implements Sender {
throw UndertowMessages.MESSAGES.dataAlreadyQueued();
}
this.callback = callback;
- if (inCallback) {
+ if (inCallback == Thread.currentThread()) {
this.buffer = buffer;
return;
}
@@ -249,6 +269,7 @@ public class AsyncSenderImpl implements Sender {
@Override
public void transferFrom(FileChannel source, IoCallback callback) {
+ writeThread = Thread.currentThread();
if (callback == null) {
throw UndertowMessages.MESSAGES.argumentCannotBeNull("callback");
}
@@ -266,7 +287,7 @@ public class AsyncSenderImpl implements Sender {
this.callback = callback;
this.fileChannel = source;
- if (inCallback) {
+ if (inCallback == Thread.currentThread()) {
return;
}
if(transferTask == null) {
@@ -297,7 +318,7 @@ public class AsyncSenderImpl implements Sender {
@Override
public void send(final String data, final Charset charset, final IoCallback callback) {
-
+ writeThread = Thread.currentThread();
if(!exchange.getConnection().isOpen()) {
invokeOnException(callback, new ClosedChannelException());
return;
@@ -408,11 +429,15 @@ public class AsyncSenderImpl implements Sender {
this.buffer = null;
this.fileChannel = null;
this.callback = null;
- inCallback = true;
+ writeThread = null;
+ inCallback = Thread.currentThread();
try {
callback.onComplete(exchange, this);
} finally {
- inCallback = false;
+ inCallback = null;
+ }
+ if (Thread.currentThread() != writeThread) {
+ return;
}
StreamSinkChannel channel = this.channel;
=====================================
core/src/main/java/io/undertow/io/BlockingSenderImpl.java
=====================================
@@ -43,7 +43,8 @@ public class BlockingSenderImpl implements Sender {
private final HttpServerExchange exchange;
private final OutputStream outputStream;
- private boolean inCall;
+ private volatile Thread inCall;
+ private volatile Thread sendThread;
private ByteBuffer[] next;
private FileChannel pendingFile;
private IoCallback queuedCallback;
@@ -55,7 +56,8 @@ public class BlockingSenderImpl implements Sender {
@Override
public void send(final ByteBuffer buffer, final IoCallback callback) {
- if (inCall) {
+ sendThread = Thread.currentThread();
+ if (inCall == Thread.currentThread()) {
queue(new ByteBuffer[]{buffer}, callback);
return;
} else {
@@ -78,7 +80,8 @@ public class BlockingSenderImpl implements Sender {
@Override
public void send(final ByteBuffer[] buffer, final IoCallback callback) {
- if (inCall) {
+ sendThread = Thread.currentThread();
+ if (inCall == Thread.currentThread()) {
queue(buffer, callback);
return;
} else {
@@ -112,7 +115,8 @@ public class BlockingSenderImpl implements Sender {
@Override
public void send(final String data, final IoCallback callback) {
byte[] bytes = data.getBytes(StandardCharsets.UTF_8);
- if (inCall) {
+ sendThread = Thread.currentThread();
+ if (inCall == Thread.currentThread()) {
queue(new ByteBuffer[]{ByteBuffer.wrap(bytes)}, callback);
return;
} else {
@@ -138,7 +142,8 @@ public class BlockingSenderImpl implements Sender {
@Override
public void send(final String data, final Charset charset, final IoCallback callback) {
byte[] bytes = data.getBytes(charset);
- if (inCall) {
+ sendThread = Thread.currentThread();
+ if (inCall == Thread.currentThread()) {
queue(new ByteBuffer[]{ByteBuffer.wrap(bytes)}, callback);
return;
}else {
@@ -173,7 +178,8 @@ public class BlockingSenderImpl implements Sender {
@Override
public void transferFrom(FileChannel source, IoCallback callback) {
- if (inCall) {
+ sendThread = Thread.currentThread();
+ if (inCall == Thread.currentThread()) {
queue(source, callback);
return;
}
@@ -271,11 +277,15 @@ public class BlockingSenderImpl implements Sender {
private void invokeOnComplete(final IoCallback callback) {
- inCall = true;
+ sendThread = null;
+ inCall = Thread.currentThread();
try {
callback.onComplete(exchange, this);
} finally {
- inCall = false;
+ inCall = null;
+ }
+ if (Thread.currentThread() != sendThread) {
+ return;
}
while (next != null || pendingFile != null) {
ByteBuffer[] next = this.next;
@@ -292,11 +302,15 @@ public class BlockingSenderImpl implements Sender {
} else if (file != null) {
performTransfer(file, queuedCallback);
}
- inCall = true;
+ sendThread = null;
+ inCall = Thread.currentThread();
try {
queuedCallback.onComplete(exchange, this);
} finally {
- inCall = false;
+ inCall = null;
+ }
+ if (Thread.currentThread() != sendThread) {
+ return;
}
}
}
=====================================
core/src/main/java/io/undertow/protocols/alpn/JDK9AlpnProvider.java
=====================================
@@ -22,6 +22,8 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLParameters;
@@ -38,16 +40,39 @@ public class JDK9AlpnProvider implements ALPNProvider {
public static final JDK9ALPNMethods JDK_9_ALPN_METHODS;
+ private static final String JDK8_SUPPORT_PROPERTY = "io.undertow.protocols.alpn.jdk8";
static {
+ // This property must be checked outside of the privileged action as the user should explicitly provide read
+ // access to it. A value of true is the only supported value.
+ final boolean addSupportIfExists = Boolean.getBoolean(JDK8_SUPPORT_PROPERTY);
JDK_9_ALPN_METHODS = AccessController.doPrivileged(new PrivilegedAction<JDK9ALPNMethods>() {
@Override
public JDK9ALPNMethods run() {
try {
- Method setApplicationProtocols = SSLParameters.class.getMethod("setApplicationProtocols", String[].class);
- Method getApplicationProtocol = SSLEngine.class.getMethod("getApplicationProtocol");
- UndertowLogger.ROOT_LOGGER.debug("Using JDK9 ALPN");
- return new JDK9ALPNMethods(setApplicationProtocols, getApplicationProtocol);
+ final String javaVersion = System.getProperty("java.specification.version");
+ int vmVersion = 8;
+ try {
+ final Matcher matcher = Pattern.compile("^(?:1\\.)?(\\d+)$").matcher(javaVersion);
+ if (matcher.find()) {
+ vmVersion = Integer.parseInt(matcher.group(1));
+ }
+ } catch (Exception ignore) {
+ }
+ // There was a backport of the ALPN support to Java 8 in rev 251. If a non-JDK implementation of the
+ // SSLEngine is used these methods throw an UnsupportedOperationException by default. However the
+ // methods would exist and could result in issues. These methods can still be used by providing the
+ // io.undertow.protocols.alpn.jdk8=true system property and support for Java 8 known in the
+ // SSLEngine implementation being provided.
+ if (vmVersion > 8 || addSupportIfExists) {
+ Method setApplicationProtocols = SSLParameters.class.getMethod("setApplicationProtocols", String[].class);
+ Method getApplicationProtocol = SSLEngine.class.getMethod("getApplicationProtocol");
+ UndertowLogger.ROOT_LOGGER.debug("Using JDK9 ALPN");
+ return new JDK9ALPNMethods(setApplicationProtocols, getApplicationProtocol);
+ }
+ UndertowLogger.ROOT_LOGGER.debugf("It's not certain ALPN support was found. " +
+ "Provider %s will be disabled.", JDK9AlpnProvider.class.getName());
+ return null;
} catch (Exception e) {
UndertowLogger.ROOT_LOGGER.debug("JDK9 ALPN not supported");
return null;
=====================================
core/src/main/java/io/undertow/server/protocol/http/HttpRequestParser.java
=====================================
@@ -391,25 +391,9 @@ public abstract class HttpRequestParser {
if (next == ' ' || next == '\t') {
if (stringBuilder.length() != 0) {
final String path = stringBuilder.toString();
- if(parseState == SECOND_SLASH) {
- exchange.setRequestPath("/");
- exchange.setRelativePath("/");
- exchange.setRequestURI(path);
- } else if (parseState < HOST_DONE && state.canonicalPath.length() == 0) {
- String decodedPath = decode(path, urlDecodeRequired, state, allowEncodedSlash, false);
- exchange.setRequestPath(decodedPath);
- exchange.setRelativePath(decodedPath);
- exchange.setRequestURI(path);
- } else {
- handleFullUrl(state, exchange, canonicalPathStart, urlDecodeRequired, path);
- }
+ parsePathComplete(state, exchange, canonicalPathStart, parseState, urlDecodeRequired, path);
exchange.setQueryString("");
state.state = ParseState.VERSION;
- state.stringBuilder.setLength(0);
- state.canonicalPath.setLength(0);
- state.parseState = 0;
- state.pos = 0;
- state.urlDecodeRequired = false;
return;
}
} else if (next == '\r' || next == '\n') {
@@ -462,35 +446,39 @@ public abstract class HttpRequestParser {
state.urlDecodeRequired = urlDecodeRequired;
}
- private void beginQueryParameters(ByteBuffer buffer, ParseState state, HttpServerExchange exchange, StringBuilder stringBuilder, int parseState, int canonicalPathStart, boolean urlDecodeRequired) throws BadRequestException {
- final String path = stringBuilder.toString();
+ private void parsePathComplete(ParseState state, HttpServerExchange exchange, int canonicalPathStart, int parseState, boolean urlDecodeRequired, String path) {
if (parseState == SECOND_SLASH) {
exchange.setRequestPath("/");
exchange.setRelativePath("/");
- exchange.setRequestURI(path);
- } else if (parseState < HOST_DONE) {
+ exchange.setRequestURI(path, true);
+ } else if (parseState < HOST_DONE && state.canonicalPath.length() == 0) {
String decodedPath = decode(path, urlDecodeRequired, state, allowEncodedSlash, false);
exchange.setRequestPath(decodedPath);
exchange.setRelativePath(decodedPath);
exchange.setRequestURI(path, false);
} else {
- handleFullUrl(state, exchange, canonicalPathStart, urlDecodeRequired, path);
+ handleFullUrl(state, exchange, canonicalPathStart, urlDecodeRequired, path, parseState);
}
- state.state = ParseState.QUERY_PARAMETERS;
state.stringBuilder.setLength(0);
state.canonicalPath.setLength(0);
state.parseState = 0;
state.pos = 0;
state.urlDecodeRequired = false;
+ }
+
+ private void beginQueryParameters(ByteBuffer buffer, ParseState state, HttpServerExchange exchange, StringBuilder stringBuilder, int parseState, int canonicalPathStart, boolean urlDecodeRequired) throws BadRequestException {
+ final String path = stringBuilder.toString();
+ parsePathComplete(state, exchange, canonicalPathStart, parseState, urlDecodeRequired, path);
+ state.state = ParseState.QUERY_PARAMETERS;
handleQueryParameters(buffer, state, exchange);
}
- private void handleFullUrl(ParseState state, HttpServerExchange exchange, int canonicalPathStart, boolean urlDecodeRequired, String path) {
+ private void handleFullUrl(ParseState state, HttpServerExchange exchange, int canonicalPathStart, boolean urlDecodeRequired, String path, int parseState) {
state.canonicalPath.append(path.substring(canonicalPathStart));
String thePath = decode(state.canonicalPath.toString(), urlDecodeRequired, state, allowEncodedSlash, false);
exchange.setRequestPath(thePath);
exchange.setRelativePath(thePath);
- exchange.setRequestURI(path, true);
+ exchange.setRequestURI(path, parseState == HOST_DONE);
}
@@ -613,22 +601,9 @@ public abstract class HttpRequestParser {
if (next == ' ' || next == '\t' || next == '?') {
handleParsedParam(param, stringBuilder.substring(pos), exchange, urlDecodeRequired, state);
final String path = stringBuilder.toString();
- if(state.parseState == SECOND_SLASH) {
- exchange.setRequestPath("/");
- exchange.setRelativePath("/");
- exchange.setRequestURI(path);
- } else {
- String decodedPath = decode(state.canonicalPath.toString(), urlDecodeRequired, state, allowEncodedSlash, false);
- exchange.setRequestPath(decodedPath);
- exchange.setRelativePath(decodedPath);
- exchange.setRequestURI(path);
- }
+ // the canonicalPathStart should be the current length to not add anything to it
+ parsePathComplete(state, exchange, path.length(), state.parseState, urlDecodeRequired, path);
state.state = ParseState.VERSION;
- state.stringBuilder.setLength(0);
- state.canonicalPath.setLength(0);
- state.parseState = 0;
- state.pos = 0;
- state.urlDecodeRequired = false;
state.nextQueryParam = null;
if (next == '?') {
state.state = ParseState.QUERY_PARAMETERS;
=====================================
core/src/main/java/io/undertow/util/ByteRange.java
=====================================
@@ -157,7 +157,7 @@ public class ByteRange {
} else if(end == -1) {
//prefix range
long toWrite = resourceContentLength - start;
- if(toWrite >= 0) {
+ if (toWrite > 0) {
rangeLength = toWrite;
} else {
//ignore the range request
=====================================
core/src/main/java/io/undertow/util/Cookies.java
=====================================
@@ -270,7 +270,11 @@ public class Cookies {
state = 4;
start = i + 1;
}
- } else if (!allowHttpSepartorsV0 && LegacyCookieSupport.isHttpSeparator(c)) {
+ } else if (c != ':' && !allowHttpSepartorsV0 && LegacyCookieSupport.isHttpSeparator(c)) {
+ // http separators are not allowed in V0 cookie value unless io.undertow.legacy.cookie.ALLOW_HTTP_SEPARATORS_IN_V0 is set to true.
+ // However, "<hostcontroller-name>:<server-name>" (e.g. master:node1) is added as jvmRoute (instance-id) by default in WildFly domain mode.
+ // Though ":" is http separator, we allow it by default. Because, when Undertow runs as a proxy server (mod_cluster),
+ // we need to handle jvmRoute containing ":" in the request cookie value correctly to maintain the sticky session.
cookieCount = createCookie(name, cookie.substring(start, i), maxCookies, cookieCount, cookies, additional);
state = 4;
start = i + 1;
=====================================
core/src/test/java/io/undertow/server/handlers/RangeRequestTestCase.java
=====================================
@@ -220,6 +220,14 @@ public class RangeRequestTestCase {
Assert.assertEquals("", response);
Assert.assertEquals("bytes */10", result.getFirstHeader(Headers.CONTENT_RANGE_STRING).getValue());
+ get = new HttpGet(DefaultServer.getDefaultServerURL() + path);
+ get.addHeader(Headers.RANGE_STRING, "bytes=10-");
+ result = client.execute(get);
+ Assert.assertEquals(StatusCodes.REQUEST_RANGE_NOT_SATISFIABLE, result.getStatusLine().getStatusCode());
+ response = EntityUtils.toString(result.getEntity());
+ Assert.assertEquals("", response);
+ Assert.assertEquals("bytes */10", result.getFirstHeader(Headers.CONTENT_RANGE_STRING).getValue());
+
get = new HttpGet(DefaultServer.getDefaultServerURL() + path);
get.addHeader(Headers.RANGE_STRING, "bytes=2-3");
get.addHeader(Headers.IF_RANGE_STRING, DateUtils.toDateString(new Date(System.currentTimeMillis() + 1000)));
=====================================
core/src/test/java/io/undertow/server/handlers/session/InMemorySessionTestCase.java
=====================================
@@ -100,8 +100,6 @@ public class InMemorySessionTestCase {
}
}
-
-
@Test
public void inMemoryMaxSessionsTest() throws IOException {
@@ -166,4 +164,89 @@ public class InMemorySessionTestCase {
}
}
+ @Test // https://issues.redhat.com/browse/UNDERTOW-1419
+ public void inMemorySessionTimeoutExpirationTest() throws IOException, InterruptedException {
+
+ final int maxInactiveIntervalInSeconds = 1;
+ final int accessorThreadSleepInMilliseconds = 200;
+
+ TestHttpClient client = new TestHttpClient();
+ client.setCookieStore(new BasicCookieStore());
+ try {
+ final SessionCookieConfig sessionConfig = new SessionCookieConfig();
+ final SessionAttachmentHandler handler = new SessionAttachmentHandler(new InMemorySessionManager(""), sessionConfig);
+ handler.setNext(new HttpHandler() {
+ @Override
+ public void handleRequest(final HttpServerExchange exchange) throws Exception {
+ final SessionManager manager = exchange.getAttachment(SessionManager.ATTACHMENT_KEY);
+ Session session = manager.getSession(exchange, sessionConfig);
+ if (session == null) {
+ // set 1 second timeout for this session expiration
+ manager.setDefaultSessionTimeout(maxInactiveIntervalInSeconds);
+ session = manager.createSession(exchange, sessionConfig);
+ session.setAttribute(COUNT, 0);
+ // let's call getAttribute() some times to be sure that the session timeout is no longer bumped
+ // by the method invocation
+ Runnable r = new Runnable() {
+ public void run() {
+ Session innerThreadSession = manager.getSession(exchange, sessionConfig);
+ int iterations = ((maxInactiveIntervalInSeconds * 1000)/accessorThreadSleepInMilliseconds);
+ for (int i = 0; i <= iterations; i++) {
+ try {
+ Thread.sleep(accessorThreadSleepInMilliseconds);
+ } catch (InterruptedException e) {
+ System.out.println(
+ String.format("Unexpected error during Thread.sleep(): %s", e.getMessage()));
+ }
+ if (innerThreadSession != null) {
+ try {
+ System.out.println(String.format("Session is still valid. Attribute is: %s", innerThreadSession.getAttribute(COUNT).toString()));
+ if (i == iterations) {
+ System.out.println("Session should not still be valid!");
+ }
+ } catch (IllegalStateException e) {
+ if ((e instanceof IllegalStateException) && e.getMessage().startsWith("UT000010")) {
+ System.out.println(
+ String.format("This is expected as session is not valid anymore: %s", e.getMessage()));
+ } else {
+ System.out.println(
+ String.format("Unexpected exception while calling session.getAttribute(): %s", e.getMessage()));
+ }
+ }
+ }
+ }
+ }
+ };
+ Thread thread = new Thread(r);
+ thread.start();
+ }
+ // here the server is accessing one session attribute, so we're sure that the bumped timeout
+ // issue is being replicated and we can test for regression
+ Integer count = (Integer) session.getAttribute(COUNT);
+ exchange.getResponseHeaders().add(new HttpString(COUNT), count.toString());
+ session.setAttribute(COUNT, ++count);
+ }
+ });
+ DefaultServer.setRootHandler(handler);
+
+ HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/notamatchingpath");
+ HttpResponse result = client.execute(get);
+ Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode());
+ HttpClientUtils.readResponse(result);
+ Header[] header = result.getHeaders(COUNT);
+ Assert.assertEquals("0", header[0].getValue());
+
+ Thread.sleep(2 * 1000L);
+ // after 2 seconds from the last call, the session expiration timeout hasn't been bumped anymore,
+ // so now "COUNT" should be still set to 0 (zero)
+ get = new HttpGet(DefaultServer.getDefaultServerURL() + "/notamatchingpath");
+ result = client.execute(get);
+ Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode());
+ HttpClientUtils.readResponse(result);
+ header = result.getHeaders(COUNT);
+ Assert.assertEquals("0", header[0].getValue());
+ } finally {
+ client.getConnectionManager().shutdown();
+ }
+ }
}
=====================================
core/src/test/java/io/undertow/server/protocol/http/SimpleParserTestCase.java
=====================================
@@ -108,6 +108,22 @@ public class SimpleParserTestCase {
Assert.assertEquals(1, result.getPathParameters().size());
Assert.assertEquals("p1", result.getPathParameters().keySet().toArray()[0]);
Assert.assertSame(Protocols.HTTP_1_1, result.getProtocol());
+ Assert.assertFalse(result.isHostIncludedInRequestURI());
+ }
+
+ @Test
+ public void testMatrixParamFlagEndingWithNormalPath() throws BadRequestException {
+ byte[] in = "GET /somepath;p1/more HTTP/1.1\r\n\r\n".getBytes();
+ ParseState context = new ParseState(10);
+ HttpServerExchange result = new HttpServerExchange(null);
+ HttpRequestParser.instance(OptionMap.create(UndertowOptions.ALLOW_ENCODED_SLASH, true)).handle(ByteBuffer.wrap(in), context, result);
+ Assert.assertSame(Methods.GET, result.getRequestMethod());
+ Assert.assertEquals("/somepath;p1/more", result.getRequestURI());
+ Assert.assertEquals("/somepath/more", result.getRequestPath());
+ Assert.assertEquals(1, result.getPathParameters().size());
+ Assert.assertEquals("p1", result.getPathParameters().keySet().toArray()[0]);
+ Assert.assertSame(Protocols.HTTP_1_1, result.getProtocol());
+ Assert.assertFalse(result.isHostIncludedInRequestURI());
}
@Test
@@ -124,6 +140,7 @@ public class SimpleParserTestCase {
Assert.assertEquals("v1", result.getPathParameters().get("p1").getFirst());
Assert.assertEquals("v2", result.getPathParameters().get("p1").getLast());
Assert.assertSame(Protocols.HTTP_1_1, result.getProtocol());
+ Assert.assertFalse(result.isHostIncludedInRequestURI());
}
@Test
@@ -140,6 +157,7 @@ public class SimpleParserTestCase {
Assert.assertEquals("v1", result.getPathParameters().get("p1").getFirst());
Assert.assertEquals("v2", result.getPathParameters().get("p1").getLast());
Assert.assertSame(Protocols.HTTP_1_1, result.getProtocol());
+ Assert.assertFalse(result.isHostIncludedInRequestURI());
}
@Test
@@ -155,6 +173,23 @@ public class SimpleParserTestCase {
Assert.assertEquals("param", result.getPathParameters().keySet().toArray()[0]);
Assert.assertEquals("1", result.getPathParameters().get("param").getFirst());
Assert.assertSame(Protocols.HTTP_1_1, result.getProtocol());
+ Assert.assertTrue(result.isHostIncludedInRequestURI());
+ }
+
+ @Test
+ public void testServletURLWithPathParamEndingWithNormalPath() throws BadRequestException {
+ byte[] in = "GET http://localhost:7777/servletContext/aaaa/b;param=1/cccc HTTP/1.1\r\n\r\n".getBytes();
+ ParseState context = new ParseState(10);
+ HttpServerExchange result = new HttpServerExchange(null);
+ HttpRequestParser.instance(OptionMap.create(UndertowOptions.ALLOW_ENCODED_SLASH, true)).handle(ByteBuffer.wrap(in), context, result);
+ Assert.assertSame(Methods.GET, result.getRequestMethod());
+ Assert.assertEquals("http://localhost:7777/servletContext/aaaa/b;param=1/cccc", result.getRequestURI());
+ Assert.assertEquals("/servletContext/aaaa/b/cccc", result.getRequestPath());
+ Assert.assertEquals(1, result.getPathParameters().size());
+ Assert.assertEquals("param", result.getPathParameters().keySet().toArray()[0]);
+ Assert.assertEquals("1", result.getPathParameters().get("param").getFirst());
+ Assert.assertSame(Protocols.HTTP_1_1, result.getProtocol());
+ Assert.assertTrue(result.isHostIncludedInRequestURI());
}
@Test
@@ -171,6 +206,7 @@ public class SimpleParserTestCase {
Assert.assertEquals("bar", result.getPathParameters().get("foo").getFirst());
Assert.assertEquals("mSwrYUX8_e3ukAylNMkg3oMRglB4-YjxqeWqXQsI", result.getPathParameters().get("mysessioncookie").getFirst());
Assert.assertSame(Protocols.HTTP_1_1, result.getProtocol());
+ Assert.assertTrue(result.isHostIncludedInRequestURI());
}
@Test
@@ -186,6 +222,7 @@ public class SimpleParserTestCase {
Assert.assertEquals("param", result.getPathParameters().keySet().toArray()[0]);
Assert.assertEquals("1", result.getPathParameters().get("param").getFirst());
Assert.assertSame(Protocols.HTTP_1_1, result.getProtocol());
+ Assert.assertFalse(result.isHostIncludedInRequestURI());
}
@Test
@@ -202,6 +239,7 @@ public class SimpleParserTestCase {
Assert.assertEquals("bar", result.getPathParameters().get("foo").getFirst());
Assert.assertEquals("mSwrYUX8_e3ukAylNMkg3oMRglB4-YjxqeWqXQsI", result.getPathParameters().get("mysessioncookie").getFirst());
Assert.assertSame(Protocols.HTTP_1_1, result.getProtocol());
+ Assert.assertFalse(result.isHostIncludedInRequestURI());
}
@Test
@@ -232,6 +270,7 @@ public class SimpleParserTestCase {
Assert.assertEquals("q1=v3", result.getQueryString());
Assert.assertEquals("v3", result.getQueryParameters().get("q1").getFirst());
Assert.assertSame(Protocols.HTTP_1_1, result.getProtocol());
+ Assert.assertFalse(result.isHostIncludedInRequestURI());
}
@Test
@@ -248,6 +287,24 @@ public class SimpleParserTestCase {
Assert.assertEquals("v2", result.getPathParameters().get("p1").getLast());
Assert.assertEquals("v3", result.getQueryParameters().get("q1").getFirst());
Assert.assertSame(Protocols.HTTP_1_1, result.getProtocol());
+ Assert.assertFalse(result.isHostIncludedInRequestURI());
+ }
+
+ @Test
+ public void testServletURLMultiLevelMatrixParameter() throws BadRequestException {
+ byte[] in = "GET http://localhost:7777/some;p1=v1/path;p1=v2?q1=v3 HTTP/1.1\r\n\r\n".getBytes();
+ ParseState context = new ParseState(10);
+ HttpServerExchange result = new HttpServerExchange(null);
+ HttpRequestParser.instance(OptionMap.create(UndertowOptions.ALLOW_ENCODED_SLASH, true)).handle(ByteBuffer.wrap(in), context, result);
+ Assert.assertSame(Methods.GET, result.getRequestMethod());
+ Assert.assertEquals("http://localhost:7777/some;p1=v1/path;p1=v2", result.getRequestURI());
+ Assert.assertEquals("/some/path", result.getRequestPath());
+ Assert.assertEquals("q1=v3", result.getQueryString());
+ Assert.assertEquals("v1", result.getPathParameters().get("p1").getFirst());
+ Assert.assertEquals("v2", result.getPathParameters().get("p1").getLast());
+ Assert.assertEquals("v3", result.getQueryParameters().get("q1").getFirst());
+ Assert.assertSame(Protocols.HTTP_1_1, result.getProtocol());
+ Assert.assertTrue(result.isHostIncludedInRequestURI());
}
@Test
@@ -264,6 +321,41 @@ public class SimpleParserTestCase {
Assert.assertEquals("v2", result.getPathParameters().get("p2").getFirst());
Assert.assertEquals("v3", result.getQueryParameters().get("q1").getFirst());
Assert.assertSame(Protocols.HTTP_1_1, result.getProtocol());
+ Assert.assertFalse(result.isHostIncludedInRequestURI());
+ }
+
+ @Test
+ public void testMultiLevelMatrixParameterEndingWithNormalPathAndQuery() throws BadRequestException {
+ byte[] in = "GET /some;p1=v1/path;p1=v2/more?q1=v3 HTTP/1.1\r\n\r\n".getBytes();
+ ParseState context = new ParseState(10);
+ HttpServerExchange result = new HttpServerExchange(null);
+ HttpRequestParser.instance(OptionMap.create(UndertowOptions.ALLOW_ENCODED_SLASH, true)).handle(ByteBuffer.wrap(in), context, result);
+ Assert.assertSame(Methods.GET, result.getRequestMethod());
+ Assert.assertEquals("/some;p1=v1/path;p1=v2/more", result.getRequestURI());
+ Assert.assertEquals("/some/path/more", result.getRequestPath());
+ Assert.assertEquals("q1=v3", result.getQueryString());
+ Assert.assertEquals("v1", result.getPathParameters().get("p1").getFirst());
+ Assert.assertEquals("v2", result.getPathParameters().get("p1").getLast());
+ Assert.assertEquals("v3", result.getQueryParameters().get("q1").getFirst());
+ Assert.assertSame(Protocols.HTTP_1_1, result.getProtocol());
+ Assert.assertFalse(result.isHostIncludedInRequestURI());
+ }
+
+ @Test
+ public void testServletURLMultiLevelMatrixParameterEndingWithNormalPathAndQuery() throws BadRequestException {
+ byte[] in = "GET http://localhost:7777/some;p1=v1/path;p1=v2/more?q1=v3 HTTP/1.1\r\n\r\n".getBytes();
+ ParseState context = new ParseState(10);
+ HttpServerExchange result = new HttpServerExchange(null);
+ HttpRequestParser.instance(OptionMap.create(UndertowOptions.ALLOW_ENCODED_SLASH, true)).handle(ByteBuffer.wrap(in), context, result);
+ Assert.assertSame(Methods.GET, result.getRequestMethod());
+ Assert.assertEquals("http://localhost:7777/some;p1=v1/path;p1=v2/more", result.getRequestURI());
+ Assert.assertEquals("/some/path/more", result.getRequestPath());
+ Assert.assertEquals("q1=v3", result.getQueryString());
+ Assert.assertEquals("v1", result.getPathParameters().get("p1").getFirst());
+ Assert.assertEquals("v2", result.getPathParameters().get("p1").getLast());
+ Assert.assertEquals("v3", result.getQueryParameters().get("q1").getFirst());
+ Assert.assertSame(Protocols.HTTP_1_1, result.getProtocol());
+ Assert.assertTrue(result.isHostIncludedInRequestURI());
}
@Test
@@ -276,6 +368,7 @@ public class SimpleParserTestCase {
Assert.assertSame(Methods.GET, result.getRequestMethod());
Assert.assertEquals("/", result.getRequestPath());
Assert.assertEquals("http://myurl.com", result.getRequestURI());
+ Assert.assertTrue(result.isHostIncludedInRequestURI());
}
@Test
@@ -289,6 +382,7 @@ public class SimpleParserTestCase {
Assert.assertEquals("/goo", result.getRequestPath());
Assert.assertEquals("http://myurl.com/goo;foo=bar;blah=foobar", result.getRequestURI());
Assert.assertEquals(2, result.getPathParameters().size());
+ Assert.assertTrue(result.isHostIncludedInRequestURI());
}
@Test(expected = BadRequestException.class)
=====================================
core/src/test/java/io/undertow/util/ByteRangeTestCase.java
=====================================
@@ -107,6 +107,17 @@ public class ByteRangeTestCase {
new Date(1559820153000L), "foo").getContentRange());
Assert.assertEquals(416, byteRange.getResponseResult(0, null,
new Date(1559820153000L), "foo").getStatusCode());
+
+ Assert.assertEquals(0, byteRange.getResponseResult(6, null,
+ new Date(1559820153000L), "foo").getStart());
+ Assert.assertEquals(0, byteRange.getResponseResult(6, null,
+ new Date(1559820153000L), "foo").getEnd());
+ Assert.assertEquals(0, byteRange.getResponseResult(6, null,
+ new Date(1559820153000L), "foo").getContentLength());
+ Assert.assertEquals("bytes */6", byteRange.getResponseResult(6, null,
+ new Date(1559820153000L), "foo").getContentRange());
+ Assert.assertEquals(416, byteRange.getResponseResult(6, null,
+ new Date(1559820153000L), "foo").getStatusCode());
}
@Test
@@ -124,6 +135,17 @@ public class ByteRangeTestCase {
new Date(1559820153000L), "foo").getContentRange());
Assert.assertEquals(416, byteRange.getResponseResult(0, null,
new Date(1559820153000L), "foo").getStatusCode());
+
+ Assert.assertEquals(5, byteRange.getResponseResult(6, null,
+ new Date(1559820153000L), "foo").getStart());
+ Assert.assertEquals(5, byteRange.getResponseResult(6, null,
+ new Date(1559820153000L), "foo").getEnd());
+ Assert.assertEquals(1, byteRange.getResponseResult(6, null,
+ new Date(1559820153000L), "foo").getContentLength());
+ Assert.assertEquals("bytes 5-5/6", byteRange.getResponseResult(6, null,
+ new Date(1559820153000L), "foo").getContentRange());
+ Assert.assertEquals(206, byteRange.getResponseResult(6, null,
+ new Date(1559820153000L), "foo").getStatusCode());
}
@Test
@@ -133,13 +155,24 @@ public class ByteRangeTestCase {
Assert.assertEquals(0, byteRange.getResponseResult(0, null,
new Date(1559820153000L), "foo").getStart());
- Assert.assertEquals(-1, byteRange.getResponseResult(0, null,
+ Assert.assertEquals(0, byteRange.getResponseResult(0, null,
new Date(1559820153000L), "foo").getEnd());
Assert.assertEquals(0, byteRange.getResponseResult(0, null,
new Date(1559820153000L), "foo").getContentLength());
- Assert.assertEquals("bytes 0--1/0", byteRange.getResponseResult(0, null,
+ Assert.assertEquals("bytes */0", byteRange.getResponseResult(0, null,
new Date(1559820153000L), "foo").getContentRange());
- Assert.assertEquals(206, byteRange.getResponseResult(0, null,
+ Assert.assertEquals(416, byteRange.getResponseResult(0, null,
+ new Date(1559820153000L), "foo").getStatusCode());
+
+ Assert.assertEquals(0, byteRange.getResponseResult(6, null,
+ new Date(1559820153000L), "foo").getStart());
+ Assert.assertEquals(5, byteRange.getResponseResult(6, null,
+ new Date(1559820153000L), "foo").getEnd());
+ Assert.assertEquals(6, byteRange.getResponseResult(6, null,
+ new Date(1559820153000L), "foo").getContentLength());
+ Assert.assertEquals("bytes 0-5/6", byteRange.getResponseResult(6, null,
+ new Date(1559820153000L), "foo").getContentRange());
+ Assert.assertEquals(206, byteRange.getResponseResult(6, null,
new Date(1559820153000L), "foo").getStatusCode());
}
@@ -159,7 +192,6 @@ public class ByteRangeTestCase {
Assert.assertEquals(416, byteRange.getResponseResult(0, null,
new Date(1559820153000L), "foo").getStatusCode());
-
Assert.assertEquals(3, byteRange.getResponseResult(6, null,
new Date(1559820153000L), "foo").getStart());
Assert.assertEquals(5, byteRange.getResponseResult(6, null,
@@ -187,6 +219,17 @@ public class ByteRangeTestCase {
new Date(1559820153000L), "foo").getContentRange());
Assert.assertEquals(206, byteRange.getResponseResult(0, null,
new Date(1559820153000L), "foo").getStatusCode());
+
+ Assert.assertEquals(1, byteRange.getResponseResult(6, null,
+ new Date(1559820153000L), "foo").getStart());
+ Assert.assertEquals(5, byteRange.getResponseResult(6, null,
+ new Date(1559820153000L), "foo").getEnd());
+ Assert.assertEquals(5, byteRange.getResponseResult(6, null,
+ new Date(1559820153000L), "foo").getContentLength());
+ Assert.assertEquals("bytes 1-5/6", byteRange.getResponseResult(6, null,
+ new Date(1559820153000L), "foo").getContentRange());
+ Assert.assertEquals(206, byteRange.getResponseResult(6, null,
+ new Date(1559820153000L), "foo").getStatusCode());
}
@Test
=====================================
core/src/test/java/io/undertow/util/CookiesTestCase.java
=====================================
@@ -261,6 +261,62 @@ public class CookiesTestCase {
Assert.assertEquals("FEDEX", cookie.getValue());
}
+ @Test
+ public void testCookieContainsColonInJvmRoute() {
+ // "<hostcontroller-name>:<server-name>" (e.g. master:node1) is added as jvmRoute (instance-id) by default in WildFly domain mode.
+ // ":" is http separator, so it's not allowed in V0 cookie value.
+ // However, we need to allow it exceptionally by default. Because, when Undertow runs as a proxy server (like mod_cluster),
+ // we need to handle jvmRoute containing ":" in the request cookie value correctly to maintain the sticky session.
+
+ Map<String, Cookie> cookies = Cookies.parseRequestCookies(3, false, Arrays.asList("JSESSIONID=WCGWBPJ8DUmv0fvREqVQZb8E6bzW92iHnzysV_q_.master:node1; CUSTOMER=WILE_E COYOTE; SHIPPING=FEDEX" ), true, false);
+ Assert.assertEquals(3, cookies.size());
+ Cookie cookie = cookies.get("CUSTOMER");
+ Assert.assertNotNull(cookie);
+ Assert.assertEquals("WILE_E", cookie.getValue());
+ cookie = cookies.get("SHIPPING");
+ Assert.assertNotNull(cookie);
+ Assert.assertEquals("FEDEX", cookie.getValue());
+ cookie = cookies.get("JSESSIONID");
+ Assert.assertNotNull(cookie);
+ Assert.assertEquals("WCGWBPJ8DUmv0fvREqVQZb8E6bzW92iHnzysV_q_.master:node1", cookie.getValue());
+
+ cookies = Cookies.parseRequestCookies(3, false, Arrays.asList("JSESSIONID=WCGWBPJ8DUmv0fvREqVQZb8E6bzW92iHnzysV_q_.master:node1; CUSTOMER=WILE_E COYOTE; SHIPPING=FEDEX" ), true, true);
+ Assert.assertEquals(3, cookies.size());
+ cookie = cookies.get("CUSTOMER");
+ Assert.assertNotNull(cookie);
+ Assert.assertEquals("WILE_E COYOTE", cookie.getValue());
+ cookie = cookies.get("SHIPPING");
+ Assert.assertNotNull(cookie);
+ Assert.assertEquals("FEDEX", cookie.getValue());
+ cookie = cookies.get("JSESSIONID");
+ Assert.assertNotNull(cookie);
+ Assert.assertEquals("WCGWBPJ8DUmv0fvREqVQZb8E6bzW92iHnzysV_q_.master:node1", cookie.getValue());
+
+ cookies = Cookies.parseRequestCookies(3, false, Arrays.asList("JSESSIONID=WCGWBPJ8DUmv0fvREqVQZb8E6bzW92iHnzysV_q_.master:node1; CUSTOMER=WILE_E_COYOTE\"; SHIPPING=FEDEX" ), true, false);
+ Assert.assertEquals(3, cookies.size());
+ cookie = cookies.get("CUSTOMER");
+ Assert.assertNotNull(cookie);
+ Assert.assertEquals("WILE_E_COYOTE", cookie.getValue());
+ cookie = cookies.get("SHIPPING");
+ Assert.assertNotNull(cookie);
+ Assert.assertEquals("FEDEX", cookie.getValue());
+ cookie = cookies.get("JSESSIONID");
+ Assert.assertNotNull(cookie);
+ Assert.assertEquals("WCGWBPJ8DUmv0fvREqVQZb8E6bzW92iHnzysV_q_.master:node1", cookie.getValue());
+
+ cookies = Cookies.parseRequestCookies(3, false, Arrays.asList("JSESSIONID=WCGWBPJ8DUmv0fvREqVQZb8E6bzW92iHnzysV_q_.master:node1; CUSTOMER=WILE_E_COYOTE\"; SHIPPING=FEDEX" ), true, true);
+ Assert.assertEquals(3, cookies.size());
+ cookie = cookies.get("CUSTOMER");
+ Assert.assertNotNull(cookie);
+ Assert.assertEquals("WILE_E_COYOTE\"", cookie.getValue());
+ cookie = cookies.get("SHIPPING");
+ Assert.assertNotNull(cookie);
+ Assert.assertEquals("FEDEX", cookie.getValue());
+ cookie = cookies.get("JSESSIONID");
+ Assert.assertNotNull(cookie);
+ Assert.assertEquals("WCGWBPJ8DUmv0fvREqVQZb8E6bzW92iHnzysV_q_.master:node1", cookie.getValue());
+ }
+
@Test
public void testQuotedEscapedStringInRequestCookie() {
Map<String, Cookie> cookies = Cookies.parseRequestCookies(3, false, Arrays.asList(
=====================================
coverage-report/pom.xml
=====================================
@@ -3,7 +3,7 @@
<parent>
<groupId>io.undertow</groupId>
<artifactId>undertow-parent</artifactId>
- <version>2.1.1.Final</version>
+ <version>2.1.3.Final</version>
</parent>
<artifactId>undertow-coverage-report</artifactId>
<name>Undertow Test Coverage Report</name>
=====================================
dist/pom.xml
=====================================
@@ -25,12 +25,12 @@
<parent>
<groupId>io.undertow</groupId>
<artifactId>undertow-parent</artifactId>
- <version>2.1.1.Final</version>
+ <version>2.1.3.Final</version>
</parent>
<groupId>io.undertow</groupId>
<artifactId>undertow-dist</artifactId>
- <version>2.1.1.Final</version>
+ <version>2.1.3.Final</version>
<name>Undertow: Distribution</name>
=====================================
examples/pom.xml
=====================================
@@ -25,12 +25,12 @@
<parent>
<groupId>io.undertow</groupId>
<artifactId>undertow-parent</artifactId>
- <version>2.1.1.Final</version>
+ <version>2.1.3.Final</version>
</parent>
<groupId>io.undertow</groupId>
<artifactId>undertow-examples</artifactId>
- <version>2.1.1.Final</version>
+ <version>2.1.3.Final</version>
<name>Undertow Examples</name>
=====================================
parser-generator/pom.xml
=====================================
@@ -25,12 +25,12 @@
<parent>
<groupId>io.undertow</groupId>
<artifactId>undertow-parent</artifactId>
- <version>2.1.1.Final</version>
+ <version>2.1.3.Final</version>
</parent>
<groupId>io.undertow</groupId>
<artifactId>undertow-parser-generator</artifactId>
- <version>2.1.1.Final</version>
+ <version>2.1.3.Final</version>
<name>Undertow Parser Generator</name>
<description>An annotation processor that is used to generate the HTTP parser</description>
=====================================
pom.xml
=====================================
@@ -28,7 +28,7 @@
<groupId>io.undertow</groupId>
<artifactId>undertow-parent</artifactId>
- <version>2.1.1.Final</version>
+ <version>2.1.3.Final</version>
<name>Undertow</name>
<description>Undertow</description>
=====================================
servlet/pom.xml
=====================================
@@ -25,12 +25,12 @@
<parent>
<groupId>io.undertow</groupId>
<artifactId>undertow-parent</artifactId>
- <version>2.1.1.Final</version>
+ <version>2.1.3.Final</version>
</parent>
<groupId>io.undertow</groupId>
<artifactId>undertow-servlet</artifactId>
- <version>2.1.1.Final</version>
+ <version>2.1.3.Final</version>
<name>Undertow Servlet</name>
=====================================
servlet/src/main/java/io/undertow/servlet/core/BlockingWriterSenderImpl.java
=====================================
@@ -56,7 +56,8 @@ public class BlockingWriterSenderImpl implements Sender {
private final PrintWriter writer;
private FileChannel pendingFile;
- private boolean inCall;
+ private volatile Thread inCall;
+ private volatile Thread sendThread;
private String next;
private IoCallback queuedCallback;
@@ -68,7 +69,8 @@ public class BlockingWriterSenderImpl implements Sender {
@Override
public void send(final ByteBuffer buffer, final IoCallback callback) {
- if (inCall) {
+ sendThread = Thread.currentThread();
+ if (inCall == Thread.currentThread()) {
queue(new ByteBuffer[]{buffer}, callback);
return;
}
@@ -80,7 +82,8 @@ public class BlockingWriterSenderImpl implements Sender {
@Override
public void send(final ByteBuffer[] buffer, final IoCallback callback) {
- if (inCall) {
+ sendThread = Thread.currentThread();
+ if (inCall == Thread.currentThread()) {
queue(buffer, callback);
return;
}
@@ -94,7 +97,8 @@ public class BlockingWriterSenderImpl implements Sender {
@Override
public void send(final String data, final IoCallback callback) {
- if (inCall) {
+ sendThread = Thread.currentThread();
+ if (inCall == Thread.currentThread()) {
queue(data, callback);
return;
}
@@ -115,7 +119,8 @@ public class BlockingWriterSenderImpl implements Sender {
@Override
public void send(final String data, final Charset charset, final IoCallback callback) {
- if (inCall) {
+ sendThread = Thread.currentThread();
+ if (inCall == Thread.currentThread()) {
queue(new ByteBuffer[]{ByteBuffer.wrap(data.getBytes(charset))}, callback);
return;
}
@@ -135,7 +140,8 @@ public class BlockingWriterSenderImpl implements Sender {
@Override
public void transferFrom(FileChannel source, IoCallback callback) {
- if (inCall) {
+ sendThread = Thread.currentThread();
+ if (inCall == Thread.currentThread()) {
queue(source, callback);
return;
}
@@ -206,11 +212,15 @@ public class BlockingWriterSenderImpl implements Sender {
private void invokeOnComplete(final IoCallback callback) {
- inCall = true;
+ sendThread = null;
+ inCall = Thread.currentThread();
try {
callback.onComplete(exchange, this);
} finally {
- inCall = false;
+ inCall = null;
+ }
+ if (Thread.currentThread() != sendThread) {
+ return;
}
while (next != null) {
String next = this.next;
@@ -221,11 +231,15 @@ public class BlockingWriterSenderImpl implements Sender {
if (writer.checkError()) {
queuedCallback.onException(exchange, this, new IOException());
} else {
- inCall = true;
+ sendThread = null;
+ inCall = Thread.currentThread();
try {
queuedCallback.onComplete(exchange, this);
} finally {
- inCall = false;
+ inCall = null;
+ }
+ if (Thread.currentThread() != sendThread) {
+ return;
}
}
}
=====================================
servlet/src/main/java/io/undertow/servlet/spec/HttpServletRequestImpl.java
=====================================
@@ -682,13 +682,17 @@ public final class HttpServletRequestImpl implements HttpServletRequest {
}
public void closeAndDrainRequest() throws IOException {
- if(reader != null) {
- reader.close();
- }
- if(servletInputStream == null) {
- servletInputStream = new ServletInputStreamImpl(this);
+ try {
+ if (reader != null) {
+ reader.close();
+ }
+ if (servletInputStream == null) {
+ servletInputStream = new ServletInputStreamImpl(this);
+ }
+ servletInputStream.close();
+ } finally {
+ clearAttributes();
}
- servletInputStream.close();
}
/**
@@ -696,11 +700,15 @@ public final class HttpServletRequestImpl implements HttpServletRequest {
*
*/
public void freeResources() throws IOException {
- if(reader != null) {
- reader.close();
- }
- if(servletInputStream != null) {
- servletInputStream.close();
+ try {
+ if(reader != null) {
+ reader.close();
+ }
+ if(servletInputStream != null) {
+ servletInputStream.close();
+ }
+ } finally {
+ clearAttributes();
}
}
@@ -1188,6 +1196,12 @@ public final class HttpServletRequestImpl implements HttpServletRequest {
return "HttpServletRequestImpl [ " + getMethod() + ' ' + getRequestURI() + " ]";
}
+ public void clearAttributes() {
+ if(attributes != null) {
+ this.attributes.clear();
+ }
+ }
+
@Override
public PushBuilder newPushBuilder() {
if(exchange.getConnection().isPushSupported()) {
=====================================
servlet/src/main/java/io/undertow/servlet/spec/ServletInputStreamImpl.java
=====================================
@@ -67,6 +67,7 @@ public class ServletInputStreamImpl extends ServletInputStream {
private volatile int state;
private volatile AsyncContextImpl asyncContext;
private volatile PooledByteBuffer pooled;
+ private volatile boolean asyncIoStarted;
private static final AtomicIntegerFieldUpdater<ServletInputStreamImpl> stateUpdater = AtomicIntegerFieldUpdater.newUpdater(ServletInputStreamImpl.class, "state");
@@ -102,6 +103,10 @@ public class ServletInputStreamImpl extends ServletInputStream {
}
}
}
+ if (!asyncIoStarted) {
+ //make sure we don't call resumeReads unless we have started async IO
+ return false;
+ }
boolean ready = anyAreSet(state, FLAG_READY) && !finished;
if(!ready && listener != null && !finished) {
channel.resumeReads();
@@ -135,6 +140,7 @@ public class ServletInputStreamImpl extends ServletInputStream {
channel.getIoThread().execute(new Runnable() {
@Override
public void run() {
+ asyncIoStarted = true;
internalListener.handleEvent(channel);
}
});
=====================================
servlet/src/main/java/io/undertow/servlet/spec/ServletOutputStreamImpl.java
=====================================
@@ -73,6 +73,7 @@ public class ServletOutputStreamImpl extends ServletOutputStream implements Buff
private StreamSinkChannel channel;
private long written;
private volatile int state;
+ private volatile boolean asyncIoStarted;
private AsyncContextImpl asyncContext;
private WriteListener listener;
@@ -756,6 +757,11 @@ public class ServletOutputStreamImpl extends ServletOutputStream implements Buff
//TODO: is this the correct behaviour?
throw UndertowServletMessages.MESSAGES.streamNotInAsyncMode();
}
+ if (!asyncIoStarted) {
+ //if we don't add this guard here calls to isReady could start async IO too soon
+ //resulting in a 'resuming + dispatched' message
+ return false;
+ }
if (!anyAreSet(state, FLAG_READY)) {
if (channel != null) {
channel.resumeWrites();
@@ -790,6 +796,7 @@ public class ServletOutputStreamImpl extends ServletOutputStream implements Buff
asyncContext.addAsyncTask(new Runnable() {
@Override
public void run() {
+ asyncIoStarted = true;
if (channel == null) {
servletRequestContext.getExchange().getIoThread().execute(new Runnable() {
@Override
=====================================
websockets-jsr/pom.xml
=====================================
@@ -25,12 +25,12 @@
<parent>
<groupId>io.undertow</groupId>
<artifactId>undertow-parent</artifactId>
- <version>2.1.1.Final</version>
+ <version>2.1.3.Final</version>
</parent>
<groupId>io.undertow</groupId>
<artifactId>undertow-websockets-jsr</artifactId>
- <version>2.1.1.Final</version>
+ <version>2.1.3.Final</version>
<name>Undertow WebSockets JSR356 implementations</name>
View it on GitLab: https://salsa.debian.org/java-team/undertow/-/commit/19a11c128e7b516b83dc389fbb6723dc9991f4ef
--
View it on GitLab: https://salsa.debian.org/java-team/undertow/-/commit/19a11c128e7b516b83dc389fbb6723dc9991f4ef
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-java-commits/attachments/20200604/74a0ad59/attachment.html>
More information about the pkg-java-commits
mailing list