You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2022/10/19 14:39:18 UTC

[tomcat] branch loom created (now 6a22f2f399)

This is an automated email from the ASF dual-hosted git repository.

markt pushed a change to branch loom
in repository https://gitbox.apache.org/repos/asf/tomcat.git


      at 6a22f2f399 Use correct class

This branch includes the following new commits:

     new 6d4ee27781 Refactor. Introduce AbstractHttp11Processor.
     new 38c63375b3 Refactor to prepare for Loom specific Http11InputBuffer
     new 6a22f2f399 Use correct class

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org


[tomcat] 02/03: Refactor to prepare for Loom specific Http11InputBuffer

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

markt pushed a commit to branch loom
in repository https://gitbox.apache.org/repos/asf/tomcat.git

commit 38c63375b30fe283a205b17cb6016c95bd3cff35
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Wed Oct 12 18:00:40 2022 +0100

    Refactor to prepare for Loom specific Http11InputBuffer
---
 ...tBuffer.java => AbstractHttp11InputBuffer.java} |   30 +-
 .../coyote/http11/AbstractHttp11Processor.java     |    9 +-
 .../apache/coyote/http11/Http11InputBuffer.java    | 1221 +-------------------
 java/org/apache/coyote/http11/Http11Processor.java |   10 +
 .../coyote/http11/Http11LoomInputBuffer.java       |   12 +-
 .../apache/coyote/http11/Http11LoomProcessor.java  |   10 +
 6 files changed, 54 insertions(+), 1238 deletions(-)

diff --git a/java/org/apache/coyote/http11/Http11InputBuffer.java b/java/org/apache/coyote/http11/AbstractHttp11InputBuffer.java
similarity index 98%
copy from java/org/apache/coyote/http11/Http11InputBuffer.java
copy to java/org/apache/coyote/http11/AbstractHttp11InputBuffer.java
index ddd7e2d1e2..8c9f53e162 100644
--- a/java/org/apache/coyote/http11/Http11InputBuffer.java
+++ b/java/org/apache/coyote/http11/AbstractHttp11InputBuffer.java
@@ -26,7 +26,6 @@ import org.apache.coyote.CloseNowException;
 import org.apache.coyote.InputBuffer;
 import org.apache.coyote.Request;
 import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.util.buf.MessageBytes;
 import org.apache.tomcat.util.http.HeaderUtil;
 import org.apache.tomcat.util.http.MimeHeaders;
@@ -36,15 +35,13 @@ import org.apache.tomcat.util.net.SocketWrapperBase;
 import org.apache.tomcat.util.res.StringManager;
 
 /**
- * InputBuffer for HTTP that provides request header parsing as well as transfer
- * encoding.
+ * Abstract base implementation of InputBuffer for HTTP that provides request
+ * header parsing as well as transfer encoding.
  */
