[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