-public class Http11InputBuffer implements InputBuffer, ApplicationBufferHandler {
+public abstract class AbstractHttp11InputBuffer implements InputBuffer, ApplicationBufferHandler {
 
     // -------------------------------------------------------------- Constants
 
-    private static final Log log = LogFactory.getLog(Http11InputBuffer.class);
-
     /**
      * The string manager for this package.
      */
@@ -153,7 +150,7 @@ public class Http11InputBuffer implements InputBuffer, ApplicationBufferHandler
 
     // ----------------------------------------------------------- Constructors
 
-    public Http11InputBuffer(Request request, int headerBufferSize,
+    public AbstractHttp11InputBuffer(Request request, int headerBufferSize,
             boolean rejectIllegalHeader, HttpParser httpParser) {
 
         this.request = request;
@@ -696,8 +693,8 @@ public class Http11InputBuffer implements InputBuffer, ApplicationBufferHandler
                 available = byteBuffer.remaining();
             }
         } catch (IOException ioe) {
-            if (log.isDebugEnabled()) {
-                log.debug(sm.getString("iib.available.readFail"), ioe);
+            if (getLog().isDebugEnabled()) {
+                getLog().debug(sm.getString("iib.available.readFail"), ioe);
             }
             // Not ideal. This will indicate that data is available which should
             // trigger a read which in turn will trigger another IOException and
@@ -771,8 +768,8 @@ public class Http11InputBuffer implements InputBuffer, ApplicationBufferHandler
      */
     private boolean fill(boolean block) throws IOException {
 
-        if (log.isDebugEnabled()) {
-            log.debug("Before fill(): parsingHeader: [" + parsingHeader +
+        if (getLog().isDebugEnabled()) {
+            getLog().debug("Before fill(): parsingHeader: [" + parsingHeader +
                     "], parsingRequestLine: [" + parsingRequestLine +
                     "], parsingRequestLinePhase: [" + parsingRequestLinePhase +
                     "], parsingRequestLineStart: [" + parsingRequestLineStart +
@@ -826,8 +823,8 @@ public class Http11InputBuffer implements InputBuffer, ApplicationBufferHandler
             }
         }
 
-        if (log.isDebugEnabled()) {
-            log.debug("Received ["
+        if (getLog().isDebugEnabled()) {
+            getLog().debug("Received ["
                     + new String(byteBuffer.array(), byteBuffer.position(), byteBuffer.remaining(), StandardCharsets.ISO_8859_1) + "]");
         }
 
@@ -1085,14 +1082,14 @@ public class Http11InputBuffer implements InputBuffer, ApplicationBufferHandler
                 headerData.lastSignificantChar = pos;
             }
         }
-        if (rejectThisHeader || log.isDebugEnabled()) {
+        if (rejectThisHeader || getLog().isDebugEnabled()) {
             String message = sm.getString("iib.invalidheader",
                     HeaderUtil.toPrintableString(byteBuffer.array(), headerData.lineStart,
                             headerData.lastSignificantChar - headerData.lineStart + 1));
             if (rejectThisHeader) {
                 throw new IllegalArgumentException(message);
             }
-            log.debug(message);
+            getLog().debug(message);
         }
 
         headerParsePos = HeaderParsePosition.HEADER_START;
@@ -1100,6 +1097,9 @@ public class Http11InputBuffer implements InputBuffer, ApplicationBufferHandler
     }
 
 
+    protected abstract Log getLog();
+
+
     // ----------------------------------------------------------- Inner classes
 
     private enum HeaderParseStatus {
diff --git a/java/org/apache/coyote/http11/AbstractHttp11Processor.java b/java/org/apache/coyote/http11/AbstractHttp11Processor.java
index b63b824bc6..db1d743e8f 100644
--- a/java/org/apache/coyote/http11/AbstractHttp11Processor.java
+++ b/java/org/apache/coyote/http11/AbstractHttp11Processor.java
@@ -79,7 +79,7 @@ public abstract class AbstractHttp11Processor extends AbstractProcessor {
     /**
      * Input.
      */
-    private final Http11InputBuffer inputBuffer;
+    private final AbstractHttp11InputBuffer inputBuffer;
 
 
     /**
@@ -155,8 +155,7 @@ public abstract class AbstractHttp11Processor extends AbstractProcessor {
         httpParser = new HttpParser(protocol.getRelaxedPathChars(),
                 protocol.getRelaxedQueryChars());
 
-        inputBuffer = new Http11InputBuffer(request, protocol.getMaxHttpRequestHeaderSize(),
-                protocol.getRejectIllegalHeader(), httpParser);
+        inputBuffer = createInputBuffer(request, protocol, httpParser);
         request.setInputBuffer(inputBuffer);
 
         outputBuffer = new Http11OutputBuffer(response, protocol.getMaxHttpResponseHeaderSize());
@@ -187,6 +186,10 @@ public abstract class AbstractHttp11Processor extends AbstractProcessor {
     }
 
 
+    protected abstract AbstractHttp11InputBuffer createInputBuffer(Request request,
+            AbstractHttp11Protocol<?> protocol, HttpParser httpParser);
+
+
     /**
      * Determine if we must drop the connection because of the HTTP status
      * code.  Use the same list of codes as Apache/httpd.
diff --git a/java/org/apache/coyote/http11/Http11InputBuffer.java b/java/org/apache/coyote/http11/Http11InputBuffer.java
index ddd7e2d1e2..56832039d9 100644
--- a/java/org/apache/coyote/http11/Http11InputBuffer.java
+++ b/java/org/apache/coyote/http11/Http11InputBuffer.java
@@ -16,1237 +16,28 @@
  */
 package org.apache.coyote.http11;
 
-import java.io.EOFException;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-
-import org.apache.coyote.CloseNowException;
-import org.apache.coyote.InputBuffer;
 import org.apache.coyote.Request;
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
-import org.apache.tomcat.util.buf.MessageBytes;
-import org.apache.tomcat.util.http.HeaderUtil;
-import org.apache.tomcat.util.http.MimeHeaders;
 import org.apache.tomcat.util.http.parser.HttpParser;
-import org.apache.tomcat.util.net.ApplicationBufferHandler;
-import org.apache.tomcat.util.net.SocketWrapperBase;
-import org.apache.tomcat.util.res.StringManager;
 
 /**
  * InputBuffer for HTTP that provides request header parsing as well as transfer
  * encoding.
  */
-public class Http11InputBuffer implements InputBuffer, ApplicationBufferHandler {
-
-    // -------------------------------------------------------------- Constants
+public class Http11InputBuffer extends AbstractHttp11InputBuffer {
 
     private static final Log log = LogFactory.getLog(Http11InputBuffer.class);
 
-    /**
-     * The string manager for this package.
-     */
-    private static final StringManager sm = StringManager.getManager(Http11InputBuffer.class);
-
-
-    private static final byte[] CLIENT_PREFACE_START =
-            "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".getBytes(StandardCharsets.ISO_8859_1);
-
-    /**
-     * Associated Coyote request.
-     */
-    private final Request request;
-
-
-    /**
-     * Headers of the associated request.
-     */
-    private final MimeHeaders headers;
-
-
-    private final boolean rejectIllegalHeader;
-
-    /**
-     * State.
-     */
-    private volatile boolean parsingHeader;
-
-
-    /**
-     * Swallow input ? (in the case of an expectation)
-     */
-    private boolean swallowInput;
-
-
-    /**
-     * The read buffer.
-     */
-    private ByteBuffer byteBuffer;
-
-
-    /**
-     * Pos of the end of the header in the buffer, which is also the
-     * start of the body.
-     */
-    private int end;
-
-
-    /**
-     * Wrapper that provides access to the underlying socket.
-     */
-    private SocketWrapperBase<?> wrapper;
-
-
-    /**
-     * Underlying input buffer.
-     */
-    private InputBuffer inputStreamInputBuffer;
-
-
-    /**
-     * Filter library.
-     * Note: Filter[Constants.CHUNKED_FILTER] is always the "chunked" filter.
-     */
-    private InputFilter[] filterLibrary;
-
-
-    /**
-     * Active filters (in order).
-     */
-    private InputFilter[] activeFilters;
-
-
-    /**
-     * Index of the last active filter.
-     */
-    private int lastActiveFilter;
-
-
-    /**
-     * Parsing state - used for non blocking parsing so that
-     * when more data arrives, we can pick up where we left off.
-     */
-    private byte prevChr = 0;
-    private byte chr = 0;
-    private volatile boolean parsingRequestLine;
-    private int parsingRequestLinePhase = 0;
-    private boolean parsingRequestLineEol = false;
-    private int parsingRequestLineStart = 0;
-    private int parsingRequestLineQPos = -1;
-    private HeaderParsePosition headerParsePos;
-    private final HeaderParseData headerData = new HeaderParseData();
-    private final HttpParser httpParser;
-
-    /**
-     * Maximum allowed size of the HTTP request line plus headers plus any
-     * leading blank lines.
-     */
-    private final int headerBufferSize;
-
-    /**
-     * Known size of the NioChannel read buffer.
-     */
-    private int socketReadBufferSize;
-
-
-    // ----------------------------------------------------------- Constructors
-
-    public Http11InputBuffer(Request request, int headerBufferSize,
-            boolean rejectIllegalHeader, HttpParser httpParser) {
-
-        this.request = request;
-        headers = request.getMimeHeaders();
-
-        this.headerBufferSize = headerBufferSize;
-        this.rejectIllegalHeader = rejectIllegalHeader;
-        this.httpParser = httpParser;
-
-        filterLibrary = new InputFilter[0];
-        activeFilters = new InputFilter[0];
-        lastActiveFilter = -1;
-
-        parsingHeader = true;
-        parsingRequestLine = true;
-        parsingRequestLinePhase = 0;
-        parsingRequestLineEol = false;
-        parsingRequestLineStart = 0;
-        parsingRequestLineQPos = -1;
-        headerParsePos = HeaderParsePosition.HEADER_START;
-        swallowInput = true;
-
-        inputStreamInputBuffer = new SocketInputBuffer();
-    }
-
-
-    // ------------------------------------------------------------- Properties
-
-    /**
-     * Add an input filter to the filter library.
-     *
-     * @throws NullPointerException if the supplied filter is null
-     */
-    void addFilter(InputFilter filter) {
-
-        if (filter == null) {
-            throw new NullPointerException(sm.getString("iib.filter.npe"));
-        }
-
-        InputFilter[] newFilterLibrary = Arrays.copyOf(filterLibrary, filterLibrary.length + 1);
-        newFilterLibrary[filterLibrary.length] = filter;
-        filterLibrary = newFilterLibrary;
-
-        activeFilters = new InputFilter[filterLibrary.length];
-    }
-
-
-    /**
-     * Get filters.
-     */
-    InputFilter[] getFilters() {
-        return filterLibrary;
-    }
-
-
-    /**
-     * Add an input filter to the filter library.
-     */
-    void addActiveFilter(InputFilter filter) {
-
-        if (lastActiveFilter == -1) {
-            filter.setBuffer(inputStreamInputBuffer);
-        } else {
-            for (int i = 0; i <= lastActiveFilter; i++) {
-                if (activeFilters[i] == filter) {
-                    return;
-                }
-            }
-            filter.setBuffer(activeFilters[lastActiveFilter]);
-        }
-
-        activeFilters[++lastActiveFilter] = filter;
-
-        filter.setRequest(request);
-    }
-
-
-    /**
-     * Set the swallow input flag.
-     */
-    void setSwallowInput(boolean swallowInput) {
-        this.swallowInput = swallowInput;
-    }
-
-
-    // ---------------------------------------------------- InputBuffer Methods
-
-    @Override
-    public int doRead(ApplicationBufferHandler handler) throws IOException {
-        if (lastActiveFilter == -1) {
-            return inputStreamInputBuffer.doRead(handler);
-        } else {
-            return activeFilters[lastActiveFilter].doRead(handler);
-        }
-    }
-
-
-    // ------------------------------------------------------- Protected Methods
-
-    /**
-     * Recycle the input buffer. This should be called when closing the
-     * connection.
-     */
-    void recycle() {
-        wrapper = null;
-        request.recycle();
-
-        for (int i = 0; i <= lastActiveFilter; i++) {
-            activeFilters[i].recycle();
-        }
-
-        byteBuffer.limit(0).position(0);
-        lastActiveFilter = -1;
-        swallowInput = true;
-
-        chr = 0;
-        prevChr = 0;
-        headerParsePos = HeaderParsePosition.HEADER_START;
-        parsingRequestLinePhase = 0;
-        parsingRequestLineEol = false;
-        parsingRequestLineStart = 0;
-        parsingRequestLineQPos = -1;
-        headerData.recycle();
-        // Recycled last because they are volatile
-        // All variables visible to this thread are guaranteed to be visible to
-        // any other thread once that thread reads the same volatile. The first
-        // action when parsing input data is to read one of these volatiles.
-        parsingRequestLine = true;
-        parsingHeader = true;
-    }
-
-
-    /**
-     * End processing of current HTTP request.
-     * Note: All bytes of the current request should have been already
-     * consumed. This method only resets all the pointers so that we are ready
-     * to parse the next HTTP request.
-     */
-    void nextRequest() {
-        request.recycle();
 
-        if (byteBuffer.position() > 0) {
-            if (byteBuffer.remaining() > 0) {
-                // Copy leftover bytes to the beginning of the buffer
-                byteBuffer.compact();
-                byteBuffer.flip();
-            } else {
-                // Reset position and limit to 0
-                byteBuffer.position(0).limit(0);
-            }
-        }
-
-        // Recycle filters
-        for (int i = 0; i <= lastActiveFilter; i++) {
-            activeFilters[i].recycle();
-        }
-
-        // Reset pointers
-        lastActiveFilter = -1;
-        parsingHeader = true;
-        swallowInput = true;
-
-        headerParsePos = HeaderParsePosition.HEADER_START;
-        parsingRequestLine = true;
-        parsingRequestLinePhase = 0;
-        parsingRequestLineEol = false;
-        parsingRequestLineStart = 0;
-        parsingRequestLineQPos = -1;
-        headerData.recycle();
-    }
-
-
-    /**
-     * Read the request line. This function is meant to be used during the
-     * HTTP request header parsing. Do NOT attempt to read the request body
-     * using it.
-     *
-     * @throws IOException If an exception occurs during the underlying socket
-     * read operations, or if the given buffer is not big enough to accommodate
-     * the whole line.
-     *
-     * @return true if data is properly fed; false if no data is available
-     * immediately and thread should be freed
-     */
-    boolean parseRequestLine(boolean keptAlive, int connectionTimeout, int keepAliveTimeout)
-            throws IOException {
-
-        // check state
-        if (!parsingRequestLine) {
-            return true;
-        }
-        //
-        // Skipping blank lines
-        //
-        if (parsingRequestLinePhase < 2) {
-            do {
-                // Read new bytes if needed
-                if (byteBuffer.position() >= byteBuffer.limit()) {
-                    if (keptAlive) {
-                        // Haven't read any request data yet so use the keep-alive
-                        // timeout.
-                        wrapper.setReadTimeout(keepAliveTimeout);
-                    }
-                    if (!fill(false)) {
-                        // A read is pending, so no longer in initial state
-                        parsingRequestLinePhase = 1;
-                        return false;
-                    }
-                    // At least one byte of the request has been received.
-                    // Switch to the socket timeout.
-                    wrapper.setReadTimeout(connectionTimeout);
-                }
-                if (!keptAlive && byteBuffer.position() == 0 && byteBuffer.limit() >= CLIENT_PREFACE_START.length) {
-                    boolean prefaceMatch = true;
-                    for (int i = 0; i < CLIENT_PREFACE_START.length && prefaceMatch; i++) {
-                        if (CLIENT_PREFACE_START[i] != byteBuffer.get(i)) {
-                            prefaceMatch = false;
-                        }
-                    }
-                    if (prefaceMatch) {
-                        // HTTP/2 preface matched
-                        parsingRequestLinePhase = -1;
-                        return false;
-                    }
-                }
-                // Set the start time once we start reading data (even if it is
-                // just skipping blank lines)
-                if (request.getStartTimeNanos() < 0) {
-                    request.setStartTimeNanos(System.nanoTime());
-                }
-                chr = byteBuffer.get();
-            } while ((chr == Constants.CR) || (chr == Constants.LF));
-            byteBuffer.position(byteBuffer.position() - 1);
-
-            parsingRequestLineStart = byteBuffer.position();
-            parsingRequestLinePhase = 2;
-        }
-        if (parsingRequestLinePhase == 2) {
-            //
-            // Reading the method name
-            // Method name is a token
-            //
-            boolean space = false;
-            while (!space) {
-                // Read new bytes if needed
-                if (byteBuffer.position() >= byteBuffer.limit()) {
-                    if (!fill(false)) {
-                        return false;
-                    }
-                }
-                // Spec says method name is a token followed by a single SP but
-                // also be tolerant of multiple SP and/or HT.
-                int pos = byteBuffer.position();
-                chr = byteBuffer.get();
-                if (chr == Constants.SP || chr == Constants.HT) {
-                    space = true;
-                    request.method().setBytes(byteBuffer.array(), parsingRequestLineStart,
-                            pos - parsingRequestLineStart);
-                } else if (!HttpParser.isToken(chr)) {
-                    // Avoid unknown protocol triggering an additional error
-                    request.protocol().setString(Constants.HTTP_11);
-                    String invalidMethodValue = parseInvalid(parsingRequestLineStart, byteBuffer);
-                    throw new IllegalArgumentException(sm.getString("iib.invalidmethod", invalidMethodValue));
-                }
-            }
-            parsingRequestLinePhase = 3;
-        }
-        if (parsingRequestLinePhase == 3) {
-            // Spec says single SP but also be tolerant of multiple SP and/or HT
-            boolean space = true;
-            while (space) {
-                // Read new bytes if needed
-                if (byteBuffer.position() >= byteBuffer.limit()) {
-                    if (!fill(false)) {
-                        return false;
-                    }
-                }
-                chr = byteBuffer.get();
-                if (!(chr == Constants.SP || chr == Constants.HT)) {
-                    space = false;
-                    byteBuffer.position(byteBuffer.position() - 1);
-                }
-            }
-            parsingRequestLineStart = byteBuffer.position();
-            parsingRequestLinePhase = 4;
-        }
-        if (parsingRequestLinePhase == 4) {
-            // Mark the current buffer position
-
-            int end = 0;
-            //
-            // Reading the URI
-            //
-            boolean space = false;
-            while (!space) {
-                // Read new bytes if needed
-                if (byteBuffer.position() >= byteBuffer.limit()) {
-                    if (!fill(false)) {
-                        return false;
-                    }
-                }
-                int pos = byteBuffer.position();
-                prevChr = chr;
-                chr = byteBuffer.get();
-                if (prevChr == Constants.CR && chr != Constants.LF) {
-                    // CR not followed by LF so not an HTTP/0.9 request and
-                    // therefore invalid. Trigger error handling.
-                    // Avoid unknown protocol triggering an additional error
-                    request.protocol().setString(Constants.HTTP_11);
-                    String invalidRequestTarget = parseInvalid(parsingRequestLineStart, byteBuffer);
-                    throw new IllegalArgumentException(sm.getString("iib.invalidRequestTarget", invalidRequestTarget));
-                }
-                if (chr == Constants.SP || chr == Constants.HT) {
-                    space = true;
-                    end = pos;
-                } else if (chr == Constants.CR) {
-                    // HTTP/0.9 style request. CR is optional. LF is not.
-                } else if (chr == Constants.LF) {
-                    // HTTP/0.9 style request
-                    // Stop this processing loop
-                    space = true;
-                    // Set blank protocol (indicates HTTP/0.9)
-                    request.protocol().setString("");
-                    // Skip the protocol processing
-                    parsingRequestLinePhase = 7;
-                    if (prevChr == Constants.CR) {
-                        end = pos - 1;
-                    } else {
-                        end = pos;
-                    }
-                } else if (chr == Constants.QUESTION && parsingRequestLineQPos == -1) {
-                    parsingRequestLineQPos = pos;
-                } else if (parsingRequestLineQPos != -1 && !httpParser.isQueryRelaxed(chr)) {
-                    // Avoid unknown protocol triggering an additional error
-                    request.protocol().setString(Constants.HTTP_11);
-                    // %nn decoding will be checked at the point of decoding
-                    String invalidRequestTarget = parseInvalid(parsingRequestLineStart, byteBuffer);
-                    throw new IllegalArgumentException(sm.getString("iib.invalidRequestTarget", invalidRequestTarget));
-                } else if (httpParser.isNotRequestTargetRelaxed(chr)) {
-                    // Avoid unknown protocol triggering an additional error
-                    request.protocol().setString(Constants.HTTP_11);
-                    // This is a general check that aims to catch problems early
-                    // Detailed checking of each part of the request target will
-                    // happen in Http11Processor#prepareRequest()
-                    String invalidRequestTarget = parseInvalid(parsingRequestLineStart, byteBuffer);
-                    throw new IllegalArgumentException(sm.getString("iib.invalidRequestTarget", invalidRequestTarget));
-                }
-            }
-            if (parsingRequestLineQPos >= 0) {
-                request.queryString().setBytes(byteBuffer.array(), parsingRequestLineQPos + 1,
-                        end - parsingRequestLineQPos - 1);
-                request.requestURI().setBytes(byteBuffer.array(), parsingRequestLineStart,
-                        parsingRequestLineQPos - parsingRequestLineStart);
-            } else {
-                request.requestURI().setBytes(byteBuffer.array(), parsingRequestLineStart,
-                        end - parsingRequestLineStart);
-            }
-            // HTTP/0.9 processing jumps to stage 7.
-            // Don't want to overwrite that here.
-            if (parsingRequestLinePhase == 4) {
-                parsingRequestLinePhase = 5;
-            }
-        }
-        if (parsingRequestLinePhase == 5) {
-            // Spec says single SP but also be tolerant of multiple and/or HT
-            boolean space = true;
-            while (space) {
-                // Read new bytes if needed
-                if (byteBuffer.position() >= byteBuffer.limit()) {
-                    if (!fill(false)) {
-                        return false;
-                    }
-                }
-                byte chr = byteBuffer.get();
-                if (!(chr == Constants.SP || chr == Constants.HT)) {
-                    space = false;
-                    byteBuffer.position(byteBuffer.position() - 1);
-                }
-            }
-            parsingRequestLineStart = byteBuffer.position();
-            parsingRequestLinePhase = 6;
-
-            // Mark the current buffer position
-            end = 0;
-        }
-        if (parsingRequestLinePhase == 6) {
-            //
-            // Reading the protocol
-            // Protocol is always "HTTP/" DIGIT "." DIGIT
-            //
-            while (!parsingRequestLineEol) {
-                // Read new bytes if needed
-                if (byteBuffer.position() >= byteBuffer.limit()) {
-                    if (!fill(false)) {
-                        return false;
-                    }
-                }
-
-                int pos = byteBuffer.position();
-                prevChr = chr;
-                chr = byteBuffer.get();
-                if (chr == Constants.CR) {
-                    // Possible end of request line. Need LF next else invalid.
-                } else if (prevChr == Constants.CR && chr == Constants.LF) {
-                    // CRLF is the standard line terminator
-                    end = pos - 1;
-                    parsingRequestLineEol = true;
-                } else if (chr == Constants.LF) {
-                    // LF is an optional line terminator
-                    end = pos;
-                    parsingRequestLineEol = true;
-                } else if (prevChr == Constants.CR || !HttpParser.isHttpProtocol(chr)) {
-                    String invalidProtocol = parseInvalid(parsingRequestLineStart, byteBuffer);
-                    throw new IllegalArgumentException(sm.getString("iib.invalidHttpProtocol", invalidProtocol));
-                }
-            }
-
-            if ((end - parsingRequestLineStart) > 0) {
-                request.protocol().setBytes(byteBuffer.array(), parsingRequestLineStart,
-                        end - parsingRequestLineStart);
-                parsingRequestLinePhase = 7;
-            }
-            // If no protocol is found, the ISE below will be triggered.
-        }
-        if (parsingRequestLinePhase == 7) {
-            // Parsing is complete. Return and clean-up.
-            parsingRequestLine = false;
-            parsingRequestLinePhase = 0;
-            parsingRequestLineEol = false;
-            parsingRequestLineStart = 0;
-            return true;
-        }
-        throw new IllegalStateException(sm.getString("iib.invalidPhase", Integer.valueOf(parsingRequestLinePhase)));
-    }
-
-
-    /**
-     * Parse the HTTP headers.
-     */
-    boolean parseHeaders() throws IOException {
-        if (!parsingHeader) {
-            throw new IllegalStateException(sm.getString("iib.parseheaders.ise.error"));
-        }
-
-        HeaderParseStatus status = HeaderParseStatus.HAVE_MORE_HEADERS;
-
-        do {
-            status = parseHeader();
-            // Checking that
-            // (1) Headers plus request line size does not exceed its limit
-            // (2) There are enough bytes to avoid expanding the buffer when
-            // reading body
-            // Technically, (2) is technical limitation, (1) is logical
-            // limitation to enforce the meaning of headerBufferSize
-            // From the way how buf is allocated and how blank lines are being
-            // read, it should be enough to check (1) only.
-            if (byteBuffer.position() > headerBufferSize || byteBuffer.capacity() - byteBuffer.position() < socketReadBufferSize) {
-                throw new IllegalArgumentException(sm.getString("iib.requestheadertoolarge.error"));
-            }
-        } while (status == HeaderParseStatus.HAVE_MORE_HEADERS);
-        if (status == HeaderParseStatus.DONE) {
-            parsingHeader = false;
-            end = byteBuffer.position();
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-
-    int getParsingRequestLinePhase() {
-        return parsingRequestLinePhase;
-    }
-
-
-    private String parseInvalid(int startPos, ByteBuffer buffer) {
-        // Look for the next space
-        byte b = 0;
-        while (buffer.hasRemaining() && b != 0x20) {
-            b = buffer.get();
-        }
-        String result = HeaderUtil.toPrintableString(buffer.array(), buffer.arrayOffset() + startPos, buffer.position() - startPos);
-        if (b != 0x20) {
-            // Ran out of buffer rather than found a space
-            result = result + "...";
-        }
-        return result;
-    }
-
-
-    /**
-     * End request (consumes leftover bytes).
-     *
-     * @throws IOException an underlying I/O error occurred
-     */
-    void endRequest() throws IOException {
-
-        if (swallowInput && (lastActiveFilter != -1)) {
-            int extraBytes = (int) activeFilters[lastActiveFilter].end();
-            byteBuffer.position(byteBuffer.position() - extraBytes);
-        }
-    }
-
-
-    @Override
-    public int available() {
-        return available(false);
-    }
-
-
-    /**
-     * Available bytes in the buffers for the current request.
-     *
-     * Note that when requests are pipelined, the data in byteBuffer may relate
-     * to the next request rather than this one.
-     */
-    int available(boolean read) {
-        int available;
-
-        if (lastActiveFilter == -1) {
-            available = inputStreamInputBuffer.available();
-        } else {
-            available = activeFilters[lastActiveFilter].available();
-        }
-
-        // Only try a non-blocking read if:
-        // - there is no data in the filters
-        // - the caller requested a read
-        // - there is no data in byteBuffer
-        // - the socket wrapper indicates a read is allowed
-        //
-        // Notes: 1. When pipelined requests are being used available may be
-        //        zero even when byteBuffer has data. This is because the data
-        //        in byteBuffer is for the next request. We don't want to
-        //        attempt a read in this case.
-        //        2. wrapper.hasDataToRead() is present to handle the NIO2 case
-        try {
-            if (available == 0 && read && !byteBuffer.hasRemaining() && wrapper.hasDataToRead()) {
-                fill(false);
-                available = byteBuffer.remaining();
-            }
-        } catch (IOException ioe) {
-            if (log.isDebugEnabled()) {
-                log.debug(sm.getString("iib.available.readFail"), ioe);
-            }
-            // Not ideal. This will indicate that data is available which should
-            // trigger a read which in turn will trigger another IOException and
-            // that one can be thrown.
-            available = 1;
-        }
-        return available;
-    }
-
-
-    /**
-     * Has all of the request body been read? There are subtle differences
-     * between this and available() &gt; 0 primarily because of having to handle
-     * faking non-blocking reads with the blocking IO connector.
-     */
-    boolean isFinished() {
-        // The active filters have the definitive information on whether or not
-        // the current request body has been read. Note that byteBuffer may
-        // contain pipelined data so is not a good indicator.
-        if (lastActiveFilter >= 0) {
-            return activeFilters[lastActiveFilter].isFinished();
-        } else {
-            // No filters. Assume request is not finished. EOF will signal end of
-            // request.
-            return false;
-        }
-    }
-
-    ByteBuffer getLeftover() {
-        int available = byteBuffer.remaining();
-        if (available > 0) {
-            return ByteBuffer.wrap(byteBuffer.array(), byteBuffer.position(), available);
-        } else {
-            return null;
-        }
-    }
-
-
-    boolean isChunking() {
-        for (int i = 0; i < lastActiveFilter; i++) {
-            if (activeFilters[i] == filterLibrary[Constants.CHUNKED_FILTER]) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-
-    void init(SocketWrapperBase<?> socketWrapper) {
-
-        wrapper = socketWrapper;
-        wrapper.setAppReadBufHandler(this);
-
-        int bufLength = headerBufferSize +
-                wrapper.getSocketBufferHandler().getReadBuffer().capacity();
-        if (byteBuffer == null || byteBuffer.capacity() < bufLength) {
-            byteBuffer = ByteBuffer.allocate(bufLength);
-            byteBuffer.position(0).limit(0);
-        }
-    }
-
-
-
-    // --------------------------------------------------------- Private Methods
-
-    /**
-     * Attempts to read some data into the input buffer.
-     *
-     * @return <code>true</code> if more data was added to the input buffer
-     *         otherwise <code>false</code>
-     */
-    private boolean fill(boolean block) throws IOException {
-
-        if (log.isDebugEnabled()) {
-            log.debug("Before fill(): parsingHeader: [" + parsingHeader +
-                    "], parsingRequestLine: [" + parsingRequestLine +
-                    "], parsingRequestLinePhase: [" + parsingRequestLinePhase +
-                    "], parsingRequestLineStart: [" + parsingRequestLineStart +
-                    "], byteBuffer.position(): [" + byteBuffer.position() +
-                    "], byteBuffer.limit(): [" + byteBuffer.limit() +
-                    "], end: [" + end + "]");
-        }
-
-        if (parsingHeader) {
-            if (byteBuffer.limit() >= headerBufferSize) {
-                if (parsingRequestLine) {
-                    // Avoid unknown protocol triggering an additional error
-                    request.protocol().setString(Constants.HTTP_11);
-                }
-                throw new IllegalArgumentException(sm.getString("iib.requestheadertoolarge.error"));
-            }
-        } else {
-            byteBuffer.limit(end).position(end);
-        }
-
-        int nRead = -1;
-        int mark = byteBuffer.position();
-        try {
-            if (byteBuffer.position() < byteBuffer.limit()) {
-                byteBuffer.position(byteBuffer.limit());
-            }
-            byteBuffer.limit(byteBuffer.capacity());
-            SocketWrapperBase<?> socketWrapper = this.wrapper;
-            if (socketWrapper != null) {
-                nRead = socketWrapper.read(block, byteBuffer);
-            } else {
-                throw new CloseNowException(sm.getString("iib.eof.error"));
-            }
-        } finally {
-            // Ensure that the buffer limit and position are returned to a
-            // consistent "ready for read" state if an error occurs during in
-            // the above code block.
-            // Some error conditions can result in the position being reset to
-            // zero which also invalidates the mark.
-            // https://bz.apache.org/bugzilla/show_bug.cgi?id=65677
-            if (byteBuffer.position() >= mark) {
-                // // Position and mark are consistent. Assume a read (possibly
-                // of zero bytes) has occurred.
-                byteBuffer.limit(byteBuffer.position());
-                byteBuffer.position(mark);
-            } else {
-                // Position and mark are inconsistent. Set position and limit to
-                // zero so effectively no data is reported as read.
-                byteBuffer.position(0);
-                byteBuffer.limit(0);
-            }
-        }
-
-        if (log.isDebugEnabled()) {
-            log.debug("Received ["
-                    + new String(byteBuffer.array(), byteBuffer.position(), byteBuffer.remaining(), StandardCharsets.ISO_8859_1) + "]");
-        }
-
-        if (nRead > 0) {
-            return true;
-        } else if (nRead == -1) {
-            throw new EOFException(sm.getString("iib.eof.error"));
-        } else {
-            return false;
-        }
-
-    }
-
-
-    /**
-     * Parse an HTTP header.
-     *
-     * @return One of {@link HeaderParseStatus#NEED_MORE_DATA},
-     * {@link HeaderParseStatus#HAVE_MORE_HEADERS} or
-     * {@link HeaderParseStatus#DONE}.
-     */
-    private HeaderParseStatus parseHeader() throws IOException {
-
-        while (headerParsePos == HeaderParsePosition.HEADER_START) {
-
-            // Read new bytes if needed
-            if (byteBuffer.position() >= byteBuffer.limit()) {
-                if (!fill(false)) {
-                    return HeaderParseStatus.NEED_MORE_DATA;
-                }
-            }
-
-            prevChr = chr;
-            chr = byteBuffer.get();
-
-            if (chr == Constants.CR && prevChr != Constants.CR) {
-                // Possible start of CRLF - process the next byte.
-            } else if (chr == Constants.LF) {
-                // CRLF or LF is an acceptable line terminator
-                return HeaderParseStatus.DONE;
-            } else {
-                if (prevChr == Constants.CR) {
-                    // Must have read two bytes (first was CR, second was not LF)
-                    byteBuffer.position(byteBuffer.position() - 2);
-                } else {
-                    // Must have only read one byte
-                    byteBuffer.position(byteBuffer.position() - 1);
-                }
-                break;
-            }
-        }
-
-        if (headerParsePos == HeaderParsePosition.HEADER_START) {
-            // Mark the current buffer position
-            headerData.start = byteBuffer.position();
-            headerData.lineStart = headerData.start;
-            headerParsePos = HeaderParsePosition.HEADER_NAME;
-        }
-
-        //
-        // Reading the header name
-        // Header name is always US-ASCII
-        //
-
-        while (headerParsePos == HeaderParsePosition.HEADER_NAME) {
-
-            // Read new bytes if needed
-            if (byteBuffer.position() >= byteBuffer.limit()) {
-                if (!fill(false)) { // parse header
-                    return HeaderParseStatus.NEED_MORE_DATA;
-                }
-            }
-
-            int pos = byteBuffer.position();
-            chr = byteBuffer.get();
-            if (chr == Constants.COLON) {
-                headerParsePos = HeaderParsePosition.HEADER_VALUE_START;
-                headerData.headerValue = headers.addValue(byteBuffer.array(), headerData.start,
-                        pos - headerData.start);
-                pos = byteBuffer.position();
-                // Mark the current buffer position
-                headerData.start = pos;
-                headerData.realPos = pos;
-                headerData.lastSignificantChar = pos;
-                break;
-            } else if (!HttpParser.isToken(chr)) {
-                // Non-token characters are illegal in header names
-                // Parsing continues so the error can be reported in context
-                headerData.lastSignificantChar = pos;
-                byteBuffer.position(byteBuffer.position() - 1);
-                // skipLine() will handle the error
-                return skipLine(false);
-            }
-
-            // chr is next byte of header name. Convert to lowercase.
-            if ((chr >= Constants.A) && (chr <= Constants.Z)) {
-                byteBuffer.put(pos, (byte) (chr - Constants.LC_OFFSET));
-            }
-        }
-
-        // Skip the line and ignore the header
-        if (headerParsePos == HeaderParsePosition.HEADER_SKIPLINE) {
-            return skipLine(false);
-        }
-
-        //
-        // Reading the header value (which can be spanned over multiple lines)
-        //
-
-        while (headerParsePos == HeaderParsePosition.HEADER_VALUE_START ||
-               headerParsePos == HeaderParsePosition.HEADER_VALUE ||
-               headerParsePos == HeaderParsePosition.HEADER_MULTI_LINE) {
-
-            if (headerParsePos == HeaderParsePosition.HEADER_VALUE_START) {
-                // Skipping spaces
-                while (true) {
-                    // Read new bytes if needed
-                    if (byteBuffer.position() >= byteBuffer.limit()) {
-                        if (!fill(false)) {// parse header
-                            // HEADER_VALUE_START
-                            return HeaderParseStatus.NEED_MORE_DATA;
-                        }
-                    }
-
-                    chr = byteBuffer.get();
-                    if (!(chr == Constants.SP || chr == Constants.HT)) {
-                        headerParsePos = HeaderParsePosition.HEADER_VALUE;
-                        byteBuffer.position(byteBuffer.position() - 1);
-                        // Avoids prevChr = chr at start of header value
-                        // parsing which causes problems when chr is CR
-                        // (in the case of an empty header value)
-                        chr = 0;
-                        break;
-                    }
-                }
-            }
-            if (headerParsePos == HeaderParsePosition.HEADER_VALUE) {
-
-                // Reading bytes until the end of the line
-                boolean eol = false;
-                while (!eol) {
-
-                    // Read new bytes if needed
-                    if (byteBuffer.position() >= byteBuffer.limit()) {
-                        if (!fill(false)) {// parse header
-                            // HEADER_VALUE
-                            return HeaderParseStatus.NEED_MORE_DATA;
-                        }
-                    }
-
-                    prevChr = chr;
-                    chr = byteBuffer.get();
-                    if (chr == Constants.CR && prevChr != Constants.CR) {
-                        // CR is only permitted at the start of a CRLF sequence.
-                        // Possible start of CRLF - process the next byte.
-                    } else if (chr == Constants.LF) {
-                        // CRLF or LF is an acceptable line terminator
-                        eol = true;
-                    } else if (prevChr == Constants.CR) {
-                        // Invalid value - also need to delete header
-                        return skipLine(true);
-                    } else if (chr != Constants.HT && HttpParser.isControl(chr)) {
-                        // Invalid value - also need to delete header
-                        return skipLine(true);
-                    } else if (chr == Constants.SP || chr == Constants.HT) {
-                        byteBuffer.put(headerData.realPos, chr);
-                        headerData.realPos++;
-                    } else {
-                        byteBuffer.put(headerData.realPos, chr);
-                        headerData.realPos++;
-                        headerData.lastSignificantChar = headerData.realPos;
-                    }
-                }
-
-                // Ignore whitespaces at the end of the line
-                headerData.realPos = headerData.lastSignificantChar;
-
-                // Checking the first character of the new line. If the character
-                // is a LWS, then it's a multiline header
-                headerParsePos = HeaderParsePosition.HEADER_MULTI_LINE;
-            }
-            // Read new bytes if needed
-            if (byteBuffer.position() >= byteBuffer.limit()) {
-                if (!fill(false)) {// parse header
-                    // HEADER_MULTI_LINE
-                    return HeaderParseStatus.NEED_MORE_DATA;
-                }
-            }
-
-            byte peek = byteBuffer.get(byteBuffer.position());
-            if (headerParsePos == HeaderParsePosition.HEADER_MULTI_LINE) {
-                if ((peek != Constants.SP) && (peek != Constants.HT)) {
-                    headerParsePos = HeaderParsePosition.HEADER_START;
-                    break;
-                } else {
-                    // Copying one extra space in the buffer (since there must
-                    // be at least one space inserted between the lines)
-                    byteBuffer.put(headerData.realPos, peek);
-                    headerData.realPos++;
-                    headerParsePos = HeaderParsePosition.HEADER_VALUE_START;
-                }
-            }
-        }
-        // Set the header value
-        headerData.headerValue.setBytes(byteBuffer.array(), headerData.start,
-                headerData.lastSignificantChar - headerData.start);
-        headerData.recycle();
-        return HeaderParseStatus.HAVE_MORE_HEADERS;
-    }
-
-
-    private HeaderParseStatus skipLine(boolean deleteHeader) throws IOException {
-        boolean rejectThisHeader = rejectIllegalHeader;
-        // Check if rejectIllegalHeader is disabled and needs to be overridden
-        // for this header. The header name is required to determine if this
-        // override is required. The header name is only available once the
-        // header has been created. If the header has been created then
-        // deleteHeader will be true.
-        if (!rejectThisHeader && deleteHeader) {
-            if (headers.getName(headers.size() - 1).equalsIgnoreCase("content-length")) {
-                // Malformed content-length headers must always be rejected
-                // RFC 9112, section 6.3, bullet 5.
-                rejectThisHeader = true;
-            } else {
-                // Only need to delete the header if the request isn't going to
-                // be rejected (it will be the most recent one)
-                headers.removeHeader(headers.size() - 1);
-            }
-        }
-
-        // Parse the rest of the invalid header so we can construct a useful
-        // exception and/or debug message.
-        headerParsePos = HeaderParsePosition.HEADER_SKIPLINE;
-        boolean eol = false;
-
-        // Reading bytes until the end of the line
-        while (!eol) {
-
-            // Read new bytes if needed
-            if (byteBuffer.position() >= byteBuffer.limit()) {
-                if (!fill(false)) {
-                    return HeaderParseStatus.NEED_MORE_DATA;
-                }
-            }
-
-            int pos = byteBuffer.position();
-            prevChr = chr;
-            chr = byteBuffer.get();
-            if (chr == Constants.CR) {
-                // Skip
-            } else if (chr == Constants.LF) {
-                // CRLF or LF is an acceptable line terminator
-                eol = true;
-            } else {
-                headerData.lastSignificantChar = pos;
-            }
-        }
-        if (rejectThisHeader || log.isDebugEnabled()) {
-            String message = sm.getString("iib.invalidheader",
-                    HeaderUtil.toPrintableString(byteBuffer.array(), headerData.lineStart,
-                            headerData.lastSignificantChar - headerData.lineStart + 1));
-            if (rejectThisHeader) {
-                throw new IllegalArgumentException(message);
-            }
-            log.debug(message);
-        }
-
-        headerParsePos = HeaderParsePosition.HEADER_START;
-        return HeaderParseStatus.HAVE_MORE_HEADERS;
-    }
-
-
-    // ----------------------------------------------------------- Inner classes
-
-    private enum HeaderParseStatus {
-        DONE, HAVE_MORE_HEADERS, NEED_MORE_DATA
-    }
-
-
-    private enum HeaderParsePosition {
-        /**
-         * Start of a new header. A CRLF here means that there are no more
-         * headers. Any other character starts a header name.
-         */
-        HEADER_START,
-        /**
-         * Reading a header name. All characters of header are HTTP_TOKEN_CHAR.
-         * Header name is followed by ':'. No whitespace is allowed.<br>
-         * Any non-HTTP_TOKEN_CHAR (this includes any whitespace) encountered
-         * before ':' will result in the whole line being ignored.
-         */
-        HEADER_NAME,
-        /**
-         * Skipping whitespace before text of header value starts, either on the
-         * first line of header value (just after ':') or on subsequent lines
-         * when it is known that subsequent line starts with SP or HT.
-         */
-        HEADER_VALUE_START,
-        /**
-         * Reading the header value. We are inside the value. Either on the
-         * first line or on any subsequent line. We come into this state from
-         * HEADER_VALUE_START after the first non-SP/non-HT byte is encountered
-         * on the line.
-         */
-        HEADER_VALUE,
-        /**
-         * Before reading a new line of a header. Once the next byte is peeked,
-         * the state changes without advancing our position. The state becomes
-         * either HEADER_VALUE_START (if that first byte is SP or HT), or
-         * HEADER_START (otherwise).
-         */
-        HEADER_MULTI_LINE,
-        /**
-         * Reading all bytes until the next CRLF. The line is being ignored.
-         */
-        HEADER_SKIPLINE
-    }
-
-
-    private static class HeaderParseData {
-        /**
-         * The first character of the header line.
-         */
-        int lineStart = 0;
-        /**
-         * When parsing header name: first character of the header.<br>
-         * When skipping broken header line: first character of the header.<br>
-         * When parsing header value: first character after ':'.
-         */
-        int start = 0;
-        /**
-         * When parsing header name: not used (stays as 0).<br>
-         * When skipping broken header line: not used (stays as 0).<br>
-         * When parsing header value: starts as the first character after ':'.
-         * Then is increased as far as more bytes of the header are harvested.
-         * Bytes from buf[pos] are copied to buf[realPos]. Thus the string from
-         * [start] to [realPos-1] is the prepared value of the header, with
-         * whitespaces removed as needed.<br>
-         */
-        int realPos = 0;
-        /**
-         * When parsing header name: not used (stays as 0).<br>
-         * When skipping broken header line: last non-CR/non-LF character.<br>
-         * When parsing header value: position after the last not-LWS character.<br>
-         */
-        int lastSignificantChar = 0;
-        /**
-         * MB that will store the value of the header. It is null while parsing
-         * header name and is created after the name has been parsed.
-         */
-        MessageBytes headerValue = null;
-        public void recycle() {
-            lineStart = 0;
-            start = 0;
-            realPos = 0;
-            lastSignificantChar = 0;
-            headerValue = null;
-        }
-    }
-
-
-    // ------------------------------------- InputStreamInputBuffer Inner Class
-
-    /**
-     * This class is an input buffer which will read its data from an input
-     * stream.
-     */
-    private class SocketInputBuffer implements InputBuffer {
-
-        @Override
-        public int doRead(ApplicationBufferHandler handler) throws IOException {
-
-            if (byteBuffer.position() >= byteBuffer.limit()) {
-                // The application is reading the HTTP request body
-                boolean block = (request.getReadListener() == null);
-                if (!fill(block)) {
-                    if (block) {
-                        return -1;
-                    } else {
-                        return 0;
-                    }
-                }
-            }
-
-            int length = byteBuffer.remaining();
-            handler.setByteBuffer(byteBuffer.duplicate());
-            byteBuffer.position(byteBuffer.limit());
-
-            return length;
-        }
-
-        @Override
-        public int available() {
-            return byteBuffer.remaining();
-        }
-    }
-
-
-    @Override
-    public void setByteBuffer(ByteBuffer buffer) {
-        byteBuffer = buffer;
-    }
-
-
-    @Override
-    public ByteBuffer getByteBuffer() {
-        return byteBuffer;
+    public Http11InputBuffer(Request request, int headerBufferSize, boolean rejectIllegalHeader,
+            HttpParser httpParser) {
+        super(request, headerBufferSize, rejectIllegalHeader, httpParser);
     }
 
 
     @Override
-    public void expand(int size) {
-        if (byteBuffer.capacity() >= size) {
-            byteBuffer.limit(size);
-        }
-        ByteBuffer temp = ByteBuffer.allocate(size);
-        temp.put(byteBuffer);
-        byteBuffer = temp;
-        byteBuffer.mark();
-        temp = null;
+    protected Log getLog() {
+        return log;
     }
 }
diff --git a/java/org/apache/coyote/http11/Http11Processor.java b/java/org/apache/coyote/http11/Http11Processor.java
index bfca3b93ba..f607755525 100644
--- a/java/org/apache/coyote/http11/Http11Processor.java
+++ b/java/org/apache/coyote/http11/Http11Processor.java
@@ -17,8 +17,10 @@
 package org.apache.coyote.http11;
 
 import org.apache.coyote.Adapter;
+import org.apache.coyote.Request;
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.http.parser.HttpParser;
 
 public class Http11Processor extends AbstractHttp11Processor {
 
@@ -30,6 +32,14 @@ public class Http11Processor extends AbstractHttp11Processor {
     }
 
 
+    @Override
+    protected AbstractHttp11InputBuffer createInputBuffer(Request request, AbstractHttp11Protocol<?> protocol,
+            HttpParser httpParser) {
+        return new Http11InputBuffer(request, protocol.getMaxHttpRequestHeaderSize(),
+                protocol.getRejectIllegalHeader(), httpParser);
+    }
+
+
     @Override
     protected Log getLog() {
         return log;
diff --git a/java/org/apache/coyote/http11/Http11Processor.java b/modules/loom/src/main/java/org/apache/coyote/http11/Http11LoomInputBuffer.java
similarity index 68%
copy from java/org/apache/coyote/http11/Http11Processor.java
copy to modules/loom/src/main/java/org/apache/coyote/http11/Http11LoomInputBuffer.java
index bfca3b93ba..08e43eb5c0 100644
--- a/java/org/apache/coyote/http11/Http11Processor.java
+++ b/modules/loom/src/main/java/org/apache/coyote/http11/Http11LoomInputBuffer.java
@@ -16,17 +16,19 @@
  */
 package org.apache.coyote.http11;
 
-import org.apache.coyote.Adapter;
+import org.apache.coyote.Request;
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.http.parser.HttpParser;
 
-public class Http11Processor extends AbstractHttp11Processor {
+public class Http11LoomInputBuffer extends AbstractHttp11InputBuffer {
 
-    private static final Log log = LogFactory.getLog(Http11Processor.class);
+    private static final Log log = LogFactory.getLog(Http11LoomInputBuffer.class);
 
 
-    public Http11Processor(AbstractHttp11Protocol<?> protocol, Adapter adapter) {
-        super(protocol, adapter);
+    public Http11LoomInputBuffer(Request request, int headerBufferSize, boolean rejectIllegalHeader,
+            HttpParser httpParser) {
+        super(request, headerBufferSize, rejectIllegalHeader, httpParser);
     }
 
 
diff --git a/modules/loom/src/main/java/org/apache/coyote/http11/Http11LoomProcessor.java b/modules/loom/src/main/java/org/apache/coyote/http11/Http11LoomProcessor.java
index bfd16c7026..ef838ac580 100644
--- a/modules/loom/src/main/java/org/apache/coyote/http11/Http11LoomProcessor.java
+++ b/modules/loom/src/main/java/org/apache/coyote/http11/Http11LoomProcessor.java
@@ -17,8 +17,10 @@
 package org.apache.coyote.http11;
 
 import org.apache.coyote.Adapter;
+import org.apache.coyote.Request;
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.http.parser.HttpParser;
 
 public class Http11LoomProcessor extends AbstractHttp11Processor {
 
@@ -30,6 +32,14 @@ public class Http11LoomProcessor extends AbstractHttp11Processor {
     }
 
 
+    @Override
+    protected AbstractHttp11InputBuffer createInputBuffer(Request request, AbstractHttp11Protocol<?> protocol,
+            HttpParser httpParser) {
+        return new Http11LoomInputBuffer(request, protocol.getMaxHttpRequestHeaderSize(),
+                protocol.getRejectIllegalHeader(), httpParser);
+    }
+
+
     @Override
     protected Log getLog() {
         return log;


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org


[tomcat] 01/03: Refactor. Introduce AbstractHttp11Processor.

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

markt pushed a commit to branch loom
in repository https://gitbox.apache.org/repos/asf/tomcat.git

commit 6d4ee277815db06db6d9b5a8cfc028c14d2899ca
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Wed Oct 12 16:51:29 2022 +0100

    Refactor. Introduce AbstractHttp11Processor.
    
    This abstract class is introduced to allow for Loom and non-Loom
    Processors to use different Http11InputBuffers.
---
 ...Processor.java => AbstractHttp11Processor.java} |   58 +-
 java/org/apache/coyote/http11/Http11Processor.java | 1422 +-------------------
 .../apache/coyote/http11/Http11LoomProcessor.java  |   13 +-
 3 files changed, 38 insertions(+), 1455 deletions(-)

diff --git a/java/org/apache/coyote/http11/Http11Processor.java b/java/org/apache/coyote/http11/AbstractHttp11Processor.java
similarity index 96%
copy from java/org/apache/coyote/http11/Http11Processor.java
copy to java/org/apache/coyote/http11/AbstractHttp11Processor.java
index c27ff911f4..b63b824bc6 100644
--- a/java/org/apache/coyote/http11/Http11Processor.java
+++ b/java/org/apache/coyote/http11/AbstractHttp11Processor.java
@@ -48,8 +48,6 @@ import org.apache.coyote.http11.filters.VoidInputFilter;
 import org.apache.coyote.http11.filters.VoidOutputFilter;
 import org.apache.coyote.http11.upgrade.InternalHttpUpgradeHandler;
 import org.apache.coyote.http11.upgrade.UpgradeApplicationBufferHandler;
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.util.ExceptionUtils;
 import org.apache.tomcat.util.buf.ByteChunk;
 import org.apache.tomcat.util.buf.MessageBytes;
@@ -67,9 +65,7 @@ import org.apache.tomcat.util.net.SendfileState;
 import org.apache.tomcat.util.net.SocketWrapperBase;
 import org.apache.tomcat.util.res.StringManager;
 
-public class Http11Processor extends AbstractProcessor {
-
-    private static final Log log = LogFactory.getLog(Http11Processor.class);
+public abstract class AbstractHttp11Processor extends AbstractProcessor {
 
     /**
      * The string manager for this package.
@@ -152,7 +148,7 @@ public class Http11Processor extends AbstractProcessor {
     private SendfileDataBase sendfileData = null;
 
 
-    public Http11Processor(AbstractHttp11Protocol<?> protocol, Adapter adapter) {
+    public AbstractHttp11Processor(AbstractHttp11Protocol<?> protocol, Adapter adapter) {
         super(adapter);
         this.protocol = protocol;
 
@@ -218,8 +214,8 @@ public class Http11Processor extends AbstractProcessor {
             // 400 - Bad request
             response.setStatus(400);
             setErrorState(ErrorState.CLOSE_CLEAN, null);
-            if (log.isDebugEnabled()) {
-                log.debug(sm.getString("http11processor.request.prepare") +
+            if (getLog().isDebugEnabled()) {
+                getLog().debug(sm.getString("http11processor.request.prepare") +
                           " Transfer encoding lists chunked before [" + encodingName + "]");
             }
             return;
@@ -240,8 +236,8 @@ public class Http11Processor extends AbstractProcessor {
             // 501 - Unimplemented
             response.setStatus(501);
             setErrorState(ErrorState.CLOSE_CLEAN, null);
-            if (log.isDebugEnabled()) {
-                log.debug(sm.getString("http11processor.request.prepare") +
+            if (getLog().isDebugEnabled()) {
+                getLog().debug(sm.getString("http11processor.request.prepare") +
                           " Unsupported transfer encoding [" + encodingName + "]");
             }
         }
@@ -304,8 +300,8 @@ public class Http11Processor extends AbstractProcessor {
                     }
                 }
             } catch (IOException e) {
-                if (log.isDebugEnabled()) {
-                    log.debug(sm.getString("http11processor.header.parse"), e);
+                if (getLog().isDebugEnabled()) {
+                    getLog().debug(sm.getString("http11processor.header.parse"), e);
                 }
                 setErrorState(ErrorState.CLOSE_CONNECTION_NOW, e);
                 break;
@@ -319,10 +315,10 @@ public class Http11Processor extends AbstractProcessor {
                             message += sm.getString("http11processor.fallToDebug");
                             //$FALL-THROUGH$
                         case INFO:
-                            log.info(message, t);
+                            getLog().info(message, t);
                             break;
                         case DEBUG:
-                            log.debug(message, t);
+                            getLog().debug(message, t);
                     }
                 }
                 // 400 - Bad Request
@@ -376,8 +372,8 @@ public class Http11Processor extends AbstractProcessor {
                     prepareRequest();
                 } catch (Throwable t) {
                     ExceptionUtils.handleThrowable(t);
-                    if (log.isDebugEnabled()) {
-                        log.debug(sm.getString("http11processor.request.prepare"), t);
+                    if (getLog().isDebugEnabled()) {
+                        getLog().debug(sm.getString("http11processor.request.prepare"), t);
                     }
                     // 500 - Internal Server Error
                     response.setStatus(500);
@@ -410,7 +406,7 @@ public class Http11Processor extends AbstractProcessor {
                 } catch (InterruptedIOException e) {
                     setErrorState(ErrorState.CLOSE_CONNECTION_NOW, e);
                 } catch (HeadersTooLargeException e) {
-                    log.error(sm.getString("http11processor.request.process"), e);
+                    getLog().error(sm.getString("http11processor.request.process"), e);
                     // The response should not have been committed but check it
                     // anyway to be safe
                     if (response.isCommitted()) {
@@ -423,7 +419,7 @@ public class Http11Processor extends AbstractProcessor {
                     }
                 } catch (Throwable t) {
                     ExceptionUtils.handleThrowable(t);
-                    log.error(sm.getString("http11processor.request.process"), t);
+                    getLog().error(sm.getString("http11processor.request.process"), t);
                     // 500 - Internal Server Error
                     response.setStatus(500);
                     setErrorState(ErrorState.CLOSE_CLEAN, t);
@@ -621,8 +617,8 @@ public class Http11Processor extends AbstractProcessor {
             // Send 505; Unsupported HTTP version
             response.setStatus(505);
             setErrorState(ErrorState.CLOSE_CLEAN, null);
-            if (log.isDebugEnabled()) {
-                log.debug(sm.getString("http11processor.request.prepare")+
+            if (getLog().isDebugEnabled()) {
+                getLog().debug(sm.getString("http11processor.request.prepare")+
                           " Unsupported HTTP version \""+protocolMB+"\"");
             }
         }
@@ -873,8 +869,8 @@ public class Http11Processor extends AbstractProcessor {
     private void badRequest(String errorKey) {
         response.setStatus(400);
         setErrorState(ErrorState.CLOSE_CLEAN, null);
-        if (log.isDebugEnabled()) {
-            log.debug(sm.getString(errorKey));
+        if (getLog().isDebugEnabled()) {
+            getLog().debug(sm.getString(errorKey));
         }
     }
 
@@ -1066,7 +1062,7 @@ public class Http11Processor extends AbstractProcessor {
                     outputBuffer.sendHeader(headers.getName(i), headers.getValue(i));
                 } catch (IllegalArgumentException iae) {
                     // Log the problematic header
-                    log.warn(sm.getString("http11processor.response.invalidHeader",
+                    getLog().warn(sm.getString("http11processor.response.invalidHeader",
                             headers.getName(i), headers.getValue(i)), iae);
                     // Remove the problematic header
                     headers.removeHeader(i);
@@ -1175,12 +1171,6 @@ public class Http11Processor extends AbstractProcessor {
     }
 
 
-    @Override
-    protected Log getLog() {
-        return log;
-    }
-
-
     @Override
     protected ServletConnection getServletConnection() {
         return socketWrapper.getServletConnection("http/1.1", "");
@@ -1218,7 +1208,7 @@ public class Http11Processor extends AbstractProcessor {
                 // written in the Adapter.service method.
                 response.setStatus(500);
                 setErrorState(ErrorState.CLOSE_NOW, t);
-                log.error(sm.getString("http11processor.request.finish"), t);
+                getLog().error(sm.getString("http11processor.request.finish"), t);
             }
         }
         if (getErrorState().isIoAllowed()) {
@@ -1230,7 +1220,7 @@ public class Http11Processor extends AbstractProcessor {
             } catch (Throwable t) {
                 ExceptionUtils.handleThrowable(t);
                 setErrorState(ErrorState.CLOSE_NOW, t);
-                log.error(sm.getString("http11processor.response.finish"), t);
+                getLog().error(sm.getString("http11processor.response.finish"), t);
             }
         }
     }
@@ -1320,7 +1310,7 @@ public class Http11Processor extends AbstractProcessor {
                     request.setAttribute(SSLSupport.CERTIFICATE_KEY, sslO);
                 }
             } catch (IOException ioe) {
-                log.warn(sm.getString("http11processor.socket.ssl"), ioe);
+                getLog().warn(sm.getString("http11processor.socket.ssl"), ioe);
             }
         }
     }
@@ -1422,8 +1412,8 @@ public class Http11Processor extends AbstractProcessor {
             switch (result) {
             case ERROR:
                 // Write failed
-                if (log.isDebugEnabled()) {
-                    log.debug(sm.getString("http11processor.sendfile.error"));
+                if (getLog().isDebugEnabled()) {
+                    getLog().debug(sm.getString("http11processor.sendfile.error"));
                 }
                 setErrorState(ErrorState.CLOSE_CONNECTION_NOW, null);
                 //$FALL-THROUGH$
diff --git a/java/org/apache/coyote/http11/Http11Processor.java b/java/org/apache/coyote/http11/Http11Processor.java
index c27ff911f4..bfca3b93ba 100644
--- a/java/org/apache/coyote/http11/Http11Processor.java
+++ b/java/org/apache/coyote/http11/Http11Processor.java
@@ -16,1162 +16,17 @@
  */
 package org.apache.coyote.http11;
 
-import java.io.IOException;
-import java.io.InterruptedIOException;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.regex.Pattern;
-
-import jakarta.servlet.ServletConnection;
-import jakarta.servlet.http.HttpServletResponse;
-
-import org.apache.coyote.AbstractProcessor;
-import org.apache.coyote.ActionCode;
 import org.apache.coyote.Adapter;
-import org.apache.coyote.ContinueResponseTiming;
-import org.apache.coyote.ErrorState;
-import org.apache.coyote.Request;
-import org.apache.coyote.RequestInfo;
-import org.apache.coyote.UpgradeProtocol;
-import org.apache.coyote.UpgradeToken;
-import org.apache.coyote.http11.filters.BufferedInputFilter;
-import org.apache.coyote.http11.filters.ChunkedInputFilter;
-import org.apache.coyote.http11.filters.ChunkedOutputFilter;
-import org.apache.coyote.http11.filters.GzipOutputFilter;
-import org.apache.coyote.http11.filters.IdentityInputFilter;
-import org.apache.coyote.http11.filters.IdentityOutputFilter;
-import org.apache.coyote.http11.filters.SavedRequestInputFilter;
-import org.apache.coyote.http11.filters.VoidInputFilter;
-import org.apache.coyote.http11.filters.VoidOutputFilter;
-import org.apache.coyote.http11.upgrade.InternalHttpUpgradeHandler;
-import org.apache.coyote.http11.upgrade.UpgradeApplicationBufferHandler;
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
-import org.apache.tomcat.util.ExceptionUtils;
-import org.apache.tomcat.util.buf.ByteChunk;
-import org.apache.tomcat.util.buf.MessageBytes;
-import org.apache.tomcat.util.http.FastHttpDateFormat;
-import org.apache.tomcat.util.http.MimeHeaders;
-import org.apache.tomcat.util.http.parser.HttpParser;
-import org.apache.tomcat.util.http.parser.TokenList;
-import org.apache.tomcat.util.log.UserDataHelper;
-import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
-import org.apache.tomcat.util.net.ApplicationBufferHandler;
-import org.apache.tomcat.util.net.SSLSupport;
-import org.apache.tomcat.util.net.SendfileDataBase;
-import org.apache.tomcat.util.net.SendfileKeepAliveState;
-import org.apache.tomcat.util.net.SendfileState;
-import org.apache.tomcat.util.net.SocketWrapperBase;
-import org.apache.tomcat.util.res.StringManager;
 
-public class Http11Processor extends AbstractProcessor {
+public class Http11Processor extends AbstractHttp11Processor {
 
     private static final Log log = LogFactory.getLog(Http11Processor.class);
 
-    /**
-     * The string manager for this package.
-     */
-    private static final StringManager sm = StringManager.getManager(Http11Processor.class);
-
-
-    private final AbstractHttp11Protocol<?> protocol;
-
-
-    /**
-     * Input.
-     */
-    private final Http11InputBuffer inputBuffer;
-
-
-    /**
-     * Output.
-     */
-    private final Http11OutputBuffer outputBuffer;
-
-
-    private final HttpParser httpParser;
-
-
-    /**
-     * Tracks how many internal filters are in the filter library so they
-     * are skipped when looking for pluggable filters.
-     */
-    private int pluggableFilterIndex = Integer.MAX_VALUE;
-
-
-    /**
-     * Keep-alive.
-     */
-    private volatile boolean keepAlive = true;
-
-
-    /**
-     * Flag used to indicate that the socket should be kept open (e.g. for keep
-     * alive or send file).
-     */
-    private volatile boolean openSocket = false;
-
-
-    /**
-     * Flag that indicates if the request headers have been completely read.
-     */
-    private volatile boolean readComplete = true;
-
-    /**
-     * HTTP/1.1 flag.
-     */
-    private boolean http11 = true;
-
-
-    /**
-     * HTTP/0.9 flag.
-     */
-    private boolean http09 = false;
-
-
-    /**
-     * Content delimiter for the request (if false, the connection will
-     * be closed at the end of the request).
-     */
-    private boolean contentDelimitation = true;
-
-
-    /**
-     * Instance of the new protocol to use after the HTTP connection has been
-     * upgraded.
-     */
-    private UpgradeToken upgradeToken = null;
-
-
-    /**
-     * Sendfile data.
-     */
-    private SendfileDataBase sendfileData = null;
-
 
     public Http11Processor(AbstractHttp11Protocol<?> protocol, Adapter adapter) {
-        super(adapter);
-        this.protocol = protocol;
-
-        httpParser = new HttpParser(protocol.getRelaxedPathChars(),
-                protocol.getRelaxedQueryChars());
-
-        inputBuffer = new Http11InputBuffer(request, protocol.getMaxHttpRequestHeaderSize(),
-                protocol.getRejectIllegalHeader(), httpParser);
-        request.setInputBuffer(inputBuffer);
-
-        outputBuffer = new Http11OutputBuffer(response, protocol.getMaxHttpResponseHeaderSize());
-        response.setOutputBuffer(outputBuffer);
-
-        // Create and add the identity filters.
-        inputBuffer.addFilter(new IdentityInputFilter(protocol.getMaxSwallowSize()));
-        outputBuffer.addFilter(new IdentityOutputFilter());
-
-        // Create and add the chunked filters.
-        inputBuffer.addFilter(new ChunkedInputFilter(protocol.getMaxTrailerSize(),
-                protocol.getAllowedTrailerHeadersInternal(), protocol.getMaxExtensionSize(),
-                protocol.getMaxSwallowSize()));
-        outputBuffer.addFilter(new ChunkedOutputFilter());
-
-        // Create and add the void filters.
-        inputBuffer.addFilter(new VoidInputFilter());
-        outputBuffer.addFilter(new VoidOutputFilter());
-
-        // Create and add buffered input filter
-        inputBuffer.addFilter(new BufferedInputFilter(protocol.getMaxSwallowSize()));
-
-        // Create and add the gzip filters.
-        //inputBuffer.addFilter(new GzipInputFilter());
-        outputBuffer.addFilter(new GzipOutputFilter());
-
-        pluggableFilterIndex = inputBuffer.getFilters().length;
-    }
-
-
-    /**
-     * Determine if we must drop the connection because of the HTTP status
-     * code.  Use the same list of codes as Apache/httpd.
-     */
-    private static boolean statusDropsConnection(int status) {
-        return status == 400 /* SC_BAD_REQUEST */ ||
-               status == 408 /* SC_REQUEST_TIMEOUT */ ||
-               status == 411 /* SC_LENGTH_REQUIRED */ ||
-               status == 413 /* SC_REQUEST_ENTITY_TOO_LARGE */ ||
-               status == 414 /* SC_REQUEST_URI_TOO_LONG */ ||
-               status == 500 /* SC_INTERNAL_SERVER_ERROR */ ||
-               status == 503 /* SC_SERVICE_UNAVAILABLE */ ||
-               status == 501 /* SC_NOT_IMPLEMENTED */;
-    }
-
-
-    /**
-     * Add an input filter to the current request. If the encoding is not
-     * supported, a 501 response will be returned to the client.
-     */
-    private void addInputFilter(InputFilter[] inputFilters, String encodingName) {
-        if (contentDelimitation) {
-            // Chunked has already been specified and it must be the final
-            // encoding.
-            // 400 - Bad request
-            response.setStatus(400);
-            setErrorState(ErrorState.CLOSE_CLEAN, null);
-            if (log.isDebugEnabled()) {
-                log.debug(sm.getString("http11processor.request.prepare") +
-                          " Transfer encoding lists chunked before [" + encodingName + "]");
-            }
-            return;
-        }
-
-        // Parsing trims and converts to lower case.
-        if (encodingName.equals("chunked")) {
-            inputBuffer.addActiveFilter(inputFilters[Constants.CHUNKED_FILTER]);
-            contentDelimitation = true;
-        } else {
-            for (int i = pluggableFilterIndex; i < inputFilters.length; i++) {
-                if (inputFilters[i].getEncodingName().toString().equals(encodingName)) {
-                    inputBuffer.addActiveFilter(inputFilters[i]);
-                    return;
-                }
-            }
-            // Unsupported transfer encoding
-            // 501 - Unimplemented
-            response.setStatus(501);
-            setErrorState(ErrorState.CLOSE_CLEAN, null);
-            if (log.isDebugEnabled()) {
-                log.debug(sm.getString("http11processor.request.prepare") +
-                          " Unsupported transfer encoding [" + encodingName + "]");
-            }
-        }
-    }
-
-
-    @Override
-    public SocketState service(SocketWrapperBase<?> socketWrapper)
-        throws IOException {
-        RequestInfo rp = request.getRequestProcessor();
-        rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);
-
-        // Setting up the I/O
-        setSocketWrapper(socketWrapper);
-
-        // Flags
-        keepAlive = true;
-        openSocket = false;
-        readComplete = true;
-        boolean keptAlive = false;
-        SendfileState sendfileState = SendfileState.DONE;
-
-        while (!getErrorState().isError() && keepAlive && !isAsync() && upgradeToken == null &&
-                sendfileState == SendfileState.DONE && !protocol.isPaused()) {
-
-            // Parsing the request header
-            try {
-                if (!inputBuffer.parseRequestLine(keptAlive, protocol.getConnectionTimeout(),
-                        protocol.getKeepAliveTimeout())) {
-                    if (inputBuffer.getParsingRequestLinePhase() == -1) {
-                        return SocketState.UPGRADING;
-                    } else if (handleIncompleteRequestLineRead()) {
-                        break;
-                    }
-                }
-
-                // Process the Protocol component of the request line
-                // Need to know if this is an HTTP 0.9 request before trying to
-                // parse headers.
-                prepareRequestProtocol();
-
-                if (protocol.isPaused()) {
-                    // 503 - Service unavailable
-                    response.setStatus(503);
-                    setErrorState(ErrorState.CLOSE_CLEAN, null);
-                } else {
-                    keptAlive = true;
-                    // Set this every time in case limit has been changed via JMX
-                    request.getMimeHeaders().setLimit(protocol.getMaxHeaderCount());
-                    // Don't parse headers for HTTP/0.9
-                    if (!http09 && !inputBuffer.parseHeaders()) {
-                        // We've read part of the request, don't recycle it
-                        // instead associate it with the socket
-                        openSocket = true;
-                        readComplete = false;
-                        break;
-                    }
-                    if (!protocol.getDisableUploadTimeout()) {
-                        socketWrapper.setReadTimeout(protocol.getConnectionUploadTimeout());
-                    }
-                }
-            } catch (IOException e) {
-                if (log.isDebugEnabled()) {
-                    log.debug(sm.getString("http11processor.header.parse"), e);
-                }
-                setErrorState(ErrorState.CLOSE_CONNECTION_NOW, e);
-                break;
-            } catch (Throwable t) {
-                ExceptionUtils.handleThrowable(t);
-                UserDataHelper.Mode logMode = userDataHelper.getNextMode();
-                if (logMode != null) {
-                    String message = sm.getString("http11processor.header.parse");
-                    switch (logMode) {
-                        case INFO_THEN_DEBUG:
-                            message += sm.getString("http11processor.fallToDebug");
-                            //$FALL-THROUGH$
-                        case INFO:
-                            log.info(message, t);
-                            break;
-                        case DEBUG:
-                            log.debug(message, t);
-                    }
-                }
-                // 400 - Bad Request
-                response.setStatus(400);
-                setErrorState(ErrorState.CLOSE_CLEAN, t);
-            }
-
-            // Has an upgrade been requested?
-            if (isConnectionToken(request.getMimeHeaders(), "upgrade")) {
-                // Check the protocol
-                String requestedProtocol = request.getHeader("Upgrade");
-
-                UpgradeProtocol upgradeProtocol = protocol.getUpgradeProtocol(requestedProtocol);
-                if (upgradeProtocol != null) {
-                    if (upgradeProtocol.accept(request)) {
-                        // Create clone of request for upgraded protocol
-                        Request upgradeRequest = null;
-                        try {
-                            upgradeRequest = cloneRequest(request);
-                        } catch (ByteChunk.BufferOverflowException ioe) {
-                            response.setStatus(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE);
-                            setErrorState(ErrorState.CLOSE_CLEAN, null);
-                        } catch (IOException ioe) {
-                            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
-                            setErrorState(ErrorState.CLOSE_CLEAN, ioe);
-                        }
-
-                        if (upgradeRequest != null) {
-                            // Complete the HTTP/1.1 upgrade process
-                            response.setStatus(HttpServletResponse.SC_SWITCHING_PROTOCOLS);
-                            response.setHeader("Connection", "Upgrade");
-                            response.setHeader("Upgrade", requestedProtocol);
-                            action(ActionCode.CLOSE,  null);
-                            getAdapter().log(request, response, 0);
-
-                            // Continue processing using new protocol
-                            InternalHttpUpgradeHandler upgradeHandler =
-                                    upgradeProtocol.getInternalUpgradeHandler(socketWrapper, getAdapter(), upgradeRequest);
-                            UpgradeToken upgradeToken = new UpgradeToken(upgradeHandler, null, null, requestedProtocol);
-                            action(ActionCode.UPGRADE, upgradeToken);
-                            return SocketState.UPGRADING;
-                        }
-                    }
-                }
-            }
-
-            if (getErrorState().isIoAllowed()) {
-                // Setting up filters, and parse some request headers
-                rp.setStage(org.apache.coyote.Constants.STAGE_PREPARE);
-                try {
-                    prepareRequest();
-                } catch (Throwable t) {
-                    ExceptionUtils.handleThrowable(t);
-                    if (log.isDebugEnabled()) {
-                        log.debug(sm.getString("http11processor.request.prepare"), t);
-                    }
-                    // 500 - Internal Server Error
-                    response.setStatus(500);
-                    setErrorState(ErrorState.CLOSE_CLEAN, t);
-                }
-            }
-
-            int maxKeepAliveRequests = protocol.getMaxKeepAliveRequests();
-            if (maxKeepAliveRequests == 1) {
-                keepAlive = false;
-            } else if (maxKeepAliveRequests > 0 &&
-                    socketWrapper.decrementKeepAlive() <= 0) {
-                keepAlive = false;
-            }
-
-            // Process the request in the adapter
-            if (getErrorState().isIoAllowed()) {
-                try {
-                    rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
-                    getAdapter().service(request, response);
-                    // Handle when the response was committed before a serious
-                    // error occurred.  Throwing a ServletException should both
-                    // set the status to 500 and set the errorException.
-                    // If we fail here, then the response is likely already
-                    // committed, so we can't try and set headers.
-                    if(keepAlive && !getErrorState().isError() && !isAsync() &&
-                            statusDropsConnection(response.getStatus())) {
-                        setErrorState(ErrorState.CLOSE_CLEAN, null);
-                    }
-                } catch (InterruptedIOException e) {
-                    setErrorState(ErrorState.CLOSE_CONNECTION_NOW, e);
-                } catch (HeadersTooLargeException e) {
-                    log.error(sm.getString("http11processor.request.process"), e);
-                    // The response should not have been committed but check it
-                    // anyway to be safe
-                    if (response.isCommitted()) {
-                        setErrorState(ErrorState.CLOSE_NOW, e);
-                    } else {
-                        response.reset();
-                        response.setStatus(500);
-                        setErrorState(ErrorState.CLOSE_CLEAN, e);
-                        response.setHeader("Connection", "close"); // TODO: Remove
-                    }
-                } catch (Throwable t) {
-                    ExceptionUtils.handleThrowable(t);
-                    log.error(sm.getString("http11processor.request.process"), t);
-                    // 500 - Internal Server Error
-                    response.setStatus(500);
-                    setErrorState(ErrorState.CLOSE_CLEAN, t);
-                    getAdapter().log(request, response, 0);
-                }
-            }
-
-            // Finish the handling of the request
-            rp.setStage(org.apache.coyote.Constants.STAGE_ENDINPUT);
-            if (!isAsync()) {
-                // If this is an async request then the request ends when it has
-                // been completed. The AsyncContext is responsible for calling
-                // endRequest() in that case.
-                endRequest();
-            }
-            rp.setStage(org.apache.coyote.Constants.STAGE_ENDOUTPUT);
-
-            // If there was an error, make sure the request is counted as
-            // and error, and update the statistics counter
-            if (getErrorState().isError()) {
-                response.setStatus(500);
-            }
-
-            if (!isAsync() || getErrorState().isError()) {
-                request.updateCounters();
-                if (getErrorState().isIoAllowed()) {
-                    inputBuffer.nextRequest();
-                    outputBuffer.nextRequest();
-                }
-            }
-
-            if (!protocol.getDisableUploadTimeout()) {
-                int connectionTimeout = protocol.getConnectionTimeout();
-                if(connectionTimeout > 0) {
-                    socketWrapper.setReadTimeout(connectionTimeout);
-                } else {
-                    socketWrapper.setReadTimeout(0);
-                }
-            }
-
-            rp.setStage(org.apache.coyote.Constants.STAGE_KEEPALIVE);
-
-            sendfileState = processSendfile(socketWrapper);
-        }
-
-        rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
-
-        if (getErrorState().isError() || (protocol.isPaused() && !isAsync())) {
-            return SocketState.CLOSED;
-        } else if (isAsync()) {
-            return SocketState.LONG;
-        } else if (isUpgrade()) {
-            return SocketState.UPGRADING;
-        } else {
-            if (sendfileState == SendfileState.PENDING) {
-                return SocketState.SENDFILE;
-            } else {
-                if (openSocket) {
-                    if (readComplete) {
-                        return SocketState.OPEN;
-                    } else {
-                        return SocketState.LONG;
-                    }
-                } else {
-                    return SocketState.CLOSED;
-                }
-            }
-        }
-    }
-
-
-    @Override
-    protected final void setSocketWrapper(SocketWrapperBase<?> socketWrapper) {
-        super.setSocketWrapper(socketWrapper);
-        inputBuffer.init(socketWrapper);
-        outputBuffer.init(socketWrapper);
-    }
-
-
-    private Request cloneRequest(Request source) throws IOException {
-        Request dest = new Request();
-
-        // Transfer the minimal information required for the copy of the Request
-        // that is passed to the HTTP upgrade process
-        dest.decodedURI().duplicate(source.decodedURI());
-        dest.method().duplicate(source.method());
-        dest.getMimeHeaders().duplicate(source.getMimeHeaders());
-        dest.requestURI().duplicate(source.requestURI());
-        dest.queryString().duplicate(source.queryString());
-
-        // Preparation for reading the request body
-        MimeHeaders headers = source.getMimeHeaders();
-        prepareExpectation(headers);
-        prepareInputFilters(headers);
-        ack(ContinueResponseTiming.ALWAYS);
-
-        // Need to read and buffer the request body, if any. RFC 7230 requires
-        // that the request is fully read before the upgrade takes place.
-        ByteChunk body = new ByteChunk();
-        int maxSavePostSize = protocol.getMaxSavePostSize();
-        if (maxSavePostSize != 0) {
-            body.setLimit(maxSavePostSize);
-            ApplicationBufferHandler buffer = new UpgradeApplicationBufferHandler();
-
-            while (source.getInputBuffer().doRead(buffer) >= 0) {
-                body.append(buffer.getByteBuffer());
-            }
-        }
-
-        // Make the buffered request body available to the upgraded protocol.
-        SavedRequestInputFilter srif = new SavedRequestInputFilter(body);
-        dest.setInputBuffer(srif);
-
-        return dest;
-    }
-
-
-    private boolean handleIncompleteRequestLineRead() {
-        // Haven't finished reading the request so keep the socket
-        // open
-        openSocket = true;
-        // Check to see if we have read any of the request line yet
-        if (inputBuffer.getParsingRequestLinePhase() > 1) {
-            // Started to read request line.
-            if (protocol.isPaused()) {
-                // Partially processed the request so need to respond
-                response.setStatus(503);
-                setErrorState(ErrorState.CLOSE_CLEAN, null);
-                return false;
-            } else {
-                // Need to keep processor associated with socket
-                readComplete = false;
-            }
-        }
-        return true;
-    }
-
-
-    private void checkExpectationAndResponseStatus() {
-        if (request.hasExpectation() && !isRequestBodyFullyRead() &&
-                (response.getStatus() < 200 || response.getStatus() > 299)) {
-            // Client sent Expect: 100-continue but received a
-            // non-2xx final response. Disable keep-alive (if enabled)
-            // to ensure that the connection is closed. Some clients may
-            // still send the body, some may send the next request.
-            // No way to differentiate, so close the connection to
-            // force the client to send the next request.
-            inputBuffer.setSwallowInput(false);
-            keepAlive = false;
-        }
-    }
-
-
-    private void checkMaxSwallowSize() {
-        // Parse content-length header
-        long contentLength = -1;
-        try {
-            contentLength = request.getContentLengthLong();
-        } catch (Exception e) {
-            // Ignore, an error here is already processed in prepareRequest
-            // but is done again since the content length is still -1
-        }
-        if (contentLength > 0 && protocol.getMaxSwallowSize() > -1 &&
-                (contentLength - request.getBytesRead() > protocol.getMaxSwallowSize())) {
-            // There is more data to swallow than Tomcat will accept so the
-            // connection is going to be closed. Disable keep-alive which will
-            // trigger adding the "Connection: close" header if not already
-            // present.
-            keepAlive = false;
-        }
-    }
-
-
-    private void prepareRequestProtocol() {
-
-        MessageBytes protocolMB = request.protocol();
-        if (protocolMB.equals(Constants.HTTP_11)) {
-            http09 = false;
-            http11 = true;
-            protocolMB.setString(Constants.HTTP_11);
-        } else if (protocolMB.equals(Constants.HTTP_10)) {
-            http09 = false;
-            http11 = false;
-            keepAlive = false;
-            protocolMB.setString(Constants.HTTP_10);
-        } else if (protocolMB.equals("")) {
-            // HTTP/0.9
-            http09 = true;
-            http11 = false;
-            keepAlive = false;
-        } else {
-            // Unsupported protocol
-            http09 = false;
-            http11 = false;
-            // Send 505; Unsupported HTTP version
-            response.setStatus(505);
-            setErrorState(ErrorState.CLOSE_CLEAN, null);
-            if (log.isDebugEnabled()) {
-                log.debug(sm.getString("http11processor.request.prepare")+
-                          " Unsupported HTTP version \""+protocolMB+"\"");
-            }
-        }
-    }
-
-
-    /**
-     * After reading the request headers, we have to setup the request filters.
-     */
-    private void prepareRequest() throws IOException {
-
-        if (protocol.isSSLEnabled()) {
-            request.scheme().setString("https");
-        }
-
-        MimeHeaders headers = request.getMimeHeaders();
-
-        // Check connection header
-        MessageBytes connectionValueMB = headers.getValue(Constants.CONNECTION);
-        if (connectionValueMB != null && !connectionValueMB.isNull()) {
-            Set<String> tokens = new HashSet<>();
-            TokenList.parseTokenList(headers.values(Constants.CONNECTION), tokens);
-            if (tokens.contains(Constants.CLOSE)) {
-                keepAlive = false;
-            } else if (tokens.contains(Constants.KEEP_ALIVE_HEADER_VALUE_TOKEN)) {
-                keepAlive = true;
-            }
-        }
-
-        if (http11) {
-            prepareExpectation(headers);
-        }
-
-        // Check user-agent header
-        Pattern restrictedUserAgents = protocol.getRestrictedUserAgentsPattern();
-        if (restrictedUserAgents != null && (http11 || keepAlive)) {
-            MessageBytes userAgentValueMB = headers.getValue("user-agent");
-            // Check in the restricted list, and adjust the http11
-            // and keepAlive flags accordingly
-            if(userAgentValueMB != null && !userAgentValueMB.isNull()) {
-                String userAgentValue = userAgentValueMB.toString();
-                if (restrictedUserAgents.matcher(userAgentValue).matches()) {
-                    http11 = false;
-                    keepAlive = false;
-                }
-            }
-        }
-
-
-        // Check host header
-        MessageBytes hostValueMB = null;
-        try {
-            hostValueMB = headers.getUniqueValue("host");
-        } catch (IllegalArgumentException iae) {
-            // Multiple Host headers are not permitted
-            badRequest("http11processor.request.multipleHosts");
-        }
-        if (http11 && hostValueMB == null) {
-            badRequest("http11processor.request.noHostHeader");
-        }
-
-        // Check for an absolute-URI less the query string which has already
-        // been removed during the parsing of the request line
-        ByteChunk uriBC = request.requestURI().getByteChunk();
-        byte[] uriB = uriBC.getBytes();
-        if (uriBC.startsWithIgnoreCase("http", 0)) {
-            int pos = 4;
-            // Check for https
-            if (uriBC.startsWithIgnoreCase("s", pos)) {
-                pos++;
-            }
-            // Next 3 characters must be "://"
-            if (uriBC.startsWith("://", pos)) {
-                pos += 3;
-                int uriBCStart = uriBC.getStart();
-
-                // '/' does not appear in the authority so use the first
-                // instance to split the authority and the path segments
-                int slashPos = uriBC.indexOf('/', pos);
-                // '@' in the authority delimits the userinfo
-                int atPos = uriBC.indexOf('@', pos);
-                if (slashPos > -1 && atPos > slashPos) {
-                    // First '@' is in the path segments so no userinfo
-                    atPos = -1;
-                }
-
-                if (slashPos == -1) {
-                    slashPos = uriBC.getLength();
-                    // Set URI as "/". Use 6 as it will always be a '/'.
-                    // 01234567
-                    // http://
-                    // https://
-                    request.requestURI().setBytes(uriB, uriBCStart + 6, 1);
-                } else {
-                    request.requestURI().setBytes(uriB, uriBCStart + slashPos, uriBC.getLength() - slashPos);
-                }
-
-                // Skip any user info
-                if (atPos != -1) {
-                    // Validate the userinfo
-                    for (; pos < atPos; pos++) {
-                        byte c = uriB[uriBCStart + pos];
-                        if (!HttpParser.isUserInfo(c)) {
-                            // Strictly there needs to be a check for valid %nn
-                            // encoding here but skip it since it will never be
-                            // decoded because the userinfo is ignored
-                            badRequest("http11processor.request.invalidUserInfo");
-                            break;
-                        }
-                    }
-                    // Skip the '@'
-                    pos = atPos + 1;
-                }
-
-                if (http11) {
-                    // Missing host header is illegal but handled above
-                    if (hostValueMB != null) {
-                        // Any host in the request line must be consistent with
-                        // the Host header
-                        if (!hostValueMB.getByteChunk().equals(
-                                uriB, uriBCStart + pos, slashPos - pos)) {
-                            if (protocol.getAllowHostHeaderMismatch()) {
-                                // The requirements of RFC 2616 are being
-                                // applied. If the host header and the request
-                                // line do not agree, the request line takes
-                                // precedence
-                                hostValueMB = headers.setValue("host");
-                                hostValueMB.setBytes(uriB, uriBCStart + pos, slashPos - pos);
-                            } else {
-                                // The requirements of RFC 7230 are being
-                                // applied. If the host header and the request
-                                // line do not agree, trigger a 400 response.
-                                badRequest("http11processor.request.inconsistentHosts");
-                            }
-                        }
-                    }
-                } else {
-                    // Not HTTP/1.1 - no Host header so generate one since
-                    // Tomcat internals assume it is set
-                    try {
-                        hostValueMB = headers.setValue("host");
-                        hostValueMB.setBytes(uriB, uriBCStart + pos, slashPos - pos);
-                    } catch (IllegalStateException e) {
-                        // Edge case
-                        // If the request has too many headers it won't be
-                        // possible to create the host header. Ignore this as
-                        // processing won't reach the point where the Tomcat
-                        // internals expect there to be a host header.
-                    }
-                }
-            } else {
-                badRequest("http11processor.request.invalidScheme");
-            }
-        }
-
-        // Validate the characters in the URI. %nn decoding will be checked at
-        // the point of decoding.
-        for (int i = uriBC.getStart(); i < uriBC.getEnd(); i++) {
-            if (!httpParser.isAbsolutePathRelaxed(uriB[i])) {
-                badRequest("http11processor.request.invalidUri");
-                break;
-            }
-        }
-
-        // Input filter setup
-        prepareInputFilters(headers);
-
-        // Validate host name and extract port if present
-        parseHost(hostValueMB);
-
-        if (!getErrorState().isIoAllowed()) {
-            getAdapter().log(request, response, 0);
-        }
-    }
-
-
-    private void prepareExpectation(MimeHeaders headers) {
-        MessageBytes expectMB = headers.getValue("expect");
-        if (expectMB != null && !expectMB.isNull()) {
-            if (expectMB.toString().trim().equalsIgnoreCase("100-continue")) {
-                request.setExpectation(true);
-            } else {
-                response.setStatus(HttpServletResponse.SC_EXPECTATION_FAILED);
-                setErrorState(ErrorState.CLOSE_CLEAN, null);
-            }
-        }
-    }
-
-    private void prepareInputFilters(MimeHeaders headers) throws IOException {
-
-        contentDelimitation = false;
-
-        InputFilter[] inputFilters = inputBuffer.getFilters();
-
-        // Parse transfer-encoding header
-        // HTTP specs say an HTTP 1.1 server should accept any recognised
-        // HTTP 1.x header from a 1.x client unless the specs says otherwise.
-        if (!http09) {
-            MessageBytes transferEncodingValueMB = headers.getValue("transfer-encoding");
-            if (transferEncodingValueMB != null) {
-                List<String> encodingNames = new ArrayList<>();
-                if (TokenList.parseTokenList(headers.values("transfer-encoding"), encodingNames)) {
-                    for (String encodingName : encodingNames) {
-                        addInputFilter(inputFilters, encodingName);
-                    }
-                } else {
-                    // Invalid transfer encoding
-                    badRequest("http11processor.request.invalidTransferEncoding");
-                }
-            }
-        }
-
-        // Parse content-length header
-        long contentLength = -1;
-        try {
-            contentLength = request.getContentLengthLong();
-        } catch (NumberFormatException e) {
-            badRequest("http11processor.request.nonNumericContentLength");
-        } catch (IllegalArgumentException e) {
-            badRequest("http11processor.request.multipleContentLength");
-        }
-        if (contentLength >= 0) {
-            if (contentDelimitation) {
-                // contentDelimitation being true at this point indicates that
-                // chunked encoding is being used but chunked encoding should
-                // not be used with a content length. RFC 2616, section 4.4,
-                // bullet 3 states Content-Length must be ignored in this case -
-                // so remove it.
-                headers.removeHeader("content-length");
-                request.setContentLength(-1);
-                keepAlive = false;
-            } else {
-                inputBuffer.addActiveFilter(inputFilters[Constants.IDENTITY_FILTER]);
-                contentDelimitation = true;
-            }
-        }
-
-        if (!contentDelimitation) {
-            // If there's no content length
-            // (broken HTTP/1.0 or HTTP/1.1), assume
-            // the client is not broken and didn't send a body
-            inputBuffer.addActiveFilter(inputFilters[Constants.VOID_FILTER]);
-            contentDelimitation = true;
-        }
-    }
-
-
-    private void badRequest(String errorKey) {
-        response.setStatus(400);
-        setErrorState(ErrorState.CLOSE_CLEAN, null);
-        if (log.isDebugEnabled()) {
-            log.debug(sm.getString(errorKey));
-        }
-    }
-
-
-    /**
-     * When committing the response, we have to validate the set of headers, as
-     * well as setup the response filters.
-     */
-    @Override
-    protected final void prepareResponse() throws IOException {
-
-        boolean entityBody = true;
-        contentDelimitation = false;
-
-        OutputFilter[] outputFilters = outputBuffer.getFilters();
-
-        if (http09 == true) {
-            // HTTP/0.9
-            outputBuffer.addActiveFilter(outputFilters[Constants.IDENTITY_FILTER]);
-            outputBuffer.commit();
-            return;
-        }
-
-        int statusCode = response.getStatus();
-        if (statusCode < 200 || statusCode == 204 || statusCode == 205 ||
-                statusCode == 304) {
-            // No entity body
-            outputBuffer.addActiveFilter
-                (outputFilters[Constants.VOID_FILTER]);
-            entityBody = false;
-            contentDelimitation = true;
-            if (statusCode == 205) {
-                // RFC 7231 requires the server to explicitly signal an empty
-                // response in this case
-                response.setContentLength(0);
-            } else {
-                response.setContentLength(-1);
-            }
-        }
-
-        MessageBytes methodMB = request.method();
-        if (methodMB.equals("HEAD")) {
-            // No entity body
-            outputBuffer.addActiveFilter
-                (outputFilters[Constants.VOID_FILTER]);
-            contentDelimitation = true;
-        }
-
-        // Sendfile support
-        if (protocol.getUseSendfile()) {
-            prepareSendfile(outputFilters);
-        }
-
-        // Check for compression
-        boolean useCompression = false;
-        if (entityBody && sendfileData == null) {
-            useCompression = protocol.useCompression(request, response);
-        }
-
-        MimeHeaders headers = response.getMimeHeaders();
-        // A SC_NO_CONTENT response may include entity headers
-        if (entityBody || statusCode == HttpServletResponse.SC_NO_CONTENT) {
-            String contentType = response.getContentType();
-            if (contentType != null) {
-                headers.setValue("Content-Type").setString(contentType);
-            }
-            String contentLanguage = response.getContentLanguage();
-            if (contentLanguage != null) {
-                headers.setValue("Content-Language")
-                    .setString(contentLanguage);
-            }
-        }
-
-        long contentLength = response.getContentLengthLong();
-        boolean connectionClosePresent = isConnectionToken(headers, Constants.CLOSE);
-        if (http11 && response.getTrailerFields() != null) {
-            // If trailer fields are set, always use chunking
-            outputBuffer.addActiveFilter(outputFilters[Constants.CHUNKED_FILTER]);
-            contentDelimitation = true;
-            headers.addValue(Constants.TRANSFERENCODING).setString(Constants.CHUNKED);
-        } else if (contentLength != -1) {
-            headers.setValue("Content-Length").setLong(contentLength);
-            outputBuffer.addActiveFilter(outputFilters[Constants.IDENTITY_FILTER]);
-            contentDelimitation = true;
-        } else {
-            // If the response code supports an entity body and we're on
-            // HTTP 1.1 then we chunk unless we have a Connection: close header
-            if (http11 && entityBody && !connectionClosePresent) {
-                outputBuffer.addActiveFilter(outputFilters[Constants.CHUNKED_FILTER]);
-                contentDelimitation = true;
-                headers.addValue(Constants.TRANSFERENCODING).setString(Constants.CHUNKED);
-            } else {
-                outputBuffer.addActiveFilter(outputFilters[Constants.IDENTITY_FILTER]);
-            }
-        }
-
-        if (useCompression) {
-            outputBuffer.addActiveFilter(outputFilters[Constants.GZIP_FILTER]);
-        }
-
-        // Add date header unless application has already set one (e.g. in a
-        // Caching Filter)
-        if (headers.getValue("Date") == null) {
-            headers.addValue("Date").setString(
-                    FastHttpDateFormat.getCurrentDate());
-        }
-
-        // FIXME: Add transfer encoding header
-
-        if ((entityBody) && (!contentDelimitation) || connectionClosePresent) {
-            // Disable keep-alive if:
-            // - there is a response body but way for the client to determine
-            //   the content length information; or
-            // - there is a "connection: close" header present
-            // This will cause the "connection: close" header to be added if it
-            // is not already present.
-            keepAlive = false;
-        }
-
-        // This may disabled keep-alive to check before working out the
-        // Connection header.
-        checkExpectationAndResponseStatus();
-
-        // This may disable keep-alive if there is more body to swallow
-        // than the configuration allows
-        checkMaxSwallowSize();
-
-        // If we know that the request is bad this early, add the
-        // Connection: close header.
-        if (keepAlive && statusDropsConnection(statusCode)) {
-            keepAlive = false;
-        }
-        if (!keepAlive) {
-            // Avoid adding the close header twice
-            if (!connectionClosePresent) {
-                headers.addValue(Constants.CONNECTION).setString(
-                        Constants.CLOSE);
-            }
-        } else if (!getErrorState().isError()) {
-            if (!http11) {
-                headers.addValue(Constants.CONNECTION).setString(Constants.KEEP_ALIVE_HEADER_VALUE_TOKEN);
-            }
-
-            if (protocol.getUseKeepAliveResponseHeader()) {
-                boolean connectionKeepAlivePresent =
-                    isConnectionToken(request.getMimeHeaders(), Constants.KEEP_ALIVE_HEADER_VALUE_TOKEN);
-
-                if (connectionKeepAlivePresent) {
-                    int keepAliveTimeout = protocol.getKeepAliveTimeout();
-
-                    if (keepAliveTimeout > 0) {
-                        String value = "timeout=" + keepAliveTimeout / 1000L;
-                        headers.setValue(Constants.KEEP_ALIVE_HEADER_NAME).setString(value);
-
-                        if (http11) {
-                            // Append if there is already a Connection header,
-                            // else create the header
-                            MessageBytes connectionHeaderValue = headers.getValue(Constants.CONNECTION);
-                            if (connectionHeaderValue == null) {
-                                headers.addValue(Constants.CONNECTION).setString(Constants.KEEP_ALIVE_HEADER_VALUE_TOKEN);
-                            } else {
-                                connectionHeaderValue.setString(
-                                        connectionHeaderValue.getString() + ", " + Constants.KEEP_ALIVE_HEADER_VALUE_TOKEN);
-                            }
-                        }
-                    }
-                }
-            }
-        }
-
-        // Add server header
-        String server = protocol.getServer();
-        if (server == null) {
-            if (protocol.getServerRemoveAppProvidedValues()) {
-                headers.removeHeader("server");
-            }
-        } else {
-            // server always overrides anything the app might set
-            headers.setValue("Server").setString(server);
-        }
-
-        // Build the response header
-        try {
-            outputBuffer.sendStatus();
-
-            int size = headers.size();
-            for (int i = 0; i < size; i++) {
-                try {
-                    outputBuffer.sendHeader(headers.getName(i), headers.getValue(i));
-                } catch (IllegalArgumentException iae) {
-                    // Log the problematic header
-                    log.warn(sm.getString("http11processor.response.invalidHeader",
-                            headers.getName(i), headers.getValue(i)), iae);
-                    // Remove the problematic header
-                    headers.removeHeader(i);
-                    size--;
-                    // Header buffer is corrupted. Reset it and start again.
-                    outputBuffer.resetHeaderBuffer();
-                    i = 0;
-                    outputBuffer.sendStatus();
-                }
-            }
-            outputBuffer.endHeaders();
-        } catch (Throwable t) {
-            ExceptionUtils.handleThrowable(t);
-            // If something goes wrong, reset the header buffer so the error
-            // response can be written instead.
-            outputBuffer.resetHeaderBuffer();
-            throw t;
-        }
-
-        outputBuffer.commit();
-    }
-
-    private static boolean isConnectionToken(MimeHeaders headers, String token) throws IOException {
-        MessageBytes connection = headers.getValue(Constants.CONNECTION);
-        if (connection == null) {
-            return false;
-        }
-
-        Set<String> tokens = new HashSet<>();
-        TokenList.parseTokenList(headers.values(Constants.CONNECTION), tokens);
-        return tokens.contains(token);
-    }
-
-
-    private void prepareSendfile(OutputFilter[] outputFilters) {
-        String fileName = (String) request.getAttribute(
-                org.apache.coyote.Constants.SENDFILE_FILENAME_ATTR);
-        if (fileName == null) {
-            sendfileData = null;
-        } else {
-            // No entity body sent here
-            outputBuffer.addActiveFilter(outputFilters[Constants.VOID_FILTER]);
-            contentDelimitation = true;
-            long pos = ((Long) request.getAttribute(
-                    org.apache.coyote.Constants.SENDFILE_FILE_START_ATTR)).longValue();
-            long end = ((Long) request.getAttribute(
-                    org.apache.coyote.Constants.SENDFILE_FILE_END_ATTR)).longValue();
-            sendfileData = socketWrapper.createSendfileData(fileName, pos, end - pos);
-        }
-    }
-
-
-    /*
-     * Note: populateHost() is not over-ridden.
-     *       request.serverName() will be set to return the default host name by
-     *       the Mapper.
-     */
-
-
-    /**
-     * {@inheritDoc}
-     * <p>
-     * This implementation provides the server port from the local port.
-     */
-    @Override
-    protected void populatePort() {
-        // Ensure the local port field is populated before using it.
-        request.action(ActionCode.REQ_LOCALPORT_ATTRIBUTE, request);
-        request.setServerPort(request.getLocalPort());
-    }
-
-
-    @Override
-    protected boolean flushBufferedWrite() throws IOException {
-        if (outputBuffer.hasDataToWrite()) {
-            if (outputBuffer.flushBuffer(false)) {
-                // The buffer wasn't fully flushed so re-register the
-                // socket for write. Note this does not go via the
-                // Response since the write registration state at
-                // that level should remain unchanged. Once the buffer
-                // has been emptied then the code below will call
-                // Adaptor.asyncDispatch() which will enable the
-                // Response to respond to this event.
-                outputBuffer.registerWriteInterest();
-                return true;
-            }
-        }
-        return false;
-    }
-
-
-    @Override
-    protected SocketState dispatchEndRequest() {
-        if (!keepAlive || protocol.isPaused()) {
-            return SocketState.CLOSED;
-        } else {
-            endRequest();
-            inputBuffer.nextRequest();
-            outputBuffer.nextRequest();
-            if (socketWrapper.isReadPending()) {
-                return SocketState.LONG;
-            } else {
-                return SocketState.OPEN;
-            }
-        }
+        super(protocol, adapter);
     }
 
 
@@ -1179,277 +34,4 @@ public class Http11Processor extends AbstractProcessor {
     protected Log getLog() {
         return log;
     }
-
-
-    @Override
-    protected ServletConnection getServletConnection() {
-        return socketWrapper.getServletConnection("http/1.1", "");
-    }
-
-
-    /*
-     * No more input will be passed to the application. Remaining input will be
-     * swallowed or the connection dropped depending on the error and
-     * expectation status.
-     */
-    private void endRequest() {
-        if (getErrorState().isError()) {
-            // If we know we are closing the connection, don't drain
-            // input. This way uploading a 100GB file doesn't tie up the
-            // thread if the servlet has rejected it.
-            inputBuffer.setSwallowInput(false);
-        } else {
-            // Need to check this again here in case the response was
-            // committed before the error that requires the connection
-            // to be closed occurred.
-            checkExpectationAndResponseStatus();
-        }
-
-        // Finish the handling of the request
-        if (getErrorState().isIoAllowed()) {
-            try {
-                inputBuffer.endRequest();
-            } catch (IOException e) {
-                setErrorState(ErrorState.CLOSE_CONNECTION_NOW, e);
-            } catch (Throwable t) {
-                ExceptionUtils.handleThrowable(t);
-                // 500 - Internal Server Error
-                // Can't add a 500 to the access log since that has already been
-                // written in the Adapter.service method.
-                response.setStatus(500);
-                setErrorState(ErrorState.CLOSE_NOW, t);
-                log.error(sm.getString("http11processor.request.finish"), t);
-            }
-        }
-        if (getErrorState().isIoAllowed()) {
-            try {
-                action(ActionCode.COMMIT, null);
-                outputBuffer.end();
-            } catch (IOException e) {
-                setErrorState(ErrorState.CLOSE_CONNECTION_NOW, e);
-            } catch (Throwable t) {
-                ExceptionUtils.handleThrowable(t);
-                setErrorState(ErrorState.CLOSE_NOW, t);
-                log.error(sm.getString("http11processor.response.finish"), t);
-            }
-        }
-    }
-
-
-    @Override
-    protected final void finishResponse() throws IOException {
-        outputBuffer.end();
-    }
-
-
-    @Override
-    protected final void ack(ContinueResponseTiming continueResponseTiming) {
-        // Only try and send the ACK for ALWAYS or if the timing of the request
-        // to send the ACK matches the current configuration.
-        if (continueResponseTiming == ContinueResponseTiming.ALWAYS ||
-                continueResponseTiming == protocol.getContinueResponseTimingInternal()) {
-            // Acknowledge request
-            // Send a 100 status back if it makes sense (response not committed
-            // yet, and client specified an expectation for 100-continue)
-            if (!response.isCommitted() && request.hasExpectation()) {
-                try {
-                    outputBuffer.sendAck();
-                } catch (IOException e) {
-                    setErrorState(ErrorState.CLOSE_CONNECTION_NOW, e);
-                }
-            }
-        }
-    }
-
-
-    @Override
-    protected final void flush() throws IOException {
-        outputBuffer.flush();
-    }
-
-
-    @Override
-    protected final int available(boolean doRead) {
-        return inputBuffer.available(doRead);
-    }
-
-
-    @Override
-    protected final void setRequestBody(ByteChunk body) {
-        InputFilter savedBody = new SavedRequestInputFilter(body);
-        Http11InputBuffer internalBuffer = (Http11InputBuffer) request.getInputBuffer();
-        internalBuffer.addActiveFilter(savedBody);
-    }
-
-
-    @Override
-    protected final void setSwallowResponse() {
-        outputBuffer.responseFinished = true;
-    }
-
-
-    @Override
-    protected final void disableSwallowRequest() {
-        inputBuffer.setSwallowInput(false);
-    }
-
-
-    @Override
-    protected final void sslReHandShake() throws IOException {
-        if (sslSupport != null) {
-            // Consume and buffer the request body, so that it does not
-            // interfere with the client's handshake messages
-            InputFilter[] inputFilters = inputBuffer.getFilters();
-            ((BufferedInputFilter) inputFilters[Constants.BUFFERED_FILTER]).setLimit(
-                    protocol.getMaxSavePostSize());
-            inputBuffer.addActiveFilter(inputFilters[Constants.BUFFERED_FILTER]);
-
-            /*
-             * Outside the try/catch because we want I/O errors during
-             * renegotiation to be thrown for the caller to handle since they
-             * will be fatal to the connection.
-             */
-            socketWrapper.doClientAuth(sslSupport);
-            try {
-                /*
-                 * Errors processing the cert chain do not affect the client
-                 * connection so they can be logged and swallowed here.
-                 */
-                Object sslO = sslSupport.getPeerCertificateChain();
-                if (sslO != null) {
-                    request.setAttribute(SSLSupport.CERTIFICATE_KEY, sslO);
-                }
-            } catch (IOException ioe) {
-                log.warn(sm.getString("http11processor.socket.ssl"), ioe);
-            }
-        }
-    }
-
-
-    @Override
-    protected final boolean isRequestBodyFullyRead() {
-        return inputBuffer.isFinished();
-    }
-
-
-    @Override
-    protected final void registerReadInterest() {
-        socketWrapper.registerReadInterest();
-    }
-
-
-    @Override
-    protected final boolean isReadyForWrite() {
-        return outputBuffer.isReady();
-    }
-
-
-    @Override
-    public UpgradeToken getUpgradeToken() {
-        return upgradeToken;
-    }
-
-
-    @Override
-    protected final void doHttpUpgrade(UpgradeToken upgradeToken) {
-        this.upgradeToken = upgradeToken;
-        // Stop further HTTP output
-        outputBuffer.responseFinished = true;
-    }
-
-
-    @Override
-    public ByteBuffer getLeftoverInput() {
-        return inputBuffer.getLeftover();
-    }
-
-
-    @Override
-    public boolean isUpgrade() {
-        return upgradeToken != null;
-    }
-
-
-    @Override
-    protected boolean isTrailerFieldsReady() {
-        if (inputBuffer.isChunking()) {
-            return inputBuffer.isFinished();
-        } else {
-            return true;
-        }
-    }
-
-
-    @Override
-    protected boolean isTrailerFieldsSupported() {
-        // Request must be HTTP/1.1 to support trailer fields
-        if (!http11) {
-            return false;
-        }
-
-        // If the response is not yet committed, chunked encoding can be used
-        // and the trailer fields sent
-        if (!response.isCommitted()) {
-            return true;
-        }
-
-        // Response has been committed - need to see if chunked is being used
-        return outputBuffer.isChunking();
-    }
-
-
-    /**
-     * Trigger sendfile processing if required.
-     *
-     * @return The state of send file processing
-     */
-    private SendfileState processSendfile(SocketWrapperBase<?> socketWrapper) {
-        openSocket = keepAlive;
-        // Done is equivalent to sendfile not being used
-        SendfileState result = SendfileState.DONE;
-        // Do sendfile as needed: add socket to sendfile and end
-        if (sendfileData != null && !getErrorState().isError()) {
-            if (keepAlive) {
-                if (available(false) == 0) {
-                    sendfileData.keepAliveState = SendfileKeepAliveState.OPEN;
-                } else {
-                    sendfileData.keepAliveState = SendfileKeepAliveState.PIPELINED;
-                }
-            } else {
-                sendfileData.keepAliveState = SendfileKeepAliveState.NONE;
-            }
-            result = socketWrapper.processSendfile(sendfileData);
-            switch (result) {
-            case ERROR:
-                // Write failed
-                if (log.isDebugEnabled()) {
-                    log.debug(sm.getString("http11processor.sendfile.error"));
-                }
-                setErrorState(ErrorState.CLOSE_CONNECTION_NOW, null);
-                //$FALL-THROUGH$
-            default:
-                sendfileData = null;
-            }
-        }
-        return result;
-    }
-
-
-    @Override
-    public final void recycle() {
-        getAdapter().checkRecycled(request, response);
-        super.recycle();
-        inputBuffer.recycle();
-        outputBuffer.recycle();
-        upgradeToken = null;
-        socketWrapper = null;
-        sendfileData = null;
-        sslSupport = null;
-    }
-
-
-    @Override
-    public void pause() {
-        // NOOP for HTTP
-    }
 }
diff --git a/modules/loom/src/main/java/org/apache/coyote/http11/Http11LoomProcessor.java b/modules/loom/src/main/java/org/apache/coyote/http11/Http11LoomProcessor.java
index cc1e5de4d0..bfd16c7026 100644
--- a/modules/loom/src/main/java/org/apache/coyote/http11/Http11LoomProcessor.java
+++ b/modules/loom/src/main/java/org/apache/coyote/http11/Http11LoomProcessor.java
@@ -17,10 +17,21 @@
 package org.apache.coyote.http11;
 
 import org.apache.coyote.Adapter;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+public class Http11LoomProcessor extends AbstractHttp11Processor {
+
+    private static final Log log = LogFactory.getLog(Http11LoomProcessor.class);
 
-public class Http11LoomProcessor extends Http11Processor {
 
     public Http11LoomProcessor(AbstractHttp11Protocol<?> protocol, Adapter adapter) {
         super(protocol, adapter);
     }
+
+
+    @Override
+    protected Log getLog() {
+        return log;
+    }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org


Re: [tomcat] branch loom created (now 6a22f2f399)

Posted by Christopher Schultz <ch...@christopherschultz.net>.
Mark,

On 10/20/22 05:28, Mark Thomas wrote:
> On 20/10/2022 09:32, Rémy Maucherat wrote:
> 
> <snip/>
> 
>> This is interesting since now is the best time to discuss long term 
>> plans.
>>
>> We'll see if there are some scheduling gains with Loom ... But I don't
>> think thread scheduling was the problem in Tomcat. Maybe lock
>> concurrency but it seems these had been eliminated (or improved at
>> least). I still don't understand how it could possibly really work
>> though, right now it feels like asking if Windows 3 cooperative
>> multitasking could work (= it works as long as all apps and libraries
>> are perfectly coded and there's no bug whatsoever). Also having to
>> avoid native code is annoying (just when there's finally something
>> good like Panama ...).
>>
>> I went through the Jakarta Servlet mailing list and there are some
>> discussions about Loom and a possible lower level API. It seems
>> there's a discussion between blocking and non blocking APIs. Well, IMO
>> we need both ...
>> - Blocking: For Loom. And mostly it could simply be
>> ServletRequest/Response/Cookie/IS/OS without any of the other items.
>> So it looks like everything is in there already.
>> - Async: Great if Loom isn't that appropriate for real work. However,
>> the listeners used for the Servlet API are not a very nice async API
>> (of course they are very appropriate as an extension for a blocking
>> API). So maybe that's where the new work is: come up with an async API
>> ?
>>
>> What do you think ?
> 
> There are a lot of dimensions to this problem space.
> 
> I have an initial implementation of the Loom module that supports the 
> blocking parts of the Servlet API. I am currently working on the 
> necessary refactoring to optimise that for Loom. I hope to get that 
> committed later this week or early next.
> 
> My working assumption is that any benefits Loom can provide will be in 
> the blocking API. I don't see any way Loom could support the async and 
> non-blocking APIs without adding some overhead - even if that overhead 
> is negligible.
> 
> Therefore, rather than moving on to try and implement support for the 
> async and non-blocking parts of the Servlet API with the Loom connector, 
> I'd like to spend some time exploring the performance of Loom with the 
> blocking API. Hopefully, others here will also be able to undertake 
> their own performance tests.
> 
> I have only tested a couple of scenarios that aren't particularly suited 
> to Loom (requests to a simple servlet) and the results are increased CPU 
> usage and reduced throughput with Loom compared to NIO. I want to look 
> at some tests that are more tailored to demonstrate the benefits of Loom.
> 
> My expectation is that we will identify some scenarios where switching 
> to the Loom module provides immediate benefits with no application 
> changes. What will be interesting is how that compares to refactoring 
> the application to use async and/or non-blocking.
> 
> The other question is the extent to which the refactoring I have 
> completed so far is necessary. I am not seeing much benefit from the 
> optimisations I am implementing. It may be that a much simpler approach 
> of integrating Loom (essentially a custom executor) is good enough. Note 
> that this may mean undoing some of the refactoring I am currently 
> implementing in Tomcat 11.
> 
> Once we have that data, then I think we'll be in a position to decide 
> the extent to which it makes sense to support Loom and whether or not to 
> proceed and implement support for async and non-blocking in the Loom 
> module.
> 
> There is clearly a demand for a lower-level HTTP API. The question for 
> me at this point is whether that should be async based or blocking 
> (Loom) based. Based on what I have seen so far, I think Greg Wilkins has 
> called it correctly and that async will have the edge. However, that is 
> more judgement/instinct than based on hard data. I want to see more data 
> before making forming a final view.

Comet FTW?

-chris

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org


Re: [tomcat] branch loom created (now 6a22f2f399)

Posted by Christopher Schultz <ch...@christopherschultz.net>.
Rémy,

On 10/21/22 05:27, Rémy Maucherat wrote:
> On Thu, Oct 20, 2022 at 3:46 PM Rémy Maucherat <re...@apache.org> wrote:
>> Ok, that is reasonable and seeing what can be expected in the best
>> case is a good plan. Async will indeed be a bit more expensive with
>> Loom, just like blocking is more expensive with NIO(2).
> 
> I'm starting testing, and it looks like the basics work, as you said.
> The first item is to identify a best case scenario for Loom. I decided
> to try with a static file (in the context of Tomcat, a low level API
> would be very similar in terms of IO), but maybe this is not it and a
> Servlet doing computations would be better for Loom (with less IO
> load).
> Pulling out my good old ab, I have some rather interesting results.
> -c 1 (no concurrency) is looking awesome for Loom, where it basically
> destroys NIO.
> However, it degrades quickly from there. -c 2 is already a lot closer.
> -c 5 is the inflexion point where NIO pulls ahead. Then as concurrency
> increases Loom becomes slower. Thankfully, it doesn't degrade too much
> from there and with high concurrency it remains reasonably close. I
> don't have a high core count CPU though so this could make for some
> differences as well.
> 
> Overall I would say this matches your finding. I tested with my
> panama-foreign Java "20" build. We should probably wait for the real
> Java 20 to start making decisions.

If the performance of Loom is currently essentially on-par with NIO, my 
expectation is that, over time, it will increase quite a bit. I'm sure 
there is tons of code in the Loom implementation that can be optimized, 
removed, etc. whereas NIO has had a decade of similar optimizations already.

But if we have to have 25% more code to get 5-10% performance 
improvement, I'm not sure it's worth it. (I'm sure our colleagues at AWS 
will disagree!)

-chris

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org


Re: [tomcat] branch loom created (now 6a22f2f399)

Posted by Rémy Maucherat <re...@apache.org>.
On Thu, Oct 20, 2022 at 3:46 PM Rémy Maucherat <re...@apache.org> wrote:
> Ok, that is reasonable and seeing what can be expected in the best
> case is a good plan. Async will indeed be a bit more expensive with
> Loom, just like blocking is more expensive with NIO(2).

I'm starting testing, and it looks like the basics work, as you said.
The first item is to identify a best case scenario for Loom. I decided
to try with a static file (in the context of Tomcat, a low level API
would be very similar in terms of IO), but maybe this is not it and a
Servlet doing computations would be better for Loom (with less IO
load).
Pulling out my good old ab, I have some rather interesting results.
-c 1 (no concurrency) is looking awesome for Loom, where it basically
destroys NIO.
However, it degrades quickly from there. -c 2 is already a lot closer.
-c 5 is the inflexion point where NIO pulls ahead. Then as concurrency
increases Loom becomes slower. Thankfully, it doesn't degrade too much
from there and with high concurrency it remains reasonably close. I
don't have a high core count CPU though so this could make for some
differences as well.

Overall I would say this matches your finding. I tested with my
panama-foreign Java "20" build. We should probably wait for the real
Java 20 to start making decisions.

Rémy

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org


Re: [tomcat] branch loom created (now 6a22f2f399)

Posted by Rémy Maucherat <re...@apache.org>.
On Thu, Oct 20, 2022 at 11:28 AM Mark Thomas <ma...@apache.org> wrote:
>
> On 20/10/2022 09:32, Rémy Maucherat wrote:
>
> <snip/>
>
> > This is interesting since now is the best time to discuss long term plans.
> >
> > We'll see if there are some scheduling gains with Loom ... But I don't
> > think thread scheduling was the problem in Tomcat. Maybe lock
> > concurrency but it seems these had been eliminated (or improved at
> > least). I still don't understand how it could possibly really work
> > though, right now it feels like asking if Windows 3 cooperative
> > multitasking could work (= it works as long as all apps and libraries
> > are perfectly coded and there's no bug whatsoever). Also having to
> > avoid native code is annoying (just when there's finally something
> > good like Panama ...).
> >
> > I went through the Jakarta Servlet mailing list and there are some
> > discussions about Loom and a possible lower level API. It seems
> > there's a discussion between blocking and non blocking APIs. Well, IMO
> > we need both ...
> > - Blocking: For Loom. And mostly it could simply be
> > ServletRequest/Response/Cookie/IS/OS without any of the other items.
> > So it looks like everything is in there already.
> > - Async: Great if Loom isn't that appropriate for real work. However,
> > the listeners used for the Servlet API are not a very nice async API
> > (of course they are very appropriate as an extension for a blocking
> > API). So maybe that's where the new work is: come up with an async API
> > ?
> >
> > What do you think ?
>
> There are a lot of dimensions to this problem space.
>
> I have an initial implementation of the Loom module that supports the
> blocking parts of the Servlet API. I am currently working on the
> necessary refactoring to optimise that for Loom. I hope to get that
> committed later this week or early next.
>
> My working assumption is that any benefits Loom can provide will be in
> the blocking API. I don't see any way Loom could support the async and
> non-blocking APIs without adding some overhead - even if that overhead
> is negligible.
>
> Therefore, rather than moving on to try and implement support for the
> async and non-blocking parts of the Servlet API with the Loom connector,
> I'd like to spend some time exploring the performance of Loom with the
> blocking API. Hopefully, others here will also be able to undertake
> their own performance tests.

Ok, that is reasonable and seeing what can be expected in the best
case is a good plan. Async will indeed be a bit more expensive with
Loom, just like blocking is more expensive with NIO(2).

> I have only tested a couple of scenarios that aren't particularly suited
> to Loom (requests to a simple servlet) and the results are increased CPU
> usage and reduced throughput with Loom compared to NIO. I want to look
> at some tests that are more tailored to demonstrate the benefits of Loom.
>
> My expectation is that we will identify some scenarios where switching
> to the Loom module provides immediate benefits with no application
> changes. What will be interesting is how that compares to refactoring
> the application to use async and/or non-blocking.
>
> The other question is the extent to which the refactoring I have
> completed so far is necessary. I am not seeing much benefit from the
> optimisations I am implementing. It may be that a much simpler approach
> of integrating Loom (essentially a custom executor) is good enough. Note
> that this may mean undoing some of the refactoring I am currently
> implementing in Tomcat 11.
>
> Once we have that data, then I think we'll be in a position to decide
> the extent to which it makes sense to support Loom and whether or not to
> proceed and implement support for async and non-blocking in the Loom module.

+1 then. I hope there will be *something* that works more efficiently at least.

> There is clearly a demand for a lower-level HTTP API. The question for
> me at this point is whether that should be async based or blocking
> (Loom) based. Based on what I have seen so far, I think Greg Wilkins has
> called it correctly and that async will have the edge. However, that is
> more judgement/instinct than based on hard data. I want to see more data
> before making forming a final view.

Ok, let's wait for the results. I'll test it too.

Rémy

> Mark
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
> For additional commands, e-mail: dev-help@tomcat.apache.org
>

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org


Re: [tomcat] branch loom created (now 6a22f2f399)

Posted by Mark Thomas <ma...@apache.org>.
On 20/10/2022 09:32, Rémy Maucherat wrote:

<snip/>

> This is interesting since now is the best time to discuss long term plans.
> 
> We'll see if there are some scheduling gains with Loom ... But I don't
> think thread scheduling was the problem in Tomcat. Maybe lock
> concurrency but it seems these had been eliminated (or improved at
> least). I still don't understand how it could possibly really work
> though, right now it feels like asking if Windows 3 cooperative
> multitasking could work (= it works as long as all apps and libraries
> are perfectly coded and there's no bug whatsoever). Also having to
> avoid native code is annoying (just when there's finally something
> good like Panama ...).
> 
> I went through the Jakarta Servlet mailing list and there are some
> discussions about Loom and a possible lower level API. It seems
> there's a discussion between blocking and non blocking APIs. Well, IMO
> we need both ...
> - Blocking: For Loom. And mostly it could simply be
> ServletRequest/Response/Cookie/IS/OS without any of the other items.
> So it looks like everything is in there already.
> - Async: Great if Loom isn't that appropriate for real work. However,
> the listeners used for the Servlet API are not a very nice async API
> (of course they are very appropriate as an extension for a blocking
> API). So maybe that's where the new work is: come up with an async API
> ?
> 
> What do you think ?

There are a lot of dimensions to this problem space.

I have an initial implementation of the Loom module that supports the 
blocking parts of the Servlet API. I am currently working on the 
necessary refactoring to optimise that for Loom. I hope to get that 
committed later this week or early next.

My working assumption is that any benefits Loom can provide will be in 
the blocking API. I don't see any way Loom could support the async and 
non-blocking APIs without adding some overhead - even if that overhead 
is negligible.

Therefore, rather than moving on to try and implement support for the 
async and non-blocking parts of the Servlet API with the Loom connector, 
I'd like to spend some time exploring the performance of Loom with the 
blocking API. Hopefully, others here will also be able to undertake 
their own performance tests.

I have only tested a couple of scenarios that aren't particularly suited 
to Loom (requests to a simple servlet) and the results are increased CPU 
usage and reduced throughput with Loom compared to NIO. I want to look 
at some tests that are more tailored to demonstrate the benefits of Loom.

My expectation is that we will identify some scenarios where switching 
to the Loom module provides immediate benefits with no application 
changes. What will be interesting is how that compares to refactoring 
the application to use async and/or non-blocking.

The other question is the extent to which the refactoring I have 
completed so far is necessary. I am not seeing much benefit from the 
optimisations I am implementing. It may be that a much simpler approach 
of integrating Loom (essentially a custom executor) is good enough. Note 
that this may mean undoing some of the refactoring I am currently 
implementing in Tomcat 11.

Once we have that data, then I think we'll be in a position to decide 
the extent to which it makes sense to support Loom and whether or not to 
proceed and implement support for async and non-blocking in the Loom module.

There is clearly a demand for a lower-level HTTP API. The question for 
me at this point is whether that should be async based or blocking 
(Loom) based. Based on what I have seen so far, I think Greg Wilkins has 
called it correctly and that async will have the edge. However, that is 
more judgement/instinct than based on hard data. I want to see more data 
before making forming a final view.

Mark

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org


Re: [tomcat] branch loom created (now 6a22f2f399)

Posted by Rémy Maucherat <re...@apache.org>.
Hi Mark,

On Wed, Oct 19, 2022 at 4:41 PM Mark Thomas <ma...@apache.org> wrote:
>
> On 19/10/2022 15:39, markt@apache.org wrote:
> > This is an automated email from the ASF dual-hosted git repository.
> >
> > markt pushed a change to branch loom
> > in repository https://gitbox.apache.org/repos/asf/tomcat.git
>
> Sorry for the noise.
>
> This is a local experimental branch I didn't mean to push.

This is interesting since now is the best time to discuss long term plans.

We'll see if there are some scheduling gains with Loom ... But I don't
think thread scheduling was the problem in Tomcat. Maybe lock
concurrency but it seems these had been eliminated (or improved at
least). I still don't understand how it could possibly really work
though, right now it feels like asking if Windows 3 cooperative
multitasking could work (= it works as long as all apps and libraries
are perfectly coded and there's no bug whatsoever). Also having to
avoid native code is annoying (just when there's finally something
good like Panama ...).

I went through the Jakarta Servlet mailing list and there are some
discussions about Loom and a possible lower level API. It seems
there's a discussion between blocking and non blocking APIs. Well, IMO
we need both ...
- Blocking: For Loom. And mostly it could simply be
ServletRequest/Response/Cookie/IS/OS without any of the other items.
So it looks like everything is in there already.
- Async: Great if Loom isn't that appropriate for real work. However,
the listeners used for the Servlet API are not a very nice async API
(of course they are very appropriate as an extension for a blocking
API). So maybe that's where the new work is: come up with an async API
?

What do you think ?

Rémy

> Mark
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
> For additional commands, e-mail: dev-help@tomcat.apache.org
>

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org


Re: [tomcat] branch loom created (now 6a22f2f399)

Posted by Mark Thomas <ma...@apache.org>.
On 19/10/2022 15:39, markt@apache.org wrote:
> This is an automated email from the ASF dual-hosted git repository.
> 
> markt pushed a change to branch loom
> in repository https://gitbox.apache.org/repos/asf/tomcat.git

Sorry for the noise.

This is a local experimental branch I didn't mean to push.

Mark

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org


[tomcat] 03/03: Use correct class

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

markt pushed a commit to branch loom
in repository https://gitbox.apache.org/repos/asf/tomcat.git

commit 6a22f2f399bfe42c0627e537bc9ce612e6ea3fdb
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Wed Oct 19 15:39:06 2022 +0100

    Use correct class
---
 java/org/apache/coyote/http11/AbstractHttp11InputBuffer.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/java/org/apache/coyote/http11/AbstractHttp11InputBuffer.java b/java/org/apache/coyote/http11/AbstractHttp11InputBuffer.java
index 8c9f53e162..a18bd09007 100644
--- a/java/org/apache/coyote/http11/AbstractHttp11InputBuffer.java
+++ b/java/org/apache/coyote/http11/AbstractHttp11InputBuffer.java
@@ -45,7 +45,7 @@ public abstract class AbstractHttp11InputBuffer implements InputBuffer, Applicat
     /**
      * The string manager for this package.
      */
-    private static final StringManager sm = StringManager.getManager(Http11InputBuffer.class);
+    private static final StringManager sm = StringManager.getManager(AbstractHttp11InputBuffer.class);
 
 
     private static final byte[] CLIENT_PREFACE_START =


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org