org.apache.coyote.Request - java examples

Here are the examples of the java api org.apache.coyote.Request taken from open source projects. By voting up you can indicate which examples are most useful and appropriate.

54 Examples 7

19 View Complete Implementation : InputBuffer.java
Copyright Apache License 2.0
Author : how2j
// ------------------------------------------------------------- Properties
/**
 * replacedociated Coyote request.
 *
 * @param coyoteRequest
 *            replacedociated Coyote request
 */
public void setRequest(Request coyoteRequest) {
    this.coyoteRequest = coyoteRequest;
}

19 View Complete Implementation : InputBuffer.java
Copyright Apache License 2.0
Author : apache
// ------------------------------------------------------------- Properties
/**
 * replacedociated Coyote request.
 *
 * @param coyoteRequest replacedociated Coyote request
 */
public void setRequest(Request coyoteRequest) {
    this.coyoteRequest = coyoteRequest;
}

18 View Complete Implementation : VoidInputFilter.java
Copyright Apache License 2.0
Author : how2j
// --------------------------------------------------- OutputFilter Methods
/**
 * Set the replacedociated request.
 */
@Override
public void setRequest(Request request) {
// NOOP: Request isn't used so ignore it
}

18 View Complete Implementation : VoidInputFilter.java
Copyright Apache License 2.0
Author : apache
// ---------------------------------------------------- InputFilter Methods
/**
 * Set the replacedociated request.
 */
@Override
public void setRequest(Request request) {
// NOOP: Request isn't used so ignore it
}

18 View Complete Implementation : StreamProcessor.java
Copyright Apache License 2.0
Author : apache
// Static so it can be used by Stream to build the MimeHeaders required for
// an ACK. For that use case coyoteRequest, protocol and stream will be null.
static void prepareHeaders(Request coyoteRequest, Response coyoteResponse, boolean noSendfile, Http2Protocol protocol, Stream stream) {
    MimeHeaders headers = coyoteResponse.getMimeHeaders();
    int statusCode = coyoteResponse.getStatus();
    // Add the pseudo header for status
    headers.addValue(":status").setString(Integer.toString(statusCode));
    // Check to see if a response body is present
    if (!(statusCode < 200 || statusCode == 204 || statusCode == 205 || statusCode == 304)) {
        String contentType = coyoteResponse.getContentType();
        if (contentType != null) {
            headers.setValue("content-type").setString(contentType);
        }
        String contentLanguage = coyoteResponse.getContentLanguage();
        if (contentLanguage != null) {
            headers.setValue("content-language").setString(contentLanguage);
        }
        // Add a content-length header if a content length has been set unless
        // the application has already added one
        long contentLength = coyoteResponse.getContentLengthLong();
        if (contentLength != -1 && headers.getValue("content-length") == null) {
            headers.addValue("content-length").setLong(contentLength);
        }
    } else {
        if (statusCode == 205) {
            // RFC 7231 requires the server to explicitly signal an empty
            // response in this case
            coyoteResponse.setContentLength(0);
        } else {
            coyoteResponse.setContentLength(-1);
        }
    }
    // Add date header unless it is an informational response or the
    // application has already set one
    if (statusCode >= 200 && headers.getValue("date") == null) {
        headers.addValue("date").setString(FastHttpDateFormat.getCurrentDate());
    }
    // Compression can't be used with sendfile
    if (noSendfile && protocol != null && protocol.useCompression(coyoteRequest, coyoteResponse)) {
        // Enable compression. Headers will have been set. Need to configure
        // output filter at this point.
        stream.addOutputFilter(new GzipOutputFilter());
    }
}

18 View Complete Implementation : ChunkedInputFilter.java
Copyright Apache License 2.0
Author : codefollower
/**
 * Chunked input filter. Parses chunked data according to
 * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1">http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1</a><br>
 *
 * @author Remy Maucherat
 * @author Filip Hanik
 */
public clreplaced ChunkedInputFilter implements InputFilter {

    // -------------------------------------------------------------- Constants
    protected static final String ENCODING_NAME = "chunked";

    protected static final ByteChunk ENCODING = new ByteChunk();

    // ----------------------------------------------------- Static Initializer
    static {
        ENCODING.setBytes(ENCODING_NAME.getBytes(StandardCharsets.ISO_8859_1), 0, ENCODING_NAME.length());
    }

    // ----------------------------------------------------- Instance Variables
    /**
     * Next buffer in the pipeline.
     */
    protected InputBuffer buffer;

    /**
     * Number of bytes remaining in the current chunk.
     */
    protected int remaining = 0;

    /**
     * Position in the buffer.
     */
    protected int pos = 0;

    /**
     * Last valid byte in the buffer.
     */
    protected int lastValid = 0;

    /**
     * Read bytes buffer.
     */
    protected byte[] buf = null;

    /**
     * Byte chunk used to read bytes.
     */
    protected final ByteChunk readChunk = new ByteChunk();

    /**
     * Flag set to true when the end chunk has been read.
     */
    protected boolean endChunk = false;

    /**
     * Byte chunk used to store trailing headers.
     */
    protected final ByteChunk trailingHeaders = new ByteChunk();

    /**
     * Flag set to true if the next call to doRead() must parse a CRLF pair
     * before doing anything else.
     */
    protected boolean needCRLFParse = false;

    /**
     * Request being parsed.
     */
    private Request request;

    /**
     * Limit for extension size.
     */
    private final long maxExtensionSize;

    /**
     * Limit for trailer size.
     */
    private final int maxTrailerSize;

    /**
     * Size of extensions processed for this request.
     */
    private long extensionSize;

    // ----------------------------------------------------------- Constructors
    public ChunkedInputFilter(int maxTrailerSize, int maxExtensionSize) {
        this.trailingHeaders.setLimit(maxTrailerSize);
        this.maxExtensionSize = maxExtensionSize;
        this.maxTrailerSize = maxTrailerSize;
    }

    // ---------------------------------------------------- InputBuffer Methods
    /**
     * Read bytes.
     *
     * @return If the filter does request length control, this value is
     * significant; it should be the number of bytes consumed from the buffer,
     * up until the end of the current request body, or the buffer length,
     * whichever is greater. If the filter does not do request body length
     * control, the returned value should be -1.
     */
    @Override
    public int doRead(ByteChunk chunk, Request req) throws IOException {
        if (endChunk)
            return -1;
        if (needCRLFParse) {
            needCRLFParse = false;
            parseCRLF(false);
        }
        if (remaining <= 0) {
            if (!parseChunkHeader()) {
                throw new IOException("Invalid chunk header");
            }
            if (endChunk) {
                parseEndChunk();
                return -1;
            }
        }
        int result = 0;
        if (pos >= lastValid) {
            if (readBytes() < 0) {
                throw new IOException("Unexpected end of stream whilst reading request body");
            }
        }
        if (remaining > (lastValid - pos)) {
            result = lastValid - pos;
            remaining = remaining - result;
            chunk.setBytes(buf, pos, result);
            pos = lastValid;
        } else {
            result = remaining;
            chunk.setBytes(buf, pos, remaining);
            pos = pos + remaining;
            remaining = 0;
            // we need a CRLF
            if ((pos + 1) >= lastValid) {
                // if we call parseCRLF we overrun the buffer here
                // so we defer it to the next call BZ 11117
                needCRLFParse = true;
            } else {
                // parse the CRLF immediately
                parseCRLF(false);
            }
        }
        return result;
    }

    // ---------------------------------------------------- InputFilter Methods
    /**
     * Read the content length from the request.
     */
    @Override
    public void setRequest(Request request) {
        this.request = request;
    }

    /**
     * End the current request.
     */
    @Override
    public long end() throws IOException {
        // Consume extra bytes : parse the stream until the end chunk is found
        while (doRead(readChunk, null) >= 0) {
        // NOOP: Just consume the input
        }
        // Return the number of extra bytes which were consumed
        return (lastValid - pos);
    }

    /**
     * Amount of bytes still available in a buffer.
     */
    @Override
    public int available() {
        return (lastValid - pos);
    }

    /**
     * Set the next buffer in the filter pipeline.
     */
    @Override
    public void setBuffer(InputBuffer buffer) {
        this.buffer = buffer;
    }

    /**
     * Make the filter ready to process the next request.
     */
    @Override
    public void recycle() {
        remaining = 0;
        pos = 0;
        lastValid = 0;
        endChunk = false;
        needCRLFParse = false;
        trailingHeaders.recycle();
        trailingHeaders.setLimit(maxTrailerSize);
        extensionSize = 0;
    }

    /**
     * Return the name of the replacedociated encoding; Here, the value is
     * "idenreplacedy".
     */
    @Override
    public ByteChunk getEncodingName() {
        return ENCODING;
    }

    // ------------------------------------------------------ Protected Methods
    /**
     * Read bytes from the previous buffer.
     */
    protected int readBytes() throws IOException {
        int nRead = buffer.doRead(readChunk, null);
        pos = readChunk.getStart();
        lastValid = pos + nRead;
        buf = readChunk.getBytes();
        return nRead;
    }

    /**
     * Parse the header of a chunk.
     * A chunk header can look like one of the following:<br />
     * A10CRLF<br />
     * F23;chunk-extension to be ignoredCRLF
     *
     * <p>
     * The letters before CRLF or ';' (whatever comes first) must be valid hex
     * digits. We should not parse F23IAMGONNAMESSTHISUP34CRLF as a valid
     * header according to the spec.
     */
    protected boolean parseChunkHeader() throws IOException {
        int result = 0;
        boolean eol = false;
        boolean readDigit = false;
        boolean extension = false;
        while (!eol) {
            if (pos >= lastValid) {
                if (readBytes() <= 0)
                    return false;
            }
            if (buf[pos] == Constants.CR || buf[pos] == Constants.LF) {
                parseCRLF(false);
                eol = true;
            } else if (buf[pos] == Constants.SEMI_COLON && !extension) {
                // First semi-colon marks the start of the extension. Further
                // semi-colons may appear to separate multiple chunk-extensions.
                // These need to be processed as part of parsing the extensions.
                extension = true;
                extensionSize++;
            } else if (!extension) {
                // don't read data after the trailer
                int charValue = HexUtils.getDec(buf[pos]);
                if (charValue != -1) {
                    readDigit = true;
                    result *= 16;
                    result += charValue;
                } else {
                    // we shouldn't allow invalid, non hex characters
                    // in the chunked header
                    return false;
                }
            } else {
                // Extension 'parsing'
                // Note that the chunk-extension is neither parsed nor
                // validated. Currently it is simply ignored.
                extensionSize++;
                if (maxExtensionSize > -1 && extensionSize > maxExtensionSize) {
                    throw new IOException("maxExtensionSize exceeded");
                }
            }
            // Parsing the CRLF increments pos
            if (!eol) {
                pos++;
            }
        }
        if (!readDigit)
            return false;
        if (result == 0)
            endChunk = true;
        remaining = result;
        if (remaining < 0)
            return false;
        return true;
    }

    /**
     * Parse CRLF at end of chunk.
     *
     * @param   tolerant    Should tolerant parsing (LF and CRLF) be used? This
     *                      is recommended (RFC2616, section 19.3) for message
     *                      headers.
     */
    protected void parseCRLF(boolean tolerant) throws IOException {
        boolean eol = false;
        boolean crfound = false;
        while (!eol) {
            if (pos >= lastValid) {
                if (readBytes() <= 0)
                    throw new IOException("Invalid CRLF");
            }
            if (buf[pos] == Constants.CR) {
                if (crfound)
                    throw new IOException("Invalid CRLF, two CR characters encountered.");
                crfound = true;
            } else if (buf[pos] == Constants.LF) {
                if (!tolerant && !crfound) {
                    throw new IOException("Invalid CRLF, no CR character encountered.");
                }
                eol = true;
            } else {
                throw new IOException("Invalid CRLF");
            }
            pos++;
        }
    }

    /**
     * Parse end chunk data.
     */
    protected void parseEndChunk() throws IOException {
        // Handle optional trailer headers
        while (parseHeader()) {
        // Loop until we run out of headers
        }
    }

    private boolean parseHeader() throws IOException {
        MimeHeaders headers = request.getMimeHeaders();
        byte chr = 0;
        // Read new bytes if needed
        if (pos >= lastValid) {
            if (readBytes() < 0)
                throw new EOFException("Unexpected end of stream whilst reading trailer headers for chunked request");
        }
        chr = buf[pos];
        // CRLF terminates the request
        if (chr == Constants.CR || chr == Constants.LF) {
            parseCRLF(false);
            return false;
        }
        // Mark the current buffer position
        int start = trailingHeaders.getEnd();
        // 
        // Reading the header name
        // Header name is always US-ASCII
        // 
        boolean colon = false;
        while (!colon) {
            // Read new bytes if needed
            if (pos >= lastValid) {
                if (readBytes() < 0)
                    throw new EOFException("Unexpected end of stream whilst reading trailer headers for chunked request");
            }
            chr = buf[pos];
            if ((chr >= Constants.A) && (chr <= Constants.Z)) {
                chr = (byte) (chr - Constants.LC_OFFSET);
            }
            if (chr == Constants.COLON) {
                colon = true;
            } else {
                trailingHeaders.append(chr);
            }
            pos++;
        }
        MessageBytes headerValue = headers.addValue(trailingHeaders.getBytes(), start, trailingHeaders.getEnd() - start);
        // Mark the current buffer position
        start = trailingHeaders.getEnd();
        // 
        // Reading the header value (which can be spanned over multiple lines)
        // 
        boolean eol = false;
        boolean validLine = true;
        int lastSignificantChar = 0;
        while (validLine) {
            boolean space = true;
            // Skipping spaces
            while (space) {
                // Read new bytes if needed
                if (pos >= lastValid) {
                    if (readBytes() < 0)
                        throw new EOFException("Unexpected end of stream whilst reading trailer headers for chunked request");
                }
                chr = buf[pos];
                if ((chr == Constants.SP) || (chr == Constants.HT)) {
                    pos++;
                    // If we swallow whitespace, make sure it counts towards the
                    // limit placed on trailing header size
                    int newlimit = trailingHeaders.getLimit() - 1;
                    if (trailingHeaders.getEnd() > newlimit) {
                        throw new IOException("Exceeded maxTrailerSize");
                    }
                    trailingHeaders.setLimit(newlimit);
                } else {
                    space = false;
                }
            }
            // Reading bytes until the end of the line
            while (!eol) {
                // Read new bytes if needed
                if (pos >= lastValid) {
                    if (readBytes() < 0)
                        throw new EOFException("Unexpected end of stream whilst reading trailer headers for chunked request");
                }
                chr = buf[pos];
                if (chr == Constants.CR || chr == Constants.LF) {
                    parseCRLF(true);
                    eol = true;
                } else if (chr == Constants.SP) {
                    trailingHeaders.append(chr);
                } else {
                    trailingHeaders.append(chr);
                    lastSignificantChar = trailingHeaders.getEnd();
                }
                if (!eol) {
                    pos++;
                }
            }
            // Checking the first character of the new line. If the character
            // is a LWS, then it's a multiline header
            // Read new bytes if needed
            if (pos >= lastValid) {
                if (readBytes() < 0)
                    throw new EOFException("Unexpected end of stream whilst reading trailer headers for chunked request");
            }
            chr = buf[pos];
            if ((chr != Constants.SP) && (chr != Constants.HT)) {
                validLine = false;
            } else {
                eol = false;
                // Copying one extra space in the buffer (since there must
                // be at least one space inserted between the lines)
                trailingHeaders.append(chr);
            }
        }
        // Set the header value
        headerValue.setBytes(trailingHeaders.getBytes(), start, lastSignificantChar - start);
        return true;
    }

    @Override
    public boolean isFinished() {
        return endChunk;
    }
}

18 View Complete Implementation : IdentityInputFilter.java
Copyright Apache License 2.0
Author : apache
// ---------------------------------------------------- InputFilter Methods
/**
 * Read the content length from the request.
 */
@Override
public void setRequest(Request request) {
    contentLength = request.getContentLengthLong();
    remaining = contentLength;
}

18 View Complete Implementation : Stream.java
Copyright Apache License 2.0
Author : apache
final void push(Request request) throws IOException {
    // Can only push when supported and from a peer initiated stream
    if (!isPushSupported() || getIdAsInt() % 2 == 0) {
        return;
    }
    // Set the special HTTP/2 headers
    request.getMimeHeaders().addValue(":method").duplicate(request.method());
    request.getMimeHeaders().addValue(":scheme").duplicate(request.scheme());
    StringBuilder path = new StringBuilder(request.requestURI().toString());
    if (!request.queryString().isNull()) {
        path.append('?');
        path.append(request.queryString().toString());
    }
    request.getMimeHeaders().addValue(":path").setString(path.toString());
    // Authority needs to include the port only if a non-standard port is
    // being used.
    if (!(request.scheme().equals("http") && request.getServerPort() == 80) && !(request.scheme().equals("https") && request.getServerPort() == 443)) {
        request.getMimeHeaders().addValue(":authority").setString(request.serverName().getString() + ":" + request.getServerPort());
    } else {
        request.getMimeHeaders().addValue(":authority").duplicate(request.serverName());
    }
    push(handler, request, this);
}

18 View Complete Implementation : ChunkedInputFilter.java
Copyright Apache License 2.0
Author : apache
// ---------------------------------------------------- InputFilter Methods
/**
 * Read the content length from the request.
 */
@Override
public void setRequest(Request request) {
    this.request = request;
}

18 View Complete Implementation : AbstractInputBuffer.java
Copyright Apache License 2.0
Author : apache
// ---------------------------------------------------- InputBuffer Methods
/**
 * Read some bytes.
 */
@Override
public int doRead(ByteChunk chunk, Request req) throws IOException {
    if (lastActiveFilter == -1)
        return inputStreamInputBuffer.doRead(chunk, req);
    else
        return activeFilters[lastActiveFilter].doRead(chunk, req);
}

18 View Complete Implementation : BufferedInputFilter.java
Copyright Apache License 2.0
Author : apache
// ---------------------------------------------------- InputBuffer Methods
/**
 * Reads the request body and buffers it.
 */
@Override
public void setRequest(Request request) {
    // save off the Request body
    try {
        while (buffer.doRead(tempRead, request) >= 0) {
            buffered.append(tempRead);
            tempRead.recycle();
        }
    } catch (IOException ioe) {
        // No need for i18n - this isn't going to get logged anywhere
        throw new IllegalStateException("Request body too large for buffer");
    }
}

18 View Complete Implementation : VoidInputFilter.java
Copyright Apache License 2.0
Author : apache
// ---------------------------------------------------- InputBuffer Methods
/**
 * Write some bytes.
 *
 * @return number of bytes written by the filter
 */
@Override
public int doRead(ByteChunk chunk, Request req) throws IOException {
    return -1;
}

18 View Complete Implementation : VoidInputFilter.java
Copyright Apache License 2.0
Author : codefollower
// ---------------------------------------------------- InputBuffer Methods
/**
 * Write some bytes.
 *
 * @return number of bytes written by the filter
 */
@Override
public int doRead(ByteChunk chunk, Request req) throws IOException {
    read = true;
    return -1;
}

18 View Complete Implementation : StreamProcessor.java
Copyright Apache License 2.0
Author : apache
@Override
protected final void doPush(Request pushTarget) {
    try {
        stream.push(pushTarget);
    } catch (IOException ioe) {
        setErrorState(ErrorState.CLOSE_CONNECTION_NOW, ioe);
        response.setErrorException(ioe);
    }
}

18 View Complete Implementation : Stream.java
Copyright Apache License 2.0
Author : apache
private static void push(final Http2UpgradeHandler handler, final Request request, final Stream stream) throws IOException {
    if (org.apache.coyote.Constants.IS_SECURITY_ENABLED) {
        try {
            AccessController.doPrivileged(new PrivilegedPush(handler, request, stream));
        } catch (PrivilegedActionException ex) {
            Exception e = ex.getException();
            if (e instanceof IOException) {
                throw (IOException) e;
            } else {
                throw new IOException(ex);
            }
        }
    } else {
        handler.push(request, stream);
    }
}

18 View Complete Implementation : StreamProcessor.java
Copyright MIT License
Author : chenmudu
// Static so it can be used by Stream to build the MimeHeaders required for
// an ACK. For that use case coyoteRequest, protocol and stream will be null.
static void prepareHeaders(Request coyoteRequest, Response coyoteResponse, Http2Protocol protocol, Stream stream) {
    MimeHeaders headers = coyoteResponse.getMimeHeaders();
    int statusCode = coyoteResponse.getStatus();
    // Add the pseudo header for status
    headers.addValue(":status").setString(Integer.toString(statusCode));
    // Check to see if a response body is present
    if (!(statusCode < 200 || statusCode == 204 || statusCode == 205 || statusCode == 304)) {
        String contentType = coyoteResponse.getContentType();
        if (contentType != null) {
            headers.setValue("content-type").setString(contentType);
        }
        String contentLanguage = coyoteResponse.getContentLanguage();
        if (contentLanguage != null) {
            headers.setValue("content-language").setString(contentLanguage);
        }
        // Add a content-length header if a content length has been set unless
        // the application has already added one
        long contentLength = coyoteResponse.getContentLengthLong();
        if (contentLength != -1 && headers.getValue("content-length") == null) {
            headers.addValue("content-length").setLong(contentLength);
        }
    } else {
        if (statusCode == 205) {
            // RFC 7231 requires the server to explicitly signal an empty
            // response in this case
            coyoteResponse.setContentLength(0);
        } else {
            coyoteResponse.setContentLength(-1);
        }
    }
    // Add date header unless it is an informational response or the
    // application has already set one
    if (statusCode >= 200 && headers.getValue("date") == null) {
        headers.addValue("date").setString(FastHttpDateFormat.getCurrentDate());
    }
    if (protocol != null && protocol.useCompression(coyoteRequest, coyoteResponse)) {
        // Enable compression. Headers will have been set. Need to configure
        // output filter at this point.
        stream.addOutputFilter(new GzipOutputFilter());
    }
}

18 View Complete Implementation : Http2Protocol.java
Copyright Apache License 2.0
Author : apache
@Override
public InternalHttpUpgradeHandler getInternalUpgradeHandler(SocketWrapperBase<?> socketWrapper, Adapter adapter, Request coyoteRequest) {
    return socketWrapper.hasAsyncIO() ? new Http2AsyncUpgradeHandler(this, adapter, coyoteRequest) : new Http2UpgradeHandler(this, adapter, coyoteRequest);
}

18 View Complete Implementation : VoidInputFilter.java
Copyright Apache License 2.0
Author : how2j
// ----------------------------------------------------- Instance Variables
// --------------------------------------------------- OutputBuffer Methods
/**
 * Write some bytes.
 *
 * @return number of bytes written by the filter
 */
@Override
public int doRead(ByteChunk chunk, Request req) throws IOException {
    return -1;
}

18 View Complete Implementation : AbstractHttp11Protocol.java
Copyright Apache License 2.0
Author : apache
public boolean useCompression(Request request, Response response) {
    return compressionConfig.useCompression(request, response);
}

18 View Complete Implementation : Http2Protocol.java
Copyright MIT License
Author : chenmudu
@Override
public InternalHttpUpgradeHandler getInternalUpgradeHandler(Adapter adapter, Request coyoteRequest) {
    Http2UpgradeHandler result = new Http2UpgradeHandler(this, adapter, coyoteRequest);
    result.setReadTimeout(getReadTimeout());
    result.setKeepAliveTimeout(getKeepAliveTimeout());
    result.setWriteTimeout(getWriteTimeout());
    result.setMaxConcurrentStreams(getMaxConcurrentStreams());
    result.setMaxConcurrentStreamExecution(getMaxConcurrentStreamExecution());
    result.setInitialWindowSize(getInitialWindowSize());
    result.setAllowedTrailerHeaders(allowedTrailerHeaders);
    result.setMaxHeaderCount(getMaxHeaderCount());
    result.setMaxHeaderSize(getMaxHeaderSize());
    result.setMaxTrailerCount(getMaxTrailerCount());
    result.setMaxTrailerSize(getMaxTrailerSize());
    result.setInitiatePingDisabled(initiatePingDisabled);
    return result;
}

17 View Complete Implementation : InputBuffer.java
Copyright MIT License
Author : chenmudu
/**
 * The buffer used by Tomcat request. This is a derivative of the Tomcat 3.3
 * OutputBuffer, adapted to handle input instead of output. This allows
 * complete recycling of the facade objects (the ServletInputStream and the
 * BufferedReader).
 *
 * @author Remy Maucherat
 */
public clreplaced InputBuffer extends Reader implements ByteChunk.ByteInputChannel, ApplicationBufferHandler {

    /**
     * The string manager for this package.
     */
    protected static final StringManager sm = StringManager.getManager(InputBuffer.clreplaced);

    private static final Log log = LogFactory.getLog(InputBuffer.clreplaced);

    public static final int DEFAULT_BUFFER_SIZE = 8 * 1024;

    // The buffer can be used for byte[] and char[] reading
    // ( this is needed to support ServletInputStream and BufferedReader )
    public final int INITIAL_STATE = 0;

    public final int CHAR_STATE = 1;

    public final int BYTE_STATE = 2;

    /**
     * Encoder cache.
     */
    private static final ConcurrentMap<Charset, SynchronizedStack<B2CConverter>> encoders = new ConcurrentHashMap<>();

    // ----------------------------------------------------- Instance Variables
    /**
     * The byte buffer.
     */
    private ByteBuffer bb;

    /**
     * The char buffer.
     */
    private CharBuffer cb;

    /**
     * State of the output buffer.
     */
    private int state = 0;

    /**
     * Flag which indicates if the input buffer is closed.
     */
    private boolean closed = false;

    /**
     * Encoding to use.
     */
    private String enc;

    /**
     * Current byte to char converter.
     */
    protected B2CConverter conv;

    /**
     * replacedociated Coyote request.
     */
    private Request coyoteRequest;

    /**
     * Buffer position.
     */
    private int markPos = -1;

    /**
     * Char buffer limit.
     */
    private int readLimit;

    /**
     * Buffer size.
     */
    private final int size;

    // ----------------------------------------------------------- Constructors
    /**
     * Default constructor. Allocate the buffer with the default buffer size.
     */
    public InputBuffer() {
        this(DEFAULT_BUFFER_SIZE);
    }

    /**
     * Alternate constructor which allows specifying the initial buffer size.
     *
     * @param size Buffer size to use
     */
    public InputBuffer(int size) {
        this.size = size;
        bb = ByteBuffer.allocate(size);
        clear(bb);
        cb = CharBuffer.allocate(size);
        clear(cb);
        readLimit = size;
    }

    // ------------------------------------------------------------- Properties
    /**
     * replacedociated Coyote request.
     *
     * @param coyoteRequest replacedociated Coyote request
     */
    public void setRequest(Request coyoteRequest) {
        this.coyoteRequest = coyoteRequest;
    }

    // --------------------------------------------------------- Public Methods
    /**
     * Recycle the output buffer.
     */
    public void recycle() {
        state = INITIAL_STATE;
        // If usage of mark made the buffer too big, reallocate it
        if (cb.capacity() > size) {
            cb = CharBuffer.allocate(size);
            clear(cb);
        } else {
            clear(cb);
        }
        readLimit = size;
        markPos = -1;
        clear(bb);
        closed = false;
        if (conv != null) {
            conv.recycle();
            encoders.get(conv.getCharset()).push(conv);
            conv = null;
        }
        enc = null;
    }

    /**
     * Close the input buffer.
     *
     * @throws IOException An underlying IOException occurred
     */
    @Override
    public void close() throws IOException {
        closed = true;
    }

    public int available() {
        int available = availableInThisBuffer();
        if (available == 0) {
            coyoteRequest.action(ActionCode.AVAILABLE, Boolean.valueOf(coyoteRequest.getReadListener() != null));
            available = (coyoteRequest.getAvailable() > 0) ? 1 : 0;
        }
        return available;
    }

    private int availableInThisBuffer() {
        int available = 0;
        if (state == BYTE_STATE) {
            available = bb.remaining();
        } else if (state == CHAR_STATE) {
            available = cb.remaining();
        }
        return available;
    }

    public void setReadListener(ReadListener listener) {
        coyoteRequest.setReadListener(listener);
        // The container is responsible for the first call to
        // listener.onDataAvailable(). If isReady() returns true, the container
        // needs to call listener.onDataAvailable() from a new thread. If
        // isReady() returns false, the socket will be registered for read and
        // the container will call listener.onDataAvailable() once data arrives.
        // Must call isFinished() first as a call to isReady() if the request
        // has been finished will register the socket for read interest and that
        // is not required.
        if (!coyoteRequest.isFinished() && isReady()) {
            coyoteRequest.action(ActionCode.DISPATCH_READ, null);
            if (!ContainerThreadMarker.isContainerThread()) {
                // Not on a container thread so need to execute the dispatch
                coyoteRequest.action(ActionCode.DISPATCH_EXECUTE, null);
            }
        }
    }

    public boolean isFinished() {
        int available = 0;
        if (state == BYTE_STATE) {
            available = bb.remaining();
        } else if (state == CHAR_STATE) {
            available = cb.remaining();
        }
        if (available > 0) {
            return false;
        } else {
            return coyoteRequest.isFinished();
        }
    }

    public boolean isReady() {
        if (coyoteRequest.getReadListener() == null) {
            if (log.isDebugEnabled()) {
                log.debug(sm.getString("inputBuffer.requiresNonBlocking"));
            }
            return false;
        }
        if (isFinished()) {
            // If this is a non-container thread, need to trigger a read
            // which will eventually lead to a call to onAllDataRead() via a
            // container thread.
            if (!ContainerThreadMarker.isContainerThread()) {
                coyoteRequest.action(ActionCode.DISPATCH_READ, null);
                coyoteRequest.action(ActionCode.DISPATCH_EXECUTE, null);
            }
            return false;
        }
        // Checking for available data at the network level and registering for
        // read can be done sequentially for HTTP/1.x and AJP as there is only
        // ever a single thread processing the socket at any one time. However,
        // for HTTP/2 there is one thread processing the connection and separate
        // threads for each stream. For HTTP/2 the two operations have to be
        // performed atomically else it is possible for the connection thread to
        // read more data in to the buffer after the stream thread checks for
        // available network data but before it registers for read.
        if (availableInThisBuffer() > 0) {
            return true;
        }
        AtomicBoolean result = new AtomicBoolean();
        coyoteRequest.action(ActionCode.NB_READ_INTEREST, result);
        return result.get();
    }

    boolean isBlocking() {
        return coyoteRequest.getReadListener() == null;
    }

    // ------------------------------------------------- Bytes Handling Methods
    /**
     * Reads new bytes in the byte chunk.
     *
     * @throws IOException An underlying IOException occurred
     */
    @Override
    public int realReadBytes() throws IOException {
        if (closed) {
            return -1;
        }
        if (coyoteRequest == null) {
            return -1;
        }
        if (state == INITIAL_STATE) {
            state = BYTE_STATE;
        }
        try {
            return coyoteRequest.doRead(this);
        } catch (IOException ioe) {
            // An IOException on a read is almost always due to
            // the remote client aborting the request.
            throw new ClientAbortException(ioe);
        }
    }

    public int readByte() throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        if (checkByteBufferEof()) {
            return -1;
        }
        return bb.get() & 0xFF;
    }

    public int read(byte[] b, int off, int len) throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        if (checkByteBufferEof()) {
            return -1;
        }
        int n = Math.min(len, bb.remaining());
        bb.get(b, off, n);
        return n;
    }

    /**
     * Transfers bytes from the buffer to the specified ByteBuffer. After the
     * operation the position of the ByteBuffer will be returned to the one
     * before the operation, the limit will be the position incremented by
     * the number of the transferred bytes.
     *
     * @param to the ByteBuffer into which bytes are to be written.
     * @return an integer specifying the actual number of bytes read, or -1 if
     *         the end of the stream is reached
     * @throws IOException if an input or output exception has occurred
     */
    public int read(ByteBuffer to) throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        if (checkByteBufferEof()) {
            return -1;
        }
        int n = Math.min(to.remaining(), bb.remaining());
        int orgLimit = bb.limit();
        bb.limit(bb.position() + n);
        to.put(bb);
        bb.limit(orgLimit);
        to.limit(to.position()).position(to.position() - n);
        return n;
    }

    // ------------------------------------------------- Chars Handling Methods
    /**
     * @param s     New encoding value
     *
     * @deprecated This method will be removed in Tomcat 9.0.x
     */
    @Deprecated
    public void setEncoding(String s) {
        enc = s;
    }

    public int realReadChars() throws IOException {
        checkConverter();
        boolean eof = false;
        if (bb.remaining() <= 0) {
            int nRead = realReadBytes();
            if (nRead < 0) {
                eof = true;
            }
        }
        if (markPos == -1) {
            clear(cb);
        } else {
            // Make sure there's enough space in the worst case
            makeSpace(bb.remaining());
            if ((cb.capacity() - cb.limit()) == 0 && bb.remaining() != 0) {
                // We went over the limit
                clear(cb);
                markPos = -1;
            }
        }
        state = CHAR_STATE;
        conv.convert(bb, cb, this, eof);
        if (cb.remaining() == 0 && eof) {
            return -1;
        } else {
            return cb.remaining();
        }
    }

    @Override
    public int read() throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        if (checkCharBufferEof()) {
            return -1;
        }
        return cb.get();
    }

    @Override
    public int read(char[] cbuf) throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        return read(cbuf, 0, cbuf.length);
    }

    @Override
    public int read(char[] cbuf, int off, int len) throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        if (checkCharBufferEof()) {
            return -1;
        }
        int n = Math.min(len, cb.remaining());
        cb.get(cbuf, off, n);
        return n;
    }

    @Override
    public long skip(long n) throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        if (n < 0) {
            throw new IllegalArgumentException();
        }
        long nRead = 0;
        while (nRead < n) {
            if (cb.remaining() >= n) {
                cb.position(cb.position() + (int) n);
                nRead = n;
            } else {
                nRead += cb.remaining();
                cb.position(cb.limit());
                int nb = realReadChars();
                if (nb < 0) {
                    break;
                }
            }
        }
        return nRead;
    }

    @Override
    public boolean ready() throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        if (state == INITIAL_STATE) {
            state = CHAR_STATE;
        }
        return (available() > 0);
    }

    @Override
    public boolean markSupported() {
        return true;
    }

    @Override
    public void mark(int readAheadLimit) throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        if (cb.remaining() <= 0) {
            clear(cb);
        } else {
            if ((cb.capacity() > (2 * size)) && (cb.remaining()) < (cb.position())) {
                cb.compact();
                cb.flip();
            }
        }
        readLimit = cb.position() + readAheadLimit + size;
        markPos = cb.position();
    }

    @Override
    public void reset() throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        if (state == CHAR_STATE) {
            if (markPos < 0) {
                clear(cb);
                markPos = -1;
                throw new IOException();
            } else {
                cb.position(markPos);
            }
        } else {
            clear(bb);
        }
    }

    public void checkConverter() throws IOException {
        if (conv != null) {
            return;
        }
        Charset charset = null;
        if (coyoteRequest != null) {
            charset = coyoteRequest.getCharset();
        }
        if (charset == null) {
            if (enc == null) {
                charset = org.apache.coyote.Constants.DEFAULT_BODY_CHARSET;
            } else {
                charset = B2CConverter.getCharset(enc);
            }
        }
        SynchronizedStack<B2CConverter> stack = encoders.get(charset);
        if (stack == null) {
            stack = new SynchronizedStack<>();
            encoders.putIfAbsent(charset, stack);
            stack = encoders.get(charset);
        }
        conv = stack.pop();
        if (conv == null) {
            conv = createConverter(charset);
        }
    }

    private static B2CConverter createConverter(final Charset charset) throws IOException {
        if (SecurityUtil.isPackageProtectionEnabled()) {
            try {
                return AccessController.doPrivileged(new PrivilegedExceptionAction<B2CConverter>() {

                    @Override
                    public B2CConverter run() throws IOException {
                        return new B2CConverter(charset);
                    }
                });
            } catch (PrivilegedActionException ex) {
                Exception e = ex.getException();
                if (e instanceof IOException) {
                    throw (IOException) e;
                } else {
                    throw new IOException(e);
                }
            }
        } else {
            return new B2CConverter(charset);
        }
    }

    @Override
    public void setByteBuffer(ByteBuffer buffer) {
        bb = buffer;
    }

    @Override
    public ByteBuffer getByteBuffer() {
        return bb;
    }

    @Override
    public void expand(int size) {
    // no-op
    }

    private boolean checkByteBufferEof() throws IOException {
        if (bb.remaining() == 0) {
            int n = realReadBytes();
            if (n < 0) {
                return true;
            }
        }
        return false;
    }

    private boolean checkCharBufferEof() throws IOException {
        if (cb.remaining() == 0) {
            int n = realReadChars();
            if (n < 0) {
                return true;
            }
        }
        return false;
    }

    private void clear(Buffer buffer) {
        buffer.rewind().limit(0);
    }

    private void makeSpace(int count) {
        int desiredSize = cb.limit() + count;
        if (desiredSize > readLimit) {
            desiredSize = readLimit;
        }
        if (desiredSize <= cb.capacity()) {
            return;
        }
        int newSize = 2 * cb.capacity();
        if (desiredSize >= newSize) {
            newSize = 2 * cb.capacity() + count;
        }
        if (newSize > readLimit) {
            newSize = readLimit;
        }
        CharBuffer tmp = CharBuffer.allocate(newSize);
        int oldPosition = cb.position();
        cb.position(0);
        tmp.put(cb);
        tmp.flip();
        tmp.position(oldPosition);
        cb = tmp;
        tmp = null;
    }
}

16 View Complete Implementation : InputBuffer.java
Copyright Apache License 2.0
Author : how2j
/**
 * The buffer used by Tomcat request. This is a derivative of the Tomcat 3.3
 * OutputBuffer, adapted to handle input instead of output. This allows complete
 * recycling of the facade objects (the ServletInputStream and the
 * BufferedReader).
 *
 * @author Remy Maucherat
 */
public clreplaced InputBuffer extends Reader implements ByteChunk.ByteInputChannel, CharChunk.CharInputChannel, CharChunk.CharOutputChannel {

    /**
     * The string manager for this package.
     */
    protected static final StringManager sm = StringManager.getManager(Constants.Package);

    // -------------------------------------------------------------- Constants
    public static final String DEFAULT_ENCODING = org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING;

    public static final int DEFAULT_BUFFER_SIZE = 8 * 1024;

    // The buffer can be used for byte[] and char[] reading
    // ( this is needed to support ServletInputStream and BufferedReader )
    public final int INITIAL_STATE = 0;

    public final int CHAR_STATE = 1;

    public final int BYTE_STATE = 2;

    // ----------------------------------------------------- Instance Variables
    /**
     * The byte buffer.
     */
    private final ByteChunk bb;

    /**
     * The chunk buffer.
     */
    private CharChunk cb;

    /**
     * State of the output buffer.
     */
    private int state = 0;

    /**
     * Flag which indicates if the input buffer is closed.
     */
    private boolean closed = false;

    /**
     * Encoding to use.
     */
    private String enc;

    /**
     * Encoder is set.
     */
    private boolean gotEnc = false;

    /**
     * List of encoders.
     */
    private final Map<String, B2CConverter> encoders = new ConcurrentHashMap<String, B2CConverter>();

    /**
     * Current byte to char converter.
     */
    protected B2CConverter conv;

    /**
     * replacedociated Coyote request.
     */
    private Request coyoteRequest;

    /**
     * Buffer position.
     */
    private int markPos = -1;

    /**
     * Buffer size.
     */
    private int size = -1;

    // ----------------------------------------------------------- Constructors
    /**
     * Default constructor. Allocate the buffer with the default buffer size.
     */
    public InputBuffer() {
        this(DEFAULT_BUFFER_SIZE);
    }

    /**
     * Alternate constructor which allows specifying the initial buffer size.
     *
     * @param size
     *            Buffer size to use
     */
    public InputBuffer(int size) {
        this.size = size;
        bb = new ByteChunk(size);
        bb.setLimit(size);
        bb.setByteInputChannel(this);
        cb = new CharChunk(size);
        cb.setLimit(size);
        cb.setOptimizedWrite(false);
        cb.setCharInputChannel(this);
        cb.setCharOutputChannel(this);
    }

    // ------------------------------------------------------------- Properties
    /**
     * replacedociated Coyote request.
     *
     * @param coyoteRequest
     *            replacedociated Coyote request
     */
    public void setRequest(Request coyoteRequest) {
        this.coyoteRequest = coyoteRequest;
    }

    /**
     * Get replacedociated Coyote request.
     *
     * @return the replacedociated Coyote request
     */
    @Deprecated
    public Request getRequest() {
        return this.coyoteRequest;
    }

    // --------------------------------------------------------- Public Methods
    /**
     * Recycle the output buffer.
     */
    public void recycle() {
        state = INITIAL_STATE;
        // If usage of mark made the buffer too big, reallocate it
        if (cb.getChars().length > size) {
            cb = new CharChunk(size);
            cb.setLimit(size);
            cb.setOptimizedWrite(false);
            cb.setCharInputChannel(this);
            cb.setCharOutputChannel(this);
        } else {
            cb.recycle();
        }
        markPos = -1;
        bb.recycle();
        closed = false;
        if (conv != null) {
            conv.recycle();
        }
        gotEnc = false;
        enc = null;
    }

    /**
     * Clear cached encoders (to save memory for Comet requests).
     */
    public void clearEncoders() {
        encoders.clear();
    }

    /**
     * Close the input buffer.
     *
     * @throws IOException
     *             An underlying IOException occurred
     */
    @Override
    public void close() throws IOException {
        closed = true;
    }

    public int available() {
        int available = 0;
        if (state == BYTE_STATE) {
            available = bb.getLength();
        } else if (state == CHAR_STATE) {
            available = cb.getLength();
        }
        if (available == 0) {
            coyoteRequest.action(ActionCode.AVAILABLE, null);
            available = (coyoteRequest.getAvailable() > 0) ? 1 : 0;
        }
        return available;
    }

    // ------------------------------------------------- Bytes Handling Methods
    /**
     * Reads new bytes in the byte chunk.
     *
     * @param cbuf
     *            Byte buffer to be written to the response
     * @param off
     *            Offset
     * @param len
     *            Length
     *
     * @throws IOException
     *             An underlying IOException occurred
     */
    @Override
    public int realReadBytes(byte[] cbuf, int off, int len) throws IOException {
        if (closed) {
            return -1;
        }
        if (coyoteRequest == null) {
            return -1;
        }
        if (state == INITIAL_STATE) {
            state = BYTE_STATE;
        }
        int result = coyoteRequest.doRead(bb);
        return result;
    }

    public int readByte() throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        return bb.substract();
    }

    public int read(byte[] b, int off, int len) throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        return bb.substract(b, off, len);
    }

    // ------------------------------------------------- Chars Handling Methods
    /**
     * Since the converter will use append, it is possible to get chars to be
     * removed from the buffer for "writing". Since the chars have already been
     * read before, they are ignored. If a mark was set, then the mark is lost.
     */
    @Override
    public void realWriteChars(char[] c, int off, int len) throws IOException {
        markPos = -1;
        cb.setOffset(0);
        cb.setEnd(0);
    }

    public void setEncoding(String s) {
        enc = s;
    }

    @Override
    public int realReadChars(char[] cbuf, int off, int len) throws IOException {
        if (!gotEnc) {
            setConverter();
        }
        boolean eof = false;
        if (bb.getLength() <= 0) {
            int nRead = realReadBytes(bb.getBytes(), 0, bb.getBytes().length);
            if (nRead < 0) {
                eof = true;
            }
        }
        if (markPos == -1) {
            cb.setOffset(0);
            cb.setEnd(0);
        } else {
            // Make sure there's enough space in the worst case
            cb.makeSpace(bb.getLength());
            if ((cb.getBuffer().length - cb.getEnd()) == 0 && bb.getLength() != 0) {
                // We went over the limit
                cb.setOffset(0);
                cb.setEnd(0);
                markPos = -1;
            }
        }
        state = CHAR_STATE;
        conv.convert(bb, cb, eof);
        if (cb.getLength() == 0 && eof) {
            return -1;
        } else {
            return cb.getLength();
        }
    }

    @Override
    public int read() throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        return cb.substract();
    }

    @Override
    public int read(char[] cbuf) throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        return read(cbuf, 0, cbuf.length);
    }

    @Override
    public int read(char[] cbuf, int off, int len) throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        return cb.substract(cbuf, off, len);
    }

    @Override
    public long skip(long n) throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        if (n < 0) {
            throw new IllegalArgumentException();
        }
        long nRead = 0;
        while (nRead < n) {
            if (cb.getLength() >= n) {
                cb.setOffset(cb.getStart() + (int) n);
                nRead = n;
            } else {
                nRead += cb.getLength();
                cb.setOffset(cb.getEnd());
                int toRead = 0;
                if (cb.getChars().length < (n - nRead)) {
                    toRead = cb.getChars().length;
                } else {
                    toRead = (int) (n - nRead);
                }
                int nb = realReadChars(cb.getChars(), 0, toRead);
                if (nb < 0) {
                    break;
                }
            }
        }
        return nRead;
    }

    @Override
    public boolean ready() throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        return (available() > 0);
    }

    @Override
    public boolean markSupported() {
        return true;
    }

    @Override
    public void mark(int readAheadLimit) throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        if (cb.getLength() <= 0) {
            cb.setOffset(0);
            cb.setEnd(0);
        } else {
            if ((cb.getBuffer().length > (2 * size)) && (cb.getLength()) < (cb.getStart())) {
                System.arraycopy(cb.getBuffer(), cb.getStart(), cb.getBuffer(), 0, cb.getLength());
                cb.setEnd(cb.getLength());
                cb.setOffset(0);
            }
        }
        cb.setLimit(cb.getStart() + readAheadLimit + size);
        markPos = cb.getStart();
    }

    @Override
    public void reset() throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        if (state == CHAR_STATE) {
            if (markPos < 0) {
                cb.recycle();
                markPos = -1;
                throw new IOException();
            } else {
                cb.setOffset(markPos);
            }
        } else {
            bb.recycle();
        }
    }

    public void checkConverter() throws IOException {
        if (!gotEnc) {
            setConverter();
        }
    }

    protected void setConverter() throws IOException {
        if (coyoteRequest != null) {
            enc = coyoteRequest.getCharacterEncoding();
        }
        gotEnc = true;
        if (enc == null) {
            enc = DEFAULT_ENCODING;
        }
        conv = encoders.get(enc);
        if (conv == null) {
            if (SecurityUtil.isPackageProtectionEnabled()) {
                try {
                    conv = AccessController.doPrivileged(new PrivilegedExceptionAction<B2CConverter>() {

                        @Override
                        public B2CConverter run() throws IOException {
                            return new B2CConverter(enc);
                        }
                    });
                } catch (PrivilegedActionException ex) {
                    Exception e = ex.getException();
                    if (e instanceof IOException) {
                        throw (IOException) e;
                    }
                }
            } else {
                conv = new B2CConverter(enc);
            }
            encoders.put(enc, conv);
        }
    }
}

16 View Complete Implementation : AbstractInputBuffer.java
Copyright Apache License 2.0
Author : how2j
public abstract clreplaced AbstractInputBuffer<S> implements InputBuffer {

    /**
     * The string manager for this package.
     */
    protected static final StringManager sm = StringManager.getManager(Constants.Package);

    /**
     * replacedociated Coyote request.
     */
    protected Request request;

    /**
     * Headers of the replacedociated request.
     */
    protected MimeHeaders headers;

    /**
     * State.
     */
    protected boolean parsingHeader;

    /**
     * Swallow input ? (in the case of an expectation)
     */
    protected boolean swallowInput;

    /**
     * Pointer to the current read buffer.
     */
    protected byte[] buf;

    /**
     * Last valid byte.
     */
    protected int lastValid;

    /**
     * Position in the buffer.
     */
    protected int pos;

    /**
     * Pos of the end of the header in the buffer, which is also the start of
     * the body.
     */
    protected int end;

    /**
     * Underlying input buffer.
     */
    protected InputBuffer inputStreamInputBuffer;

    /**
     * Filter library. Note: Filter[0] is always the "chunked" filter.
     */
    protected InputFilter[] filterLibrary;

    /**
     * Active filters (in order).
     */
    protected InputFilter[] activeFilters;

    /**
     * Index of the last active filter.
     */
    protected int lastActiveFilter;

    // ------------------------------------------------------------- Properties
    /**
     * Add an input filter to the filter library.
     */
    public void addFilter(InputFilter filter) {
        // FIXME: Check for null ?
        InputFilter[] newFilterLibrary = new InputFilter[filterLibrary.length + 1];
        for (int i = 0; i < filterLibrary.length; i++) {
            newFilterLibrary[i] = filterLibrary[i];
        }
        newFilterLibrary[filterLibrary.length] = filter;
        filterLibrary = newFilterLibrary;
        activeFilters = new InputFilter[filterLibrary.length];
    }

    /**
     * Get filters.
     */
    public InputFilter[] getFilters() {
        return filterLibrary;
    }

    /**
     * Add an input filter to the filter library.
     */
    public 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.
     */
    public void setSwallowInput(boolean swallowInput) {
        this.swallowInput = swallowInput;
    }

    /**
     * Implementations are expected to call {@link Request#setStartTime(long)}
     * as soon as the first byte is read from the request.
     */
    public abstract boolean parseRequestLine(boolean useAvailableDataOnly) throws IOException;

    public abstract boolean parseHeaders() throws IOException;

    protected abstract boolean fill(boolean block) throws IOException;

    protected abstract void init(SocketWrapper<S> socketWrapper, AbstractEndpoint<S> endpoint) throws IOException;

    // --------------------------------------------------------- Public Methods
    /**
     * Recycle the input buffer. This should be called when closing the
     * connection.
     */
    public void recycle() {
        // Recycle Request object
        request.recycle();
        // Recycle filters
        for (int i = 0; i <= lastActiveFilter; i++) {
            activeFilters[i].recycle();
        }
        lastValid = 0;
        pos = 0;
        lastActiveFilter = -1;
        parsingHeader = true;
        swallowInput = 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.
     */
    public void nextRequest() {
        // Recycle Request object
        request.recycle();
        // Copy leftover bytes to the beginning of the buffer
        if (lastValid - pos > 0 && pos > 0) {
            System.arraycopy(buf, pos, buf, 0, lastValid - pos);
        }
        // Always reset pos to zero
        lastValid = lastValid - pos;
        pos = 0;
        // Recycle filters
        for (int i = 0; i <= lastActiveFilter; i++) {
            activeFilters[i].recycle();
        }
        // Reset pointers
        lastActiveFilter = -1;
        parsingHeader = true;
        swallowInput = true;
    }

    /**
     * End request (consumes leftover bytes).
     *
     * @throws IOException
     *             an underlying I/O error occurred
     */
    public void endRequest() throws IOException {
        if (swallowInput && (lastActiveFilter != -1)) {
            int extraBytes = (int) activeFilters[lastActiveFilter].end();
            pos = pos - extraBytes;
        }
    }

    /**
     * Available bytes in the buffers (note that due to encoding, this may not
     * correspond).
     */
    public int available() {
        int result = (lastValid - pos);
        if ((result == 0) && (lastActiveFilter >= 0)) {
            for (int i = 0; (result == 0) && (i <= lastActiveFilter); i++) {
                result = activeFilters[i].available();
            }
        }
        return result;
    }

    // ---------------------------------------------------- InputBuffer Methods
    /**
     * Read some bytes.
     */
    @Override
    public int doRead(ByteChunk chunk, Request req) throws IOException {
        if (lastActiveFilter == -1)
            return inputStreamInputBuffer.doRead(chunk, req);
        else
            return activeFilters[lastActiveFilter].doRead(chunk, req);
    }
}

16 View Complete Implementation : ChunkedInputFilter.java
Copyright Apache License 2.0
Author : how2j
// ---------------------------------------------------- InputBuffer Methods
/**
 * Read bytes.
 *
 * @return If the filter does request length control, this value is
 *         significant; it should be the number of bytes consumed from the
 *         buffer, up until the end of the current request body, or the
 *         buffer length, whichever is greater. If the filter does not do
 *         request body length control, the returned value should be -1.
 */
@Override
public int doRead(ByteChunk chunk, Request req) throws IOException {
    if (endChunk) {
        return -1;
    }
    checkError();
    if (needCRLFParse) {
        needCRLFParse = false;
        parseCRLF(false);
    }
    if (remaining <= 0) {
        if (!parseChunkHeader()) {
            throwIOException(sm.getString("chunkedInputFilter.invalidHeader"));
        }
        if (endChunk) {
            parseEndChunk();
            return -1;
        }
    }
    int result = 0;
    if (pos >= lastValid) {
        if (readBytes() < 0) {
            throwIOException(sm.getString("chunkedInputFilter.eos"));
        }
    }
    if (remaining > (lastValid - pos)) {
        result = lastValid - pos;
        remaining = remaining - result;
        chunk.setBytes(buf, pos, result);
        pos = lastValid;
    } else {
        result = remaining;
        chunk.setBytes(buf, pos, remaining);
        pos = pos + remaining;
        remaining = 0;
        // we need a CRLF
        if ((pos + 1) >= lastValid) {
            // if we call parseCRLF we overrun the buffer here
            // so we defer it to the next call BZ 11117
            needCRLFParse = true;
        } else {
            // parse the CRLF immediately
            parseCRLF(false);
        }
    }
    return result;
}

16 View Complete Implementation : InputBuffer.java
Copyright Apache License 2.0
Author : tryandcatch
/**
 * The buffer used by Tomcat request. This is a derivative of the Tomcat 3.3
 * OutputBuffer, adapted to handle input instead of output. This allows
 * complete recycling of the facade objects (the ServletInputStream and the
 * BufferedReader).
 *
 * @author Remy Maucherat
 */
public clreplaced InputBuffer extends Reader implements ByteChunk.ByteInputChannel, CharChunk.CharInputChannel, CharChunk.CharOutputChannel {

    /**
     * The string manager for this package.
     */
    protected static final StringManager sm = StringManager.getManager(Constants.Package);

    // -------------------------------------------------------------- Constants
    public static final String DEFAULT_ENCODING = org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING;

    public static final int DEFAULT_BUFFER_SIZE = 8 * 1024;

    // The buffer can be used for byte[] and char[] reading
    // ( this is needed to support ServletInputStream and BufferedReader )
    public final int INITIAL_STATE = 0;

    public final int CHAR_STATE = 1;

    public final int BYTE_STATE = 2;

    // ----------------------------------------------------- Instance Variables
    /**
     * The byte buffer.
     */
    private final ByteChunk bb;

    /**
     * The chunk buffer.
     */
    private CharChunk cb;

    /**
     * State of the output buffer.
     */
    private int state = 0;

    /**
     * Flag which indicates if the input buffer is closed.
     */
    private boolean closed = false;

    /**
     * Encoding to use.
     */
    private String enc;

    /**
     * Encoder is set.
     */
    private boolean gotEnc = false;

    /**
     * List of encoders.
     */
    protected final ConcurrentHashMap<String, B2CConverter> encoders = new ConcurrentHashMap<String, B2CConverter>();

    /**
     * Current byte to char converter.
     */
    protected B2CConverter conv;

    /**
     * replacedociated Coyote request.
     */
    private Request coyoteRequest;

    /**
     * Buffer position.
     */
    private int markPos = -1;

    /**
     * Buffer size.
     */
    private int size = -1;

    // ----------------------------------------------------------- Constructors
    /**
     * Default constructor. Allocate the buffer with the default buffer size.
     */
    public InputBuffer() {
        this(DEFAULT_BUFFER_SIZE);
    }

    /**
     * Alternate constructor which allows specifying the initial buffer size.
     *
     * @param size Buffer size to use
     */
    public InputBuffer(int size) {
        this.size = size;
        bb = new ByteChunk(size);
        bb.setLimit(size);
        bb.setByteInputChannel(this);
        cb = new CharChunk(size);
        cb.setLimit(size);
        cb.setOptimizedWrite(false);
        cb.setCharInputChannel(this);
        cb.setCharOutputChannel(this);
    }

    // ------------------------------------------------------------- Properties
    /**
     * replacedociated Coyote request.
     *
     * @param coyoteRequest replacedociated Coyote request
     */
    public void setRequest(Request coyoteRequest) {
        this.coyoteRequest = coyoteRequest;
    }

    /**
     * Get replacedociated Coyote request.
     *
     * @return the replacedociated Coyote request
     */
    @Deprecated
    public Request getRequest() {
        return this.coyoteRequest;
    }

    // --------------------------------------------------------- Public Methods
    /**
     * Recycle the output buffer.
     */
    public void recycle() {
        state = INITIAL_STATE;
        // If usage of mark made the buffer too big, reallocate it
        if (cb.getChars().length > size) {
            cb = new CharChunk(size);
            cb.setLimit(size);
            cb.setOptimizedWrite(false);
            cb.setCharInputChannel(this);
            cb.setCharOutputChannel(this);
        } else {
            cb.recycle();
        }
        markPos = -1;
        bb.recycle();
        closed = false;
        if (conv != null) {
            conv.recycle();
        }
        gotEnc = false;
        enc = null;
    }

    /**
     * Clear cached encoders (to save memory for Comet requests).
     */
    public void clearEncoders() {
        encoders.clear();
    }

    /**
     * Close the input buffer.
     *
     * @throws IOException An underlying IOException occurred
     */
    @Override
    public void close() throws IOException {
        closed = true;
    }

    public int available() {
        int available = 0;
        if (state == BYTE_STATE) {
            available = bb.getLength();
        } else if (state == CHAR_STATE) {
            available = cb.getLength();
        }
        if (available == 0) {
            coyoteRequest.action(ActionCode.AVAILABLE, null);
            available = (coyoteRequest.getAvailable() > 0) ? 1 : 0;
        }
        return available;
    }

    // ------------------------------------------------- Bytes Handling Methods
    /**
     * Reads new bytes in the byte chunk.
     *
     * @param cbuf Byte buffer to be written to the response
     * @param off Offset
     * @param len Length
     *
     * @throws IOException An underlying IOException occurred
     */
    @Override
    public int realReadBytes(byte[] cbuf, int off, int len) throws IOException {
        if (closed) {
            return -1;
        }
        if (coyoteRequest == null) {
            return -1;
        }
        if (state == INITIAL_STATE) {
            state = BYTE_STATE;
        }
        int result = coyoteRequest.doRead(bb);
        return result;
    }

    public int readByte() throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        return bb.substract();
    }

    public int read(byte[] b, int off, int len) throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        return bb.substract(b, off, len);
    }

    // ------------------------------------------------- Chars Handling Methods
    /**
     * Since the converter will use append, it is possible to get chars to
     * be removed from the buffer for "writing". Since the chars have already
     * been read before, they are ignored. If a mark was set, then the
     * mark is lost.
     */
    @Override
    public void realWriteChars(char[] c, int off, int len) throws IOException {
        markPos = -1;
        cb.setOffset(0);
        cb.setEnd(0);
    }

    public void setEncoding(String s) {
        enc = s;
    }

    @Override
    public int realReadChars(char[] cbuf, int off, int len) throws IOException {
        if (!gotEnc) {
            setConverter();
        }
        boolean eof = false;
        if (bb.getLength() <= 0) {
            int nRead = realReadBytes(bb.getBytes(), 0, bb.getBytes().length);
            if (nRead < 0) {
                eof = true;
            }
        }
        if (markPos == -1) {
            cb.setOffset(0);
            cb.setEnd(0);
        } else {
            // Make sure there's enough space in the worst case
            cb.makeSpace(bb.getLength());
            if ((cb.getBuffer().length - cb.getEnd()) == 0) {
                // We went over the limit
                cb.setOffset(0);
                cb.setEnd(0);
                markPos = -1;
            }
        }
        state = CHAR_STATE;
        conv.convert(bb, cb, eof);
        if (cb.getLength() == 0 && eof) {
            return -1;
        } else {
            return cb.getLength();
        }
    }

    @Override
    public int read() throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        return cb.substract();
    }

    @Override
    public int read(char[] cbuf) throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        return read(cbuf, 0, cbuf.length);
    }

    @Override
    public int read(char[] cbuf, int off, int len) throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        return cb.substract(cbuf, off, len);
    }

    @Override
    public long skip(long n) throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        if (n < 0) {
            throw new IllegalArgumentException();
        }
        long nRead = 0;
        while (nRead < n) {
            if (cb.getLength() >= n) {
                cb.setOffset(cb.getStart() + (int) n);
                nRead = n;
            } else {
                nRead += cb.getLength();
                cb.setOffset(cb.getEnd());
                int toRead = 0;
                if (cb.getChars().length < (n - nRead)) {
                    toRead = cb.getChars().length;
                } else {
                    toRead = (int) (n - nRead);
                }
                int nb = realReadChars(cb.getChars(), 0, toRead);
                if (nb < 0) {
                    break;
                }
            }
        }
        return nRead;
    }

    @Override
    public boolean ready() throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        return (available() > 0);
    }

    @Override
    public boolean markSupported() {
        return true;
    }

    @Override
    public void mark(int readAheadLimit) throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        if (cb.getLength() <= 0) {
            cb.setOffset(0);
            cb.setEnd(0);
        } else {
            if ((cb.getBuffer().length > (2 * size)) && (cb.getLength()) < (cb.getStart())) {
                System.arraycopy(cb.getBuffer(), cb.getStart(), cb.getBuffer(), 0, cb.getLength());
                cb.setEnd(cb.getLength());
                cb.setOffset(0);
            }
        }
        cb.setLimit(cb.getStart() + readAheadLimit + size);
        markPos = cb.getStart();
    }

    @Override
    public void reset() throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        if (state == CHAR_STATE) {
            if (markPos < 0) {
                cb.recycle();
                markPos = -1;
                throw new IOException();
            } else {
                cb.setOffset(markPos);
            }
        } else {
            bb.recycle();
        }
    }

    public void checkConverter() throws IOException {
        if (!gotEnc) {
            setConverter();
        }
    }

    protected void setConverter() throws IOException {
        if (coyoteRequest != null) {
            enc = coyoteRequest.getCharacterEncoding();
        }
        gotEnc = true;
        if (enc == null) {
            enc = DEFAULT_ENCODING;
        }
        conv = encoders.get(enc);
        if (conv == null) {
            if (SecurityUtil.isPackageProtectionEnabled()) {
                try {
                    conv = AccessController.doPrivileged(new PrivilegedExceptionAction<B2CConverter>() {

                        @Override
                        public B2CConverter run() throws IOException {
                            return new B2CConverter(enc);
                        }
                    });
                } catch (PrivilegedActionException ex) {
                    Exception e = ex.getException();
                    if (e instanceof IOException) {
                        throw (IOException) e;
                    }
                }
            } else {
                conv = new B2CConverter(enc);
            }
            encoders.put(enc, conv);
        }
    }
}

16 View Complete Implementation : AbstractInputBuffer.java
Copyright Apache License 2.0
Author : tryandcatch
public abstract clreplaced AbstractInputBuffer<S> implements InputBuffer {

    protected static final boolean[] HTTP_TOKEN_CHAR = new boolean[128];

    /**
     * The string manager for this package.
     */
    protected static final StringManager sm = StringManager.getManager(Constants.Package);

    static {
        for (int i = 0; i < 128; i++) {
            if (i < 32) {
                HTTP_TOKEN_CHAR[i] = false;
            } else if (i == 127) {
                HTTP_TOKEN_CHAR[i] = false;
            } else if (i == '(') {
                HTTP_TOKEN_CHAR[i] = false;
            } else if (i == ')') {
                HTTP_TOKEN_CHAR[i] = false;
            } else if (i == '<') {
                HTTP_TOKEN_CHAR[i] = false;
            } else if (i == '>') {
                HTTP_TOKEN_CHAR[i] = false;
            } else if (i == '@') {
                HTTP_TOKEN_CHAR[i] = false;
            } else if (i == ',') {
                HTTP_TOKEN_CHAR[i] = false;
            } else if (i == ';') {
                HTTP_TOKEN_CHAR[i] = false;
            } else if (i == ':') {
                HTTP_TOKEN_CHAR[i] = false;
            } else if (i == '\\') {
                HTTP_TOKEN_CHAR[i] = false;
            } else if (i == '\"') {
                HTTP_TOKEN_CHAR[i] = false;
            } else if (i == '/') {
                HTTP_TOKEN_CHAR[i] = false;
            } else if (i == '[') {
                HTTP_TOKEN_CHAR[i] = false;
            } else if (i == ']') {
                HTTP_TOKEN_CHAR[i] = false;
            } else if (i == '?') {
                HTTP_TOKEN_CHAR[i] = false;
            } else if (i == '=') {
                HTTP_TOKEN_CHAR[i] = false;
            } else if (i == '{') {
                HTTP_TOKEN_CHAR[i] = false;
            } else if (i == '}') {
                HTTP_TOKEN_CHAR[i] = false;
            } else if (i == ' ') {
                HTTP_TOKEN_CHAR[i] = false;
            } else {
                HTTP_TOKEN_CHAR[i] = true;
            }
        }
    }

    /**
     * replacedociated Coyote request.
     */
    protected Request request;

    /**
     * Headers of the replacedociated request.
     */
    protected MimeHeaders headers;

    /**
     * State.
     */
    protected boolean parsingHeader;

    /**
     * Swallow input ? (in the case of an expectation)
     */
    protected boolean swallowInput;

    /**
     * Pointer to the current read buffer.
     */
    protected byte[] buf;

    /**
     * Last valid byte.
     */
    protected int lastValid;

    /**
     * Position in the buffer.
     */
    protected int pos;

    /**
     * Pos of the end of the header in the buffer, which is also the
     * start of the body.
     */
    protected int end;

    /**
     * Underlying input buffer.
     */
    protected InputBuffer inputStreamInputBuffer;

    /**
     * Filter library.
     * Note: Filter[0] is always the "chunked" filter.
     */
    protected InputFilter[] filterLibrary;

    /**
     * Active filters (in order).
     */
    protected InputFilter[] activeFilters;

    /**
     * Index of the last active filter.
     */
    protected int lastActiveFilter;

    // ------------------------------------------------------------- Properties
    /**
     * Add an input filter to the filter library.
     */
    public void addFilter(InputFilter filter) {
        // FIXME: Check for null ?
        InputFilter[] newFilterLibrary = new InputFilter[filterLibrary.length + 1];
        for (int i = 0; i < filterLibrary.length; i++) {
            newFilterLibrary[i] = filterLibrary[i];
        }
        newFilterLibrary[filterLibrary.length] = filter;
        filterLibrary = newFilterLibrary;
        activeFilters = new InputFilter[filterLibrary.length];
    }

    /**
     * Get filters.
     */
    public InputFilter[] getFilters() {
        return filterLibrary;
    }

    /**
     * Add an input filter to the filter library.
     */
    public 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.
     */
    public void setSwallowInput(boolean swallowInput) {
        this.swallowInput = swallowInput;
    }

    /**
     * Implementations are expected to call {@link Request#setStartTime(long)}
     * as soon as the first byte is read from the request.
     */
    public abstract boolean parseRequestLine(boolean useAvailableDataOnly) throws IOException;

    public abstract boolean parseHeaders() throws IOException;

    protected abstract boolean fill(boolean block) throws IOException;

    protected abstract void init(SocketWrapper<S> socketWrapper, AbstractEndpoint<S> endpoint) throws IOException;

    // --------------------------------------------------------- Public Methods
    /**
     * Recycle the input buffer. This should be called when closing the
     * connection.
     */
    public void recycle() {
        // Recycle Request object
        request.recycle();
        // Recycle filters
        for (int i = 0; i <= lastActiveFilter; i++) {
            activeFilters[i].recycle();
        }
        lastValid = 0;
        pos = 0;
        lastActiveFilter = -1;
        parsingHeader = true;
        swallowInput = 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.
     */
    public void nextRequest() {
        // Recycle Request object
        request.recycle();
        // Copy leftover bytes to the beginning of the buffer
        if (lastValid - pos > 0 && pos > 0) {
            System.arraycopy(buf, pos, buf, 0, lastValid - pos);
        }
        // Always reset pos to zero
        lastValid = lastValid - pos;
        pos = 0;
        // Recycle filters
        for (int i = 0; i <= lastActiveFilter; i++) {
            activeFilters[i].recycle();
        }
        // Reset pointers
        lastActiveFilter = -1;
        parsingHeader = true;
        swallowInput = true;
    }

    /**
     * End request (consumes leftover bytes).
     *
     * @throws IOException an underlying I/O error occurred
     */
    public void endRequest() throws IOException {
        if (swallowInput && (lastActiveFilter != -1)) {
            int extraBytes = (int) activeFilters[lastActiveFilter].end();
            pos = pos - extraBytes;
        }
    }

    /**
     * Available bytes in the buffers (note that due to encoding, this may not
     * correspond).
     */
    public int available() {
        int result = (lastValid - pos);
        if ((result == 0) && (lastActiveFilter >= 0)) {
            for (int i = 0; (result == 0) && (i <= lastActiveFilter); i++) {
                result = activeFilters[i].available();
            }
        }
        return result;
    }

    // ---------------------------------------------------- InputBuffer Methods
    /**
     * Read some bytes.
     */
    @Override
    public int doRead(ByteChunk chunk, Request req) throws IOException {
        if (lastActiveFilter == -1)
            return inputStreamInputBuffer.doRead(chunk, req);
        else
            return activeFilters[lastActiveFilter].doRead(chunk, req);
    }
}

16 View Complete Implementation : ChunkedInputFilter.java
Copyright Apache License 2.0
Author : tryandcatch
/**
 * Chunked input filter. Parses chunked data according to
 * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1">http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1</a><br>
 *
 * @author Remy Maucherat
 * @author Filip Hanik
 */
public clreplaced ChunkedInputFilter implements InputFilter {

    private static final StringManager sm = StringManager.getManager(ChunkedInputFilter.clreplaced.getPackage().getName());

    // -------------------------------------------------------------- Constants
    protected static final String ENCODING_NAME = "chunked";

    protected static final ByteChunk ENCODING = new ByteChunk();

    // ----------------------------------------------------- Static Initializer
    static {
        ENCODING.setBytes(ENCODING_NAME.getBytes(Charset.defaultCharset()), 0, ENCODING_NAME.length());
    }

    // ----------------------------------------------------- Instance Variables
    /**
     * Next buffer in the pipeline.
     */
    protected InputBuffer buffer;

    /**
     * Number of bytes remaining in the current chunk.
     */
    protected int remaining = 0;

    /**
     * Position in the buffer.
     */
    protected int pos = 0;

    /**
     * Last valid byte in the buffer.
     */
    protected int lastValid = 0;

    /**
     * Read bytes buffer.
     */
    protected byte[] buf = null;

    /**
     * Byte chunk used to read bytes.
     */
    protected ByteChunk readChunk = new ByteChunk();

    /**
     * Flag set to true when the end chunk has been read.
     */
    protected boolean endChunk = false;

    /**
     * Byte chunk used to store trailing headers.
     */
    protected ByteChunk trailingHeaders = new ByteChunk();

    /**
     * Flag set to true if the next call to doRead() must parse a CRLF pair
     * before doing anything else.
     */
    protected boolean needCRLFParse = false;

    /**
     * Request being parsed.
     */
    private Request request;

    /**
     * Limit for extension size.
     */
    private final long maxExtensionSize;

    /**
     * Limit for trailer size.
     */
    private final int maxTrailerSize;

    /**
     * Size of extensions processed for this request.
     */
    private long extensionSize;

    private final int maxSwallowSize;

    /**
     * Flag that indicates if an error has occurred.
     */
    private boolean error;

    private final Set<String> allowedTrailerHeaders;

    // ----------------------------------------------------------- Constructors
    public ChunkedInputFilter(int maxTrailerSize, Set<String> allowedTrailerHeaders, int maxExtensionSize, int maxSwallowSize) {
        this.trailingHeaders.setLimit(maxTrailerSize);
        this.allowedTrailerHeaders = allowedTrailerHeaders;
        this.maxExtensionSize = maxExtensionSize;
        this.maxTrailerSize = maxTrailerSize;
        this.maxSwallowSize = maxSwallowSize;
    }

    // ---------------------------------------------------- InputBuffer Methods
    /**
     * Read bytes.
     *
     * @return If the filter does request length control, this value is
     * significant; it should be the number of bytes consumed from the buffer,
     * up until the end of the current request body, or the buffer length,
     * whichever is greater. If the filter does not do request body length
     * control, the returned value should be -1.
     */
    @Override
    public int doRead(ByteChunk chunk, Request req) throws IOException {
        if (endChunk) {
            return -1;
        }
        checkError();
        if (needCRLFParse) {
            needCRLFParse = false;
            parseCRLF(false);
        }
        if (remaining <= 0) {
            if (!parseChunkHeader()) {
                throwIOException(sm.getString("chunkedInputFilter.invalidHeader"));
            }
            if (endChunk) {
                parseEndChunk();
                return -1;
            }
        }
        int result = 0;
        if (pos >= lastValid) {
            if (readBytes() < 0) {
                throwIOException(sm.getString("chunkedInputFilter.eos"));
            }
        }
        if (remaining > (lastValid - pos)) {
            result = lastValid - pos;
            remaining = remaining - result;
            chunk.setBytes(buf, pos, result);
            pos = lastValid;
        } else {
            result = remaining;
            chunk.setBytes(buf, pos, remaining);
            pos = pos + remaining;
            remaining = 0;
            // we need a CRLF
            if ((pos + 1) >= lastValid) {
                // if we call parseCRLF we overrun the buffer here
                // so we defer it to the next call BZ 11117
                needCRLFParse = true;
            } else {
                // parse the CRLF immediately
                parseCRLF(false);
            }
        }
        return result;
    }

    // ---------------------------------------------------- InputFilter Methods
    /**
     * Read the content length from the request.
     */
    @Override
    public void setRequest(Request request) {
        this.request = request;
    }

    /**
     * End the current request.
     */
    @Override
    public long end() throws IOException {
        long swallowed = 0;
        int read = 0;
        // Consume extra bytes : parse the stream until the end chunk is found
        while ((read = doRead(readChunk, null)) >= 0) {
            swallowed += read;
            if (maxSwallowSize > -1 && swallowed > maxSwallowSize) {
                throwIOException(sm.getString("inputFilter.maxSwallow"));
            }
        }
        // Return the number of extra bytes which were consumed
        return lastValid - pos;
    }

    /**
     * Amount of bytes still available in a buffer.
     */
    @Override
    public int available() {
        return lastValid - pos;
    }

    /**
     * Set the next buffer in the filter pipeline.
     */
    @Override
    public void setBuffer(InputBuffer buffer) {
        this.buffer = buffer;
    }

    /**
     * Make the filter ready to process the next request.
     */
    @Override
    public void recycle() {
        remaining = 0;
        pos = 0;
        lastValid = 0;
        endChunk = false;
        needCRLFParse = false;
        trailingHeaders.recycle();
        trailingHeaders.setLimit(maxTrailerSize);
        extensionSize = 0;
        error = false;
    }

    /**
     * Return the name of the replacedociated encoding; Here, the value is
     * "idenreplacedy".
     */
    @Override
    public ByteChunk getEncodingName() {
        return ENCODING;
    }

    // ------------------------------------------------------ Protected Methods
    /**
     * Read bytes from the previous buffer.
     */
    protected int readBytes() throws IOException {
        int nRead = buffer.doRead(readChunk, null);
        pos = readChunk.getStart();
        lastValid = pos + nRead;
        buf = readChunk.getBytes();
        return nRead;
    }

    /**
     * Parse the header of a chunk.
     * A chunk header can look like one of the following:<br />
     * A10CRLF<br />
     * F23;chunk-extension to be ignoredCRLF
     *
     * <p>
     * The letters before CRLF or ';' (whatever comes first) must be valid hex
     * digits. We should not parse F23IAMGONNAMESSTHISUP34CRLF as a valid
     * header according to the spec.
     */
    protected boolean parseChunkHeader() throws IOException {
        int result = 0;
        boolean eol = false;
        int readDigit = 0;
        boolean extension = false;
        while (!eol) {
            if (pos >= lastValid) {
                if (readBytes() <= 0)
                    return false;
            }
            if (buf[pos] == Constants.CR || buf[pos] == Constants.LF) {
                parseCRLF(false);
                eol = true;
            } else if (buf[pos] == Constants.SEMI_COLON && !extension) {
                // First semi-colon marks the start of the extension. Further
                // semi-colons may appear to separate multiple chunk-extensions.
                // These need to be processed as part of parsing the extensions.
                extension = true;
                extensionSize++;
            } else if (!extension) {
                // don't read data after the trailer
                int charValue = HexUtils.getDec(buf[pos]);
                if (charValue != -1 && readDigit < 8) {
                    readDigit++;
                    result = (result << 4) | charValue;
                } else {
                    // we shouldn't allow invalid, non hex characters
                    // in the chunked header
                    return false;
                }
            } else {
                // Extension 'parsing'
                // Note that the chunk-extension is neither parsed nor
                // validated. Currently it is simply ignored.
                extensionSize++;
                if (maxExtensionSize > -1 && extensionSize > maxExtensionSize) {
                    throwIOException(sm.getString("chunkedInputFilter.maxExtension"));
                }
            }
            // Parsing the CRLF increments pos
            if (!eol) {
                pos++;
            }
        }
        if (readDigit == 0 || result < 0) {
            return false;
        }
        if (result == 0) {
            endChunk = true;
        }
        remaining = result;
        return true;
    }

    /**
     * Parse CRLF at end of chunk.
     * @deprecated  Use {@link #parseCRLF(boolean)}
     */
    @Deprecated
    protected boolean parseCRLF() throws IOException {
        parseCRLF(false);
        return true;
    }

    /**
     * Parse CRLF at end of chunk.
     *
     * @param   tolerant    Should tolerant parsing (LF and CRLF) be used? This
     *                      is recommended (RFC2616, section 19.3) for message
     *                      headers.
     */
    protected void parseCRLF(boolean tolerant) throws IOException {
        boolean eol = false;
        boolean crfound = false;
        while (!eol) {
            if (pos >= lastValid) {
                if (readBytes() <= 0) {
                    throwIOException(sm.getString("chunkedInputFilter.invalidCrlfNoData"));
                }
            }
            if (buf[pos] == Constants.CR) {
                if (crfound) {
                    throwIOException(sm.getString("chunkedInputFilter.invalidCrlfCRCR"));
                }
                crfound = true;
            } else if (buf[pos] == Constants.LF) {
                if (!tolerant && !crfound) {
                    throwIOException(sm.getString("chunkedInputFilter.invalidCrlfNoCR"));
                }
                eol = true;
            } else {
                throwIOException(sm.getString("chunkedInputFilter.invalidCrlf"));
            }
            pos++;
        }
    }

    /**
     * Parse end chunk data.
     */
    protected void parseEndChunk() throws IOException {
        // Handle optional trailer headers
        while (parseHeader()) {
        // Loop until we run out of headers
        }
    }

    private boolean parseHeader() throws IOException {
        MimeHeaders headers = request.getMimeHeaders();
        byte chr = 0;
        // Read new bytes if needed
        if (pos >= lastValid) {
            if (readBytes() < 0) {
                throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
            }
        }
        chr = buf[pos];
        // CRLF terminates the request
        if (chr == Constants.CR || chr == Constants.LF) {
            parseCRLF(false);
            return false;
        }
        // Mark the current buffer position
        int startPos = trailingHeaders.getEnd();
        // 
        // Reading the header name
        // Header name is always US-ASCII
        // 
        boolean colon = false;
        while (!colon) {
            // Read new bytes if needed
            if (pos >= lastValid) {
                if (readBytes() < 0) {
                    throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
                }
            }
            chr = buf[pos];
            if ((chr >= Constants.A) && (chr <= Constants.Z)) {
                chr = (byte) (chr - Constants.LC_OFFSET);
            }
            if (chr == Constants.COLON) {
                colon = true;
            } else {
                trailingHeaders.append(chr);
            }
            pos++;
        }
        int colonPos = trailingHeaders.getEnd();
        // 
        // Reading the header value (which can be spanned over multiple lines)
        // 
        boolean eol = false;
        boolean validLine = true;
        int lastSignificantChar = 0;
        while (validLine) {
            boolean space = true;
            // Skipping spaces
            while (space) {
                // Read new bytes if needed
                if (pos >= lastValid) {
                    if (readBytes() < 0) {
                        throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
                    }
                }
                chr = buf[pos];
                if ((chr == Constants.SP) || (chr == Constants.HT)) {
                    pos++;
                    // If we swallow whitespace, make sure it counts towards the
                    // limit placed on trailing header size
                    int newlimit = trailingHeaders.getLimit() - 1;
                    if (trailingHeaders.getEnd() > newlimit) {
                        throwIOException(sm.getString("chunkedInputFilter.maxTrailer"));
                    }
                    trailingHeaders.setLimit(newlimit);
                } else {
                    space = false;
                }
            }
            // Reading bytes until the end of the line
            while (!eol) {
                // Read new bytes if needed
                if (pos >= lastValid) {
                    if (readBytes() < 0) {
                        throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
                    }
                }
                chr = buf[pos];
                if (chr == Constants.CR || chr == Constants.LF) {
                    parseCRLF(true);
                    eol = true;
                } else if (chr == Constants.SP) {
                    trailingHeaders.append(chr);
                } else {
                    trailingHeaders.append(chr);
                    lastSignificantChar = trailingHeaders.getEnd();
                }
                if (!eol) {
                    pos++;
                }
            }
            // Checking the first character of the new line. If the character
            // is a LWS, then it's a multiline header
            // Read new bytes if needed
            if (pos >= lastValid) {
                if (readBytes() < 0) {
                    throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
                }
            }
            chr = buf[pos];
            if ((chr != Constants.SP) && (chr != Constants.HT)) {
                validLine = false;
            } else {
                eol = false;
                // Copying one extra space in the buffer (since there must
                // be at least one space inserted between the lines)
                trailingHeaders.append(chr);
            }
        }
        String headerName = new String(trailingHeaders.getBytes(), startPos, colonPos - startPos, "ISO_8859_1");
        if (allowedTrailerHeaders.contains(headerName.trim().toLowerCase(Locale.ENGLISH))) {
            MessageBytes headerValue = headers.addValue(headerName);
            // Set the header value
            headerValue.setBytes(trailingHeaders.getBytes(), colonPos, lastSignificantChar - colonPos);
        }
        return true;
    }

    private void throwIOException(String msg) throws IOException {
        error = true;
        throw new IOException(msg);
    }

    private void throwEOFException(String msg) throws IOException {
        error = true;
        throw new EOFException(msg);
    }

    private void checkError() throws IOException {
        if (error) {
            throw new IOException(sm.getString("chunkedInputFilter.error"));
        }
    }
}

16 View Complete Implementation : ChunkedInputFilter.java
Copyright Apache License 2.0
Author : codefollower
// ---------------------------------------------------- InputBuffer Methods
/**
 * Read bytes.
 *
 * @return If the filter does request length control, this value is
 * significant; it should be the number of bytes consumed from the buffer,
 * up until the end of the current request body, or the buffer length,
 * whichever is greater. If the filter does not do request body length
 * control, the returned value should be -1.
 */
@Override
public int doRead(ByteChunk chunk, Request req) throws IOException {
    if (endChunk)
        return -1;
    if (needCRLFParse) {
        needCRLFParse = false;
        parseCRLF(false);
    }
    if (remaining <= 0) {
        if (!parseChunkHeader()) {
            throw new IOException("Invalid chunk header");
        }
        if (endChunk) {
            parseEndChunk();
            return -1;
        }
    }
    int result = 0;
    if (pos >= lastValid) {
        if (readBytes() < 0) {
            throw new IOException("Unexpected end of stream whilst reading request body");
        }
    }
    if (remaining > (lastValid - pos)) {
        result = lastValid - pos;
        remaining = remaining - result;
        chunk.setBytes(buf, pos, result);
        pos = lastValid;
    } else {
        result = remaining;
        chunk.setBytes(buf, pos, remaining);
        pos = pos + remaining;
        remaining = 0;
        // we need a CRLF
        if ((pos + 1) >= lastValid) {
            // if we call parseCRLF we overrun the buffer here
            // so we defer it to the next call BZ 11117
            needCRLFParse = true;
        } else {
            // parse the CRLF immediately
            parseCRLF(false);
        }
    }
    return result;
}

16 View Complete Implementation : AbstractInputBuffer.java
Copyright Apache License 2.0
Author : wangyingjie
public abstract clreplaced AbstractInputBuffer<S> implements InputBuffer {

    /**
     * The string manager for this package.
     */
    protected static final StringManager sm = StringManager.getManager(Constants.Package);

    /**
     * replacedociated Coyote request.
     */
    protected Request request;

    /**
     * Headers of the replacedociated request.
     */
    protected MimeHeaders headers;

    /**
     * State.
     */
    protected boolean parsingHeader;

    /**
     * Swallow input ? (in the case of an expectation)
     */
    protected boolean swallowInput;

    /**
     * Pointer to the current read buffer.
     */
    protected byte[] buf;

    /**
     * Last valid byte.
     */
    protected int lastValid;

    /**
     * Position in the buffer.
     */
    protected int pos;

    /**
     * Pos of the end of the header in the buffer, which is also the
     * start of the body.
     */
    protected int end;

    /**
     * Underlying input buffer.
     */
    protected InputBuffer inputStreamInputBuffer;

    /**
     * Filter library.
     * Note: Filter[0] is always the "chunked" filter.
     */
    protected InputFilter[] filterLibrary;

    /**
     * Active filters (in order).
     */
    protected InputFilter[] activeFilters;

    /**
     * Index of the last active filter.
     */
    protected int lastActiveFilter;

    // ------------------------------------------------------------- Properties
    /**
     * Add an input filter to the filter library.
     */
    public void addFilter(InputFilter filter) {
        // FIXME: Check for null ?
        InputFilter[] newFilterLibrary = new InputFilter[filterLibrary.length + 1];
        for (int i = 0; i < filterLibrary.length; i++) {
            newFilterLibrary[i] = filterLibrary[i];
        }
        newFilterLibrary[filterLibrary.length] = filter;
        filterLibrary = newFilterLibrary;
        activeFilters = new InputFilter[filterLibrary.length];
    }

    /**
     * Get filters.
     */
    public InputFilter[] getFilters() {
        return filterLibrary;
    }

    /**
     * Add an input filter to the filter library.
     */
    public 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.
     */
    public void setSwallowInput(boolean swallowInput) {
        this.swallowInput = swallowInput;
    }

    /**
     * Implementations are expected to call {@link Request#setStartTime(long)}
     * as soon as the first byte is read from the request.
     */
    public abstract boolean parseRequestLine(boolean useAvailableDataOnly) throws IOException;

    public abstract boolean parseHeaders() throws IOException;

    protected abstract boolean fill(boolean block) throws IOException;

    protected abstract void init(SocketWrapper<S> socketWrapper, AbstractEndpoint<S> endpoint) throws IOException;

    // --------------------------------------------------------- Public Methods
    /**
     * Recycle the input buffer. This should be called when closing the
     * connection.
     */
    public void recycle() {
        // Recycle Request object
        request.recycle();
        // Recycle filters
        for (int i = 0; i <= lastActiveFilter; i++) {
            activeFilters[i].recycle();
        }
        lastValid = 0;
        pos = 0;
        lastActiveFilter = -1;
        parsingHeader = true;
        swallowInput = 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.
     */
    public void nextRequest() {
        // Recycle Request object
        request.recycle();
        // Copy leftover bytes to the beginning of the buffer
        if (lastValid - pos > 0 && pos > 0) {
            System.arraycopy(buf, pos, buf, 0, lastValid - pos);
        }
        // Always reset pos to zero
        lastValid = lastValid - pos;
        pos = 0;
        // Recycle filters
        for (int i = 0; i <= lastActiveFilter; i++) {
            activeFilters[i].recycle();
        }
        // Reset pointers
        lastActiveFilter = -1;
        parsingHeader = true;
        swallowInput = true;
    }

    /**
     * End request (consumes leftover bytes).
     *
     * @throws IOException an underlying I/O error occurred
     */
    public void endRequest() throws IOException {
        if (swallowInput && (lastActiveFilter != -1)) {
            int extraBytes = (int) activeFilters[lastActiveFilter].end();
            pos = pos - extraBytes;
        }
    }

    /**
     * Available bytes in the buffers (note that due to encoding, this may not
     * correspond).
     */
    public int available() {
        int result = (lastValid - pos);
        if ((result == 0) && (lastActiveFilter >= 0)) {
            for (int i = 0; (result == 0) && (i <= lastActiveFilter); i++) {
                result = activeFilters[i].available();
            }
        }
        return result;
    }

    // ---------------------------------------------------- InputBuffer Methods
    /**
     * Read some bytes.
     */
    @Override
    public int doRead(ByteChunk chunk, Request req) throws IOException {
        if (lastActiveFilter == -1)
            return inputStreamInputBuffer.doRead(chunk, req);
        else
            return activeFilters[lastActiveFilter].doRead(chunk, req);
    }
}

16 View Complete Implementation : ChunkedInputFilter.java
Copyright Apache License 2.0
Author : wangyingjie
/**
 * Chunked input filter. Parses chunked data according to
 * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1">http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1</a><br>
 *
 * @author Remy Maucherat
 * @author Filip Hanik
 */
public clreplaced ChunkedInputFilter implements InputFilter {

    private static final StringManager sm = StringManager.getManager(ChunkedInputFilter.clreplaced.getPackage().getName());

    // -------------------------------------------------------------- Constants
    protected static final String ENCODING_NAME = "chunked";

    protected static final ByteChunk ENCODING = new ByteChunk();

    // ----------------------------------------------------- Static Initializer
    static {
        ENCODING.setBytes(ENCODING_NAME.getBytes(Charset.defaultCharset()), 0, ENCODING_NAME.length());
    }

    // ----------------------------------------------------- Instance Variables
    /**
     * Next buffer in the pipeline.
     */
    protected InputBuffer buffer;

    /**
     * Number of bytes remaining in the current chunk.
     */
    protected int remaining = 0;

    /**
     * Position in the buffer.
     */
    protected int pos = 0;

    /**
     * Last valid byte in the buffer.
     */
    protected int lastValid = 0;

    /**
     * Read bytes buffer.
     */
    protected byte[] buf = null;

    /**
     * Byte chunk used to read bytes.
     */
    protected ByteChunk readChunk = new ByteChunk();

    /**
     * Flag set to true when the end chunk has been read.
     */
    protected boolean endChunk = false;

    /**
     * Byte chunk used to store trailing headers.
     */
    protected ByteChunk trailingHeaders = new ByteChunk();

    /**
     * Flag set to true if the next call to doRead() must parse a CRLF pair
     * before doing anything else.
     */
    protected boolean needCRLFParse = false;

    /**
     * Request being parsed.
     */
    private Request request;

    /**
     * Limit for extension size.
     */
    private final long maxExtensionSize;

    /**
     * Limit for trailer size.
     */
    private final int maxTrailerSize;

    /**
     * Size of extensions processed for this request.
     */
    private long extensionSize;

    private final int maxSwallowSize;

    /**
     * Flag that indicates if an error has occurred.
     */
    private boolean error;

    private final Set<String> allowedTrailerHeaders;

    // ----------------------------------------------------------- Constructors
    public ChunkedInputFilter(int maxTrailerSize, Set<String> allowedTrailerHeaders, int maxExtensionSize, int maxSwallowSize) {
        this.trailingHeaders.setLimit(maxTrailerSize);
        this.allowedTrailerHeaders = allowedTrailerHeaders;
        this.maxExtensionSize = maxExtensionSize;
        this.maxTrailerSize = maxTrailerSize;
        this.maxSwallowSize = maxSwallowSize;
    }

    // ---------------------------------------------------- InputBuffer Methods
    /**
     * Read bytes.
     *
     * @return If the filter does request length control, this value is
     * significant; it should be the number of bytes consumed from the buffer,
     * up until the end of the current request body, or the buffer length,
     * whichever is greater. If the filter does not do request body length
     * control, the returned value should be -1.
     */
    @Override
    public int doRead(ByteChunk chunk, Request req) throws IOException {
        if (endChunk) {
            return -1;
        }
        checkError();
        if (needCRLFParse) {
            needCRLFParse = false;
            parseCRLF(false);
        }
        if (remaining <= 0) {
            if (!parseChunkHeader()) {
                throwIOException(sm.getString("chunkedInputFilter.invalidHeader"));
            }
            if (endChunk) {
                parseEndChunk();
                return -1;
            }
        }
        int result = 0;
        if (pos >= lastValid) {
            if (readBytes() < 0) {
                throwIOException(sm.getString("chunkedInputFilter.eos"));
            }
        }
        if (remaining > (lastValid - pos)) {
            result = lastValid - pos;
            remaining = remaining - result;
            chunk.setBytes(buf, pos, result);
            pos = lastValid;
        } else {
            result = remaining;
            chunk.setBytes(buf, pos, remaining);
            pos = pos + remaining;
            remaining = 0;
            // we need a CRLF
            if ((pos + 1) >= lastValid) {
                // if we call parseCRLF we overrun the buffer here
                // so we defer it to the next call BZ 11117
                needCRLFParse = true;
            } else {
                // parse the CRLF immediately
                parseCRLF(false);
            }
        }
        return result;
    }

    // ---------------------------------------------------- InputFilter Methods
    /**
     * Read the content length from the request.
     */
    @Override
    public void setRequest(Request request) {
        this.request = request;
    }

    /**
     * End the current request.
     */
    @Override
    public long end() throws IOException {
        long swallowed = 0;
        int read = 0;
        // Consume extra bytes : parse the stream until the end chunk is found
        while ((read = doRead(readChunk, null)) >= 0) {
            swallowed += read;
            if (maxSwallowSize > -1 && swallowed > maxSwallowSize) {
                throwIOException(sm.getString("inputFilter.maxSwallow"));
            }
        }
        // Return the number of extra bytes which were consumed
        return lastValid - pos;
    }

    /**
     * Amount of bytes still available in a buffer.
     */
    @Override
    public int available() {
        return lastValid - pos;
    }

    /**
     * Set the next buffer in the filter pipeline.
     */
    @Override
    public void setBuffer(InputBuffer buffer) {
        this.buffer = buffer;
    }

    /**
     * Make the filter ready to process the next request.
     */
    @Override
    public void recycle() {
        remaining = 0;
        pos = 0;
        lastValid = 0;
        endChunk = false;
        needCRLFParse = false;
        trailingHeaders.recycle();
        trailingHeaders.setLimit(maxTrailerSize);
        extensionSize = 0;
        error = false;
    }

    /**
     * Return the name of the replacedociated encoding; Here, the value is
     * "idenreplacedy".
     */
    @Override
    public ByteChunk getEncodingName() {
        return ENCODING;
    }

    // ------------------------------------------------------ Protected Methods
    /**
     * Read bytes from the previous buffer.
     */
    protected int readBytes() throws IOException {
        int nRead = buffer.doRead(readChunk, null);
        pos = readChunk.getStart();
        lastValid = pos + nRead;
        buf = readChunk.getBytes();
        return nRead;
    }

    /**
     * Parse the header of a chunk.
     * A chunk header can look like one of the following:<br />
     * A10CRLF<br />
     * F23;chunk-extension to be ignoredCRLF
     *
     * <p>
     * The letters before CRLF or ';' (whatever comes first) must be valid hex
     * digits. We should not parse F23IAMGONNAMESSTHISUP34CRLF as a valid
     * header according to the spec.
     */
    protected boolean parseChunkHeader() throws IOException {
        int result = 0;
        boolean eol = false;
        int readDigit = 0;
        boolean extension = false;
        while (!eol) {
            if (pos >= lastValid) {
                if (readBytes() <= 0)
                    return false;
            }
            if (buf[pos] == Constants.CR || buf[pos] == Constants.LF) {
                parseCRLF(false);
                eol = true;
            } else if (buf[pos] == Constants.SEMI_COLON && !extension) {
                // First semi-colon marks the start of the extension. Further
                // semi-colons may appear to separate multiple chunk-extensions.
                // These need to be processed as part of parsing the extensions.
                extension = true;
                extensionSize++;
            } else if (!extension) {
                // don't read data after the trailer
                int charValue = HexUtils.getDec(buf[pos]);
                if (charValue != -1 && readDigit < 8) {
                    readDigit++;
                    result = (result << 4) | charValue;
                } else {
                    // we shouldn't allow invalid, non hex characters
                    // in the chunked header
                    return false;
                }
            } else {
                // Extension 'parsing'
                // Note that the chunk-extension is neither parsed nor
                // validated. Currently it is simply ignored.
                extensionSize++;
                if (maxExtensionSize > -1 && extensionSize > maxExtensionSize) {
                    throwIOException(sm.getString("chunkedInputFilter.maxExtension"));
                }
            }
            // Parsing the CRLF increments pos
            if (!eol) {
                pos++;
            }
        }
        if (readDigit == 0 || result < 0) {
            return false;
        }
        if (result == 0) {
            endChunk = true;
        }
        remaining = result;
        return true;
    }

    /**
     * Parse CRLF at end of chunk.
     * @deprecated  Use {@link #parseCRLF(boolean)}
     */
    @Deprecated
    protected boolean parseCRLF() throws IOException {
        parseCRLF(false);
        return true;
    }

    /**
     * Parse CRLF at end of chunk.
     *
     * @param   tolerant    Should tolerant parsing (LF and CRLF) be used? This
     *                      is recommended (RFC2616, section 19.3) for message
     *                      headers.
     */
    protected void parseCRLF(boolean tolerant) throws IOException {
        boolean eol = false;
        boolean crfound = false;
        while (!eol) {
            if (pos >= lastValid) {
                if (readBytes() <= 0) {
                    throwIOException(sm.getString("chunkedInputFilter.invalidCrlfNoData"));
                }
            }
            if (buf[pos] == Constants.CR) {
                if (crfound) {
                    throwIOException(sm.getString("chunkedInputFilter.invalidCrlfCRCR"));
                }
                crfound = true;
            } else if (buf[pos] == Constants.LF) {
                if (!tolerant && !crfound) {
                    throwIOException(sm.getString("chunkedInputFilter.invalidCrlfNoCR"));
                }
                eol = true;
            } else {
                throwIOException(sm.getString("chunkedInputFilter.invalidCrlf"));
            }
            pos++;
        }
    }

    /**
     * Parse end chunk data.
     */
    protected void parseEndChunk() throws IOException {
        // Handle optional trailer headers
        while (parseHeader()) {
        // Loop until we run out of headers
        }
    }

    private boolean parseHeader() throws IOException {
        MimeHeaders headers = request.getMimeHeaders();
        byte chr = 0;
        // Read new bytes if needed
        if (pos >= lastValid) {
            if (readBytes() < 0) {
                throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
            }
        }
        chr = buf[pos];
        // CRLF terminates the request
        if (chr == Constants.CR || chr == Constants.LF) {
            parseCRLF(false);
            return false;
        }
        // Mark the current buffer position
        int startPos = trailingHeaders.getEnd();
        // 
        // Reading the header name
        // Header name is always US-ASCII
        // 
        boolean colon = false;
        while (!colon) {
            // Read new bytes if needed
            if (pos >= lastValid) {
                if (readBytes() < 0) {
                    throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
                }
            }
            chr = buf[pos];
            if ((chr >= Constants.A) && (chr <= Constants.Z)) {
                chr = (byte) (chr - Constants.LC_OFFSET);
            }
            if (chr == Constants.COLON) {
                colon = true;
            } else {
                trailingHeaders.append(chr);
            }
            pos++;
        }
        int colonPos = trailingHeaders.getEnd();
        // 
        // Reading the header value (which can be spanned over multiple lines)
        // 
        boolean eol = false;
        boolean validLine = true;
        int lastSignificantChar = 0;
        while (validLine) {
            boolean space = true;
            // Skipping spaces
            while (space) {
                // Read new bytes if needed
                if (pos >= lastValid) {
                    if (readBytes() < 0) {
                        throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
                    }
                }
                chr = buf[pos];
                if ((chr == Constants.SP) || (chr == Constants.HT)) {
                    pos++;
                    // If we swallow whitespace, make sure it counts towards the
                    // limit placed on trailing header size
                    int newlimit = trailingHeaders.getLimit() - 1;
                    if (trailingHeaders.getEnd() > newlimit) {
                        throwIOException(sm.getString("chunkedInputFilter.maxTrailer"));
                    }
                    trailingHeaders.setLimit(newlimit);
                } else {
                    space = false;
                }
            }
            // Reading bytes until the end of the line
            while (!eol) {
                // Read new bytes if needed
                if (pos >= lastValid) {
                    if (readBytes() < 0) {
                        throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
                    }
                }
                chr = buf[pos];
                if (chr == Constants.CR || chr == Constants.LF) {
                    parseCRLF(true);
                    eol = true;
                } else if (chr == Constants.SP) {
                    trailingHeaders.append(chr);
                } else {
                    trailingHeaders.append(chr);
                    lastSignificantChar = trailingHeaders.getEnd();
                }
                if (!eol) {
                    pos++;
                }
            }
            // Checking the first character of the new line. If the character
            // is a LWS, then it's a multiline header
            // Read new bytes if needed
            if (pos >= lastValid) {
                if (readBytes() < 0) {
                    throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
                }
            }
            chr = buf[pos];
            if ((chr != Constants.SP) && (chr != Constants.HT)) {
                validLine = false;
            } else {
                eol = false;
                // Copying one extra space in the buffer (since there must
                // be at least one space inserted between the lines)
                trailingHeaders.append(chr);
            }
        }
        String headerName = new String(trailingHeaders.getBytes(), startPos, colonPos - startPos, "ISO_8859_1");
        if (allowedTrailerHeaders.contains(headerName.toLowerCase(Locale.ENGLISH))) {
            MessageBytes headerValue = headers.addValue(headerName);
            // Set the header value
            headerValue.setBytes(trailingHeaders.getBytes(), colonPos, lastSignificantChar - colonPos);
        }
        return true;
    }

    private void throwIOException(String msg) throws IOException {
        error = true;
        throw new IOException(msg);
    }

    private void throwEOFException(String msg) throws IOException {
        error = true;
        throw new EOFException(msg);
    }

    private void checkError() throws IOException {
        if (error) {
            throw new IOException(sm.getString("chunkedInputFilter.error"));
        }
    }
}

16 View Complete Implementation : InputBuffer.java
Copyright Apache License 2.0
Author : apache
/**
 * The buffer used by Tomcat request. This is a derivative of the Tomcat 3.3
 * OutputBuffer, adapted to handle input instead of output. This allows
 * complete recycling of the facade objects (the ServletInputStream and the
 * BufferedReader).
 *
 * @author Remy Maucherat
 */
public clreplaced InputBuffer extends Reader implements ByteChunk.ByteInputChannel, ApplicationBufferHandler {

    /**
     * The string manager for this package.
     */
    protected static final StringManager sm = StringManager.getManager(InputBuffer.clreplaced);

    private static final Log log = LogFactory.getLog(InputBuffer.clreplaced);

    public static final int DEFAULT_BUFFER_SIZE = 8 * 1024;

    // The buffer can be used for byte[] and char[] reading
    // ( this is needed to support ServletInputStream and BufferedReader )
    public final int INITIAL_STATE = 0;

    public final int CHAR_STATE = 1;

    public final int BYTE_STATE = 2;

    /**
     * Encoder cache.
     */
    private static final Map<Charset, SynchronizedStack<B2CConverter>> encoders = new ConcurrentHashMap<>();

    // ----------------------------------------------------- Instance Variables
    /**
     * The byte buffer.
     */
    private ByteBuffer bb;

    /**
     * The char buffer.
     */
    private CharBuffer cb;

    /**
     * State of the output buffer.
     */
    private int state = 0;

    /**
     * Flag which indicates if the input buffer is closed.
     */
    private boolean closed = false;

    /**
     * Current byte to char converter.
     */
    protected B2CConverter conv;

    /**
     * replacedociated Coyote request.
     */
    private Request coyoteRequest;

    /**
     * Buffer position.
     */
    private int markPos = -1;

    /**
     * Char buffer limit.
     */
    private int readLimit;

    /**
     * Buffer size.
     */
    private final int size;

    // ----------------------------------------------------------- Constructors
    /**
     * Default constructor. Allocate the buffer with the default buffer size.
     */
    public InputBuffer() {
        this(DEFAULT_BUFFER_SIZE);
    }

    /**
     * Alternate constructor which allows specifying the initial buffer size.
     *
     * @param size Buffer size to use
     */
    public InputBuffer(int size) {
        this.size = size;
        bb = ByteBuffer.allocate(size);
        clear(bb);
        cb = CharBuffer.allocate(size);
        clear(cb);
        readLimit = size;
    }

    // ------------------------------------------------------------- Properties
    /**
     * replacedociated Coyote request.
     *
     * @param coyoteRequest replacedociated Coyote request
     */
    public void setRequest(Request coyoteRequest) {
        this.coyoteRequest = coyoteRequest;
    }

    // --------------------------------------------------------- Public Methods
    /**
     * Recycle the output buffer.
     */
    public void recycle() {
        state = INITIAL_STATE;
        // If usage of mark made the buffer too big, reallocate it
        if (cb.capacity() > size) {
            cb = CharBuffer.allocate(size);
            clear(cb);
        } else {
            clear(cb);
        }
        readLimit = size;
        markPos = -1;
        clear(bb);
        closed = false;
        if (conv != null) {
            conv.recycle();
            encoders.get(conv.getCharset()).push(conv);
            conv = null;
        }
    }

    /**
     * Close the input buffer.
     *
     * @throws IOException An underlying IOException occurred
     */
    @Override
    public void close() throws IOException {
        closed = true;
    }

    public int available() {
        int available = availableInThisBuffer();
        if (available == 0) {
            coyoteRequest.action(ActionCode.AVAILABLE, Boolean.valueOf(coyoteRequest.getReadListener() != null));
            available = (coyoteRequest.getAvailable() > 0) ? 1 : 0;
        }
        return available;
    }

    private int availableInThisBuffer() {
        int available = 0;
        if (state == BYTE_STATE) {
            available = bb.remaining();
        } else if (state == CHAR_STATE) {
            available = cb.remaining();
        }
        return available;
    }

    public void setReadListener(ReadListener listener) {
        coyoteRequest.setReadListener(listener);
        // The container is responsible for the first call to
        // listener.onDataAvailable(). If isReady() returns true, the container
        // needs to call listener.onDataAvailable() from a new thread. If
        // isReady() returns false, the socket will be registered for read and
        // the container will call listener.onDataAvailable() once data arrives.
        // Must call isFinished() first as a call to isReady() if the request
        // has been finished will register the socket for read interest and that
        // is not required.
        if (!coyoteRequest.isFinished() && isReady()) {
            coyoteRequest.action(ActionCode.DISPATCH_READ, null);
            if (!ContainerThreadMarker.isContainerThread()) {
                // Not on a container thread so need to execute the dispatch
                coyoteRequest.action(ActionCode.DISPATCH_EXECUTE, null);
            }
        }
    }

    public boolean isFinished() {
        int available = 0;
        if (state == BYTE_STATE) {
            available = bb.remaining();
        } else if (state == CHAR_STATE) {
            available = cb.remaining();
        }
        if (available > 0) {
            return false;
        } else {
            return coyoteRequest.isFinished();
        }
    }

    public boolean isReady() {
        if (coyoteRequest.getReadListener() == null) {
            if (log.isDebugEnabled()) {
                log.debug(sm.getString("inputBuffer.requiresNonBlocking"));
            }
            return false;
        }
        if (isFinished()) {
            // If this is a non-container thread, need to trigger a read
            // which will eventually lead to a call to onAllDataRead() via a
            // container thread.
            if (!ContainerThreadMarker.isContainerThread()) {
                coyoteRequest.action(ActionCode.DISPATCH_READ, null);
                coyoteRequest.action(ActionCode.DISPATCH_EXECUTE, null);
            }
            return false;
        }
        // Checking for available data at the network level and registering for
        // read can be done sequentially for HTTP/1.x and AJP as there is only
        // ever a single thread processing the socket at any one time. However,
        // for HTTP/2 there is one thread processing the connection and separate
        // threads for each stream. For HTTP/2 the two operations have to be
        // performed atomically else it is possible for the connection thread to
        // read more data in to the buffer after the stream thread checks for
        // available network data but before it registers for read.
        if (availableInThisBuffer() > 0) {
            return true;
        }
        AtomicBoolean result = new AtomicBoolean();
        coyoteRequest.action(ActionCode.NB_READ_INTEREST, result);
        return result.get();
    }

    boolean isBlocking() {
        return coyoteRequest.getReadListener() == null;
    }

    // ------------------------------------------------- Bytes Handling Methods
    /**
     * Reads new bytes in the byte chunk.
     *
     * @throws IOException An underlying IOException occurred
     */
    @Override
    public int realReadBytes() throws IOException {
        if (closed) {
            return -1;
        }
        if (coyoteRequest == null) {
            return -1;
        }
        if (state == INITIAL_STATE) {
            state = BYTE_STATE;
        }
        try {
            return coyoteRequest.doRead(this);
        } catch (IOException ioe) {
            // An IOException on a read is almost always due to
            // the remote client aborting the request.
            throw new ClientAbortException(ioe);
        }
    }

    public int readByte() throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        if (checkByteBufferEof()) {
            return -1;
        }
        return bb.get() & 0xFF;
    }

    public int read(byte[] b, int off, int len) throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        if (checkByteBufferEof()) {
            return -1;
        }
        int n = Math.min(len, bb.remaining());
        bb.get(b, off, n);
        return n;
    }

    /**
     * Transfers bytes from the buffer to the specified ByteBuffer. After the
     * operation the position of the ByteBuffer will be returned to the one
     * before the operation, the limit will be the position incremented by
     * the number of the transferred bytes.
     *
     * @param to the ByteBuffer into which bytes are to be written.
     * @return an integer specifying the actual number of bytes read, or -1 if
     *         the end of the stream is reached
     * @throws IOException if an input or output exception has occurred
     */
    public int read(ByteBuffer to) throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        if (checkByteBufferEof()) {
            return -1;
        }
        int n = Math.min(to.remaining(), bb.remaining());
        int orgLimit = bb.limit();
        bb.limit(bb.position() + n);
        to.put(bb);
        bb.limit(orgLimit);
        to.limit(to.position()).position(to.position() - n);
        return n;
    }

    // ------------------------------------------------- Chars Handling Methods
    public int realReadChars() throws IOException {
        checkConverter();
        boolean eof = false;
        if (bb.remaining() <= 0) {
            int nRead = realReadBytes();
            if (nRead < 0) {
                eof = true;
            }
        }
        if (markPos == -1) {
            clear(cb);
        } else {
            // Make sure there's enough space in the worst case
            makeSpace(bb.remaining());
            if ((cb.capacity() - cb.limit()) == 0 && bb.remaining() != 0) {
                // We went over the limit
                clear(cb);
                markPos = -1;
            }
        }
        state = CHAR_STATE;
        conv.convert(bb, cb, this, eof);
        if (cb.remaining() == 0 && eof) {
            return -1;
        } else {
            return cb.remaining();
        }
    }

    @Override
    public int read() throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        if (checkCharBufferEof()) {
            return -1;
        }
        return cb.get();
    }

    @Override
    public int read(char[] cbuf) throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        return read(cbuf, 0, cbuf.length);
    }

    @Override
    public int read(char[] cbuf, int off, int len) throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        if (checkCharBufferEof()) {
            return -1;
        }
        int n = Math.min(len, cb.remaining());
        cb.get(cbuf, off, n);
        return n;
    }

    @Override
    public long skip(long n) throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        if (n < 0) {
            throw new IllegalArgumentException();
        }
        long nRead = 0;
        while (nRead < n) {
            if (cb.remaining() >= n) {
                cb.position(cb.position() + (int) n);
                nRead = n;
            } else {
                nRead += cb.remaining();
                cb.position(cb.limit());
                int nb = realReadChars();
                if (nb < 0) {
                    break;
                }
            }
        }
        return nRead;
    }

    @Override
    public boolean ready() throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        if (state == INITIAL_STATE) {
            state = CHAR_STATE;
        }
        return (available() > 0);
    }

    @Override
    public boolean markSupported() {
        return true;
    }

    @Override
    public void mark(int readAheadLimit) throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        if (cb.remaining() <= 0) {
            clear(cb);
        } else {
            if ((cb.capacity() > (2 * size)) && (cb.remaining()) < (cb.position())) {
                cb.compact();
                cb.flip();
            }
        }
        readLimit = cb.position() + readAheadLimit + size;
        markPos = cb.position();
    }

    @Override
    public void reset() throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        if (state == CHAR_STATE) {
            if (markPos < 0) {
                clear(cb);
                markPos = -1;
                throw new IOException();
            } else {
                cb.position(markPos);
            }
        } else {
            clear(bb);
        }
    }

    public void checkConverter() throws IOException {
        if (conv != null) {
            return;
        }
        Charset charset = null;
        if (coyoteRequest != null) {
            charset = coyoteRequest.getCharset();
        }
        if (charset == null) {
            charset = org.apache.coyote.Constants.DEFAULT_BODY_CHARSET;
        }
        SynchronizedStack<B2CConverter> stack = encoders.get(charset);
        if (stack == null) {
            stack = new SynchronizedStack<>();
            encoders.putIfAbsent(charset, stack);
            stack = encoders.get(charset);
        }
        conv = stack.pop();
        if (conv == null) {
            conv = createConverter(charset);
        }
    }

    private static B2CConverter createConverter(Charset charset) throws IOException {
        if (SecurityUtil.isPackageProtectionEnabled()) {
            try {
                return AccessController.doPrivileged(new PrivilegedCreateConverter(charset));
            } catch (PrivilegedActionException ex) {
                Exception e = ex.getException();
                if (e instanceof IOException) {
                    throw (IOException) e;
                } else {
                    throw new IOException(e);
                }
            }
        } else {
            return new B2CConverter(charset);
        }
    }

    @Override
    public void setByteBuffer(ByteBuffer buffer) {
        bb = buffer;
    }

    @Override
    public ByteBuffer getByteBuffer() {
        return bb;
    }

    @Override
    public void expand(int size) {
    // no-op
    }

    private boolean checkByteBufferEof() throws IOException {
        if (bb.remaining() == 0) {
            int n = realReadBytes();
            if (n < 0) {
                return true;
            }
        }
        return false;
    }

    private boolean checkCharBufferEof() throws IOException {
        if (cb.remaining() == 0) {
            int n = realReadChars();
            if (n < 0) {
                return true;
            }
        }
        return false;
    }

    private void clear(Buffer buffer) {
        buffer.rewind().limit(0);
    }

    private void makeSpace(int count) {
        int desiredSize = cb.limit() + count;
        if (desiredSize > readLimit) {
            desiredSize = readLimit;
        }
        if (desiredSize <= cb.capacity()) {
            return;
        }
        int newSize = 2 * cb.capacity();
        if (desiredSize >= newSize) {
            newSize = 2 * cb.capacity() + count;
        }
        if (newSize > readLimit) {
            newSize = readLimit;
        }
        CharBuffer tmp = CharBuffer.allocate(newSize);
        int oldPosition = cb.position();
        cb.position(0);
        tmp.put(cb);
        tmp.flip();
        tmp.position(oldPosition);
        cb = tmp;
        tmp = null;
    }

    private static clreplaced PrivilegedCreateConverter implements PrivilegedExceptionAction<B2CConverter> {

        private final Charset charset;

        public PrivilegedCreateConverter(Charset charset) {
            this.charset = charset;
        }

        @Override
        public B2CConverter run() throws IOException {
            return new B2CConverter(charset);
        }
    }
}

16 View Complete Implementation : ChunkedInputFilter.java
Copyright Apache License 2.0
Author : apache
/**
 * Chunked input filter. Parses chunked data according to
 * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1">http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1</a><br>
 *
 * @author Remy Maucherat
 */
public clreplaced ChunkedInputFilter implements InputFilter, ApplicationBufferHandler {

    private static final StringManager sm = StringManager.getManager(ChunkedInputFilter.clreplaced.getPackage().getName());

    // -------------------------------------------------------------- Constants
    protected static final String ENCODING_NAME = "chunked";

    protected static final ByteChunk ENCODING = new ByteChunk();

    // ----------------------------------------------------- Static Initializer
    static {
        ENCODING.setBytes(ENCODING_NAME.getBytes(StandardCharsets.ISO_8859_1), 0, ENCODING_NAME.length());
    }

    // ----------------------------------------------------- Instance Variables
    /**
     * Next buffer in the pipeline.
     */
    protected InputBuffer buffer;

    /**
     * Number of bytes remaining in the current chunk.
     */
    protected int remaining = 0;

    /**
     * Byte chunk used to read bytes.
     */
    protected ByteBuffer readChunk;

    /**
     * Flag set to true when the end chunk has been read.
     */
    protected boolean endChunk = false;

    /**
     * Byte chunk used to store trailing headers.
     */
    protected final ByteChunk trailingHeaders = new ByteChunk();

    /**
     * Flag set to true if the next call to doRead() must parse a CRLF pair
     * before doing anything else.
     */
    protected boolean needCRLFParse = false;

    /**
     * Request being parsed.
     */
    private Request request;

    /**
     * Limit for extension size.
     */
    private final long maxExtensionSize;

    /**
     * Limit for trailer size.
     */
    private final int maxTrailerSize;

    /**
     * Size of extensions processed for this request.
     */
    private long extensionSize;

    private final int maxSwallowSize;

    /**
     * Flag that indicates if an error has occurred.
     */
    private boolean error;

    private final Set<String> allowedTrailerHeaders;

    // ----------------------------------------------------------- Constructors
    public ChunkedInputFilter(int maxTrailerSize, Set<String> allowedTrailerHeaders, int maxExtensionSize, int maxSwallowSize) {
        this.trailingHeaders.setLimit(maxTrailerSize);
        this.allowedTrailerHeaders = allowedTrailerHeaders;
        this.maxExtensionSize = maxExtensionSize;
        this.maxTrailerSize = maxTrailerSize;
        this.maxSwallowSize = maxSwallowSize;
    }

    // ---------------------------------------------------- InputBuffer Methods
    @Override
    public int doRead(ApplicationBufferHandler handler) throws IOException {
        if (endChunk) {
            return -1;
        }
        checkError();
        if (needCRLFParse) {
            needCRLFParse = false;
            parseCRLF(false);
        }
        if (remaining <= 0) {
            if (!parseChunkHeader()) {
                throwIOException(sm.getString("chunkedInputFilter.invalidHeader"));
            }
            if (endChunk) {
                parseEndChunk();
                return -1;
            }
        }
        int result = 0;
        if (readChunk == null || readChunk.position() >= readChunk.limit()) {
            if (readBytes() < 0) {
                throwIOException(sm.getString("chunkedInputFilter.eos"));
            }
        }
        if (remaining > readChunk.remaining()) {
            result = readChunk.remaining();
            remaining = remaining - result;
            if (readChunk != handler.getByteBuffer()) {
                handler.setByteBuffer(readChunk.duplicate());
            }
            readChunk.position(readChunk.limit());
        } else {
            result = remaining;
            if (readChunk != handler.getByteBuffer()) {
                handler.setByteBuffer(readChunk.duplicate());
                handler.getByteBuffer().limit(readChunk.position() + remaining);
            }
            readChunk.position(readChunk.position() + remaining);
            remaining = 0;
            // we need a CRLF
            if ((readChunk.position() + 1) >= readChunk.limit()) {
                // if we call parseCRLF we overrun the buffer here
                // so we defer it to the next call BZ 11117
                needCRLFParse = true;
            } else {
                // parse the CRLF immediately
                parseCRLF(false);
            }
        }
        return result;
    }

    // ---------------------------------------------------- InputFilter Methods
    /**
     * Read the content length from the request.
     */
    @Override
    public void setRequest(Request request) {
        this.request = request;
    }

    /**
     * End the current request.
     */
    @Override
    public long end() throws IOException {
        long swallowed = 0;
        int read = 0;
        // Consume extra bytes : parse the stream until the end chunk is found
        while ((read = doRead(this)) >= 0) {
            swallowed += read;
            if (maxSwallowSize > -1 && swallowed > maxSwallowSize) {
                throwIOException(sm.getString("inputFilter.maxSwallow"));
            }
        }
        // Return the number of extra bytes which were consumed
        return readChunk.remaining();
    }

    /**
     * Amount of bytes still available in a buffer.
     */
    @Override
    public int available() {
        return readChunk != null ? readChunk.remaining() : 0;
    }

    /**
     * Set the next buffer in the filter pipeline.
     */
    @Override
    public void setBuffer(InputBuffer buffer) {
        this.buffer = buffer;
    }

    /**
     * Make the filter ready to process the next request.
     */
    @Override
    public void recycle() {
        remaining = 0;
        if (readChunk != null) {
            readChunk.position(0).limit(0);
        }
        endChunk = false;
        needCRLFParse = false;
        trailingHeaders.recycle();
        trailingHeaders.setLimit(maxTrailerSize);
        extensionSize = 0;
        error = false;
    }

    /**
     * Return the name of the replacedociated encoding; Here, the value is
     * "idenreplacedy".
     */
    @Override
    public ByteChunk getEncodingName() {
        return ENCODING;
    }

    @Override
    public boolean isFinished() {
        return endChunk;
    }

    // ------------------------------------------------------ Protected Methods
    /**
     * Read bytes from the previous buffer.
     * @return The byte count which has been read
     * @throws IOException Read error
     */
    protected int readBytes() throws IOException {
        return buffer.doRead(this);
    }

    /**
     * Parse the header of a chunk.
     * A chunk header can look like one of the following:<br>
     * A10CRLF<br>
     * F23;chunk-extension to be ignoredCRLF
     *
     * <p>
     * The letters before CRLF or ';' (whatever comes first) must be valid hex
     * digits. We should not parse F23IAMGONNAMESSTHISUP34CRLF as a valid
     * header according to the spec.
     * @return <code>true</code> if the chunk header has been
     *  successfully parsed
     * @throws IOException Read error
     */
    protected boolean parseChunkHeader() throws IOException {
        int result = 0;
        boolean eol = false;
        int readDigit = 0;
        boolean extension = false;
        while (!eol) {
            if (readChunk == null || readChunk.position() >= readChunk.limit()) {
                if (readBytes() <= 0)
                    return false;
            }
            byte chr = readChunk.get(readChunk.position());
            if (chr == Constants.CR || chr == Constants.LF) {
                parseCRLF(false);
                eol = true;
            } else if (chr == Constants.SEMI_COLON && !extension) {
                // First semi-colon marks the start of the extension. Further
                // semi-colons may appear to separate multiple chunk-extensions.
                // These need to be processed as part of parsing the extensions.
                extension = true;
                extensionSize++;
            } else if (!extension) {
                // don't read data after the trailer
                int charValue = HexUtils.getDec(chr);
                if (charValue != -1 && readDigit < 8) {
                    readDigit++;
                    result = (result << 4) | charValue;
                } else {
                    // we shouldn't allow invalid, non hex characters
                    // in the chunked header
                    return false;
                }
            } else {
                // Extension 'parsing'
                // Note that the chunk-extension is neither parsed nor
                // validated. Currently it is simply ignored.
                extensionSize++;
                if (maxExtensionSize > -1 && extensionSize > maxExtensionSize) {
                    throwIOException(sm.getString("chunkedInputFilter.maxExtension"));
                }
            }
            // Parsing the CRLF increments pos
            if (!eol) {
                readChunk.position(readChunk.position() + 1);
            }
        }
        if (readDigit == 0 || result < 0) {
            return false;
        }
        if (result == 0) {
            endChunk = true;
        }
        remaining = result;
        return true;
    }

    /**
     * Parse CRLF at end of chunk.
     *
     * @param   tolerant    Should tolerant parsing (LF and CRLF) be used? This
     *                      is recommended (RFC2616, section 19.3) for message
     *                      headers.
     * @throws IOException An error occurred parsing CRLF
     */
    protected void parseCRLF(boolean tolerant) throws IOException {
        boolean eol = false;
        boolean crfound = false;
        while (!eol) {
            if (readChunk == null || readChunk.position() >= readChunk.limit()) {
                if (readBytes() <= 0) {
                    throwIOException(sm.getString("chunkedInputFilter.invalidCrlfNoData"));
                }
            }
            byte chr = readChunk.get(readChunk.position());
            if (chr == Constants.CR) {
                if (crfound) {
                    throwIOException(sm.getString("chunkedInputFilter.invalidCrlfCRCR"));
                }
                crfound = true;
            } else if (chr == Constants.LF) {
                if (!tolerant && !crfound) {
                    throwIOException(sm.getString("chunkedInputFilter.invalidCrlfNoCR"));
                }
                eol = true;
            } else {
                throwIOException(sm.getString("chunkedInputFilter.invalidCrlf"));
            }
            readChunk.position(readChunk.position() + 1);
        }
    }

    /**
     * Parse end chunk data.
     * @throws IOException Error propagation
     */
    protected void parseEndChunk() throws IOException {
        // Handle optional trailer headers
        while (parseHeader()) {
        // Loop until we run out of headers
        }
    }

    private boolean parseHeader() throws IOException {
        Map<String, String> headers = request.getTrailerFields();
        byte chr = 0;
        // Read new bytes if needed
        if (readChunk == null || readChunk.position() >= readChunk.limit()) {
            if (readBytes() < 0) {
                throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
            }
        }
        // readBytes() above will set readChunk unless it returns a value < 0
        chr = readChunk.get(readChunk.position());
        // CRLF terminates the request
        if (chr == Constants.CR || chr == Constants.LF) {
            parseCRLF(false);
            return false;
        }
        // Mark the current buffer position
        int startPos = trailingHeaders.getEnd();
        // 
        // Reading the header name
        // Header name is always US-ASCII
        // 
        boolean colon = false;
        while (!colon) {
            // Read new bytes if needed
            if (readChunk == null || readChunk.position() >= readChunk.limit()) {
                if (readBytes() < 0) {
                    throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
                }
            }
            // readBytes() above will set readChunk unless it returns a value < 0
            chr = readChunk.get(readChunk.position());
            if ((chr >= Constants.A) && (chr <= Constants.Z)) {
                chr = (byte) (chr - Constants.LC_OFFSET);
            }
            if (chr == Constants.COLON) {
                colon = true;
            } else {
                trailingHeaders.append(chr);
            }
            readChunk.position(readChunk.position() + 1);
        }
        int colonPos = trailingHeaders.getEnd();
        // 
        // Reading the header value (which can be spanned over multiple lines)
        // 
        boolean eol = false;
        boolean validLine = true;
        int lastSignificantChar = 0;
        while (validLine) {
            boolean space = true;
            // Skipping spaces
            while (space) {
                // Read new bytes if needed
                if (readChunk == null || readChunk.position() >= readChunk.limit()) {
                    if (readBytes() < 0) {
                        throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
                    }
                }
                chr = readChunk.get(readChunk.position());
                if ((chr == Constants.SP) || (chr == Constants.HT)) {
                    readChunk.position(readChunk.position() + 1);
                    // If we swallow whitespace, make sure it counts towards the
                    // limit placed on trailing header size
                    int newlimit = trailingHeaders.getLimit() - 1;
                    if (trailingHeaders.getEnd() > newlimit) {
                        throwIOException(sm.getString("chunkedInputFilter.maxTrailer"));
                    }
                    trailingHeaders.setLimit(newlimit);
                } else {
                    space = false;
                }
            }
            // Reading bytes until the end of the line
            while (!eol) {
                // Read new bytes if needed
                if (readChunk == null || readChunk.position() >= readChunk.limit()) {
                    if (readBytes() < 0) {
                        throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
                    }
                }
                chr = readChunk.get(readChunk.position());
                if (chr == Constants.CR || chr == Constants.LF) {
                    parseCRLF(true);
                    eol = true;
                } else if (chr == Constants.SP) {
                    trailingHeaders.append(chr);
                } else {
                    trailingHeaders.append(chr);
                    lastSignificantChar = trailingHeaders.getEnd();
                }
                if (!eol) {
                    readChunk.position(readChunk.position() + 1);
                }
            }
            // Checking the first character of the new line. If the character
            // is a LWS, then it's a multiline header
            // Read new bytes if needed
            if (readChunk == null || readChunk.position() >= readChunk.limit()) {
                if (readBytes() < 0) {
                    throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
                }
            }
            chr = readChunk.get(readChunk.position());
            if ((chr != Constants.SP) && (chr != Constants.HT)) {
                validLine = false;
            } else {
                eol = false;
                // Copying one extra space in the buffer (since there must
                // be at least one space inserted between the lines)
                trailingHeaders.append(chr);
            }
        }
        String headerName = new String(trailingHeaders.getBytes(), startPos, colonPos - startPos, StandardCharsets.ISO_8859_1);
        headerName = headerName.toLowerCase(Locale.ENGLISH);
        if (allowedTrailerHeaders.contains(headerName)) {
            String value = new String(trailingHeaders.getBytes(), colonPos, lastSignificantChar - colonPos, StandardCharsets.ISO_8859_1);
            headers.put(headerName, value);
        }
        return true;
    }

    private void throwIOException(String msg) throws IOException {
        error = true;
        throw new IOException(msg);
    }

    private void throwEOFException(String msg) throws IOException {
        error = true;
        throw new EOFException(msg);
    }

    private void checkError() throws IOException {
        if (error) {
            throw new IOException(sm.getString("chunkedInputFilter.error"));
        }
    }

    @Override
    public void setByteBuffer(ByteBuffer buffer) {
        readChunk = buffer;
    }

    @Override
    public ByteBuffer getByteBuffer() {
        return readChunk;
    }

    @Override
    public void expand(int size) {
    // no-op
    }
}

16 View Complete Implementation : ChunkedInputFilter.java
Copyright Apache License 2.0
Author : how2j
/**
 * Chunked input filter. Parses chunked data according to
 * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1">http
 * ://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1</a><br>
 *
 * @author Remy Maucherat
 * @author Filip Hanik
 */
public clreplaced ChunkedInputFilter implements InputFilter {

    private static final StringManager sm = StringManager.getManager(ChunkedInputFilter.clreplaced.getPackage().getName());

    // -------------------------------------------------------------- Constants
    protected static final String ENCODING_NAME = "chunked";

    protected static final ByteChunk ENCODING = new ByteChunk();

    // ----------------------------------------------------- Static Initializer
    static {
        ENCODING.setBytes(ENCODING_NAME.getBytes(Charset.defaultCharset()), 0, ENCODING_NAME.length());
    }

    // ----------------------------------------------------- Instance Variables
    /**
     * Next buffer in the pipeline.
     */
    protected InputBuffer buffer;

    /**
     * Number of bytes remaining in the current chunk.
     */
    protected int remaining = 0;

    /**
     * Position in the buffer.
     */
    protected int pos = 0;

    /**
     * Last valid byte in the buffer.
     */
    protected int lastValid = 0;

    /**
     * Read bytes buffer.
     */
    protected byte[] buf = null;

    /**
     * Byte chunk used to read bytes.
     */
    protected ByteChunk readChunk = new ByteChunk();

    /**
     * Flag set to true when the end chunk has been read.
     */
    protected boolean endChunk = false;

    /**
     * Byte chunk used to store trailing headers.
     */
    protected ByteChunk trailingHeaders = new ByteChunk();

    /**
     * Flag set to true if the next call to doRead() must parse a CRLF pair
     * before doing anything else.
     */
    protected boolean needCRLFParse = false;

    /**
     * Request being parsed.
     */
    private Request request;

    /**
     * Limit for extension size.
     */
    private final long maxExtensionSize;

    /**
     * Limit for trailer size.
     */
    private final int maxTrailerSize;

    /**
     * Size of extensions processed for this request.
     */
    private long extensionSize;

    private final int maxSwallowSize;

    /**
     * Flag that indicates if an error has occurred.
     */
    private boolean error;

    private final Set<String> allowedTrailerHeaders;

    // ----------------------------------------------------------- Constructors
    public ChunkedInputFilter(int maxTrailerSize, Set<String> allowedTrailerHeaders, int maxExtensionSize, int maxSwallowSize) {
        this.trailingHeaders.setLimit(maxTrailerSize);
        this.allowedTrailerHeaders = allowedTrailerHeaders;
        this.maxExtensionSize = maxExtensionSize;
        this.maxTrailerSize = maxTrailerSize;
        this.maxSwallowSize = maxSwallowSize;
    }

    // ---------------------------------------------------- InputBuffer Methods
    /**
     * Read bytes.
     *
     * @return If the filter does request length control, this value is
     *         significant; it should be the number of bytes consumed from the
     *         buffer, up until the end of the current request body, or the
     *         buffer length, whichever is greater. If the filter does not do
     *         request body length control, the returned value should be -1.
     */
    @Override
    public int doRead(ByteChunk chunk, Request req) throws IOException {
        if (endChunk) {
            return -1;
        }
        checkError();
        if (needCRLFParse) {
            needCRLFParse = false;
            parseCRLF(false);
        }
        if (remaining <= 0) {
            if (!parseChunkHeader()) {
                throwIOException(sm.getString("chunkedInputFilter.invalidHeader"));
            }
            if (endChunk) {
                parseEndChunk();
                return -1;
            }
        }
        int result = 0;
        if (pos >= lastValid) {
            if (readBytes() < 0) {
                throwIOException(sm.getString("chunkedInputFilter.eos"));
            }
        }
        if (remaining > (lastValid - pos)) {
            result = lastValid - pos;
            remaining = remaining - result;
            chunk.setBytes(buf, pos, result);
            pos = lastValid;
        } else {
            result = remaining;
            chunk.setBytes(buf, pos, remaining);
            pos = pos + remaining;
            remaining = 0;
            // we need a CRLF
            if ((pos + 1) >= lastValid) {
                // if we call parseCRLF we overrun the buffer here
                // so we defer it to the next call BZ 11117
                needCRLFParse = true;
            } else {
                // parse the CRLF immediately
                parseCRLF(false);
            }
        }
        return result;
    }

    // ---------------------------------------------------- InputFilter Methods
    /**
     * Read the content length from the request.
     */
    @Override
    public void setRequest(Request request) {
        this.request = request;
    }

    /**
     * End the current request.
     */
    @Override
    public long end() throws IOException {
        long swallowed = 0;
        int read = 0;
        // Consume extra bytes : parse the stream until the end chunk is found
        while ((read = doRead(readChunk, null)) >= 0) {
            swallowed += read;
            if (maxSwallowSize > -1 && swallowed > maxSwallowSize) {
                throwIOException(sm.getString("inputFilter.maxSwallow"));
            }
        }
        // Return the number of extra bytes which were consumed
        return lastValid - pos;
    }

    /**
     * Amount of bytes still available in a buffer.
     */
    @Override
    public int available() {
        return lastValid - pos;
    }

    /**
     * Set the next buffer in the filter pipeline.
     */
    @Override
    public void setBuffer(InputBuffer buffer) {
        this.buffer = buffer;
    }

    /**
     * Make the filter ready to process the next request.
     */
    @Override
    public void recycle() {
        remaining = 0;
        pos = 0;
        lastValid = 0;
        endChunk = false;
        needCRLFParse = false;
        trailingHeaders.recycle();
        trailingHeaders.setLimit(maxTrailerSize);
        extensionSize = 0;
        error = false;
    }

    /**
     * Return the name of the replacedociated encoding; Here, the value is
     * "idenreplacedy".
     */
    @Override
    public ByteChunk getEncodingName() {
        return ENCODING;
    }

    // ------------------------------------------------------ Protected Methods
    /**
     * Read bytes from the previous buffer.
     */
    protected int readBytes() throws IOException {
        int nRead = buffer.doRead(readChunk, null);
        pos = readChunk.getStart();
        lastValid = pos + nRead;
        buf = readChunk.getBytes();
        return nRead;
    }

    /**
     * Parse the header of a chunk. A chunk header can look like one of the
     * following:<br />
     * A10CRLF<br />
     * F23;chunk-extension to be ignoredCRLF
     *
     * <p>
     * The letters before CRLF or ';' (whatever comes first) must be valid hex
     * digits. We should not parse F23IAMGONNAMESSTHISUP34CRLF as a valid header
     * according to the spec.
     */
    protected boolean parseChunkHeader() throws IOException {
        int result = 0;
        boolean eol = false;
        int readDigit = 0;
        boolean extension = false;
        while (!eol) {
            if (pos >= lastValid) {
                if (readBytes() <= 0)
                    return false;
            }
            if (buf[pos] == Constants.CR || buf[pos] == Constants.LF) {
                parseCRLF(false);
                eol = true;
            } else if (buf[pos] == Constants.SEMI_COLON && !extension) {
                // First semi-colon marks the start of the extension. Further
                // semi-colons may appear to separate multiple chunk-extensions.
                // These need to be processed as part of parsing the extensions.
                extension = true;
                extensionSize++;
            } else if (!extension) {
                // don't read data after the trailer
                int charValue = HexUtils.getDec(buf[pos]);
                if (charValue != -1 && readDigit < 8) {
                    readDigit++;
                    result = (result << 4) | charValue;
                } else {
                    // we shouldn't allow invalid, non hex characters
                    // in the chunked header
                    return false;
                }
            } else {
                // Extension 'parsing'
                // Note that the chunk-extension is neither parsed nor
                // validated. Currently it is simply ignored.
                extensionSize++;
                if (maxExtensionSize > -1 && extensionSize > maxExtensionSize) {
                    throwIOException(sm.getString("chunkedInputFilter.maxExtension"));
                }
            }
            // Parsing the CRLF increments pos
            if (!eol) {
                pos++;
            }
        }
        if (readDigit == 0 || result < 0) {
            return false;
        }
        if (result == 0) {
            endChunk = true;
        }
        remaining = result;
        return true;
    }

    /**
     * Parse CRLF at end of chunk.
     *
     * @deprecated Use {@link #parseCRLF(boolean)}
     */
    @Deprecated
    protected boolean parseCRLF() throws IOException {
        parseCRLF(false);
        return true;
    }

    /**
     * Parse CRLF at end of chunk.
     *
     * @param tolerant
     *            Should tolerant parsing (LF and CRLF) be used? This is
     *            recommended (RFC2616, section 19.3) for message headers.
     */
    protected void parseCRLF(boolean tolerant) throws IOException {
        boolean eol = false;
        boolean crfound = false;
        while (!eol) {
            if (pos >= lastValid) {
                if (readBytes() <= 0) {
                    throwIOException(sm.getString("chunkedInputFilter.invalidCrlfNoData"));
                }
            }
            if (buf[pos] == Constants.CR) {
                if (crfound) {
                    throwIOException(sm.getString("chunkedInputFilter.invalidCrlfCRCR"));
                }
                crfound = true;
            } else if (buf[pos] == Constants.LF) {
                if (!tolerant && !crfound) {
                    throwIOException(sm.getString("chunkedInputFilter.invalidCrlfNoCR"));
                }
                eol = true;
            } else {
                throwIOException(sm.getString("chunkedInputFilter.invalidCrlf"));
            }
            pos++;
        }
    }

    /**
     * Parse end chunk data.
     */
    protected void parseEndChunk() throws IOException {
        // Handle optional trailer headers
        while (parseHeader()) {
        // Loop until we run out of headers
        }
    }

    private boolean parseHeader() throws IOException {
        MimeHeaders headers = request.getMimeHeaders();
        byte chr = 0;
        // Read new bytes if needed
        if (pos >= lastValid) {
            if (readBytes() < 0) {
                throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
            }
        }
        chr = buf[pos];
        // CRLF terminates the request
        if (chr == Constants.CR || chr == Constants.LF) {
            parseCRLF(false);
            return false;
        }
        // Mark the current buffer position
        int startPos = trailingHeaders.getEnd();
        // 
        // Reading the header name
        // Header name is always US-ASCII
        // 
        boolean colon = false;
        while (!colon) {
            // Read new bytes if needed
            if (pos >= lastValid) {
                if (readBytes() < 0) {
                    throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
                }
            }
            chr = buf[pos];
            if ((chr >= Constants.A) && (chr <= Constants.Z)) {
                chr = (byte) (chr - Constants.LC_OFFSET);
            }
            if (chr == Constants.COLON) {
                colon = true;
            } else {
                trailingHeaders.append(chr);
            }
            pos++;
        }
        int colonPos = trailingHeaders.getEnd();
        // 
        // Reading the header value (which can be spanned over multiple lines)
        // 
        boolean eol = false;
        boolean validLine = true;
        int lastSignificantChar = 0;
        while (validLine) {
            boolean space = true;
            // Skipping spaces
            while (space) {
                // Read new bytes if needed
                if (pos >= lastValid) {
                    if (readBytes() < 0) {
                        throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
                    }
                }
                chr = buf[pos];
                if ((chr == Constants.SP) || (chr == Constants.HT)) {
                    pos++;
                    // If we swallow whitespace, make sure it counts towards the
                    // limit placed on trailing header size
                    int newlimit = trailingHeaders.getLimit() - 1;
                    if (trailingHeaders.getEnd() > newlimit) {
                        throwIOException(sm.getString("chunkedInputFilter.maxTrailer"));
                    }
                    trailingHeaders.setLimit(newlimit);
                } else {
                    space = false;
                }
            }
            // Reading bytes until the end of the line
            while (!eol) {
                // Read new bytes if needed
                if (pos >= lastValid) {
                    if (readBytes() < 0) {
                        throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
                    }
                }
                chr = buf[pos];
                if (chr == Constants.CR || chr == Constants.LF) {
                    parseCRLF(true);
                    eol = true;
                } else if (chr == Constants.SP) {
                    trailingHeaders.append(chr);
                } else {
                    trailingHeaders.append(chr);
                    lastSignificantChar = trailingHeaders.getEnd();
                }
                if (!eol) {
                    pos++;
                }
            }
            // Checking the first character of the new line. If the character
            // is a LWS, then it's a multiline header
            // Read new bytes if needed
            if (pos >= lastValid) {
                if (readBytes() < 0) {
                    throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
                }
            }
            chr = buf[pos];
            if ((chr != Constants.SP) && (chr != Constants.HT)) {
                validLine = false;
            } else {
                eol = false;
                // Copying one extra space in the buffer (since there must
                // be at least one space inserted between the lines)
                trailingHeaders.append(chr);
            }
        }
        String headerName = new String(trailingHeaders.getBytes(), startPos, colonPos - startPos, "ISO_8859_1");
        if (allowedTrailerHeaders.contains(headerName.toLowerCase(Locale.ENGLISH))) {
            MessageBytes headerValue = headers.addValue(headerName);
            // Set the header value
            headerValue.setBytes(trailingHeaders.getBytes(), colonPos, lastSignificantChar - colonPos);
        }
        return true;
    }

    private void throwIOException(String msg) throws IOException {
        error = true;
        throw new IOException(msg);
    }

    private void throwEOFException(String msg) throws IOException {
        error = true;
        throw new EOFException(msg);
    }

    private void checkError() throws IOException {
        if (error) {
            throw new IOException(sm.getString("chunkedInputFilter.error"));
        }
    }
}

16 View Complete Implementation : Stream.java
Copyright Apache License 2.0
Author : apache
clreplaced Stream extends AbstractStream implements HeaderEmitter {

    private static final Log log = LogFactory.getLog(Stream.clreplaced);

    private static final StringManager sm = StringManager.getManager(Stream.clreplaced);

    private static final int HEADER_STATE_START = 0;

    private static final int HEADER_STATE_PSEUDO = 1;

    private static final int HEADER_STATE_REGULAR = 2;

    private static final int HEADER_STATE_TRAILER = 3;

    private static final MimeHeaders ACK_HEADERS;

    private static final Integer HTTP_UPGRADE_STREAM = Integer.valueOf(1);

    static {
        Response response = new Response();
        response.setStatus(100);
        StreamProcessor.prepareHeaders(null, response, true, null, null);
        ACK_HEADERS = response.getMimeHeaders();
    }

    private volatile int weight = Constants.DEFAULT_WEIGHT;

    private volatile long contentLengthReceived = 0;

    private final Http2UpgradeHandler handler;

    private final StreamStateMachine state;

    private final WindowAllocationManager allocationManager = new WindowAllocationManager(this);

    // State machine would be too much overhead
    private int headerState = HEADER_STATE_START;

    private StreamException headerException = null;

    // TODO: null these when finished to reduce memory used by closed stream
    private final Request coyoteRequest;

    private StringBuilder cookieHeader = null;

    private final Response coyoteResponse = new Response();

    private final StreamInputBuffer inputBuffer;

    private final StreamOutputBuffer streamOutputBuffer = new StreamOutputBuffer();

    private final Http2OutputBuffer http2OutputBuffer = new Http2OutputBuffer(coyoteResponse, streamOutputBuffer);

    Stream(Integer identifier, Http2UpgradeHandler handler) {
        this(identifier, handler, null);
    }

    Stream(Integer identifier, Http2UpgradeHandler handler, Request coyoteRequest) {
        super(identifier);
        this.handler = handler;
        handler.addChild(this);
        setWindowSize(handler.getRemoteSettings().getInitialWindowSize());
        state = new StreamStateMachine(this);
        if (coyoteRequest == null) {
            // HTTP/2 new request
            this.coyoteRequest = new Request();
            this.inputBuffer = new StreamInputBuffer();
            this.coyoteRequest.setInputBuffer(inputBuffer);
        } else {
            // HTTP/2 Push or HTTP/1.1 upgrade
            this.coyoteRequest = coyoteRequest;
            this.inputBuffer = null;
            // Headers have been read by this point
            state.receivedStartOfHeaders();
            if (HTTP_UPGRADE_STREAM.equals(identifier)) {
                // Populate coyoteRequest from headers (HTTP/1.1 only)
                try {
                    prepareRequest();
                } catch (IllegalArgumentException iae) {
                    // Something in the headers is invalid
                    // Set correct return status
                    coyoteResponse.setStatus(400);
                    // Set error flag. This triggers error processing rather than
                    // the normal mapping
                    coyoteResponse.setError();
                }
            }
            // TODO replaceduming the body has been read at this point is not valid
            state.receivedEndOfStream();
        }
        this.coyoteRequest.setSendfile(handler.hasAsyncIO() && handler.getProtocol().getUseSendfile());
        this.coyoteResponse.setOutputBuffer(http2OutputBuffer);
        this.coyoteRequest.setResponse(coyoteResponse);
        this.coyoteRequest.protocol().setString("HTTP/2.0");
        if (this.coyoteRequest.getStartTime() < 0) {
            this.coyoteRequest.setStartTime(System.currentTimeMillis());
        }
    }

    private void prepareRequest() {
        MessageBytes hostValueMB = coyoteRequest.getMimeHeaders().getUniqueValue("host");
        if (hostValueMB == null) {
            throw new IllegalArgumentException();
        }
        // This processing expects bytes. Server push will have used a String
        // to trigger a conversion if required.
        hostValueMB.toBytes();
        ByteChunk valueBC = hostValueMB.getByteChunk();
        byte[] valueB = valueBC.getBytes();
        int valueL = valueBC.getLength();
        int valueS = valueBC.getStart();
        int colonPos = Host.parse(hostValueMB);
        if (colonPos != -1) {
            int port = 0;
            for (int i = colonPos + 1; i < valueL; i++) {
                char c = (char) valueB[i + valueS];
                if (c < '0' || c > '9') {
                    throw new IllegalArgumentException();
                }
                port = port * 10 + c - '0';
            }
            coyoteRequest.setServerPort(port);
            // Only need to copy the host name up to the :
            valueL = colonPos;
        }
        // Extract the host name
        char[] hostNameC = new char[valueL];
        for (int i = 0; i < valueL; i++) {
            hostNameC[i] = (char) valueB[i + valueS];
        }
        coyoteRequest.serverName().setChars(hostNameC, 0, valueL);
    }

    final void rePrioritise(AbstractStream parent, boolean exclusive, int weight) {
        if (log.isDebugEnabled()) {
            log.debug(sm.getString("stream.reprioritisation.debug", getConnectionId(), getIdentifier(), Boolean.toString(exclusive), parent.getIdentifier(), Integer.toString(weight)));
        }
        // Check if new parent is a descendant of this stream
        if (isDescendant(parent)) {
            parent.detachFromParent();
            // Cast is always safe since any descendant of this stream must be
            // an instance of Stream
            getParentStream().addChild((Stream) parent);
        }
        if (exclusive) {
            // Need to move children of the new parent to be children of this
            // stream. Slightly convoluted to avoid concurrent modification.
            Iterator<Stream> parentsChildren = parent.getChildStreams().iterator();
            while (parentsChildren.hasNext()) {
                Stream parentsChild = parentsChildren.next();
                parentsChildren.remove();
                this.addChild(parentsChild);
            }
        }
        detachFromParent();
        parent.addChild(this);
        this.weight = weight;
    }

    /*
     * Used when removing closed streams from the tree and we know there is no
     * need to check for circular references.
     */
    final void rePrioritise(AbstractStream parent, int weight) {
        if (log.isDebugEnabled()) {
            log.debug(sm.getString("stream.reprioritisation.debug", getConnectionId(), getIdentifier(), Boolean.FALSE, parent.getIdentifier(), Integer.toString(weight)));
        }
        parent.addChild(this);
        this.weight = weight;
    }

    final void receiveReset(long errorCode) {
        if (log.isDebugEnabled()) {
            log.debug(sm.getString("stream.reset.receive", getConnectionId(), getIdentifier(), Long.toString(errorCode)));
        }
        // Set the new state first since read and write both check this
        state.receivedReset();
        // Reads wait internally so need to call a method to break the wait()
        if (inputBuffer != null) {
            inputBuffer.receiveReset();
        }
        cancelAllocationRequests();
    }

    final void cancelAllocationRequests() {
        allocationManager.notifyAny();
    }

    final void checkState(FrameType frameType) throws Http2Exception {
        state.checkFrameType(frameType);
    }

    @Override
    final synchronized void incrementWindowSize(int windowSizeIncrement) throws Http2Exception {
        // If this is zero then any thread that has been trying to write for
        // this stream will be waiting. Notify that thread it can continue. Use
        // notify all even though only one thread is waiting to be on the safe
        // side.
        boolean notify = getWindowSize() < 1;
        super.incrementWindowSize(windowSizeIncrement);
        if (notify && getWindowSize() > 0) {
            allocationManager.notifyStream();
        }
    }

    final synchronized int reserveWindowSize(int reservation, boolean block) throws IOException {
        long windowSize = getWindowSize();
        while (windowSize < 1) {
            if (!canWrite()) {
                throw new CloseNowException(sm.getString("stream.notWritable", getConnectionId(), getIdentifier()));
            }
            if (block) {
                try {
                    long writeTimeout = handler.getProtocol().getStreamWriteTimeout();
                    allocationManager.waitForStream(writeTimeout);
                    windowSize = getWindowSize();
                    if (windowSize == 0) {
                        doWriteTimeout();
                    }
                } catch (InterruptedException e) {
                    // Possible shutdown / rst or similar. Use an IOException to
                    // signal to the client that further I/O isn't possible for this
                    // Stream.
                    throw new IOException(e);
                }
            } else {
                allocationManager.waitForStreamNonBlocking();
                return 0;
            }
        }
        int allocation;
        if (windowSize < reservation) {
            allocation = (int) windowSize;
        } else {
            allocation = reservation;
        }
        decrementWindowSize(allocation);
        return allocation;
    }

    void doWriteTimeout() throws CloseNowException {
        String msg = sm.getString("stream.writeTimeout");
        StreamException se = new StreamException(msg, Http2Error.ENHANCE_YOUR_CALM, getIdAsInt());
        // Prevent the application making further writes
        streamOutputBuffer.closed = true;
        // Prevent Tomcat's error handling trying to write
        coyoteResponse.setError();
        coyoteResponse.setErrorReported();
        // Trigger a reset once control returns to Tomcat
        streamOutputBuffer.reset = se;
        throw new CloseNowException(msg, se);
    }

    void waitForConnectionAllocation(long timeout) throws InterruptedException {
        allocationManager.waitForConnection(timeout);
    }

    void waitForConnectionAllocationNonBlocking() {
        allocationManager.waitForConnectionNonBlocking();
    }

    void notifyConnection() {
        allocationManager.notifyConnection();
    }

    @Override
    public final void emitHeader(String name, String value) throws HpackException {
        if (log.isDebugEnabled()) {
            log.debug(sm.getString("stream.header.debug", getConnectionId(), getIdentifier(), name, value));
        }
        // Header names must be lower case
        if (!name.toLowerCase(Locale.US).equals(name)) {
            throw new HpackException(sm.getString("stream.header.case", getConnectionId(), getIdentifier(), name));
        }
        if ("connection".equals(name)) {
            throw new HpackException(sm.getString("stream.header.connection", getConnectionId(), getIdentifier()));
        }
        if ("te".equals(name)) {
            if (!"trailers".equals(value)) {
                throw new HpackException(sm.getString("stream.header.te", getConnectionId(), getIdentifier(), value));
            }
        }
        if (headerException != null) {
            // Don't bother processing the header since the stream is going to
            // be reset anyway
            return;
        }
        if (name.length() == 0) {
            throw new HpackException(sm.getString("stream.header.empty", getConnectionId(), getIdentifier()));
        }
        boolean pseudoHeader = name.charAt(0) == ':';
        if (pseudoHeader && headerState != HEADER_STATE_PSEUDO) {
            headerException = new StreamException(sm.getString("stream.header.unexpectedPseudoHeader", getConnectionId(), getIdentifier(), name), Http2Error.PROTOCOL_ERROR, getIdAsInt());
            // No need for further processing. The stream will be reset.
            return;
        }
        if (headerState == HEADER_STATE_PSEUDO && !pseudoHeader) {
            headerState = HEADER_STATE_REGULAR;
        }
        switch(name) {
            case ":method":
                {
                    if (coyoteRequest.method().isNull()) {
                        coyoteRequest.method().setString(value);
                    } else {
                        throw new HpackException(sm.getString("stream.header.duplicate", getConnectionId(), getIdentifier(), ":method"));
                    }
                    break;
                }
            case ":scheme":
                {
                    if (coyoteRequest.scheme().isNull()) {
                        coyoteRequest.scheme().setString(value);
                    } else {
                        throw new HpackException(sm.getString("stream.header.duplicate", getConnectionId(), getIdentifier(), ":scheme"));
                    }
                    break;
                }
            case ":path":
                {
                    if (!coyoteRequest.requestURI().isNull()) {
                        throw new HpackException(sm.getString("stream.header.duplicate", getConnectionId(), getIdentifier(), ":path"));
                    }
                    if (value.length() == 0) {
                        throw new HpackException(sm.getString("stream.header.noPath", getConnectionId(), getIdentifier()));
                    }
                    int queryStart = value.indexOf('?');
                    String uri;
                    if (queryStart == -1) {
                        uri = value;
                    } else {
                        uri = value.substring(0, queryStart);
                        String query = value.substring(queryStart + 1);
                        coyoteRequest.queryString().setString(query);
                    }
                    // Bug 61120. Set the URI as bytes rather than String so:
                    // - any path parameters are correctly processed
                    // - the normalization security checks are performed that prevent
                    // directory traversal attacks
                    byte[] uriBytes = uri.getBytes(StandardCharsets.ISO_8859_1);
                    coyoteRequest.requestURI().setBytes(uriBytes, 0, uriBytes.length);
                    break;
                }
            case ":authority":
                {
                    if (coyoteRequest.serverName().isNull()) {
                        int i;
                        try {
                            i = Host.parse(value);
                        } catch (IllegalArgumentException iae) {
                            // Host value invalid
                            throw new HpackException(sm.getString("stream.header.invalid", getConnectionId(), getIdentifier(), ":authority", value));
                        }
                        if (i > -1) {
                            coyoteRequest.serverName().setString(value.substring(0, i));
                            coyoteRequest.setServerPort(Integer.parseInt(value.substring(i + 1)));
                        } else {
                            coyoteRequest.serverName().setString(value);
                        }
                    } else {
                        throw new HpackException(sm.getString("stream.header.duplicate", getConnectionId(), getIdentifier(), ":authority"));
                    }
                    break;
                }
            case "cookie":
                {
                    // Cookie headers need to be concatenated into a single header
                    // See RFC 7540 8.1.2.5
                    if (cookieHeader == null) {
                        cookieHeader = new StringBuilder();
                    } else {
                        cookieHeader.append("; ");
                    }
                    cookieHeader.append(value);
                    break;
                }
            default:
                {
                    if (headerState == HEADER_STATE_TRAILER && !handler.getProtocol().isTrailerHeaderAllowed(name)) {
                        break;
                    }
                    if ("expect".equals(name) && "100-continue".equals(value)) {
                        coyoteRequest.setExpectation(true);
                    }
                    if (pseudoHeader) {
                        headerException = new StreamException(sm.getString("stream.header.unknownPseudoHeader", getConnectionId(), getIdentifier(), name), Http2Error.PROTOCOL_ERROR, getIdAsInt());
                    }
                    if (headerState == HEADER_STATE_TRAILER) {
                        // HTTP/2 headers are already always lower case
                        coyoteRequest.getTrailerFields().put(name, value);
                    } else {
                        coyoteRequest.getMimeHeaders().addValue(name).setString(value);
                    }
                }
        }
    }

    @Override
    public void setHeaderException(StreamException streamException) {
        if (headerException == null) {
            headerException = streamException;
        }
    }

    @Override
    public void validateHeaders() throws StreamException {
        if (headerException == null) {
            return;
        }
        throw headerException;
    }

    final boolean receivedEndOfHeaders() throws ConnectionException {
        if (coyoteRequest.method().isNull() || coyoteRequest.scheme().isNull() || coyoteRequest.requestURI().isNull()) {
            throw new ConnectionException(sm.getString("stream.header.required", getConnectionId(), getIdentifier()), Http2Error.PROTOCOL_ERROR);
        }
        // Cookie headers need to be concatenated into a single header
        // See RFC 7540 8.1.2.5
        // Can only do this once the headers are fully received
        if (cookieHeader != null) {
            coyoteRequest.getMimeHeaders().addValue("cookie").setString(cookieHeader.toString());
        }
        return headerState == HEADER_STATE_REGULAR || headerState == HEADER_STATE_PSEUDO;
    }

    final void writeHeaders() throws IOException {
        boolean endOfStream = streamOutputBuffer.hasNoBody() && coyoteResponse.getTrailerFields() == null;
        handler.writeHeaders(this, 0, coyoteResponse.getMimeHeaders(), endOfStream, Constants.DEFAULT_HEADERS_FRAME_SIZE);
    }

    final void addOutputFilter(OutputFilter filter) {
        http2OutputBuffer.addFilter(filter);
    }

    final void writeTrailers() throws IOException {
        Supplier<Map<String, String>> supplier = coyoteResponse.getTrailerFields();
        if (supplier == null) {
            // No supplier was set, end of stream will already have been sent
            return;
        }
        // We can re-use the MimeHeaders from the response since they have
        // already been processed by the encoder at this point
        MimeHeaders mimeHeaders = coyoteResponse.getMimeHeaders();
        mimeHeaders.recycle();
        Map<String, String> headerMap = supplier.get();
        if (headerMap == null) {
            headerMap = Collections.emptyMap();
        }
        // Copy the contents of the Map to the MimeHeaders
        // TODO: Is there benefit in refactoring this? Is MimeHeaders too
        // heavyweight? Can we reduce the copy/conversions?
        for (Map.Entry<String, String> headerEntry : headerMap.entrySet()) {
            MessageBytes mb = mimeHeaders.addValue(headerEntry.getKey());
            mb.setString(headerEntry.getValue());
        }
        handler.writeHeaders(this, 0, mimeHeaders, true, Constants.DEFAULT_HEADERS_FRAME_SIZE);
    }

    final void writeAck() throws IOException {
        handler.writeHeaders(this, 0, ACK_HEADERS, false, Constants.DEFAULT_HEADERS_ACK_FRAME_SIZE);
    }

    @Override
    final String getConnectionId() {
        return handler.getConnectionId();
    }

    @Override
    final int getWeight() {
        return weight;
    }

    final Request getCoyoteRequest() {
        return coyoteRequest;
    }

    final Response getCoyoteResponse() {
        return coyoteResponse;
    }

    final ByteBuffer getInputByteBuffer() {
        return inputBuffer.getInBuffer();
    }

    final void receivedStartOfHeaders(boolean headersEndStream) throws Http2Exception {
        if (headerState == HEADER_STATE_START) {
            headerState = HEADER_STATE_PSEUDO;
            handler.getHpackDecoder().setMaxHeaderCount(handler.getProtocol().getMaxHeaderCount());
            handler.getHpackDecoder().setMaxHeaderSize(handler.getProtocol().getMaxHeaderSize());
        } else if (headerState == HEADER_STATE_PSEUDO || headerState == HEADER_STATE_REGULAR) {
            // Trailer headers MUST include the end of stream flag
            if (headersEndStream) {
                headerState = HEADER_STATE_TRAILER;
                handler.getHpackDecoder().setMaxHeaderCount(handler.getProtocol().getMaxTrailerCount());
                handler.getHpackDecoder().setMaxHeaderSize(handler.getProtocol().getMaxTrailerSize());
            } else {
                throw new ConnectionException(sm.getString("stream.trailerHeader.noEndOfStream", getConnectionId(), getIdentifier()), Http2Error.PROTOCOL_ERROR);
            }
        }
        // Parser will catch attempt to send a headers frame after the stream
        // has closed.
        state.receivedStartOfHeaders();
    }

    final void receivedData(int payloadSize) throws ConnectionException {
        contentLengthReceived += payloadSize;
        long contentLengthHeader = coyoteRequest.getContentLengthLong();
        if (contentLengthHeader > -1 && contentLengthReceived > contentLengthHeader) {
            throw new ConnectionException(sm.getString("stream.header.contentLength", getConnectionId(), getIdentifier(), Long.valueOf(contentLengthHeader), Long.valueOf(contentLengthReceived)), Http2Error.PROTOCOL_ERROR);
        }
    }

    final void receivedEndOfStream() throws ConnectionException {
        if (isContentLengthInconsistent()) {
            throw new ConnectionException(sm.getString("stream.header.contentLength", getConnectionId(), getIdentifier(), Long.valueOf(coyoteRequest.getContentLengthLong()), Long.valueOf(contentLengthReceived)), Http2Error.PROTOCOL_ERROR);
        }
        state.receivedEndOfStream();
        if (inputBuffer != null) {
            inputBuffer.notifyEof();
        }
    }

    final boolean isContentLengthInconsistent() {
        long contentLengthHeader = coyoteRequest.getContentLengthLong();
        if (contentLengthHeader > -1 && contentLengthReceived != contentLengthHeader) {
            return true;
        }
        return false;
    }

    final void sentHeaders() {
        state.sentHeaders();
    }

    final void sentEndOfStream() {
        streamOutputBuffer.endOfStreamSent = true;
        state.sentEndOfStream();
    }

    final boolean isReadyForWrite() {
        return streamOutputBuffer.isReady();
    }

    final boolean flush(boolean block) throws IOException {
        return streamOutputBuffer.flush(block);
    }

    final StreamInputBuffer getInputBuffer() {
        return inputBuffer;
    }

    final HttpOutputBuffer getOutputBuffer() {
        return http2OutputBuffer;
    }

    final void sentPushPromise() {
        state.sentPushPromise();
    }

    final boolean isActive() {
        return state.isActive();
    }

    final boolean canWrite() {
        return state.canWrite();
    }

    final boolean isClosedFinal() {
        return state.isClosedFinal();
    }

    final void closeIfIdle() {
        state.closeIfIdle();
    }

    final boolean isInputFinished() {
        return !state.isFrameTypePermitted(FrameType.DATA);
    }

    final void close(Http2Exception http2Exception) {
        if (http2Exception instanceof StreamException) {
            try {
                StreamException se = (StreamException) http2Exception;
                if (log.isDebugEnabled()) {
                    log.debug(sm.getString("stream.reset.send", getConnectionId(), getIdentifier(), se.getError()));
                }
                state.sendReset();
                handler.sendStreamReset(se);
            } catch (IOException ioe) {
                ConnectionException ce = new ConnectionException(sm.getString("stream.reset.fail"), Http2Error.PROTOCOL_ERROR);
                ce.initCause(ioe);
                handler.closeConnection(ce);
            }
        } else {
            handler.closeConnection(http2Exception);
        }
    }

    final boolean isPushSupported() {
        return handler.getRemoteSettings().getEnablePush();
    }

    final void push(Request request) throws IOException {
        // Can only push when supported and from a peer initiated stream
        if (!isPushSupported() || getIdAsInt() % 2 == 0) {
            return;
        }
        // Set the special HTTP/2 headers
        request.getMimeHeaders().addValue(":method").duplicate(request.method());
        request.getMimeHeaders().addValue(":scheme").duplicate(request.scheme());
        StringBuilder path = new StringBuilder(request.requestURI().toString());
        if (!request.queryString().isNull()) {
            path.append('?');
            path.append(request.queryString().toString());
        }
        request.getMimeHeaders().addValue(":path").setString(path.toString());
        // Authority needs to include the port only if a non-standard port is
        // being used.
        if (!(request.scheme().equals("http") && request.getServerPort() == 80) && !(request.scheme().equals("https") && request.getServerPort() == 443)) {
            request.getMimeHeaders().addValue(":authority").setString(request.serverName().getString() + ":" + request.getServerPort());
        } else {
            request.getMimeHeaders().addValue(":authority").duplicate(request.serverName());
        }
        push(handler, request, this);
    }

    boolean isTrailerFieldsReady() {
        // Once EndOfStream has been received, canRead will be false
        return !state.canRead();
    }

    boolean isTrailerFieldsSupported() {
        return !streamOutputBuffer.endOfStreamSent;
    }

    StreamException getResetException() {
        return streamOutputBuffer.reset;
    }

    private static void push(final Http2UpgradeHandler handler, final Request request, final Stream stream) throws IOException {
        if (org.apache.coyote.Constants.IS_SECURITY_ENABLED) {
            try {
                AccessController.doPrivileged(new PrivilegedPush(handler, request, stream));
            } catch (PrivilegedActionException ex) {
                Exception e = ex.getException();
                if (e instanceof IOException) {
                    throw (IOException) e;
                } else {
                    throw new IOException(ex);
                }
            }
        } else {
            handler.push(request, stream);
        }
    }

    private static clreplaced PrivilegedPush implements PrivilegedExceptionAction<Void> {

        private final Http2UpgradeHandler handler;

        private final Request request;

        private final Stream stream;

        public PrivilegedPush(Http2UpgradeHandler handler, Request request, Stream stream) {
            this.handler = handler;
            this.request = request;
            this.stream = stream;
        }

        @Override
        public Void run() throws IOException {
            handler.push(request, stream);
            return null;
        }
    }

    clreplaced StreamOutputBuffer implements HttpOutputBuffer, WriteBuffer.Sink {

        private final ByteBuffer buffer = ByteBuffer.allocate(8 * 1024);

        private final WriteBuffer writeBuffer = new WriteBuffer(32 * 1024);

        // Flag that indicates that data was left over on a previous
        // non-blocking write. Once set, this flag stays set until all the data
        // has been written.
        private boolean dataLeft;

        private volatile long written = 0;

        private int streamReservation = 0;

        private volatile boolean closed = false;

        private volatile StreamException reset = null;

        private volatile boolean endOfStreamSent = false;

        /* The write methods are synchronized to ensure that only one thread at
         * a time is able to access the buffer. Without this protection, a
         * client that performed concurrent writes could corrupt the buffer.
         */
        @Override
        public final synchronized int doWrite(ByteBuffer chunk) throws IOException {
            if (closed) {
                throw new IllegalStateException(sm.getString("stream.closed", getConnectionId(), getIdentifier()));
            }
            // chunk is always fully written
            int result = chunk.remaining();
            if (writeBuffer.isEmpty()) {
                int chunkLimit = chunk.limit();
                while (chunk.remaining() > 0) {
                    int thisTime = Math.min(buffer.remaining(), chunk.remaining());
                    chunk.limit(chunk.position() + thisTime);
                    buffer.put(chunk);
                    chunk.limit(chunkLimit);
                    if (chunk.remaining() > 0 && !buffer.hasRemaining()) {
                        // Only flush if we have more data to write and the buffer
                        // is full
                        if (flush(true, coyoteResponse.getWriteListener() == null)) {
                            writeBuffer.add(chunk);
                            dataLeft = true;
                            break;
                        }
                    }
                }
            } else {
                writeBuffer.add(chunk);
            }
            written += result;
            return result;
        }

        final synchronized boolean flush(boolean block) throws IOException {
            /*
             * Need to ensure that there is exactly one call to flush even when
             * there is no data to write.
             * Too few calls (i.e. zero) and the end of stream message is not
             * sent for a completed asynchronous write.
             * Too many calls and the end of stream message is sent too soon and
             * trailer headers are not sent.
             */
            boolean dataInBuffer = buffer.position() > 0;
            boolean flushed = false;
            if (dataInBuffer) {
                dataInBuffer = flush(false, block);
                flushed = true;
            }
            if (dataInBuffer) {
                dataLeft = true;
            } else {
                if (writeBuffer.isEmpty()) {
                    // Both buffer and writeBuffer are empty.
                    if (flushed) {
                        dataLeft = false;
                    } else {
                        dataLeft = flush(false, block);
                    }
                } else {
                    dataLeft = writeBuffer.write(this, block);
                }
            }
            return dataLeft;
        }

        private final synchronized boolean flush(boolean writeInProgress, boolean block) throws IOException {
            if (log.isDebugEnabled()) {
                log.debug(sm.getString("stream.outputBuffer.flush.debug", getConnectionId(), getIdentifier(), Integer.toString(buffer.position()), Boolean.toString(writeInProgress), Boolean.toString(closed)));
            }
            if (buffer.position() == 0) {
                if (closed && !endOfStreamSent) {
                    // Handling this special case here is simpler than trying
                    // to modify the following code to handle it.
                    handler.writeBody(Stream.this, buffer, 0, coyoteResponse.getTrailerFields() == null);
                }
                // Buffer is empty. Nothing to do.
                return false;
            }
            buffer.flip();
            int left = buffer.remaining();
            while (left > 0) {
                if (streamReservation == 0) {
                    streamReservation = reserveWindowSize(left, block);
                    if (streamReservation == 0) {
                        // Must be non-blocking.
                        // Note: Can't add to the writeBuffer here as the write
                        // may originate from the writeBuffer.
                        buffer.compact();
                        return true;
                    }
                }
                while (streamReservation > 0) {
                    int connectionReservation = handler.reserveWindowSize(Stream.this, streamReservation, block);
                    if (connectionReservation == 0) {
                        // Must be non-blocking.
                        // Note: Can't add to the writeBuffer here as the write
                        // may originate from the writeBuffer.
                        buffer.compact();
                        return true;
                    }
                    // Do the write
                    handler.writeBody(Stream.this, buffer, connectionReservation, !writeInProgress && closed && left == connectionReservation && coyoteResponse.getTrailerFields() == null);
                    streamReservation -= connectionReservation;
                    left -= connectionReservation;
                }
            }
            buffer.clear();
            return false;
        }

        final synchronized boolean isReady() {
            // Bug 63682
            // Only want to return false if the window size is zero AND we are
            // already waiting for an allocation.
            if (getWindowSize() > 0 && allocationManager.isWaitingForStream() || handler.getWindowSize() > 0 && allocationManager.isWaitingForConnection() || dataLeft) {
                return false;
            } else {
                return true;
            }
        }

        @Override
        public final long getBytesWritten() {
            return written;
        }

        @Override
        public final void end() throws IOException {
            if (reset != null) {
                throw new CloseNowException(reset);
            }
            if (!closed) {
                closed = true;
                flush(true);
                writeTrailers();
            }
        }

        /**
         * @return <code>true</code> if it is certain that the replacedociated
         *         response has no body.
         */
        final boolean hasNoBody() {
            return ((written == 0) && closed);
        }

        @Override
        public void flush() throws IOException {
            /*
             * This method should only be called during blocking I/O. All the
             * Servlet API calls that end up here are illegal during
             * non-blocking I/O. Servlet 5.4.
             * However, the wording Servlet specification states that the
             * behaviour is undefined so we do the best we can which is to
             * perform a flush using blocking I/O or non-blocking I/O based
             * depending which is currently in use.
             */
            flush(getCoyoteResponse().getWriteListener() == null);
        }

        @Override
        public synchronized boolean writeFromBuffer(ByteBuffer src, boolean blocking) throws IOException {
            int chunkLimit = src.limit();
            while (src.remaining() > 0) {
                int thisTime = Math.min(buffer.remaining(), src.remaining());
                src.limit(src.position() + thisTime);
                buffer.put(src);
                src.limit(chunkLimit);
                if (flush(false, blocking)) {
                    return true;
                }
            }
            return false;
        }
    }

    clreplaced StreamInputBuffer implements InputBuffer {

        /* Two buffers are required to avoid various multi-threading issues.
         * These issues arise from the fact that the Stream (or the
         * Request/Response) used by the application is processed in one thread
         * but the connection is processed in another. Therefore it is possible
         * that a request body frame could be received before the application
         * is ready to read it. If it isn't buffered, processing of the
         * connection (and hence all streams) would block until the application
         * read the data. Hence the incoming data has to be buffered.
         * If only one buffer was used then it could become corrupted if the
         * connection thread is trying to add to it at the same time as the
         * application is read it. While it should be possible to avoid this
         * corruption by careful use of the buffer it would still require the
         * same copies as using two buffers and the behaviour would be less
         * clear.
         *
         * The buffers are created lazily because they quickly add up to a lot
         * of memory and most requests do not have bodies.
         */
        // This buffer is used to populate the ByteChunk preplaceded in to the read
        // method
        private byte[] outBuffer;

        // This buffer is the destination for incoming data. It is normally is
        // 'write mode'.
        private volatile ByteBuffer inBuffer;

        private volatile boolean readInterest;

        private boolean resetReceived = false;

        @Override
        public final int doRead(ApplicationBufferHandler applicationBufferHandler) throws IOException {
            ensureBuffersExist();
            int written = -1;
            // Ensure that only one thread accesses inBuffer at a time
            synchronized (inBuffer) {
                boolean canRead = false;
                while (inBuffer.position() == 0 && (canRead = isActive() && !isInputFinished())) {
                    // Need to block until some data is written
                    try {
                        if (log.isDebugEnabled()) {
                            log.debug(sm.getString("stream.inputBuffer.empty"));
                        }
                        long readTimeout = handler.getProtocol().getStreamReadTimeout();
                        if (readTimeout < 0) {
                            inBuffer.wait();
                        } else {
                            inBuffer.wait(readTimeout);
                        }
                        if (resetReceived) {
                            throw new IOException(sm.getString("stream.inputBuffer.reset"));
                        }
                        if (inBuffer.position() == 0 && isActive() && !isInputFinished()) {
                            String msg = sm.getString("stream.inputBuffer.readTimeout");
                            StreamException se = new StreamException(msg, Http2Error.ENHANCE_YOUR_CALM, getIdAsInt());
                            // Trigger a reset once control returns to Tomcat
                            coyoteResponse.setError();
                            streamOutputBuffer.reset = se;
                            throw new CloseNowException(msg, se);
                        }
                    } catch (InterruptedException e) {
                        // Possible shutdown / rst or similar. Use an
                        // IOException to signal to the client that further I/O
                        // isn't possible for this Stream.
                        throw new IOException(e);
                    }
                }
                if (inBuffer.position() > 0) {
                    // Data is available in the inBuffer. Copy it to the
                    // outBuffer.
                    inBuffer.flip();
                    written = inBuffer.remaining();
                    if (log.isDebugEnabled()) {
                        log.debug(sm.getString("stream.inputBuffer.copy", Integer.toString(written)));
                    }
                    inBuffer.get(outBuffer, 0, written);
                    inBuffer.clear();
                } else if (!canRead) {
                    return -1;
                } else {
                    // Should never happen
                    throw new IllegalStateException();
                }
            }
            applicationBufferHandler.setByteBuffer(ByteBuffer.wrap(outBuffer, 0, written));
            // Increment client-side flow control windows by the number of bytes
            // read
            handler.writeWindowUpdate(Stream.this, written, true);
            return written;
        }

        final boolean isReadyForRead() {
            ensureBuffersExist();
            synchronized (this) {
                if (available() > 0) {
                    return true;
                }
                if (!isRequestBodyFullyRead()) {
                    readInterest = true;
                }
                return false;
            }
        }

        final synchronized boolean isRequestBodyFullyRead() {
            return (inBuffer == null || inBuffer.position() == 0) && isInputFinished();
        }

        final synchronized int available() {
            if (inBuffer == null) {
                return 0;
            }
            return inBuffer.position();
        }

        /*
         * Called after placing some data in the inBuffer.
         */
        final synchronized boolean onDataAvailable() {
            if (readInterest) {
                if (log.isDebugEnabled()) {
                    log.debug(sm.getString("stream.inputBuffer.dispatch"));
                }
                readInterest = false;
                coyoteRequest.action(ActionCode.DISPATCH_READ, null);
                // Always need to dispatch since this thread is processing
                // the incoming connection and streams are processed on their
                // own.
                coyoteRequest.action(ActionCode.DISPATCH_EXECUTE, null);
                return true;
            } else {
                if (log.isDebugEnabled()) {
                    log.debug(sm.getString("stream.inputBuffer.signal"));
                }
                synchronized (inBuffer) {
                    inBuffer.notifyAll();
                }
                return false;
            }
        }

        private final ByteBuffer getInBuffer() {
            ensureBuffersExist();
            return inBuffer;
        }

        final synchronized void insertReplayedBody(ByteChunk body) {
            inBuffer = ByteBuffer.wrap(body.getBytes(), body.getOffset(), body.getLength());
        }

        private final void ensureBuffersExist() {
            if (inBuffer == null) {
                // The client must obey Tomcat's window size when sending so
                // this is the initial window size set by Tomcat that the client
                // uses (i.e. the local setting is required here).
                int size = handler.getLocalSettings().getInitialWindowSize();
                synchronized (this) {
                    if (inBuffer == null) {
                        inBuffer = ByteBuffer.allocate(size);
                        outBuffer = new byte[size];
                    }
                }
            }
        }

        private final void receiveReset() {
            if (inBuffer != null) {
                synchronized (inBuffer) {
                    resetReceived = true;
                    inBuffer.notifyAll();
                }
            }
        }

        private final void notifyEof() {
            if (inBuffer != null) {
                synchronized (inBuffer) {
                    inBuffer.notifyAll();
                }
            }
        }
    }
}

16 View Complete Implementation : InputBuffer.java
Copyright Apache License 2.0
Author : apache
/**
 * The buffer used by Tomcat request. This is a derivative of the Tomcat 3.3
 * OutputBuffer, adapted to handle input instead of output. This allows
 * complete recycling of the facade objects (the ServletInputStream and the
 * BufferedReader).
 *
 * @author Remy Maucherat
 */
public clreplaced InputBuffer extends Reader implements ByteChunk.ByteInputChannel, CharChunk.CharInputChannel, CharChunk.CharOutputChannel {

    /**
     * The string manager for this package.
     */
    protected static final StringManager sm = StringManager.getManager(Constants.Package);

    // -------------------------------------------------------------- Constants
    public static final String DEFAULT_ENCODING = org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING;

    public static final int DEFAULT_BUFFER_SIZE = 8 * 1024;

    // The buffer can be used for byte[] and char[] reading
    // ( this is needed to support ServletInputStream and BufferedReader )
    public final int INITIAL_STATE = 0;

    public final int CHAR_STATE = 1;

    public final int BYTE_STATE = 2;

    // ----------------------------------------------------- Instance Variables
    /**
     * The byte buffer.
     */
    private final ByteChunk bb;

    /**
     * The chunk buffer.
     */
    private CharChunk cb;

    /**
     * State of the output buffer.
     */
    private int state = 0;

    /**
     * Flag which indicates if the input buffer is closed.
     */
    private boolean closed = false;

    /**
     * Encoding to use.
     */
    private String enc;

    /**
     * List of encoders.
     */
    protected final ConcurrentHashMap<String, B2CConverter> encoders = new ConcurrentHashMap<>();

    /**
     * Current byte to char converter.
     */
    protected B2CConverter conv;

    /**
     * replacedociated Coyote request.
     */
    private Request coyoteRequest;

    /**
     * Buffer position.
     */
    private int markPos = -1;

    /**
     * Buffer size.
     */
    private final int size;

    // ----------------------------------------------------------- Constructors
    /**
     * Default constructor. Allocate the buffer with the default buffer size.
     */
    public InputBuffer() {
        this(DEFAULT_BUFFER_SIZE);
    }

    /**
     * Alternate constructor which allows specifying the initial buffer size.
     *
     * @param size Buffer size to use
     */
    public InputBuffer(int size) {
        this.size = size;
        bb = new ByteChunk(size);
        bb.setLimit(size);
        bb.setByteInputChannel(this);
        cb = new CharChunk(size);
        cb.setLimit(size);
        cb.setOptimizedWrite(false);
        cb.setCharInputChannel(this);
        cb.setCharOutputChannel(this);
    }

    // ------------------------------------------------------------- Properties
    /**
     * replacedociated Coyote request.
     *
     * @param coyoteRequest replacedociated Coyote request
     */
    public void setRequest(Request coyoteRequest) {
        this.coyoteRequest = coyoteRequest;
    }

    // --------------------------------------------------------- Public Methods
    /**
     * Recycle the output buffer.
     */
    public void recycle() {
        state = INITIAL_STATE;
        // If usage of mark made the buffer too big, reallocate it
        if (cb.getChars().length > size) {
            cb = new CharChunk(size);
            cb.setLimit(size);
            cb.setOptimizedWrite(false);
            cb.setCharInputChannel(this);
            cb.setCharOutputChannel(this);
        } else {
            cb.recycle();
        }
        markPos = -1;
        bb.recycle();
        closed = false;
        if (conv != null) {
            conv.recycle();
            conv = null;
        }
        enc = null;
    }

    /**
     * Clear cached encoders (to save memory for Comet requests).
     */
    public void clearEncoders() {
        encoders.clear();
    }

    /**
     * Close the input buffer.
     *
     * @throws IOException An underlying IOException occurred
     */
    @Override
    public void close() throws IOException {
        closed = true;
    }

    public int available() {
        int available = 0;
        if (state == BYTE_STATE) {
            available = bb.getLength();
        } else if (state == CHAR_STATE) {
            available = cb.getLength();
        }
        if (available == 0) {
            // Written this way to avoid use of IS_COMET action where possible
            boolean readForAvailable = coyoteRequest.getReadListener() != null;
            if (!readForAvailable) {
                AtomicBoolean isComet = new AtomicBoolean();
                coyoteRequest.action(ActionCode.IS_COMET, isComet);
                readForAvailable = isComet.get();
            }
            coyoteRequest.action(ActionCode.AVAILABLE, Boolean.valueOf(readForAvailable));
            available = (coyoteRequest.getAvailable() > 0) ? 1 : 0;
        }
        return available;
    }

    public void setReadListener(ReadListener listener) {
        coyoteRequest.setReadListener(listener);
        // The container is responsible for the first call to
        // listener.onDataAvailable(). If isReady() returns true, the container
        // needs to call listener.onDataAvailable() from a new thread. If
        // isReady() returns false, the socket will be registered for read and
        // the container will call listener.onDataAvailable() once data arrives.
        // Must call isFinished() first as a call to isReady() if the request
        // has been finished will register the socket for read interest and that
        // is not required.
        if (!coyoteRequest.isFinished() && isReady()) {
            coyoteRequest.action(ActionCode.DISPATCH_READ, null);
            if (!ContainerThreadMarker.isContainerThread()) {
                // Not on a container thread so need to execute the dispatch
                coyoteRequest.action(ActionCode.DISPATCH_EXECUTE, null);
            }
        }
    }

    public boolean isFinished() {
        int available = 0;
        if (state == BYTE_STATE) {
            available = bb.getLength();
        } else if (state == CHAR_STATE) {
            available = cb.getLength();
        }
        if (available > 0) {
            return false;
        } else {
            return coyoteRequest.isFinished();
        }
    }

    public boolean isReady() {
        if (coyoteRequest.getReadListener() == null) {
            throw new IllegalStateException(sm.getString("inputBuffer.requiresNonBlocking"));
        }
        // Need to check is finished before we check available() as BIO always
        // returns 1 for isAvailable()
        if (isFinished()) {
            // If this is a non-container thread, need to trigger a read
            // which will eventually lead to a call to onAllDataRead() via a
            // container thread.
            if (!ContainerThreadMarker.isContainerThread()) {
                coyoteRequest.action(ActionCode.DISPATCH_READ, null);
                coyoteRequest.action(ActionCode.DISPATCH_EXECUTE, null);
            }
            return false;
        }
        boolean result = available() > 0;
        if (!result) {
            coyoteRequest.action(ActionCode.NB_READ_INTEREST, null);
        }
        return result;
    }

    boolean isBlocking() {
        return coyoteRequest.getReadListener() == null;
    }

    // ------------------------------------------------- Bytes Handling Methods
    /**
     * Reads new bytes in the byte chunk.
     *
     * @param cbuf Byte buffer to be written to the response
     * @param off Offset
     * @param len Length
     *
     * @throws IOException An underlying IOException occurred
     */
    @Override
    public int realReadBytes(byte[] cbuf, int off, int len) throws IOException {
        if (closed) {
            return -1;
        }
        if (coyoteRequest == null) {
            return -1;
        }
        if (state == INITIAL_STATE) {
            state = BYTE_STATE;
        }
        int result = coyoteRequest.doRead(bb);
        return result;
    }

    public int readByte() throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        return bb.substract();
    }

    public int read(byte[] b, int off, int len) throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        return bb.substract(b, off, len);
    }

    // ------------------------------------------------- Chars Handling Methods
    /**
     * Since the converter will use append, it is possible to get chars to
     * be removed from the buffer for "writing". Since the chars have already
     * been read before, they are ignored. If a mark was set, then the
     * mark is lost.
     */
    @Override
    public void realWriteChars(char[] c, int off, int len) throws IOException {
        markPos = -1;
        cb.setOffset(0);
        cb.setEnd(0);
    }

    public void setEncoding(String s) {
        enc = s;
    }

    @Override
    public int realReadChars(char[] cbuf, int off, int len) throws IOException {
        if (conv == null) {
            setConverter();
        }
        boolean eof = false;
        if (bb.getLength() <= 0) {
            int nRead = realReadBytes(bb.getBytes(), 0, bb.getBytes().length);
            if (nRead < 0) {
                eof = true;
            }
        }
        if (markPos == -1) {
            cb.setOffset(0);
            cb.setEnd(0);
        } else {
            // Make sure there's enough space in the worst case
            cb.makeSpace(bb.getLength());
            if ((cb.getBuffer().length - cb.getEnd()) == 0 && bb.getLength() != 0) {
                // We went over the limit
                cb.setOffset(0);
                cb.setEnd(0);
                markPos = -1;
            }
        }
        state = CHAR_STATE;
        conv.convert(bb, cb, eof);
        if (cb.getLength() == 0 && eof) {
            return -1;
        } else {
            return cb.getLength();
        }
    }

    @Override
    public int read() throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        return cb.substract();
    }

    @Override
    public int read(char[] cbuf) throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        return read(cbuf, 0, cbuf.length);
    }

    @Override
    public int read(char[] cbuf, int off, int len) throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        return cb.substract(cbuf, off, len);
    }

    @Override
    public long skip(long n) throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        if (n < 0) {
            throw new IllegalArgumentException();
        }
        long nRead = 0;
        while (nRead < n) {
            if (cb.getLength() >= n) {
                cb.setOffset(cb.getStart() + (int) n);
                nRead = n;
            } else {
                nRead += cb.getLength();
                cb.setOffset(cb.getEnd());
                int toRead = 0;
                if (cb.getChars().length < (n - nRead)) {
                    toRead = cb.getChars().length;
                } else {
                    toRead = (int) (n - nRead);
                }
                int nb = realReadChars(cb.getChars(), 0, toRead);
                if (nb < 0) {
                    break;
                }
            }
        }
        return nRead;
    }

    @Override
    public boolean ready() throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        if (state == INITIAL_STATE) {
            state = CHAR_STATE;
        }
        return (available() > 0);
    }

    @Override
    public boolean markSupported() {
        return true;
    }

    @Override
    public void mark(int readAheadLimit) throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        if (cb.getLength() <= 0) {
            cb.setOffset(0);
            cb.setEnd(0);
        } else {
            if ((cb.getBuffer().length > (2 * size)) && (cb.getLength()) < (cb.getStart())) {
                System.arraycopy(cb.getBuffer(), cb.getStart(), cb.getBuffer(), 0, cb.getLength());
                cb.setEnd(cb.getLength());
                cb.setOffset(0);
            }
        }
        cb.setLimit(cb.getStart() + readAheadLimit + size);
        markPos = cb.getStart();
    }

    @Override
    public void reset() throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        if (state == CHAR_STATE) {
            if (markPos < 0) {
                cb.recycle();
                markPos = -1;
                throw new IOException();
            } else {
                cb.setOffset(markPos);
            }
        } else {
            bb.recycle();
        }
    }

    public void checkConverter() throws IOException {
        if (conv == null) {
            setConverter();
        }
    }

    protected void setConverter() throws IOException {
        if (coyoteRequest != null) {
            enc = coyoteRequest.getCharacterEncoding();
        }
        if (enc == null) {
            enc = DEFAULT_ENCODING;
        }
        conv = encoders.get(enc);
        if (conv == null) {
            if (SecurityUtil.isPackageProtectionEnabled()) {
                try {
                    conv = AccessController.doPrivileged(new PrivilegedExceptionAction<B2CConverter>() {

                        @Override
                        public B2CConverter run() throws IOException {
                            return new B2CConverter(enc);
                        }
                    });
                } catch (PrivilegedActionException ex) {
                    Exception e = ex.getException();
                    if (e instanceof IOException) {
                        throw (IOException) e;
                    } else {
                        throw new IOException(e);
                    }
                }
            } else {
                conv = new B2CConverter(enc);
            }
            encoders.put(enc, conv);
        }
    }
}

16 View Complete Implementation : InputBuffer.java
Copyright Apache License 2.0
Author : wangyingjie
/**
 * The buffer used by Tomcat request. This is a derivative of the Tomcat 3.3
 * OutputBuffer, adapted to handle input instead of output. This allows
 * complete recycling of the facade objects (the ServletInputStream and the
 * BufferedReader).
 *
 * @author Remy Maucherat
 */
public clreplaced InputBuffer extends Reader implements ByteChunk.ByteInputChannel, CharChunk.CharInputChannel, CharChunk.CharOutputChannel {

    /**
     * The string manager for this package.
     */
    protected static final StringManager sm = StringManager.getManager(Constants.Package);

    // -------------------------------------------------------------- Constants
    public static final String DEFAULT_ENCODING = org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING;

    public static final int DEFAULT_BUFFER_SIZE = 8 * 1024;

    // The buffer can be used for byte[] and char[] reading
    // ( this is needed to support ServletInputStream and BufferedReader )
    public final int INITIAL_STATE = 0;

    public final int CHAR_STATE = 1;

    public final int BYTE_STATE = 2;

    // ----------------------------------------------------- Instance Variables
    /**
     * The byte buffer.
     */
    private final ByteChunk bb;

    /**
     * The chunk buffer.
     */
    private CharChunk cb;

    /**
     * State of the output buffer.
     */
    private int state = 0;

    /**
     * Flag which indicates if the input buffer is closed.
     */
    private boolean closed = false;

    /**
     * Encoding to use.
     */
    private String enc;

    /**
     * Encoder is set.
     */
    private boolean gotEnc = false;

    /**
     * List of encoders.
     */
    private final Map<String, B2CConverter> encoders = new ConcurrentHashMap<String, B2CConverter>();

    /**
     * Current byte to char converter.
     */
    protected B2CConverter conv;

    /**
     * replacedociated Coyote request.
     */
    private Request coyoteRequest;

    /**
     * Buffer position.
     */
    private int markPos = -1;

    /**
     * Buffer size.
     */
    private int size = -1;

    // ----------------------------------------------------------- Constructors
    /**
     * Default constructor. Allocate the buffer with the default buffer size.
     */
    public InputBuffer() {
        this(DEFAULT_BUFFER_SIZE);
    }

    /**
     * Alternate constructor which allows specifying the initial buffer size.
     *
     * @param size Buffer size to use
     */
    public InputBuffer(int size) {
        this.size = size;
        bb = new ByteChunk(size);
        bb.setLimit(size);
        bb.setByteInputChannel(this);
        cb = new CharChunk(size);
        cb.setLimit(size);
        cb.setOptimizedWrite(false);
        cb.setCharInputChannel(this);
        cb.setCharOutputChannel(this);
    }

    // ------------------------------------------------------------- Properties
    /**
     * replacedociated Coyote request.
     *
     * @param coyoteRequest replacedociated Coyote request
     */
    public void setRequest(Request coyoteRequest) {
        this.coyoteRequest = coyoteRequest;
    }

    /**
     * Get replacedociated Coyote request.
     *
     * @return the replacedociated Coyote request
     */
    @Deprecated
    public Request getRequest() {
        return this.coyoteRequest;
    }

    // --------------------------------------------------------- Public Methods
    /**
     * Recycle the output buffer.
     */
    public void recycle() {
        state = INITIAL_STATE;
        // If usage of mark made the buffer too big, reallocate it
        if (cb.getChars().length > size) {
            cb = new CharChunk(size);
            cb.setLimit(size);
            cb.setOptimizedWrite(false);
            cb.setCharInputChannel(this);
            cb.setCharOutputChannel(this);
        } else {
            cb.recycle();
        }
        markPos = -1;
        bb.recycle();
        closed = false;
        if (conv != null) {
            conv.recycle();
        }
        gotEnc = false;
        enc = null;
    }

    /**
     * Clear cached encoders (to save memory for Comet requests).
     */
    public void clearEncoders() {
        encoders.clear();
    }

    /**
     * Close the input buffer.
     *
     * @throws IOException An underlying IOException occurred
     */
    @Override
    public void close() throws IOException {
        closed = true;
    }

    public int available() {
        int available = 0;
        if (state == BYTE_STATE) {
            available = bb.getLength();
        } else if (state == CHAR_STATE) {
            available = cb.getLength();
        }
        if (available == 0) {
            coyoteRequest.action(ActionCode.AVAILABLE, null);
            available = (coyoteRequest.getAvailable() > 0) ? 1 : 0;
        }
        return available;
    }

    // ------------------------------------------------- Bytes Handling Methods
    /**
     * Reads new bytes in the byte chunk.
     *
     * @param cbuf Byte buffer to be written to the response
     * @param off Offset
     * @param len Length
     *
     * @throws IOException An underlying IOException occurred
     */
    @Override
    public int realReadBytes(byte[] cbuf, int off, int len) throws IOException {
        if (closed) {
            return -1;
        }
        if (coyoteRequest == null) {
            return -1;
        }
        if (state == INITIAL_STATE) {
            state = BYTE_STATE;
        }
        int result = coyoteRequest.doRead(bb);
        return result;
    }

    public int readByte() throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        return bb.substract();
    }

    public int read(byte[] b, int off, int len) throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        return bb.substract(b, off, len);
    }

    // ------------------------------------------------- Chars Handling Methods
    /**
     * Since the converter will use append, it is possible to get chars to
     * be removed from the buffer for "writing". Since the chars have already
     * been read before, they are ignored. If a mark was set, then the
     * mark is lost.
     */
    @Override
    public void realWriteChars(char[] c, int off, int len) throws IOException {
        markPos = -1;
        cb.setOffset(0);
        cb.setEnd(0);
    }

    public void setEncoding(String s) {
        enc = s;
    }

    @Override
    public int realReadChars(char[] cbuf, int off, int len) throws IOException {
        if (!gotEnc) {
            setConverter();
        }
        boolean eof = false;
        if (bb.getLength() <= 0) {
            int nRead = realReadBytes(bb.getBytes(), 0, bb.getBytes().length);
            if (nRead < 0) {
                eof = true;
            }
        }
        if (markPos == -1) {
            cb.setOffset(0);
            cb.setEnd(0);
        } else {
            // Make sure there's enough space in the worst case
            cb.makeSpace(bb.getLength());
            if ((cb.getBuffer().length - cb.getEnd()) == 0 && bb.getLength() != 0) {
                // We went over the limit
                cb.setOffset(0);
                cb.setEnd(0);
                markPos = -1;
            }
        }
        state = CHAR_STATE;
        conv.convert(bb, cb, eof);
        if (cb.getLength() == 0 && eof) {
            return -1;
        } else {
            return cb.getLength();
        }
    }

    @Override
    public int read() throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        return cb.substract();
    }

    @Override
    public int read(char[] cbuf) throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        return read(cbuf, 0, cbuf.length);
    }

    @Override
    public int read(char[] cbuf, int off, int len) throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        return cb.substract(cbuf, off, len);
    }

    @Override
    public long skip(long n) throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        if (n < 0) {
            throw new IllegalArgumentException();
        }
        long nRead = 0;
        while (nRead < n) {
            if (cb.getLength() >= n) {
                cb.setOffset(cb.getStart() + (int) n);
                nRead = n;
            } else {
                nRead += cb.getLength();
                cb.setOffset(cb.getEnd());
                int toRead = 0;
                if (cb.getChars().length < (n - nRead)) {
                    toRead = cb.getChars().length;
                } else {
                    toRead = (int) (n - nRead);
                }
                int nb = realReadChars(cb.getChars(), 0, toRead);
                if (nb < 0) {
                    break;
                }
            }
        }
        return nRead;
    }

    @Override
    public boolean ready() throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        return (available() > 0);
    }

    @Override
    public boolean markSupported() {
        return true;
    }

    @Override
    public void mark(int readAheadLimit) throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        if (cb.getLength() <= 0) {
            cb.setOffset(0);
            cb.setEnd(0);
        } else {
            if ((cb.getBuffer().length > (2 * size)) && (cb.getLength()) < (cb.getStart())) {
                System.arraycopy(cb.getBuffer(), cb.getStart(), cb.getBuffer(), 0, cb.getLength());
                cb.setEnd(cb.getLength());
                cb.setOffset(0);
            }
        }
        cb.setLimit(cb.getStart() + readAheadLimit + size);
        markPos = cb.getStart();
    }

    @Override
    public void reset() throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        if (state == CHAR_STATE) {
            if (markPos < 0) {
                cb.recycle();
                markPos = -1;
                throw new IOException();
            } else {
                cb.setOffset(markPos);
            }
        } else {
            bb.recycle();
        }
    }

    public void checkConverter() throws IOException {
        if (!gotEnc) {
            setConverter();
        }
    }

    protected void setConverter() throws IOException {
        if (coyoteRequest != null) {
            enc = coyoteRequest.getCharacterEncoding();
        }
        gotEnc = true;
        if (enc == null) {
            enc = DEFAULT_ENCODING;
        }
        conv = encoders.get(enc);
        if (conv == null) {
            if (SecurityUtil.isPackageProtectionEnabled()) {
                try {
                    conv = AccessController.doPrivileged(new PrivilegedExceptionAction<B2CConverter>() {

                        @Override
                        public B2CConverter run() throws IOException {
                            return new B2CConverter(enc);
                        }
                    });
                } catch (PrivilegedActionException ex) {
                    Exception e = ex.getException();
                    if (e instanceof IOException) {
                        throw (IOException) e;
                    }
                }
            } else {
                conv = new B2CConverter(enc);
            }
            encoders.put(enc, conv);
        }
    }
}

16 View Complete Implementation : ChunkedInputFilter.java
Copyright Apache License 2.0
Author : apache
// ---------------------------------------------------- InputBuffer Methods
/**
 * Read bytes.
 *
 * @return If the filter does request length control, this value is
 * significant; it should be the number of bytes consumed from the buffer,
 * up until the end of the current request body, or the buffer length,
 * whichever is greater. If the filter does not do request body length
 * control, the returned value should be -1.
 */
@Override
public int doRead(ByteChunk chunk, Request req) throws IOException {
    if (endChunk) {
        return -1;
    }
    checkError();
    if (needCRLFParse) {
        needCRLFParse = false;
        parseCRLF(false);
    }
    if (remaining <= 0) {
        if (!parseChunkHeader()) {
            throwIOException(sm.getString("chunkedInputFilter.invalidHeader"));
        }
        if (endChunk) {
            parseEndChunk();
            return -1;
        }
    }
    int result = 0;
    if (pos >= lastValid) {
        if (readBytes() < 0) {
            throwIOException(sm.getString("chunkedInputFilter.eos"));
        }
    }
    if (remaining > (lastValid - pos)) {
        result = lastValid - pos;
        remaining = remaining - result;
        chunk.setBytes(buf, pos, result);
        pos = lastValid;
    } else {
        result = remaining;
        chunk.setBytes(buf, pos, remaining);
        pos = pos + remaining;
        remaining = 0;
        // we need a CRLF
        if ((pos + 1) >= lastValid) {
            // if we call parseCRLF we overrun the buffer here
            // so we defer it to the next call BZ 11117
            needCRLFParse = true;
        } else {
            // parse the CRLF immediately
            parseCRLF(false);
        }
    }
    return result;
}

16 View Complete Implementation : ChunkedInputFilter.java
Copyright Apache License 2.0
Author : apache
/**
 * Chunked input filter. Parses chunked data according to
 * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1">http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1</a><br>
 *
 * @author Remy Maucherat
 */
public clreplaced ChunkedInputFilter implements InputFilter {

    private static final StringManager sm = StringManager.getManager(ChunkedInputFilter.clreplaced.getPackage().getName());

    // -------------------------------------------------------------- Constants
    protected static final String ENCODING_NAME = "chunked";

    protected static final ByteChunk ENCODING = new ByteChunk();

    // ----------------------------------------------------- Static Initializer
    static {
        ENCODING.setBytes(ENCODING_NAME.getBytes(StandardCharsets.ISO_8859_1), 0, ENCODING_NAME.length());
    }

    // ----------------------------------------------------- Instance Variables
    /**
     * Next buffer in the pipeline.
     */
    protected InputBuffer buffer;

    /**
     * Number of bytes remaining in the current chunk.
     */
    protected int remaining = 0;

    /**
     * Position in the buffer.
     */
    protected int pos = 0;

    /**
     * Last valid byte in the buffer.
     */
    protected int lastValid = 0;

    /**
     * Read bytes buffer.
     */
    protected byte[] buf = null;

    /**
     * Byte chunk used to read bytes.
     */
    protected final ByteChunk readChunk = new ByteChunk();

    /**
     * Flag set to true when the end chunk has been read.
     */
    protected boolean endChunk = false;

    /**
     * Byte chunk used to store trailing headers.
     */
    protected final ByteChunk trailingHeaders = new ByteChunk();

    /**
     * Flag set to true if the next call to doRead() must parse a CRLF pair
     * before doing anything else.
     */
    protected boolean needCRLFParse = false;

    /**
     * Request being parsed.
     */
    private Request request;

    /**
     * Limit for extension size.
     */
    private final long maxExtensionSize;

    /**
     * Limit for trailer size.
     */
    private final int maxTrailerSize;

    /**
     * Size of extensions processed for this request.
     */
    private long extensionSize;

    private final int maxSwallowSize;

    /**
     * Flag that indicates if an error has occurred.
     */
    private boolean error;

    private final Set<String> allowedTrailerHeaders;

    // ----------------------------------------------------------- Constructors
    public ChunkedInputFilter(int maxTrailerSize, Set<String> allowedTrailerHeaders, int maxExtensionSize, int maxSwallowSize) {
        this.trailingHeaders.setLimit(maxTrailerSize);
        this.allowedTrailerHeaders = allowedTrailerHeaders;
        this.maxExtensionSize = maxExtensionSize;
        this.maxTrailerSize = maxTrailerSize;
        this.maxSwallowSize = maxSwallowSize;
    }

    // ---------------------------------------------------- InputBuffer Methods
    /**
     * Read bytes.
     *
     * @return If the filter does request length control, this value is
     * significant; it should be the number of bytes consumed from the buffer,
     * up until the end of the current request body, or the buffer length,
     * whichever is greater. If the filter does not do request body length
     * control, the returned value should be -1.
     */
    @Override
    public int doRead(ByteChunk chunk, Request req) throws IOException {
        if (endChunk) {
            return -1;
        }
        checkError();
        if (needCRLFParse) {
            needCRLFParse = false;
            parseCRLF(false);
        }
        if (remaining <= 0) {
            if (!parseChunkHeader()) {
                throwIOException(sm.getString("chunkedInputFilter.invalidHeader"));
            }
            if (endChunk) {
                parseEndChunk();
                return -1;
            }
        }
        int result = 0;
        if (pos >= lastValid) {
            if (readBytes() < 0) {
                throwIOException(sm.getString("chunkedInputFilter.eos"));
            }
        }
        if (remaining > (lastValid - pos)) {
            result = lastValid - pos;
            remaining = remaining - result;
            chunk.setBytes(buf, pos, result);
            pos = lastValid;
        } else {
            result = remaining;
            chunk.setBytes(buf, pos, remaining);
            pos = pos + remaining;
            remaining = 0;
            // we need a CRLF
            if ((pos + 1) >= lastValid) {
                // if we call parseCRLF we overrun the buffer here
                // so we defer it to the next call BZ 11117
                needCRLFParse = true;
            } else {
                // parse the CRLF immediately
                parseCRLF(false);
            }
        }
        return result;
    }

    // ---------------------------------------------------- InputFilter Methods
    /**
     * Read the content length from the request.
     */
    @Override
    public void setRequest(Request request) {
        this.request = request;
    }

    /**
     * End the current request.
     */
    @Override
    public long end() throws IOException {
        long swallowed = 0;
        int read = 0;
        // Consume extra bytes : parse the stream until the end chunk is found
        while ((read = doRead(readChunk, null)) >= 0) {
            swallowed += read;
            if (maxSwallowSize > -1 && swallowed > maxSwallowSize) {
                throwIOException(sm.getString("inputFilter.maxSwallow"));
            }
        }
        // Return the number of extra bytes which were consumed
        return lastValid - pos;
    }

    /**
     * Amount of bytes still available in a buffer.
     */
    @Override
    public int available() {
        return lastValid - pos;
    }

    /**
     * Set the next buffer in the filter pipeline.
     */
    @Override
    public void setBuffer(InputBuffer buffer) {
        this.buffer = buffer;
    }

    /**
     * Make the filter ready to process the next request.
     */
    @Override
    public void recycle() {
        remaining = 0;
        pos = 0;
        lastValid = 0;
        endChunk = false;
        needCRLFParse = false;
        trailingHeaders.recycle();
        trailingHeaders.setLimit(maxTrailerSize);
        extensionSize = 0;
        error = false;
    }

    /**
     * Return the name of the replacedociated encoding; Here, the value is
     * "idenreplacedy".
     */
    @Override
    public ByteChunk getEncodingName() {
        return ENCODING;
    }

    @Override
    public boolean isFinished() {
        return endChunk;
    }

    // ------------------------------------------------------ Protected Methods
    /**
     * Read bytes from the previous buffer.
     */
    protected int readBytes() throws IOException {
        int nRead = buffer.doRead(readChunk, null);
        pos = readChunk.getStart();
        lastValid = pos + nRead;
        buf = readChunk.getBytes();
        return nRead;
    }

    /**
     * Parse the header of a chunk.
     * A chunk header can look like one of the following:<br>
     * A10CRLF<br>
     * F23;chunk-extension to be ignoredCRLF
     *
     * <p>
     * The letters before CRLF or ';' (whatever comes first) must be valid hex
     * digits. We should not parse F23IAMGONNAMESSTHISUP34CRLF as a valid
     * header according to the spec.
     */
    protected boolean parseChunkHeader() throws IOException {
        int result = 0;
        boolean eol = false;
        int readDigit = 0;
        boolean extension = false;
        while (!eol) {
            if (pos >= lastValid) {
                if (readBytes() <= 0)
                    return false;
            }
            if (buf[pos] == Constants.CR || buf[pos] == Constants.LF) {
                parseCRLF(false);
                eol = true;
            } else if (buf[pos] == Constants.SEMI_COLON && !extension) {
                // First semi-colon marks the start of the extension. Further
                // semi-colons may appear to separate multiple chunk-extensions.
                // These need to be processed as part of parsing the extensions.
                extension = true;
                extensionSize++;
            } else if (!extension) {
                // don't read data after the trailer
                int charValue = HexUtils.getDec(buf[pos]);
                if (charValue != -1 && readDigit < 8) {
                    readDigit++;
                    result = (result << 4) | charValue;
                } else {
                    // we shouldn't allow invalid, non hex characters
                    // in the chunked header
                    return false;
                }
            } else {
                // Extension 'parsing'
                // Note that the chunk-extension is neither parsed nor
                // validated. Currently it is simply ignored.
                extensionSize++;
                if (maxExtensionSize > -1 && extensionSize > maxExtensionSize) {
                    throwIOException(sm.getString("chunkedInputFilter.maxExtension"));
                }
            }
            // Parsing the CRLF increments pos
            if (!eol) {
                pos++;
            }
        }
        if (readDigit == 0 || result < 0) {
            return false;
        }
        if (result == 0) {
            endChunk = true;
        }
        remaining = result;
        return true;
    }

    /**
     * Parse CRLF at end of chunk.
     *
     * @param   tolerant    Should tolerant parsing (LF and CRLF) be used? This
     *                      is recommended (RFC2616, section 19.3) for message
     *                      headers.
     */
    protected void parseCRLF(boolean tolerant) throws IOException {
        boolean eol = false;
        boolean crfound = false;
        while (!eol) {
            if (pos >= lastValid) {
                if (readBytes() <= 0) {
                    throwIOException(sm.getString("chunkedInputFilter.invalidCrlfNoData"));
                }
            }
            if (buf[pos] == Constants.CR) {
                if (crfound) {
                    throwIOException(sm.getString("chunkedInputFilter.invalidCrlfCRCR"));
                }
                crfound = true;
            } else if (buf[pos] == Constants.LF) {
                if (!tolerant && !crfound) {
                    throwIOException(sm.getString("chunkedInputFilter.invalidCrlfNoCR"));
                }
                eol = true;
            } else {
                throwIOException(sm.getString("chunkedInputFilter.invalidCrlf"));
            }
            pos++;
        }
    }

    /**
     * Parse end chunk data.
     */
    protected void parseEndChunk() throws IOException {
        // Handle optional trailer headers
        while (parseHeader()) {
        // Loop until we run out of headers
        }
    }

    private boolean parseHeader() throws IOException {
        MimeHeaders headers = request.getMimeHeaders();
        byte chr = 0;
        // Read new bytes if needed
        if (pos >= lastValid) {
            if (readBytes() < 0) {
                throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
            }
        }
        chr = buf[pos];
        // CRLF terminates the request
        if (chr == Constants.CR || chr == Constants.LF) {
            parseCRLF(false);
            return false;
        }
        // Mark the current buffer position
        int startPos = trailingHeaders.getEnd();
        // 
        // Reading the header name
        // Header name is always US-ASCII
        // 
        boolean colon = false;
        while (!colon) {
            // Read new bytes if needed
            if (pos >= lastValid) {
                if (readBytes() < 0) {
                    throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
                }
            }
            chr = buf[pos];
            if ((chr >= Constants.A) && (chr <= Constants.Z)) {
                chr = (byte) (chr - Constants.LC_OFFSET);
            }
            if (chr == Constants.COLON) {
                colon = true;
            } else {
                trailingHeaders.append(chr);
            }
            pos++;
        }
        int colonPos = trailingHeaders.getEnd();
        // 
        // Reading the header value (which can be spanned over multiple lines)
        // 
        boolean eol = false;
        boolean validLine = true;
        int lastSignificantChar = 0;
        while (validLine) {
            boolean space = true;
            // Skipping spaces
            while (space) {
                // Read new bytes if needed
                if (pos >= lastValid) {
                    if (readBytes() < 0) {
                        throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
                    }
                }
                chr = buf[pos];
                if ((chr == Constants.SP) || (chr == Constants.HT)) {
                    pos++;
                    // If we swallow whitespace, make sure it counts towards the
                    // limit placed on trailing header size
                    int newlimit = trailingHeaders.getLimit() - 1;
                    if (trailingHeaders.getEnd() > newlimit) {
                        throwIOException(sm.getString("chunkedInputFilter.maxTrailer"));
                    }
                    trailingHeaders.setLimit(newlimit);
                } else {
                    space = false;
                }
            }
            // Reading bytes until the end of the line
            while (!eol) {
                // Read new bytes if needed
                if (pos >= lastValid) {
                    if (readBytes() < 0) {
                        throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
                    }
                }
                chr = buf[pos];
                if (chr == Constants.CR || chr == Constants.LF) {
                    parseCRLF(true);
                    eol = true;
                } else if (chr == Constants.SP) {
                    trailingHeaders.append(chr);
                } else {
                    trailingHeaders.append(chr);
                    lastSignificantChar = trailingHeaders.getEnd();
                }
                if (!eol) {
                    pos++;
                }
            }
            // Checking the first character of the new line. If the character
            // is a LWS, then it's a multiline header
            // Read new bytes if needed
            if (pos >= lastValid) {
                if (readBytes() < 0) {
                    throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
                }
            }
            chr = buf[pos];
            if ((chr != Constants.SP) && (chr != Constants.HT)) {
                validLine = false;
            } else {
                eol = false;
                // Copying one extra space in the buffer (since there must
                // be at least one space inserted between the lines)
                trailingHeaders.append(chr);
            }
        }
        String headerName = new String(trailingHeaders.getBytes(), startPos, colonPos - startPos, StandardCharsets.ISO_8859_1);
        if (allowedTrailerHeaders.contains(headerName.toLowerCase(Locale.ENGLISH))) {
            MessageBytes headerValue = headers.addValue(headerName);
            // Set the header value
            headerValue.setBytes(trailingHeaders.getBytes(), colonPos, lastSignificantChar - colonPos);
        }
        return true;
    }

    private void throwIOException(String msg) throws IOException {
        error = true;
        throw new IOException(msg);
    }

    private void throwEOFException(String msg) throws IOException {
        error = true;
        throw new EOFException(msg);
    }

    private void checkError() throws IOException {
        if (error) {
            throw new IOException(sm.getString("chunkedInputFilter.error"));
        }
    }
}

16 View Complete Implementation : ChunkedInputFilter.java
Copyright MIT License
Author : chenmudu
/**
 * Chunked input filter. Parses chunked data according to
 * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1">http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1</a><br>
 *
 * @author Remy Maucherat
 */
public clreplaced ChunkedInputFilter implements InputFilter, ApplicationBufferHandler {

    private static final StringManager sm = StringManager.getManager(ChunkedInputFilter.clreplaced.getPackage().getName());

    // -------------------------------------------------------------- Constants
    protected static final String ENCODING_NAME = "chunked";

    protected static final ByteChunk ENCODING = new ByteChunk();

    // ----------------------------------------------------- Static Initializer
    static {
        ENCODING.setBytes(ENCODING_NAME.getBytes(StandardCharsets.ISO_8859_1), 0, ENCODING_NAME.length());
    }

    // ----------------------------------------------------- Instance Variables
    /**
     * Next buffer in the pipeline.
     */
    protected InputBuffer buffer;

    /**
     * Number of bytes remaining in the current chunk.
     */
    protected int remaining = 0;

    /**
     * Byte chunk used to read bytes.
     */
    protected ByteBuffer readChunk;

    /**
     * Flag set to true when the end chunk has been read.
     */
    protected boolean endChunk = false;

    /**
     * Byte chunk used to store trailing headers.
     */
    protected final ByteChunk trailingHeaders = new ByteChunk();

    /**
     * Flag set to true if the next call to doRead() must parse a CRLF pair
     * before doing anything else.
     */
    protected boolean needCRLFParse = false;

    /**
     * Request being parsed.
     */
    private Request request;

    /**
     * Limit for extension size.
     */
    private final long maxExtensionSize;

    /**
     * Limit for trailer size.
     */
    private final int maxTrailerSize;

    /**
     * Size of extensions processed for this request.
     */
    private long extensionSize;

    private final int maxSwallowSize;

    /**
     * Flag that indicates if an error has occurred.
     */
    private boolean error;

    private final Set<String> allowedTrailerHeaders;

    // ----------------------------------------------------------- Constructors
    public ChunkedInputFilter(int maxTrailerSize, Set<String> allowedTrailerHeaders, int maxExtensionSize, int maxSwallowSize) {
        this.trailingHeaders.setLimit(maxTrailerSize);
        this.allowedTrailerHeaders = allowedTrailerHeaders;
        this.maxExtensionSize = maxExtensionSize;
        this.maxTrailerSize = maxTrailerSize;
        this.maxSwallowSize = maxSwallowSize;
    }

    // ---------------------------------------------------- InputBuffer Methods
    /**
     * @deprecated Unused. Will be removed in Tomcat 9. Use
     *             {@link #doRead(ApplicationBufferHandler)}
     */
    @Deprecated
    @Override
    public int doRead(ByteChunk chunk) throws IOException {
        if (endChunk) {
            return -1;
        }
        checkError();
        if (needCRLFParse) {
            needCRLFParse = false;
            parseCRLF(false);
        }
        if (remaining <= 0) {
            if (!parseChunkHeader()) {
                throwIOException(sm.getString("chunkedInputFilter.invalidHeader"));
            }
            if (endChunk) {
                parseEndChunk();
                return -1;
            }
        }
        int result = 0;
        if (readChunk == null || readChunk.position() >= readChunk.limit()) {
            if (readBytes() < 0) {
                throwIOException(sm.getString("chunkedInputFilter.eos"));
            }
        }
        if (remaining > readChunk.remaining()) {
            result = readChunk.remaining();
            remaining = remaining - result;
            chunk.setBytes(readChunk.array(), readChunk.arrayOffset() + readChunk.position(), result);
            readChunk.position(readChunk.limit());
        } else {
            result = remaining;
            chunk.setBytes(readChunk.array(), readChunk.arrayOffset() + readChunk.position(), remaining);
            readChunk.position(readChunk.position() + remaining);
            remaining = 0;
            // we need a CRLF
            if ((readChunk.position() + 1) >= readChunk.limit()) {
                // if we call parseCRLF we overrun the buffer here
                // so we defer it to the next call BZ 11117
                needCRLFParse = true;
            } else {
                // parse the CRLF immediately
                parseCRLF(false);
            }
        }
        return result;
    }

    @Override
    public int doRead(ApplicationBufferHandler handler) throws IOException {
        if (endChunk) {
            return -1;
        }
        checkError();
        if (needCRLFParse) {
            needCRLFParse = false;
            parseCRLF(false);
        }
        if (remaining <= 0) {
            if (!parseChunkHeader()) {
                throwIOException(sm.getString("chunkedInputFilter.invalidHeader"));
            }
            if (endChunk) {
                parseEndChunk();
                return -1;
            }
        }
        int result = 0;
        if (readChunk == null || readChunk.position() >= readChunk.limit()) {
            if (readBytes() < 0) {
                throwIOException(sm.getString("chunkedInputFilter.eos"));
            }
        }
        if (remaining > readChunk.remaining()) {
            result = readChunk.remaining();
            remaining = remaining - result;
            if (readChunk != handler.getByteBuffer()) {
                handler.setByteBuffer(readChunk.duplicate());
            }
            readChunk.position(readChunk.limit());
        } else {
            result = remaining;
            if (readChunk != handler.getByteBuffer()) {
                handler.setByteBuffer(readChunk.duplicate());
                handler.getByteBuffer().limit(readChunk.position() + remaining);
            }
            readChunk.position(readChunk.position() + remaining);
            remaining = 0;
            // we need a CRLF
            if ((readChunk.position() + 1) >= readChunk.limit()) {
                // if we call parseCRLF we overrun the buffer here
                // so we defer it to the next call BZ 11117
                needCRLFParse = true;
            } else {
                // parse the CRLF immediately
                parseCRLF(false);
            }
        }
        return result;
    }

    // ---------------------------------------------------- InputFilter Methods
    /**
     * Read the content length from the request.
     */
    @Override
    public void setRequest(Request request) {
        this.request = request;
    }

    /**
     * End the current request.
     */
    @Override
    public long end() throws IOException {
        long swallowed = 0;
        int read = 0;
        // Consume extra bytes : parse the stream until the end chunk is found
        while ((read = doRead(this)) >= 0) {
            swallowed += read;
            if (maxSwallowSize > -1 && swallowed > maxSwallowSize) {
                throwIOException(sm.getString("inputFilter.maxSwallow"));
            }
        }
        // Return the number of extra bytes which were consumed
        return readChunk.remaining();
    }

    /**
     * Amount of bytes still available in a buffer.
     */
    @Override
    public int available() {
        return readChunk != null ? readChunk.remaining() : 0;
    }

    /**
     * Set the next buffer in the filter pipeline.
     */
    @Override
    public void setBuffer(InputBuffer buffer) {
        this.buffer = buffer;
    }

    /**
     * Make the filter ready to process the next request.
     */
    @Override
    public void recycle() {
        remaining = 0;
        if (readChunk != null) {
            readChunk.position(0).limit(0);
        }
        endChunk = false;
        needCRLFParse = false;
        trailingHeaders.recycle();
        trailingHeaders.setLimit(maxTrailerSize);
        extensionSize = 0;
        error = false;
    }

    /**
     * Return the name of the replacedociated encoding; Here, the value is
     * "idenreplacedy".
     */
    @Override
    public ByteChunk getEncodingName() {
        return ENCODING;
    }

    @Override
    public boolean isFinished() {
        return endChunk;
    }

    // ------------------------------------------------------ Protected Methods
    /**
     * Read bytes from the previous buffer.
     * @return The byte count which has been read
     * @throws IOException Read error
     */
    protected int readBytes() throws IOException {
        return buffer.doRead(this);
    }

    /**
     * Parse the header of a chunk.
     * A chunk header can look like one of the following:<br>
     * A10CRLF<br>
     * F23;chunk-extension to be ignoredCRLF
     *
     * <p>
     * The letters before CRLF or ';' (whatever comes first) must be valid hex
     * digits. We should not parse F23IAMGONNAMESSTHISUP34CRLF as a valid
     * header according to the spec.
     * @return <code>true</code> if the chunk header has been
     *  successfully parsed
     * @throws IOException Read error
     */
    protected boolean parseChunkHeader() throws IOException {
        int result = 0;
        boolean eol = false;
        int readDigit = 0;
        boolean extension = false;
        while (!eol) {
            if (readChunk == null || readChunk.position() >= readChunk.limit()) {
                if (readBytes() <= 0)
                    return false;
            }
            byte chr = readChunk.get(readChunk.position());
            if (chr == Constants.CR || chr == Constants.LF) {
                parseCRLF(false);
                eol = true;
            } else if (chr == Constants.SEMI_COLON && !extension) {
                // First semi-colon marks the start of the extension. Further
                // semi-colons may appear to separate multiple chunk-extensions.
                // These need to be processed as part of parsing the extensions.
                extension = true;
                extensionSize++;
            } else if (!extension) {
                // don't read data after the trailer
                int charValue = HexUtils.getDec(chr);
                if (charValue != -1 && readDigit < 8) {
                    readDigit++;
                    result = (result << 4) | charValue;
                } else {
                    // we shouldn't allow invalid, non hex characters
                    // in the chunked header
                    return false;
                }
            } else {
                // Extension 'parsing'
                // Note that the chunk-extension is neither parsed nor
                // validated. Currently it is simply ignored.
                extensionSize++;
                if (maxExtensionSize > -1 && extensionSize > maxExtensionSize) {
                    throwIOException(sm.getString("chunkedInputFilter.maxExtension"));
                }
            }
            // Parsing the CRLF increments pos
            if (!eol) {
                readChunk.position(readChunk.position() + 1);
            }
        }
        if (readDigit == 0 || result < 0) {
            return false;
        }
        if (result == 0) {
            endChunk = true;
        }
        remaining = result;
        return true;
    }

    /**
     * Parse CRLF at end of chunk.
     *
     * @param   tolerant    Should tolerant parsing (LF and CRLF) be used? This
     *                      is recommended (RFC2616, section 19.3) for message
     *                      headers.
     * @throws IOException An error occurred parsing CRLF
     */
    protected void parseCRLF(boolean tolerant) throws IOException {
        boolean eol = false;
        boolean crfound = false;
        while (!eol) {
            if (readChunk == null || readChunk.position() >= readChunk.limit()) {
                if (readBytes() <= 0) {
                    throwIOException(sm.getString("chunkedInputFilter.invalidCrlfNoData"));
                }
            }
            byte chr = readChunk.get(readChunk.position());
            if (chr == Constants.CR) {
                if (crfound) {
                    throwIOException(sm.getString("chunkedInputFilter.invalidCrlfCRCR"));
                }
                crfound = true;
            } else if (chr == Constants.LF) {
                if (!tolerant && !crfound) {
                    throwIOException(sm.getString("chunkedInputFilter.invalidCrlfNoCR"));
                }
                eol = true;
            } else {
                throwIOException(sm.getString("chunkedInputFilter.invalidCrlf"));
            }
            readChunk.position(readChunk.position() + 1);
        }
    }

    /**
     * Parse end chunk data.
     * @throws IOException Error propagation
     */
    protected void parseEndChunk() throws IOException {
        // Handle optional trailer headers
        while (parseHeader()) {
        // Loop until we run out of headers
        }
    }

    private boolean parseHeader() throws IOException {
        MimeHeaders headers = request.getMimeHeaders();
        byte chr = 0;
        // Read new bytes if needed
        if (readChunk == null || readChunk.position() >= readChunk.limit()) {
            if (readBytes() < 0) {
                throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
            }
        }
        // readBytes() above will set readChunk unless it returns a value < 0
        chr = readChunk.get(readChunk.position());
        // CRLF terminates the request
        if (chr == Constants.CR || chr == Constants.LF) {
            parseCRLF(false);
            return false;
        }
        // Mark the current buffer position
        int startPos = trailingHeaders.getEnd();
        // 
        // Reading the header name
        // Header name is always US-ASCII
        // 
        boolean colon = false;
        while (!colon) {
            // Read new bytes if needed
            if (readChunk == null || readChunk.position() >= readChunk.limit()) {
                if (readBytes() < 0) {
                    throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
                }
            }
            // readBytes() above will set readChunk unless it returns a value < 0
            chr = readChunk.get(readChunk.position());
            if ((chr >= Constants.A) && (chr <= Constants.Z)) {
                chr = (byte) (chr - Constants.LC_OFFSET);
            }
            if (chr == Constants.COLON) {
                colon = true;
            } else {
                trailingHeaders.append(chr);
            }
            readChunk.position(readChunk.position() + 1);
        }
        int colonPos = trailingHeaders.getEnd();
        // 
        // Reading the header value (which can be spanned over multiple lines)
        // 
        boolean eol = false;
        boolean validLine = true;
        int lastSignificantChar = 0;
        while (validLine) {
            boolean space = true;
            // Skipping spaces
            while (space) {
                // Read new bytes if needed
                if (readChunk == null || readChunk.position() >= readChunk.limit()) {
                    if (readBytes() < 0) {
                        throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
                    }
                }
                chr = readChunk.get(readChunk.position());
                if ((chr == Constants.SP) || (chr == Constants.HT)) {
                    readChunk.position(readChunk.position() + 1);
                    // If we swallow whitespace, make sure it counts towards the
                    // limit placed on trailing header size
                    int newlimit = trailingHeaders.getLimit() - 1;
                    if (trailingHeaders.getEnd() > newlimit) {
                        throwIOException(sm.getString("chunkedInputFilter.maxTrailer"));
                    }
                    trailingHeaders.setLimit(newlimit);
                } else {
                    space = false;
                }
            }
            // Reading bytes until the end of the line
            while (!eol) {
                // Read new bytes if needed
                if (readChunk == null || readChunk.position() >= readChunk.limit()) {
                    if (readBytes() < 0) {
                        throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
                    }
                }
                chr = readChunk.get(readChunk.position());
                if (chr == Constants.CR || chr == Constants.LF) {
                    parseCRLF(true);
                    eol = true;
                } else if (chr == Constants.SP) {
                    trailingHeaders.append(chr);
                } else {
                    trailingHeaders.append(chr);
                    lastSignificantChar = trailingHeaders.getEnd();
                }
                if (!eol) {
                    readChunk.position(readChunk.position() + 1);
                }
            }
            // Checking the first character of the new line. If the character
            // is a LWS, then it's a multiline header
            // Read new bytes if needed
            if (readChunk == null || readChunk.position() >= readChunk.limit()) {
                if (readBytes() < 0) {
                    throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
                }
            }
            chr = readChunk.get(readChunk.position());
            if ((chr != Constants.SP) && (chr != Constants.HT)) {
                validLine = false;
            } else {
                eol = false;
                // Copying one extra space in the buffer (since there must
                // be at least one space inserted between the lines)
                trailingHeaders.append(chr);
            }
        }
        String headerName = new String(trailingHeaders.getBytes(), startPos, colonPos - startPos, StandardCharsets.ISO_8859_1);
        if (allowedTrailerHeaders.contains(headerName.toLowerCase(Locale.ENGLISH))) {
            MessageBytes headerValue = headers.addValue(headerName);
            // Set the header value
            headerValue.setBytes(trailingHeaders.getBytes(), colonPos, lastSignificantChar - colonPos);
        }
        return true;
    }

    private void throwIOException(String msg) throws IOException {
        error = true;
        throw new IOException(msg);
    }

    private void throwEOFException(String msg) throws IOException {
        error = true;
        throw new EOFException(msg);
    }

    private void checkError() throws IOException {
        if (error) {
            throw new IOException(sm.getString("chunkedInputFilter.error"));
        }
    }

    @Override
    public void setByteBuffer(ByteBuffer buffer) {
        readChunk = buffer;
    }

    @Override
    public ByteBuffer getByteBuffer() {
        return readChunk;
    }

    @Override
    public void expand(int size) {
    // no-op
    }
}

16 View Complete Implementation : Stream.java
Copyright MIT License
Author : chenmudu
public clreplaced Stream extends AbstractStream implements HeaderEmitter {

    private static final Log log = LogFactory.getLog(Stream.clreplaced);

    private static final StringManager sm = StringManager.getManager(Stream.clreplaced);

    private static final int HEADER_STATE_START = 0;

    private static final int HEADER_STATE_PSEUDO = 1;

    private static final int HEADER_STATE_REGULAR = 2;

    private static final int HEADER_STATE_TRAILER = 3;

    private static final MimeHeaders ACK_HEADERS;

    private static final Integer HTTP_UPGRADE_STREAM = Integer.valueOf(1);

    static {
        Response response = new Response();
        response.setStatus(100);
        StreamProcessor.prepareHeaders(null, response, null, null);
        ACK_HEADERS = response.getMimeHeaders();
    }

    private volatile int weight = Constants.DEFAULT_WEIGHT;

    private volatile long contentLengthReceived = 0;

    private final Http2UpgradeHandler handler;

    private final StreamStateMachine state;

    private final WindowAllocationManager allocationManager = new WindowAllocationManager(this);

    // State machine would be too much overhead
    private int headerState = HEADER_STATE_START;

    private StreamException headerException = null;

    // TODO: null these when finished to reduce memory used by closed stream
    private final Request coyoteRequest;

    private StringBuilder cookieHeader = null;

    private final Response coyoteResponse = new Response();

    private final StreamInputBuffer inputBuffer;

    private final StreamOutputBuffer streamOutputBuffer = new StreamOutputBuffer();

    private final Http2OutputBuffer http2OutputBuffer = new Http2OutputBuffer(coyoteResponse, streamOutputBuffer);

    public Stream(Integer identifier, Http2UpgradeHandler handler) {
        this(identifier, handler, null);
    }

    public Stream(Integer identifier, Http2UpgradeHandler handler, Request coyoteRequest) {
        super(identifier);
        this.handler = handler;
        handler.addChild(this);
        setWindowSize(handler.getRemoteSettings().getInitialWindowSize());
        state = new StreamStateMachine(this);
        if (coyoteRequest == null) {
            // HTTP/2 new request
            this.coyoteRequest = new Request();
            this.inputBuffer = new StreamInputBuffer();
            this.coyoteRequest.setInputBuffer(inputBuffer);
        } else {
            // HTTP/2 Push or HTTP/1.1 upgrade
            this.coyoteRequest = coyoteRequest;
            this.inputBuffer = null;
            // Headers have been read by this point
            state.receivedStartOfHeaders();
            if (HTTP_UPGRADE_STREAM.equals(identifier)) {
                // Populate coyoteRequest from headers (HTTP/1.1 only)
                try {
                    prepareRequest();
                } catch (IllegalArgumentException iae) {
                    // Something in the headers is invalid
                    // Set correct return status
                    coyoteResponse.setStatus(400);
                    // Set error flag. This triggers error processing rather than
                    // the normal mapping
                    coyoteResponse.setError();
                }
            }
            // TODO replaceduming the body has been read at this point is not valid
            state.receivedEndOfStream();
        }
        // No sendfile for HTTP/2 (it is enabled by default in the request)
        this.coyoteRequest.setSendfile(false);
        this.coyoteResponse.setOutputBuffer(http2OutputBuffer);
        this.coyoteRequest.setResponse(coyoteResponse);
        this.coyoteRequest.protocol().setString("HTTP/2.0");
        if (this.coyoteRequest.getStartTime() < 0) {
            this.coyoteRequest.setStartTime(System.currentTimeMillis());
        }
    }

    private void prepareRequest() {
        MessageBytes hostValueMB = coyoteRequest.getMimeHeaders().getUniqueValue("host");
        if (hostValueMB == null) {
            throw new IllegalArgumentException();
        }
        // This processing expects bytes. Server push will have used a String
        // to trigger a conversion if required.
        hostValueMB.toBytes();
        ByteChunk valueBC = hostValueMB.getByteChunk();
        byte[] valueB = valueBC.getBytes();
        int valueL = valueBC.getLength();
        int valueS = valueBC.getStart();
        int colonPos = Host.parse(hostValueMB);
        if (colonPos != -1) {
            int port = 0;
            for (int i = colonPos + 1; i < valueL; i++) {
                char c = (char) valueB[i + valueS];
                if (c < '0' || c > '9') {
                    throw new IllegalArgumentException();
                }
                port = port * 10 + c - '0';
            }
            coyoteRequest.setServerPort(port);
            // Only need to copy the host name up to the :
            valueL = colonPos;
        }
        // Extract the host name
        char[] hostNameC = new char[valueL];
        for (int i = 0; i < valueL; i++) {
            hostNameC[i] = (char) valueB[i + valueS];
        }
        coyoteRequest.serverName().setChars(hostNameC, 0, valueL);
    }

    void rePrioritise(AbstractStream parent, boolean exclusive, int weight) {
        if (log.isDebugEnabled()) {
            log.debug(sm.getString("stream.reprioritisation.debug", getConnectionId(), getIdentifier(), Boolean.toString(exclusive), parent.getIdentifier(), Integer.toString(weight)));
        }
        // Check if new parent is a descendant of this stream
        if (isDescendant(parent)) {
            parent.detachFromParent();
            // Cast is always safe since any descendant of this stream must be
            // an instance of Stream
            getParentStream().addChild((Stream) parent);
        }
        if (exclusive) {
            // Need to move children of the new parent to be children of this
            // stream. Slightly convoluted to avoid concurrent modification.
            Iterator<Stream> parentsChildren = parent.getChildStreams().iterator();
            while (parentsChildren.hasNext()) {
                Stream parentsChild = parentsChildren.next();
                parentsChildren.remove();
                this.addChild(parentsChild);
            }
        }
        detachFromParent();
        parent.addChild(this);
        this.weight = weight;
    }

    /*
     * Used when removing closed streams from the tree and we know there is no
     * need to check for circular references.
     */
    final void rePrioritise(AbstractStream parent, int weight) {
        if (log.isDebugEnabled()) {
            log.debug(sm.getString("stream.reprioritisation.debug", getConnectionId(), getIdentifier(), Boolean.FALSE, parent.getIdentifier(), Integer.toString(weight)));
        }
        parent.addChild(this);
        this.weight = weight;
    }

    void receiveReset(long errorCode) {
        if (log.isDebugEnabled()) {
            log.debug(sm.getString("stream.reset.debug", getConnectionId(), getIdentifier(), Long.toString(errorCode)));
        }
        // Set the new state first since read and write both check this
        state.receivedReset();
        // Reads wait internally so need to call a method to break the wait()
        if (inputBuffer != null) {
            inputBuffer.receiveReset();
        }
        cancelAllocationRequests();
    }

    final void cancelAllocationRequests() {
        allocationManager.notifyAny();
    }

    void checkState(FrameType frameType) throws Http2Exception {
        state.checkFrameType(frameType);
    }

    @Override
    protected synchronized void incrementWindowSize(int windowSizeIncrement) throws Http2Exception {
        // If this is zero then any thread that has been trying to write for
        // this stream will be waiting. Notify that thread it can continue. Use
        // notify all even though only one thread is waiting to be on the safe
        // side.
        boolean notify = getWindowSize() < 1;
        super.incrementWindowSize(windowSizeIncrement);
        if (notify && getWindowSize() > 0) {
            allocationManager.notifyStream();
        }
    }

    private synchronized int reserveWindowSize(int reservation, boolean block) throws IOException {
        long windowSize = getWindowSize();
        while (windowSize < 1) {
            if (!canWrite()) {
                throw new CloseNowException(sm.getString("stream.notWritable", getConnectionId(), getIdentifier()));
            }
            if (block) {
                try {
                    long writeTimeout = handler.getProtocol().getStreamWriteTimeout();
                    allocationManager.waitForStream(writeTimeout);
                    windowSize = getWindowSize();
                    if (windowSize == 0) {
                        doWriteTimeout();
                    }
                } catch (InterruptedException e) {
                    // Possible shutdown / rst or similar. Use an IOException to
                    // signal to the client that further I/O isn't possible for this
                    // Stream.
                    throw new IOException(e);
                }
            } else {
                allocationManager.waitForStreamNonBlocking();
                return 0;
            }
        }
        int allocation;
        if (windowSize < reservation) {
            allocation = (int) windowSize;
        } else {
            allocation = reservation;
        }
        decrementWindowSize(allocation);
        return allocation;
    }

    void doWriteTimeout() throws CloseNowException {
        String msg = sm.getString("stream.writeTimeout");
        StreamException se = new StreamException(msg, Http2Error.ENHANCE_YOUR_CALM, getIdAsInt());
        // Prevent the application making further writes
        streamOutputBuffer.closed = true;
        // Prevent Tomcat's error handling trying to write
        coyoteResponse.setError();
        coyoteResponse.setErrorReported();
        // Trigger a reset once control returns to Tomcat
        streamOutputBuffer.reset = se;
        throw new CloseNowException(msg, se);
    }

    void waitForConnectionAllocation(long timeout) throws InterruptedException {
        allocationManager.waitForConnection(timeout);
    }

    void waitForConnectionAllocationNonBlocking() {
        allocationManager.waitForConnectionNonBlocking();
    }

    void notifyConnection() {
        allocationManager.notifyConnection();
    }

    @Override
    @Deprecated
    protected synchronized void doNotifyAll() {
    // NO-OP. Unused.
    }

    @Override
    public final void emitHeader(String name, String value) throws HpackException {
        if (log.isDebugEnabled()) {
            log.debug(sm.getString("stream.header.debug", getConnectionId(), getIdentifier(), name, value));
        }
        // Header names must be lower case
        if (!name.toLowerCase(Locale.US).equals(name)) {
            throw new HpackException(sm.getString("stream.header.case", getConnectionId(), getIdentifier(), name));
        }
        if ("connection".equals(name)) {
            throw new HpackException(sm.getString("stream.header.connection", getConnectionId(), getIdentifier()));
        }
        if ("te".equals(name)) {
            if (!"trailers".equals(value)) {
                throw new HpackException(sm.getString("stream.header.te", getConnectionId(), getIdentifier(), value));
            }
        }
        if (headerException != null) {
            // Don't bother processing the header since the stream is going to
            // be reset anyway
            return;
        }
        boolean pseudoHeader = name.charAt(0) == ':';
        if (pseudoHeader && headerState != HEADER_STATE_PSEUDO) {
            headerException = new StreamException(sm.getString("stream.header.unexpectedPseudoHeader", getConnectionId(), getIdentifier(), name), Http2Error.PROTOCOL_ERROR, getIdAsInt());
            // No need for further processing. The stream will be reset.
            return;
        }
        if (headerState == HEADER_STATE_PSEUDO && !pseudoHeader) {
            headerState = HEADER_STATE_REGULAR;
        }
        switch(name) {
            case ":method":
                {
                    if (coyoteRequest.method().isNull()) {
                        coyoteRequest.method().setString(value);
                    } else {
                        throw new HpackException(sm.getString("stream.header.duplicate", getConnectionId(), getIdentifier(), ":method"));
                    }
                    break;
                }
            case ":scheme":
                {
                    if (coyoteRequest.scheme().isNull()) {
                        coyoteRequest.scheme().setString(value);
                    } else {
                        throw new HpackException(sm.getString("stream.header.duplicate", getConnectionId(), getIdentifier(), ":scheme"));
                    }
                    break;
                }
            case ":path":
                {
                    if (!coyoteRequest.requestURI().isNull()) {
                        throw new HpackException(sm.getString("stream.header.duplicate", getConnectionId(), getIdentifier(), ":path"));
                    }
                    if (value.length() == 0) {
                        throw new HpackException(sm.getString("stream.header.noPath", getConnectionId(), getIdentifier()));
                    }
                    int queryStart = value.indexOf('?');
                    String uri;
                    if (queryStart == -1) {
                        uri = value;
                    } else {
                        uri = value.substring(0, queryStart);
                        String query = value.substring(queryStart + 1);
                        coyoteRequest.queryString().setString(query);
                    }
                    // Bug 61120. Set the URI as bytes rather than String so:
                    // - any path parameters are correctly processed
                    // - the normalization security checks are performed that prevent
                    // directory traversal attacks
                    byte[] uriBytes = uri.getBytes(StandardCharsets.ISO_8859_1);
                    coyoteRequest.requestURI().setBytes(uriBytes, 0, uriBytes.length);
                    break;
                }
            case ":authority":
                {
                    if (coyoteRequest.serverName().isNull()) {
                        int i;
                        try {
                            i = Host.parse(value);
                        } catch (IllegalArgumentException iae) {
                            // Host value invalid
                            throw new HpackException(sm.getString("stream.header.invalid", getConnectionId(), getIdentifier(), ":authority", value));
                        }
                        if (i > -1) {
                            coyoteRequest.serverName().setString(value.substring(0, i));
                            coyoteRequest.setServerPort(Integer.parseInt(value.substring(i + 1)));
                        } else {
                            coyoteRequest.serverName().setString(value);
                        }
                    } else {
                        throw new HpackException(sm.getString("stream.header.duplicate", getConnectionId(), getIdentifier(), ":authority"));
                    }
                    break;
                }
            case "cookie":
                {
                    // Cookie headers need to be concatenated into a single header
                    // See RFC 7540 8.1.2.5
                    if (cookieHeader == null) {
                        cookieHeader = new StringBuilder();
                    } else {
                        cookieHeader.append("; ");
                    }
                    cookieHeader.append(value);
                    break;
                }
            default:
                {
                    if (headerState == HEADER_STATE_TRAILER && !handler.isTrailerHeaderAllowed(name)) {
                        break;
                    }
                    if ("expect".equals(name) && "100-continue".equals(value)) {
                        coyoteRequest.setExpectation(true);
                    }
                    if (pseudoHeader) {
                        headerException = new StreamException(sm.getString("stream.header.unknownPseudoHeader", getConnectionId(), getIdentifier(), name), Http2Error.PROTOCOL_ERROR, getIdAsInt());
                    }
                    // replacedume other HTTP header
                    coyoteRequest.getMimeHeaders().addValue(name).setString(value);
                }
        }
    }

    @Override
    public void setHeaderException(StreamException streamException) {
        if (headerException == null) {
            headerException = streamException;
        }
    }

    @Override
    public void validateHeaders() throws StreamException {
        if (headerException == null) {
            return;
        }
        throw headerException;
    }

    final boolean receivedEndOfHeaders() throws ConnectionException {
        if (coyoteRequest.method().isNull() || coyoteRequest.scheme().isNull() || coyoteRequest.requestURI().isNull()) {
            throw new ConnectionException(sm.getString("stream.header.required", getConnectionId(), getIdentifier()), Http2Error.PROTOCOL_ERROR);
        }
        // Cookie headers need to be concatenated into a single header
        // See RFC 7540 8.1.2.5
        // Can only do this once the headers are fully received
        if (cookieHeader != null) {
            coyoteRequest.getMimeHeaders().addValue("cookie").setString(cookieHeader.toString());
        }
        return headerState == HEADER_STATE_REGULAR || headerState == HEADER_STATE_PSEUDO;
    }

    void writeHeaders() throws IOException {
        boolean endOfStream = streamOutputBuffer.hasNoBody();
        handler.writeHeaders(this, 0, coyoteResponse.getMimeHeaders(), endOfStream, Constants.DEFAULT_HEADERS_FRAME_SIZE);
    }

    final void addOutputFilter(OutputFilter filter) {
        http2OutputBuffer.addFilter(filter);
    }

    void writeAck() throws IOException {
        handler.writeHeaders(this, 0, ACK_HEADERS, false, Constants.DEFAULT_HEADERS_ACK_FRAME_SIZE);
    }

    @Override
    protected final String getConnectionId() {
        return handler.getConnectionId();
    }

    @Override
    protected int getWeight() {
        return weight;
    }

    Request getCoyoteRequest() {
        return coyoteRequest;
    }

    Response getCoyoteResponse() {
        return coyoteResponse;
    }

    ByteBuffer getInputByteBuffer() {
        return inputBuffer.getInBuffer();
    }

    final void receivedStartOfHeaders(boolean headersEndStream) throws Http2Exception {
        if (headerState == HEADER_STATE_START) {
            headerState = HEADER_STATE_PSEUDO;
            handler.getHpackDecoder().setMaxHeaderCount(handler.getMaxHeaderCount());
            handler.getHpackDecoder().setMaxHeaderSize(handler.getMaxHeaderSize());
        } else if (headerState == HEADER_STATE_PSEUDO || headerState == HEADER_STATE_REGULAR) {
            // Trailer headers MUST include the end of stream flag
            if (headersEndStream) {
                headerState = HEADER_STATE_TRAILER;
                handler.getHpackDecoder().setMaxHeaderCount(handler.getMaxTrailerCount());
                handler.getHpackDecoder().setMaxHeaderSize(handler.getMaxTrailerSize());
            } else {
                throw new ConnectionException(sm.getString("stream.trailerHeader.noEndOfStream", getConnectionId(), getIdentifier()), Http2Error.PROTOCOL_ERROR);
            }
        }
        // Parser will catch attempt to send a headers frame after the stream
        // has closed.
        state.receivedStartOfHeaders();
    }

    final void receivedData(int payloadSize) throws ConnectionException {
        contentLengthReceived += payloadSize;
        long contentLengthHeader = coyoteRequest.getContentLengthLong();
        if (contentLengthHeader > -1 && contentLengthReceived > contentLengthHeader) {
            throw new ConnectionException(sm.getString("stream.header.contentLength", getConnectionId(), getIdentifier(), Long.valueOf(contentLengthHeader), Long.valueOf(contentLengthReceived)), Http2Error.PROTOCOL_ERROR);
        }
    }

    final void receivedEndOfStream() throws ConnectionException {
        long contentLengthHeader = coyoteRequest.getContentLengthLong();
        if (contentLengthHeader > -1 && contentLengthReceived != contentLengthHeader) {
            throw new ConnectionException(sm.getString("stream.header.contentLength", getConnectionId(), getIdentifier(), Long.valueOf(contentLengthHeader), Long.valueOf(contentLengthReceived)), Http2Error.PROTOCOL_ERROR);
        }
        state.receivedEndOfStream();
        if (inputBuffer != null) {
            inputBuffer.notifyEof();
        }
    }

    final void sentHeaders() {
        state.sentStartOfHeaders();
    }

    final void sentEndOfStream() {
        streamOutputBuffer.endOfStreamSent = true;
        state.sentEndOfStream();
    }

    final boolean isReadyForWrite() {
        return streamOutputBuffer.isReady();
    }

    final boolean flush(boolean block) throws IOException {
        return streamOutputBuffer.flush(block);
    }

    StreamInputBuffer getInputBuffer() {
        return inputBuffer;
    }

    final HttpOutputBuffer getOutputBuffer() {
        return http2OutputBuffer;
    }

    void sentPushPromise() {
        state.sentPushPromise();
    }

    boolean isActive() {
        return state.isActive();
    }

    boolean canWrite() {
        return state.canWrite();
    }

    boolean isClosedFinal() {
        return state.isClosedFinal();
    }

    void closeIfIdle() {
        state.closeIfIdle();
    }

    boolean isInputFinished() {
        return !state.isFrameTypePermitted(FrameType.DATA);
    }

    void close(Http2Exception http2Exception) {
        if (http2Exception instanceof StreamException) {
            try {
                StreamException se = (StreamException) http2Exception;
                if (log.isDebugEnabled()) {
                    log.debug(sm.getString("stream.reset.send", getConnectionId(), getIdentifier(), se.getError()));
                }
                state.sendReset();
                handler.sendStreamReset(se);
            } catch (IOException ioe) {
                ConnectionException ce = new ConnectionException(sm.getString("stream.reset.fail"), Http2Error.PROTOCOL_ERROR);
                ce.initCause(ioe);
                handler.closeConnection(ce);
            }
        } else {
            handler.closeConnection(http2Exception);
        }
        // Reads wait internally so need to call a method to break the wait()
        if (inputBuffer != null) {
            inputBuffer.receiveReset();
        }
    }

    boolean isPushSupported() {
        return handler.getRemoteSettings().getEnablePush();
    }

    final void push(Request request) throws IOException {
        // Can only push when supported and from a peer initiated stream
        if (!isPushSupported() || getIdAsInt() % 2 == 0) {
            return;
        }
        // Set the special HTTP/2 headers
        request.getMimeHeaders().addValue(":method").duplicate(request.method());
        request.getMimeHeaders().addValue(":scheme").duplicate(request.scheme());
        StringBuilder path = new StringBuilder(request.requestURI().toString());
        if (!request.queryString().isNull()) {
            path.append('?');
            path.append(request.queryString().toString());
        }
        request.getMimeHeaders().addValue(":path").setString(path.toString());
        // Authority needs to include the port only if a non-standard port is
        // being used.
        if (!(request.scheme().equals("http") && request.getServerPort() == 80) && !(request.scheme().equals("https") && request.getServerPort() == 443)) {
            request.getMimeHeaders().addValue(":authority").setString(request.serverName().getString() + ":" + request.getServerPort());
        } else {
            request.getMimeHeaders().addValue(":authority").duplicate(request.serverName());
        }
        push(handler, request, this);
    }

    StreamException getResetException() {
        return streamOutputBuffer.reset;
    }

    private static void push(final Http2UpgradeHandler handler, final Request request, final Stream stream) throws IOException {
        if (org.apache.coyote.Constants.IS_SECURITY_ENABLED) {
            try {
                AccessController.doPrivileged(new PrivilegedPush(handler, request, stream));
            } catch (PrivilegedActionException ex) {
                Exception e = ex.getException();
                if (e instanceof IOException) {
                    throw (IOException) e;
                } else {
                    throw new IOException(ex);
                }
            }
        } else {
            handler.push(request, stream);
        }
    }

    private static clreplaced PrivilegedPush implements PrivilegedExceptionAction<Void> {

        private final Http2UpgradeHandler handler;

        private final Request request;

        private final Stream stream;

        public PrivilegedPush(Http2UpgradeHandler handler, Request request, Stream stream) {
            this.handler = handler;
            this.request = request;
            this.stream = stream;
        }

        @Override
        public Void run() throws IOException {
            handler.push(request, stream);
            return null;
        }
    }

    clreplaced StreamOutputBuffer implements HttpOutputBuffer, WriteBuffer.Sink {

        private final ByteBuffer buffer = ByteBuffer.allocate(8 * 1024);

        private final WriteBuffer writeBuffer = new WriteBuffer(32 * 1024);

        // Flag that indicates that data was left over on a previous
        // non-blocking write. Once set, this flag stays set until all the data
        // has been written.
        private boolean dataLeft;

        private volatile long written = 0;

        private volatile int streamReservation = 0;

        private volatile boolean closed = false;

        private volatile StreamException reset = null;

        private volatile boolean endOfStreamSent = false;

        /* The write methods are synchronized to ensure that only one thread at
         * a time is able to access the buffer. Without this protection, a
         * client that performed concurrent writes could corrupt the buffer.
         */
        /**
         * @deprecated Unused. Will be removed in Tomcat 9. Use
         *             {@link #doWrite(ByteBuffer)}
         */
        @Deprecated
        @Override
        public synchronized int doWrite(ByteChunk chunk) throws IOException {
            if (closed) {
                throw new IllegalStateException(sm.getString("stream.closed", getConnectionId(), getIdentifier()));
            }
            if (!coyoteResponse.isCommitted()) {
                coyoteResponse.sendHeaders();
            }
            int len = chunk.getLength();
            int offset = 0;
            while (len > 0) {
                int thisTime = Math.min(buffer.remaining(), len);
                buffer.put(chunk.getBytes(), chunk.getOffset() + offset, thisTime);
                offset += thisTime;
                len -= thisTime;
                if (len > 0 && !buffer.hasRemaining()) {
                    // Only flush if we have more data to write and the buffer
                    // is full
                    if (flush(true, coyoteResponse.getWriteListener() == null)) {
                        break;
                    }
                }
            }
            written += offset;
            return offset;
        }

        @Override
        public synchronized int doWrite(ByteBuffer chunk) throws IOException {
            if (closed) {
                throw new IllegalStateException(sm.getString("stream.closed", getConnectionId(), getIdentifier()));
            }
            int totalThisTime = 0;
            if (writeBuffer.isEmpty()) {
                int chunkLimit = chunk.limit();
                while (chunk.remaining() > 0) {
                    int thisTime = Math.min(buffer.remaining(), chunk.remaining());
                    chunk.limit(chunk.position() + thisTime);
                    buffer.put(chunk);
                    chunk.limit(chunkLimit);
                    totalThisTime += thisTime;
                    if (chunk.remaining() > 0 && !buffer.hasRemaining()) {
                        // Only flush if we have more data to write and the buffer
                        // is full
                        if (flush(true, coyoteResponse.getWriteListener() == null)) {
                            totalThisTime = chunk.remaining();
                            writeBuffer.add(chunk);
                            dataLeft = true;
                            break;
                        }
                    }
                }
            } else {
                totalThisTime = chunk.remaining();
                writeBuffer.add(chunk);
            }
            written += totalThisTime;
            return totalThisTime;
        }

        public synchronized boolean flush(boolean block) throws IOException {
            /*
             * Need to ensure that there is exactly one call to flush even when
             * there is no data to write.
             * Too few calls (i.e. zero) and the end of stream message is not
             * sent for a completed asynchronous write.
             * Too many calls and the end of stream message is sent too soon and
             * trailer headers are not sent.
             */
            boolean dataInBuffer = buffer.position() > 0;
            boolean flushed = false;
            if (dataInBuffer) {
                dataInBuffer = flush(false, block);
                flushed = true;
            }
            if (dataInBuffer) {
                dataLeft = true;
            } else {
                if (writeBuffer.isEmpty()) {
                    // Both buffer and writeBuffer are empty.
                    if (flushed) {
                        dataLeft = false;
                    } else {
                        dataLeft = flush(false, block);
                    }
                } else {
                    dataLeft = writeBuffer.write(this, block);
                }
            }
            return dataLeft;
        }

        private synchronized boolean flush(boolean writeInProgress, boolean block) throws IOException {
            if (log.isDebugEnabled()) {
                log.debug(sm.getString("stream.outputBuffer.flush.debug", getConnectionId(), getIdentifier(), Integer.toString(buffer.position()), Boolean.toString(writeInProgress), Boolean.toString(closed)));
            }
            if (buffer.position() == 0) {
                if (closed && !endOfStreamSent) {
                    // Handling this special case here is simpler than trying
                    // to modify the following code to handle it.
                    handler.writeBody(Stream.this, buffer, 0, true);
                }
                // Buffer is empty. Nothing to do.
                return false;
            }
            buffer.flip();
            int left = buffer.remaining();
            while (left > 0) {
                if (streamReservation == 0) {
                    streamReservation = reserveWindowSize(left, block);
                    if (streamReservation == 0) {
                        // Must be non-blocking.
                        // Note: Can't add to the writeBuffer here as the write
                        // may originate from the writeBuffer.
                        buffer.compact();
                        return true;
                    }
                }
                while (streamReservation > 0) {
                    int connectionReservation = handler.reserveWindowSize(Stream.this, streamReservation, block);
                    if (connectionReservation == 0) {
                        // Must be non-blocking.
                        // Note: Can't add to the writeBuffer here as the write
                        // may originate from the writeBuffer.
                        buffer.compact();
                        return true;
                    }
                    // Do the write
                    handler.writeBody(Stream.this, buffer, connectionReservation, !writeInProgress && closed && left == connectionReservation);
                    streamReservation -= connectionReservation;
                    left -= connectionReservation;
                }
            }
            buffer.clear();
            return false;
        }

        synchronized boolean isReady() {
            // Bug 63682
            // Only want to return false if the window size is zero AND we are
            // already waiting for an allocation.
            if (getWindowSize() > 0 && allocationManager.isWaitingForStream() || handler.getWindowSize() > 0 && allocationManager.isWaitingForConnection() || dataLeft) {
                return false;
            } else {
                return true;
            }
        }

        @Override
        public long getBytesWritten() {
            return written;
        }

        @Override
        public final void end() throws IOException {
            if (reset != null) {
                throw new CloseNowException(reset);
            }
            if (!closed) {
                closed = true;
                flush(true);
            }
        }

        public boolean isClosed() {
            return closed;
        }

        /**
         * @return <code>true</code> if it is certain that the replacedociated
         *         response has no body.
         */
        public boolean hasNoBody() {
            return ((written == 0) && closed);
        }

        @Override
        public void flush() throws IOException {
            /*
             * This method should only be called during blocking I/O. All the
             * Servlet API calls that end up here are illegal during
             * non-blocking I/O. Servlet 5.4.
             * However, the wording Servlet specification states that the
             * behaviour is undefined so we do the best we can which is to
             * perform a flush using blocking I/O or non-blocking I/O based
             * depending which is currently in use.
             */
            flush(getCoyoteResponse().getWriteListener() == null);
        }

        @Override
        public synchronized boolean writeFromBuffer(ByteBuffer src, boolean blocking) throws IOException {
            int chunkLimit = src.limit();
            while (src.remaining() > 0) {
                int thisTime = Math.min(buffer.remaining(), src.remaining());
                src.limit(src.position() + thisTime);
                buffer.put(src);
                src.limit(chunkLimit);
                if (flush(false, blocking)) {
                    return true;
                }
            }
            return false;
        }
    }

    clreplaced StreamInputBuffer implements InputBuffer {

        /* Two buffers are required to avoid various multi-threading issues.
         * These issues arise from the fact that the Stream (or the
         * Request/Response) used by the application is processed in one thread
         * but the connection is processed in another. Therefore it is possible
         * that a request body frame could be received before the application
         * is ready to read it. If it isn't buffered, processing of the
         * connection (and hence all streams) would block until the application
         * read the data. Hence the incoming data has to be buffered.
         * If only one buffer was used then it could become corrupted if the
         * connection thread is trying to add to it at the same time as the
         * application is read it. While it should be possible to avoid this
         * corruption by careful use of the buffer it would still require the
         * same copies as using two buffers and the behaviour would be less
         * clear.
         *
         * The buffers are created lazily because they quickly add up to a lot
         * of memory and most requests do not have bodies.
         */
        // This buffer is used to populate the ByteChunk preplaceded in to the read
        // method
        private byte[] outBuffer;

        // This buffer is the destination for incoming data. It is normally is
        // 'write mode'.
        private volatile ByteBuffer inBuffer;

        private volatile boolean readInterest;

        private boolean resetReceived = false;

        /**
         * @deprecated Unused. Will be removed in Tomcat 9. Use
         *             {@link #doRead(ApplicationBufferHandler)}
         */
        @Deprecated
        @Override
        public int doRead(ByteChunk chunk) throws IOException {
            ensureBuffersExist();
            int written = -1;
            // Ensure that only one thread accesses inBuffer at a time
            synchronized (inBuffer) {
                boolean canRead = false;
                while (inBuffer.position() == 0 && (canRead = isActive() && !isInputFinished())) {
                    // Need to block until some data is written
                    try {
                        if (log.isDebugEnabled()) {
                            log.debug(sm.getString("stream.inputBuffer.empty"));
                        }
                        long readTimeout = handler.getProtocol().getStreamReadTimeout();
                        if (readTimeout < 0) {
                            inBuffer.wait();
                        } else {
                            inBuffer.wait(readTimeout);
                        }
                        if (resetReceived) {
                            throw new IOException(sm.getString("stream.inputBuffer.reset"));
                        }
                        if (inBuffer.position() == 0) {
                            String msg = sm.getString("stream.inputBuffer.readTimeout");
                            StreamException se = new StreamException(msg, Http2Error.ENHANCE_YOUR_CALM, getIdAsInt());
                            // Trigger a reset once control returns to Tomcat
                            coyoteResponse.setError();
                            streamOutputBuffer.reset = se;
                            throw new CloseNowException(msg, se);
                        }
                    } catch (InterruptedException e) {
                        // Possible shutdown / rst or similar. Use an
                        // IOException to signal to the client that further I/O
                        // isn't possible for this Stream.
                        throw new IOException(e);
                    }
                }
                if (inBuffer.position() > 0) {
                    // Data is available in the inBuffer. Copy it to the
                    // outBuffer.
                    inBuffer.flip();
                    written = inBuffer.remaining();
                    if (log.isDebugEnabled()) {
                        log.debug(sm.getString("stream.inputBuffer.copy", Integer.toString(written)));
                    }
                    inBuffer.get(outBuffer, 0, written);
                    inBuffer.clear();
                } else if (!canRead) {
                    return -1;
                } else {
                    // Should never happen
                    throw new IllegalStateException();
                }
            }
            chunk.setBytes(outBuffer, 0, written);
            // Increment client-side flow control windows by the number of bytes
            // read
            handler.writeWindowUpdate(Stream.this, written, true);
            return written;
        }

        @Override
        public int doRead(ApplicationBufferHandler applicationBufferHandler) throws IOException {
            ensureBuffersExist();
            int written = -1;
            // Ensure that only one thread accesses inBuffer at a time
            synchronized (inBuffer) {
                boolean canRead = false;
                while (inBuffer.position() == 0 && (canRead = isActive() && !isInputFinished())) {
                    // Need to block until some data is written
                    try {
                        if (log.isDebugEnabled()) {
                            log.debug(sm.getString("stream.inputBuffer.empty"));
                        }
                        long readTimeout = handler.getProtocol().getStreamReadTimeout();
                        if (readTimeout < 0) {
                            inBuffer.wait();
                        } else {
                            inBuffer.wait(readTimeout);
                        }
                        if (resetReceived) {
                            throw new IOException(sm.getString("stream.inputBuffer.reset"));
                        }
                        if (inBuffer.position() == 0 && isActive() && !isInputFinished()) {
                            String msg = sm.getString("stream.inputBuffer.readTimeout");
                            StreamException se = new StreamException(msg, Http2Error.ENHANCE_YOUR_CALM, getIdAsInt());
                            // Trigger a reset once control returns to Tomcat
                            coyoteResponse.setError();
                            streamOutputBuffer.reset = se;
                            throw new CloseNowException(msg, se);
                        }
                    } catch (InterruptedException e) {
                        // Possible shutdown / rst or similar. Use an
                        // IOException to signal to the client that further I/O
                        // isn't possible for this Stream.
                        throw new IOException(e);
                    }
                }
                if (inBuffer.position() > 0) {
                    // Data is available in the inBuffer. Copy it to the
                    // outBuffer.
                    inBuffer.flip();
                    written = inBuffer.remaining();
                    if (log.isDebugEnabled()) {
                        log.debug(sm.getString("stream.inputBuffer.copy", Integer.toString(written)));
                    }
                    inBuffer.get(outBuffer, 0, written);
                    inBuffer.clear();
                } else if (!canRead) {
                    return -1;
                } else {
                    // Should never happen
                    throw new IllegalStateException();
                }
            }
            applicationBufferHandler.setByteBuffer(ByteBuffer.wrap(outBuffer, 0, written));
            // Increment client-side flow control windows by the number of bytes
            // read
            handler.writeWindowUpdate(Stream.this, written, true);
            return written;
        }

        final boolean isReadyForRead() {
            ensureBuffersExist();
            synchronized (this) {
                if (available() > 0) {
                    return true;
                }
                if (!isRequestBodyFullyRead()) {
                    readInterest = true;
                }
                return false;
            }
        }

        synchronized boolean isRequestBodyFullyRead() {
            return (inBuffer == null || inBuffer.position() == 0) && isInputFinished();
        }

        synchronized int available() {
            if (inBuffer == null) {
                return 0;
            }
            return inBuffer.position();
        }

        /*
         * Called after placing some data in the inBuffer.
         */
        synchronized boolean onDataAvailable() {
            if (readInterest) {
                if (log.isDebugEnabled()) {
                    log.debug(sm.getString("stream.inputBuffer.dispatch"));
                }
                readInterest = false;
                coyoteRequest.action(ActionCode.DISPATCH_READ, null);
                // Always need to dispatch since this thread is processing
                // the incoming connection and streams are processed on their
                // own.
                coyoteRequest.action(ActionCode.DISPATCH_EXECUTE, null);
                return true;
            } else {
                if (log.isDebugEnabled()) {
                    log.debug(sm.getString("stream.inputBuffer.signal"));
                }
                synchronized (inBuffer) {
                    inBuffer.notifyAll();
                }
                return false;
            }
        }

        public ByteBuffer getInBuffer() {
            ensureBuffersExist();
            return inBuffer;
        }

        protected synchronized void insertReplayedBody(ByteChunk body) {
            inBuffer = ByteBuffer.wrap(body.getBytes(), body.getOffset(), body.getLength());
        }

        private void ensureBuffersExist() {
            if (inBuffer == null) {
                // The client must obey Tomcat's window size when sending so
                // this is the initial window size set by Tomcat that the client
                // uses (i.e. the local setting is required here).
                int size = handler.getLocalSettings().getInitialWindowSize();
                synchronized (this) {
                    if (inBuffer == null) {
                        inBuffer = ByteBuffer.allocate(size);
                        outBuffer = new byte[size];
                    }
                }
            }
        }

        protected void receiveReset() {
            if (inBuffer != null) {
                synchronized (inBuffer) {
                    resetReceived = true;
                    inBuffer.notifyAll();
                }
            }
        }

        private final void notifyEof() {
            if (inBuffer != null) {
                synchronized (inBuffer) {
                    inBuffer.notifyAll();
                }
            }
        }
    }
}

16 View Complete Implementation : InputBuffer.java
Copyright Apache License 2.0
Author : codefollower
/**
 * The buffer used by Tomcat request. This is a derivative of the Tomcat 3.3
 * OutputBuffer, adapted to handle input instead of output. This allows
 * complete recycling of the facade objects (the ServletInputStream and the
 * BufferedReader).
 *
 * @author Remy Maucherat
 */
public clreplaced InputBuffer extends Reader implements ByteChunk.ByteInputChannel, CharChunk.CharInputChannel, CharChunk.CharOutputChannel {

    /**
     * The string manager for this package.
     */
    protected static final StringManager sm = StringManager.getManager(Constants.Package);

    // -------------------------------------------------------------- Constants
    public static final String DEFAULT_ENCODING = org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING;

    public static final int DEFAULT_BUFFER_SIZE = 8 * 1024;

    // The buffer can be used for byte[] and char[] reading
    // ( this is needed to support ServletInputStream and BufferedReader )
    public final int INITIAL_STATE = 0;

    public final int CHAR_STATE = 1;

    public final int BYTE_STATE = 2;

    // ----------------------------------------------------- Instance Variables
    /**
     * The byte buffer.
     */
    private final ByteChunk bb;

    /**
     * The chunk buffer.
     */
    private CharChunk cb;

    /**
     * State of the output buffer.
     */
    private int state = 0;

    /**
     * Flag which indicates if the input buffer is closed.
     */
    private boolean closed = false;

    /**
     * Encoding to use.
     */
    private String enc;

    /**
     * Encoder is set.
     */
    private boolean gotEnc = false;

    /**
     * List of encoders.
     */
    protected final HashMap<String, B2CConverter> encoders = new HashMap<>();

    /**
     * Current byte to char converter.
     */
    protected B2CConverter conv;

    /**
     * replacedociated Coyote request.
     */
    private Request coyoteRequest;

    /**
     * Buffer position.
     */
    private int markPos = -1;

    /**
     * Buffer size.
     */
    private final int size;

    // ----------------------------------------------------------- Constructors
    /**
     * Default constructor. Allocate the buffer with the default buffer size.
     */
    public InputBuffer() {
        this(DEFAULT_BUFFER_SIZE);
    }

    /**
     * Alternate constructor which allows specifying the initial buffer size.
     *
     * @param size Buffer size to use
     */
    public InputBuffer(int size) {
        this.size = size;
        bb = new ByteChunk(size);
        bb.setLimit(size);
        bb.setByteInputChannel(this);
        cb = new CharChunk(size);
        cb.setLimit(size);
        cb.setOptimizedWrite(false);
        cb.setCharInputChannel(this);
        cb.setCharOutputChannel(this);
    }

    // ------------------------------------------------------------- Properties
    /**
     * replacedociated Coyote request.
     *
     * @param coyoteRequest replacedociated Coyote request
     */
    public void setRequest(Request coyoteRequest) {
        this.coyoteRequest = coyoteRequest;
    }

    // --------------------------------------------------------- Public Methods
    /**
     * Recycle the output buffer.
     */
    public void recycle() {
        state = INITIAL_STATE;
        // If usage of mark made the buffer too big, reallocate it
        if (cb.getChars().length > size) {
            cb = new CharChunk(size);
            cb.setLimit(size);
            cb.setOptimizedWrite(false);
            cb.setCharInputChannel(this);
            cb.setCharOutputChannel(this);
        } else {
            cb.recycle();
        }
        markPos = -1;
        bb.recycle();
        closed = false;
        if (conv != null) {
            conv.recycle();
        }
        gotEnc = false;
        enc = null;
    }

    /**
     * Clear cached encoders (to save memory for Comet requests).
     */
    public void clearEncoders() {
        encoders.clear();
    }

    /**
     * Close the input buffer.
     *
     * @throws IOException An underlying IOException occurred
     */
    @Override
    public void close() throws IOException {
        closed = true;
    }

    public int available() {
        int available = 0;
        if (state == BYTE_STATE) {
            available = bb.getLength();
        } else if (state == CHAR_STATE) {
            available = cb.getLength();
        }
        if (available == 0) {
            coyoteRequest.action(ActionCode.AVAILABLE, null);
            available = (coyoteRequest.getAvailable() > 0) ? 1 : 0;
        }
        return available;
    }

    public void setReadListener(ReadListener listener) {
        coyoteRequest.setReadListener(listener);
        // The container is responsible for the first call to
        // listener.onDataAvailable(). If isReady() returns true, the container
        // needs to call listener.onDataAvailable() from a new thread. If
        // isReady() returns false, the socket will be registered for read and
        // the container will call listener.onDataAvailable() once data arrives.
        // Must call isFinished() first as a call to isReady() if the request
        // has been finished will register the socket for read interest and that
        // is not required.
        if (!coyoteRequest.isFinished() && isReady()) {
            coyoteRequest.action(ActionCode.DISPATCH_READ, null);
        }
    }

    public boolean isFinished() {
        return coyoteRequest.isFinished();
    }

    public boolean isReady() {
        if (coyoteRequest.getReadListener() == null) {
            throw new IllegalStateException("not in non blocking mode.");
        }
        int available = available();
        boolean result = available > 0;
        if (!result) {
            coyoteRequest.action(ActionCode.NB_READ_INTEREST, null);
        }
        return result;
    }

    boolean isBlocking() {
        return coyoteRequest.getReadListener() != null;
    }

    // ------------------------------------------------- Bytes Handling Methods
    /**
     * Reads new bytes in the byte chunk.
     *
     * @param cbuf Byte buffer to be written to the response
     * @param off Offset
     * @param len Length
     *
     * @throws IOException An underlying IOException occurred
     */
    @Override
    public int realReadBytes(byte[] cbuf, int off, int len) throws IOException {
        if (closed) {
            return -1;
        }
        if (coyoteRequest == null) {
            return -1;
        }
        if (state == INITIAL_STATE) {
            state = BYTE_STATE;
        }
        int result = coyoteRequest.doRead(bb);
        return result;
    }

    public int readByte() throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        return bb.substract();
    }

    public int read(byte[] b, int off, int len) throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        return bb.substract(b, off, len);
    }

    // ------------------------------------------------- Chars Handling Methods
    /**
     * Since the converter will use append, it is possible to get chars to
     * be removed from the buffer for "writing". Since the chars have already
     * been read before, they are ignored. If a mark was set, then the
     * mark is lost.
     */
    @Override
    public void realWriteChars(char[] c, int off, int len) throws IOException {
        markPos = -1;
        cb.setOffset(0);
        cb.setEnd(0);
    }

    public void setEncoding(String s) {
        enc = s;
    }

    @Override
    public int realReadChars(char[] cbuf, int off, int len) throws IOException {
        if (!gotEnc) {
            setConverter();
        }
        boolean eof = false;
        if (bb.getLength() <= 0) {
            int nRead = realReadBytes(bb.getBytes(), 0, bb.getBytes().length);
            if (nRead < 0) {
                eof = true;
            }
        }
        if (markPos == -1) {
            cb.setOffset(0);
            cb.setEnd(0);
        } else {
            // Make sure there's enough space in the worst case
            cb.makeSpace(bb.getLength());
            if ((cb.getBuffer().length - cb.getEnd()) == 0) {
                // We went over the limit
                cb.setOffset(0);
                cb.setEnd(0);
                markPos = -1;
            }
        }
        state = CHAR_STATE;
        conv.convert(bb, cb, eof);
        if (cb.getLength() == 0 && eof) {
            return -1;
        } else {
            return cb.getLength();
        }
    }

    @Override
    public int read() throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        return cb.substract();
    }

    @Override
    public int read(char[] cbuf) throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        return read(cbuf, 0, cbuf.length);
    }

    @Override
    public int read(char[] cbuf, int off, int len) throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        return cb.substract(cbuf, off, len);
    }

    @Override
    public long skip(long n) throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        if (n < 0) {
            throw new IllegalArgumentException();
        }
        long nRead = 0;
        while (nRead < n) {
            if (cb.getLength() >= n) {
                cb.setOffset(cb.getStart() + (int) n);
                nRead = n;
            } else {
                nRead += cb.getLength();
                cb.setOffset(cb.getEnd());
                int toRead = 0;
                if (cb.getChars().length < (n - nRead)) {
                    toRead = cb.getChars().length;
                } else {
                    toRead = (int) (n - nRead);
                }
                int nb = realReadChars(cb.getChars(), 0, toRead);
                if (nb < 0) {
                    break;
                }
            }
        }
        return nRead;
    }

    @Override
    public boolean ready() throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        if (state == INITIAL_STATE) {
            state = CHAR_STATE;
        }
        return (available() > 0);
    }

    @Override
    public boolean markSupported() {
        return true;
    }

    @Override
    public void mark(int readAheadLimit) throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        if (cb.getLength() <= 0) {
            cb.setOffset(0);
            cb.setEnd(0);
        } else {
            if ((cb.getBuffer().length > (2 * size)) && (cb.getLength()) < (cb.getStart())) {
                System.arraycopy(cb.getBuffer(), cb.getStart(), cb.getBuffer(), 0, cb.getLength());
                cb.setEnd(cb.getLength());
                cb.setOffset(0);
            }
        }
        cb.setLimit(cb.getStart() + readAheadLimit + size);
        markPos = cb.getStart();
    }

    @Override
    public void reset() throws IOException {
        if (closed) {
            throw new IOException(sm.getString("inputBuffer.streamClosed"));
        }
        if (state == CHAR_STATE) {
            if (markPos < 0) {
                cb.recycle();
                markPos = -1;
                throw new IOException();
            } else {
                cb.setOffset(markPos);
            }
        } else {
            bb.recycle();
        }
    }

    public void checkConverter() throws IOException {
        if (!gotEnc) {
            setConverter();
        }
    }

    protected void setConverter() throws IOException {
        if (coyoteRequest != null) {
            enc = coyoteRequest.getCharacterEncoding();
        }
        gotEnc = true;
        if (enc == null) {
            enc = DEFAULT_ENCODING;
        }
        conv = encoders.get(enc);
        if (conv == null) {
            if (SecurityUtil.isPackageProtectionEnabled()) {
                try {
                    conv = AccessController.doPrivileged(new PrivilegedExceptionAction<B2CConverter>() {

                        @Override
                        public B2CConverter run() throws IOException {
                            return new B2CConverter(enc);
                        }
                    });
                } catch (PrivilegedActionException ex) {
                    Exception e = ex.getException();
                    if (e instanceof IOException) {
                        throw (IOException) e;
                    }
                }
            } else {
                conv = new B2CConverter(enc);
            }
            encoders.put(enc, conv);
        }
    }
}

16 View Complete Implementation : AbstractInputBuffer.java
Copyright Apache License 2.0
Author : codefollower
public abstract clreplaced AbstractInputBuffer<S> implements InputBuffer {

    protected static final boolean[] HTTP_TOKEN_CHAR = new boolean[128];

    /**
     * The string manager for this package.
     */
    protected static final StringManager sm = StringManager.getManager(Constants.Package);

    static {
        for (int i = 0; i < 128; i++) {
            if (i < 32) {
                HTTP_TOKEN_CHAR[i] = false;
            } else if (i == 127) {
                HTTP_TOKEN_CHAR[i] = false;
            } else if (i == '(') {
                HTTP_TOKEN_CHAR[i] = false;
            } else if (i == ')') {
                HTTP_TOKEN_CHAR[i] = false;
            } else if (i == '<') {
                HTTP_TOKEN_CHAR[i] = false;
            } else if (i == '>') {
                HTTP_TOKEN_CHAR[i] = false;
            } else if (i == '@') {
                HTTP_TOKEN_CHAR[i] = false;
            } else if (i == ',') {
                HTTP_TOKEN_CHAR[i] = false;
            } else if (i == ';') {
                HTTP_TOKEN_CHAR[i] = false;
            } else if (i == ':') {
                HTTP_TOKEN_CHAR[i] = false;
            } else if (i == '\\') {
                HTTP_TOKEN_CHAR[i] = false;
            } else if (i == '\"') {
                HTTP_TOKEN_CHAR[i] = false;
            } else if (i == '/') {
                HTTP_TOKEN_CHAR[i] = false;
            } else if (i == '[') {
                HTTP_TOKEN_CHAR[i] = false;
            } else if (i == ']') {
                HTTP_TOKEN_CHAR[i] = false;
            } else if (i == '?') {
                HTTP_TOKEN_CHAR[i] = false;
            } else if (i == '=') {
                HTTP_TOKEN_CHAR[i] = false;
            } else if (i == '{') {
                HTTP_TOKEN_CHAR[i] = false;
            } else if (i == '}') {
                HTTP_TOKEN_CHAR[i] = false;
            } else if (i == ' ') {
                HTTP_TOKEN_CHAR[i] = false;
            } else if (i == '\t') {
                HTTP_TOKEN_CHAR[i] = false;
            } else {
                HTTP_TOKEN_CHAR[i] = true;
            }
        }
    }

    /**
     * replacedociated Coyote request.
     */
    protected Request request;

    /**
     * Headers of the replacedociated request.
     */
    protected MimeHeaders headers;

    /**
     * State.
     */
    protected boolean parsingHeader;

    /**
     * Swallow input ? (in the case of an expectation)
     */
    protected boolean swallowInput;

    /**
     * Pointer to the current read buffer.
     */
    protected byte[] buf;

    /**
     * Last valid byte.
     */
    protected int lastValid;

    /**
     * Position in the buffer.
     */
    protected int pos;

    /**
     * Pos of the end of the header in the buffer, which is also the
     * start of the body.
     */
    protected int end;

    /**
     * Underlying input buffer.
     */
    protected InputBuffer inputStreamInputBuffer;

    /**
     * Filter library.
     * Note: Filter[0] is always the "chunked" filter.
     */
    protected InputFilter[] filterLibrary;

    /**
     * Active filters (in order).
     */
    protected InputFilter[] activeFilters;

    /**
     * Index of the last active filter.
     */
    protected int lastActiveFilter;

    // ------------------------------------------------------------- Properties
    /**
     * Add an input filter to the filter library.
     *
     * @throws NullPointerException if the supplied filter is null
     */
    public void addFilter(InputFilter filter) {
        if (filter == null) {
            throw new NullPointerException(sm.getString("iib.filter.npe"));
        }
        InputFilter[] newFilterLibrary = new InputFilter[filterLibrary.length + 1];
        for (int i = 0; i < filterLibrary.length; i++) {
            newFilterLibrary[i] = filterLibrary[i];
        }
        newFilterLibrary[filterLibrary.length] = filter;
        filterLibrary = newFilterLibrary;
        activeFilters = new InputFilter[filterLibrary.length];
    }

    /**
     * Get filters.
     */
    public InputFilter[] getFilters() {
        return filterLibrary;
    }

    /**
     * Add an input filter to the filter library.
     */
    public 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.
     */
    public void setSwallowInput(boolean swallowInput) {
        this.swallowInput = swallowInput;
    }

    public abstract boolean parseRequestLine(boolean useAvailableDataOnly) throws IOException;

    public abstract boolean parseHeaders() throws IOException;

    /**
     * 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>
     */
    protected abstract boolean fill(boolean block) throws IOException;

    protected abstract void init(SocketWrapper<S> socketWrapper, AbstractEndpoint<S> endpoint) throws IOException;

    protected abstract Log getLog();

    // --------------------------------------------------------- Public Methods
    /**
     * Recycle the input buffer. This should be called when closing the
     * connection.
     */
    public void recycle() {
        // Recycle Request object
        request.recycle();
        // Recycle filters
        for (int i = 0; i <= lastActiveFilter; i++) {
            activeFilters[i].recycle();
        }
        lastValid = 0;
        pos = 0;
        lastActiveFilter = -1;
        parsingHeader = true;
        swallowInput = 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.
     */
    public void nextRequest() {
        // Recycle Request object
        request.recycle();
        // Copy leftover bytes to the beginning of the buffer
        if (lastValid - pos > 0) {
            int npos = 0;
            int opos = pos;
            while (lastValid - opos > opos - npos) {
                System.arraycopy(buf, opos, buf, npos, opos - npos);
                npos += pos;
                opos += pos;
            }
            System.arraycopy(buf, opos, buf, npos, lastValid - opos);
        }
        // Recycle filters
        for (int i = 0; i <= lastActiveFilter; i++) {
            activeFilters[i].recycle();
        }
        // Reset pointers
        lastValid = lastValid - pos;
        pos = 0;
        lastActiveFilter = -1;
        parsingHeader = true;
        swallowInput = true;
    }

    /**
     * End request (consumes leftover bytes).
     *
     * @throws IOException an underlying I/O error occurred
     */
    public void endRequest() throws IOException {
        if (swallowInput && (lastActiveFilter != -1)) {
            int extraBytes = (int) activeFilters[lastActiveFilter].end();
            pos = pos - extraBytes;
        }
    }

    /**
     * Available bytes in the buffers (note that due to encoding, this may not
     * correspond).
     */
    public int available() {
        int available = lastValid - pos;
        if ((available == 0) && (lastActiveFilter >= 0)) {
            for (int i = 0; (available == 0) && (i <= lastActiveFilter); i++) {
                available = activeFilters[i].available();
            }
        }
        if (available > 0) {
            return available;
        }
        try {
            fill(false);
            available = lastValid - pos;
        } catch (IOException 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
            // 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() > 0 primarily because of having to handle
     * faking non-blocking reads with the blocking IO connector.
     */
    public boolean isFinished() {
        if (lastValid > pos) {
            // Data to read in the buffer so not finished
            return false;
        }
        /*
         * Don't use fill(false) here because in the following cirreplacedstances
         * BIO will block - possibly indefinitely
         * - client is using keep-alive and connection is still open
         * - client has sent the complete request
         * - client has not sent any of the next request (i.e. no pipelining)
         * - application has read the complete request
         */
        // Check the InputFilters
        if (lastActiveFilter >= 0) {
            return activeFilters[lastActiveFilter].isFinished();
        } else {
            // No filters. replacedume request is not finished. EOF will signal end of
            // request.
            return false;
        }
    }

    // ---------------------------------------------------- InputBuffer Methods
    /**
     * Read some bytes.
     */
    @Override
    public int doRead(ByteChunk chunk, Request req) throws IOException {
        if (lastActiveFilter == -1)
            return inputStreamInputBuffer.doRead(chunk, req);
        else
            return activeFilters[lastActiveFilter].doRead(chunk, req);
    }
}

15 View Complete Implementation : Http11InputBuffer.java
Copyright Apache License 2.0
Author : apache
/**
 * InputBuffer for HTTP that provides request header parsing as well as transfer
 * encoding.
 */
public clreplaced Http11InputBuffer implements InputBuffer, ApplicationBufferHandler {

    // -------------------------------------------------------------- Constants
    private static final Log log = LogFactory.getLog(Http11InputBuffer.clreplaced);

    /**
     * The string manager for this package.
     */
    private static final StringManager sm = StringManager.getManager(Http11InputBuffer.clreplaced);

    private static final byte[] CLIENT_PREFACE_START = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".getBytes(StandardCharsets.ISO_8859_1);

    /**
     * replacedociated Coyote request.
     */
    private final Request request;

    /**
     * Headers of the replacedociated request.
     */
    private final MimeHeaders headers;

    private final boolean rejectIllegalHeaderName;

    /**
     * State.
     */
    private 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 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 rejectIllegalHeaderName, HttpParser httpParser) {
        this.request = request;
        headers = request.getMimeHeaders();
        this.headerBufferSize = headerBufferSize;
        this.rejectIllegalHeaderName = rejectIllegalHeaderName;
        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;
        parsingHeader = true;
        swallowInput = true;
        headerParsePos = HeaderParsePosition.HEADER_START;
        parsingRequestLine = true;
        parsingRequestLinePhase = 0;
        parsingRequestLineEol = false;
        parsingRequestLineStart = 0;
        parsingRequestLineQPos = -1;
        headerData.recycle();
    }

    /**
     * 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) {
            byte chr = 0;
            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 - 1) {
                    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.getStartTime() < 0) {
                    request.setStartTime(System.currentTimeMillis());
                }
                chr = byteBuffer.get();
            } while ((chr == Constants.CR) || (chr == Constants.LF));
            byteBuffer.position(byteBuffer.position() - 1);
            parsingRequestLineStart = byteBuffer.position();
            parsingRequestLinePhase = 2;
            if (log.isDebugEnabled()) {
                log.debug("Received [" + new String(byteBuffer.array(), byteBuffer.position(), byteBuffer.remaining(), StandardCharsets.ISO_8859_1) + "]");
            }
        }
        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 (// request line parsing
                    !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();
                byte 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)) {
                    byteBuffer.position(byteBuffer.position() - 1);
                    // Avoid unknown protocol triggering an additional error
                    request.protocol().setString(Constants.HTTP_11);
                    throw new IllegalArgumentException(sm.getString("iib.invalidmethod"));
                }
            }
            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 (// request line parsing
                    !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 = 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 (// request line parsing
                    !fill(false))
                        return false;
                }
                int pos = byteBuffer.position();
                byte chr = byteBuffer.get();
                if (chr == Constants.SP || chr == Constants.HT) {
                    space = true;
                    end = pos;
                } else if (chr == Constants.CR || chr == Constants.LF) {
                    // HTTP/0.9 style request
                    parsingRequestLineEol = true;
                    space = true;
                    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
                    throw new IllegalArgumentException(sm.getString("iib.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()
                    throw new IllegalArgumentException(sm.getString("iib.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);
            }
            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 (// request line parsing
                    !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 (// request line parsing
                    !fill(false))
                        return false;
                }
                int pos = byteBuffer.position();
                byte chr = byteBuffer.get();
                if (chr == Constants.CR) {
                    end = pos;
                } else if (chr == Constants.LF) {
                    if (end == 0) {
                        end = pos;
                    }
                    parsingRequestLineEol = true;
                } else if (!HttpParser.isHttpProtocol(chr)) {
                    throw new IllegalArgumentException(sm.getString("iib.invalidHttpProtocol"));
                }
            }
            if ((end - parsingRequestLineStart) > 0) {
                request.protocol().setBytes(byteBuffer.array(), parsingRequestLineStart, end - parsingRequestLineStart);
            } else {
                request.protocol().setString("");
            }
            parsingRequestLine = false;
            parsingRequestLinePhase = 0;
            parsingRequestLineEol = false;
            parsingRequestLineStart = 0;
            return true;
        }
        throw new IllegalStateException("Invalid request line parse phase:" + 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;
    }

    /**
     * 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);
        }
    }

    /**
     * Available bytes in the buffers (note that due to encoding, this may not
     * correspond).
     */
    int available(boolean read) {
        int available = byteBuffer.remaining();
        if ((available == 0) && (lastActiveFilter >= 0)) {
            for (int i = 0; (available == 0) && (i <= lastActiveFilter); i++) {
                available = activeFilters[i].available();
            }
        }
        if (available > 0 || !read) {
            return available;
        }
        try {
            if (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() > 0 primarily because of having to handle
     * faking non-blocking reads with the blocking IO connector.
     */
    boolean isFinished() {
        if (byteBuffer.limit() > byteBuffer.position()) {
            // Data to read in the buffer so not finished
            return false;
        }
        /*
         * Don't use fill(false) here because in the following cirreplacedstances
         * BIO will block - possibly indefinitely
         * - client is using keep-alive and connection is still open
         * - client has sent the complete request
         * - client has not sent any of the next request (i.e. no pipelining)
         * - application has read the complete request
         */
        // Check the InputFilters
        if (lastActiveFilter >= 0) {
            return activeFilters[lastActiveFilter].isFinished();
        } else {
            // No filters. replacedume 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 (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);
        }
        byteBuffer.mark();
        if (byteBuffer.position() < byteBuffer.limit()) {
            byteBuffer.position(byteBuffer.limit());
        }
        byteBuffer.limit(byteBuffer.capacity());
        SocketWrapperBase<?> socketWrapper = this.wrapper;
        int nRead = -1;
        if (socketWrapper != null) {
            nRead = socketWrapper.read(block, byteBuffer);
        } else {
            throw new CloseNowException(sm.getString("iib.eof.error"));
        }
        byteBuffer.limit(byteBuffer.position()).reset();
        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 false after reading a blank line (which indicates that the
     * HTTP header parsing is done
     */
    private HeaderParseStatus parseHeader() throws IOException {
        // 
        // Check for blank line
        // 
        byte chr = 0;
        while (headerParsePos == HeaderParsePosition.HEADER_START) {
            // Read new bytes if needed
            if (byteBuffer.position() >= byteBuffer.limit()) {
                if (!fill(false)) {
                    // parse header
                    headerParsePos = HeaderParsePosition.HEADER_START;
                    return HeaderParseStatus.NEED_MORE_DATA;
                }
            }
            chr = byteBuffer.get();
            if (chr == Constants.CR) {
            // Skip
            } else if (chr == Constants.LF) {
                return HeaderParseStatus.DONE;
            } else {
                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();
            }
            // 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();
        }
        // 
        // 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);
                        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;
                        }
                    }
                    chr = byteBuffer.get();
                    if (chr == Constants.CR) {
                    // Skip
                    } else if (chr == Constants.LF) {
                        eol = 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;
                }
            }
            chr = byteBuffer.get(byteBuffer.position());
            if (headerParsePos == HeaderParsePosition.HEADER_MULTI_LINE) {
                if ((chr != Constants.SP) && (chr != 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, chr);
                    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() throws IOException {
        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();
            byte chr = byteBuffer.get();
            if (chr == Constants.CR) {
            // Skip
            } else if (chr == Constants.LF) {
                eol = true;
            } else {
                headerData.lastSignificantChar = pos;
            }
        }
        if (rejectIllegalHeaderName || log.isDebugEnabled()) {
            String message = sm.getString("iib.invalidheader", HeaderUtil.toPrintableString(byteBuffer.array(), headerData.lineStart, headerData.lastSignificantChar - headerData.lineStart + 1));
            if (rejectIllegalHeaderName) {
                throw new IllegalArgumentException(message);
            }
            log.debug(message);
        }
        headerParsePos = HeaderParsePosition.HEADER_START;
        return HeaderParseStatus.HAVE_MORE_HEADERS;
    }

    // ----------------------------------------------------------- Inner clreplacedes
    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 clreplaced 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 Clreplaced
    /**
     * This clreplaced is an input buffer which will read its data from an input
     * stream.
     */
    private clreplaced SocketInputBuffer implements InputBuffer {

        @Override
        public int doRead(ApplicationBufferHandler handler) throws IOException {
            if (byteBuffer.position() >= byteBuffer.limit()) {
                // The application is reading the HTTP request body which is
                // always a blocking operation.
                if (!fill(true))
                    return -1;
            }
            int length = byteBuffer.remaining();
            handler.setByteBuffer(byteBuffer.duplicate());
            byteBuffer.position(byteBuffer.limit());
            return length;
        }
    }

    @Override
    public void setByteBuffer(ByteBuffer buffer) {
        byteBuffer = buffer;
    }

    @Override
    public ByteBuffer getByteBuffer() {
        return byteBuffer;
    }

    @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;
    }
}

15 View Complete Implementation : Http2Protocol.java
Copyright Apache License 2.0
Author : apache
@Override
public boolean accept(Request request) {
    // Should only be one HTTP2-Settings header
    Enumeration<String> settings = request.getMimeHeaders().values("HTTP2-Settings");
    int count = 0;
    while (settings.hasMoreElements()) {
        count++;
        settings.nextElement();
    }
    if (count != 1) {
        return false;
    }
    Enumeration<String> connection = request.getMimeHeaders().values("Connection");
    boolean found = false;
    while (connection.hasMoreElements() && !found) {
        found = connection.nextElement().contains("HTTP2-Settings");
    }
    return found;
}

15 View Complete Implementation : AbstractInputBuffer.java
Copyright Apache License 2.0
Author : apache
public abstract clreplaced AbstractInputBuffer<S> implements InputBuffer {

    /**
     * The string manager for this package.
     */
    protected static final StringManager sm = StringManager.getManager(Constants.Package);

    /**
     * replacedociated Coyote request.
     */
    protected Request request;

    /**
     * Headers of the replacedociated request.
     */
    protected MimeHeaders headers;

    /**
     * State.
     */
    protected boolean parsingHeader;

    /**
     * Swallow input ? (in the case of an expectation)
     */
    protected boolean swallowInput;

    /**
     * Pointer to the current read buffer.
     */
    protected byte[] buf;

    /**
     * Last valid byte.
     */
    protected int lastValid;

    /**
     * Position in the buffer.
     */
    protected int pos;

    /**
     * Pos of the end of the header in the buffer, which is also the
     * start of the body.
     */
    protected int end;

    /**
     * Underlying input buffer.
     */
    protected InputBuffer inputStreamInputBuffer;

    /**
     * Filter library.
     * Note: Filter[Constants.CHUNKED_FILTER] is always the "chunked" filter.
     */
    protected InputFilter[] filterLibrary;

    /**
     * Active filters (in order).
     */
    protected InputFilter[] activeFilters;

    /**
     * Index of the last active filter.
     */
    protected int lastActiveFilter;

    protected boolean rejectIllegalHeaderName;

    protected HttpParser httpParser;

    // ------------------------------------------------------------- Properties
    /**
     * Add an input filter to the filter library.
     *
     * @throws NullPointerException if the supplied filter is null
     */
    public void addFilter(InputFilter filter) {
        if (filter == null) {
            throw new NullPointerException(sm.getString("iib.filter.npe"));
        }
        InputFilter[] newFilterLibrary = new InputFilter[filterLibrary.length + 1];
        for (int i = 0; i < filterLibrary.length; i++) {
            newFilterLibrary[i] = filterLibrary[i];
        }
        newFilterLibrary[filterLibrary.length] = filter;
        filterLibrary = newFilterLibrary;
        activeFilters = new InputFilter[filterLibrary.length];
    }

    /**
     * Get filters.
     */
    public InputFilter[] getFilters() {
        return filterLibrary;
    }

    /**
     * Add an input filter to the filter library.
     */
    public 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.
     */
    public void setSwallowInput(boolean swallowInput) {
        this.swallowInput = swallowInput;
    }

    /**
     * Implementations are expected to call {@link Request#setStartTime(long)}
     * as soon as the first byte is read from the request.
     */
    public abstract boolean parseRequestLine(boolean useAvailableDataOnly) throws IOException;

    public abstract boolean parseHeaders() throws IOException;

    /**
     * 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>
     */
    protected abstract boolean fill(boolean block) throws IOException;

    protected abstract void init(SocketWrapper<S> socketWrapper, AbstractEndpoint<S> endpoint) throws IOException;

    protected abstract Log getLog();

    // --------------------------------------------------------- Public Methods
    /**
     * Recycle the input buffer. This should be called when closing the
     * connection.
     */
    public void recycle() {
        // Recycle Request object
        request.recycle();
        // Recycle filters
        for (int i = 0; i <= lastActiveFilter; i++) {
            activeFilters[i].recycle();
        }
        lastValid = 0;
        pos = 0;
        lastActiveFilter = -1;
        parsingHeader = true;
        swallowInput = 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.
     */
    public void nextRequest() {
        // Recycle Request object
        request.recycle();
        // Copy leftover bytes to the beginning of the buffer
        if (lastValid - pos > 0 && pos > 0) {
            System.arraycopy(buf, pos, buf, 0, lastValid - pos);
        }
        // Always reset pos to zero
        lastValid = lastValid - pos;
        pos = 0;
        // Recycle filters
        for (int i = 0; i <= lastActiveFilter; i++) {
            activeFilters[i].recycle();
        }
        // Reset pointers
        lastActiveFilter = -1;
        parsingHeader = true;
        swallowInput = true;
    }

    /**
     * End request (consumes leftover bytes).
     *
     * @throws IOException an underlying I/O error occurred
     */
    public void endRequest() throws IOException {
        if (swallowInput && (lastActiveFilter != -1)) {
            int extraBytes = (int) activeFilters[lastActiveFilter].end();
            pos = pos - extraBytes;
        }
    }

    /**
     * Available bytes in the buffers (note that due to encoding, this may not
     * correspond).
     */
    public int available(boolean read) {
        int available = lastValid - pos;
        if ((available == 0) && (lastActiveFilter >= 0)) {
            for (int i = 0; (available == 0) && (i <= lastActiveFilter); i++) {
                available = activeFilters[i].available();
            }
        }
        if (available > 0 || !read) {
            return available;
        }
        try {
            fill(false);
            available = lastValid - pos;
        } catch (IOException 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
            // 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() > 0 primarily because of having to handle
     * faking non-blocking reads with the blocking IO connector.
     */
    public boolean isFinished() {
        if (lastValid > pos) {
            // Data to read in the buffer so not finished
            return false;
        }
        /*
         * Don't use fill(false) here because in the following cirreplacedstances
         * BIO will block - possibly indefinitely
         * - client is using keep-alive and connection is still open
         * - client has sent the complete request
         * - client has not sent any of the next request (i.e. no pipelining)
         * - application has read the complete request
         */
        // Check the InputFilters
        if (lastActiveFilter >= 0) {
            return activeFilters[lastActiveFilter].isFinished();
        } else {
            // No filters. replacedume request is not finished. EOF will signal end of
            // request.
            return false;
        }
    }

    ByteBuffer getLeftover() {
        int available = lastValid - pos;
        if (available > 0) {
            return ByteBuffer.wrap(buf, pos, available);
        } else {
            return null;
        }
    }

    /**
     * Is standard Servlet blocking IO being used for input?
     */
    protected final boolean isBlocking() {
        return request.getReadListener() == null;
    }

    // ---------------------------------------------------- InputBuffer Methods
    /**
     * Read some bytes.
     */
    @Override
    public int doRead(ByteChunk chunk, Request req) throws IOException {
        if (lastActiveFilter == -1)
            return inputStreamInputBuffer.doRead(chunk, req);
        else
            return activeFilters[lastActiveFilter].doRead(chunk, req);
    }
}

15 View Complete Implementation : BufferedInputFilter.java
Copyright Apache License 2.0
Author : apache
/**
 * Fills the given ByteChunk with the buffered request body.
 */
@Override
public int doRead(ByteChunk chunk, Request request) throws IOException {
    if (hasRead || buffered.getLength() <= 0) {
        return -1;
    }
    chunk.setBytes(buffered.getBytes(), buffered.getStart(), buffered.getLength());
    hasRead = true;
    return chunk.getLength();
}

15 View Complete Implementation : IdentityInputFilter.java
Copyright Apache License 2.0
Author : apache
// ---------------------------------------------------- InputBuffer Methods
/**
 * Read bytes.
 *
 * @return If the filter does request length control, this value is
 * significant; it should be the number of bytes consumed from the buffer,
 * up until the end of the current request body, or the buffer length,
 * whichever is greater. If the filter does not do request body length
 * control, the returned value should be -1.
 */
@Override
public int doRead(ByteChunk chunk, Request req) throws IOException {
    int result = -1;
    if (contentLength >= 0) {
        if (remaining > 0) {
            int nRead = buffer.doRead(chunk, req);
            if (nRead > remaining) {
                // The chunk is longer than the number of bytes remaining
                // in the body; changing the chunk length to the number
                // of bytes remaining
                chunk.setBytes(chunk.getBytes(), chunk.getStart(), (int) remaining);
                result = (int) remaining;
            } else {
                result = nRead;
            }
            if (nRead > 0) {
                remaining = remaining - nRead;
            }
        } else {
            // No more bytes left to be read : return -1 and clear the
            // buffer
            chunk.recycle();
            result = -1;
        }
    }
    return result;
}

15 View Complete Implementation : Http11InputBuffer.java
Copyright MIT License
Author : chenmudu
/**
 * InputBuffer for HTTP that provides request header parsing as well as transfer
 * encoding.
 *  用于和Http消息的读取和写入操作。
 */
public clreplaced Http11InputBuffer implements InputBuffer, ApplicationBufferHandler {

    // -------------------------------------------------------------- Constants
    private static final Log log = LogFactory.getLog(Http11InputBuffer.clreplaced);

    /**
     * The string manager for this package.
     */
    private static final StringManager sm = StringManager.getManager(Http11InputBuffer.clreplaced);

    private static final byte[] CLIENT_PREFACE_START = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".getBytes(StandardCharsets.ISO_8859_1);

    /**
     * replacedociated Coyote request.
     */
    private final Request request;

    /**
     * Headers of the replacedociated request.
     */
    private final MimeHeaders headers;

    private final boolean rejectIllegalHeaderName;

    /**
     * State.
     */
    private 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 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 rejectIllegalHeaderName, HttpParser httpParser) {
        this.request = request;
        headers = request.getMimeHeaders();
        this.headerBufferSize = headerBufferSize;
        this.rejectIllegalHeaderName = rejectIllegalHeaderName;
        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 = new InputFilter[filterLibrary.length + 1];
        for (int i = 0; i < filterLibrary.length; i++) {
            newFilterLibrary[i] = filterLibrary[i];
        }
        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
    /**
     * @deprecated Unused. Will be removed in Tomcat 9. Use
     *             {@link #doRead(ApplicationBufferHandler)}
     */
    @Deprecated
    @Override
    public int doRead(ByteChunk chunk) throws IOException {
        if (lastActiveFilter == -1)
            return inputStreamInputBuffer.doRead(chunk);
        else
            return activeFilters[lastActiveFilter].doRead(chunk);
    }

    @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;
        parsingHeader = true;
        swallowInput = true;
        headerParsePos = HeaderParsePosition.HEADER_START;
        parsingRequestLine = true;
        parsingRequestLinePhase = 0;
        parsingRequestLineEol = false;
        parsingRequestLineStart = 0;
        parsingRequestLineQPos = -1;
        headerData.recycle();
    }

    /**
     * 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) throws IOException {
        // check state
        if (!parsingRequestLine) {
            return true;
        }
        // 
        // Skipping blank lines
        // 
        if (parsingRequestLinePhase < 2) {
            byte chr = 0;
            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(wrapper.getEndpoint().getKeepAliveTimeout());
                    }
                    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(wrapper.getEndpoint().getConnectionTimeout());
                }
                if (!keptAlive && byteBuffer.position() == 0 && byteBuffer.limit() >= CLIENT_PREFACE_START.length - 1) {
                    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.getStartTime() < 0) {
                    request.setStartTime(System.currentTimeMillis());
                }
                chr = byteBuffer.get();
            } while ((chr == Constants.CR) || (chr == Constants.LF));
            byteBuffer.position(byteBuffer.position() - 1);
            parsingRequestLineStart = byteBuffer.position();
            parsingRequestLinePhase = 2;
            if (log.isDebugEnabled()) {
                log.debug("Received [" + new String(byteBuffer.array(), byteBuffer.position(), byteBuffer.remaining(), StandardCharsets.ISO_8859_1) + "]");
            }
        }
        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 (// request line parsing
                    !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();
                byte 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)) {
                    byteBuffer.position(byteBuffer.position() - 1);
                    // Avoid unknown protocol triggering an additional error
                    request.protocol().setString(Constants.HTTP_11);
                    throw new IllegalArgumentException(sm.getString("iib.invalidmethod"));
                }
            }
            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 (// request line parsing
                    !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 = 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 (// request line parsing
                    !fill(false))
                        return false;
                }
                int pos = byteBuffer.position();
                byte chr = byteBuffer.get();
                if (chr == Constants.SP || chr == Constants.HT) {
                    space = true;
                    end = pos;
                } else if (chr == Constants.CR || chr == Constants.LF) {
                    // HTTP/0.9 style request
                    parsingRequestLineEol = true;
                    space = true;
                    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
                    throw new IllegalArgumentException(sm.getString("iib.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()
                    throw new IllegalArgumentException(sm.getString("iib.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);
            }
            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 (// request line parsing
                    !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 (// request line parsing
                    !fill(false))
                        return false;
                }
                int pos = byteBuffer.position();
                byte chr = byteBuffer.get();
                if (chr == Constants.CR) {
                    end = pos;
                } else if (chr == Constants.LF) {
                    if (end == 0) {
                        end = pos;
                    }
                    parsingRequestLineEol = true;
                } else if (!HttpParser.isHttpProtocol(chr)) {
                    throw new IllegalArgumentException(sm.getString("iib.invalidHttpProtocol"));
                }
            }
            if ((end - parsingRequestLineStart) > 0) {
                request.protocol().setBytes(byteBuffer.array(), parsingRequestLineStart, end - parsingRequestLineStart);
            } else {
                request.protocol().setString("");
            }
            parsingRequestLine = false;
            parsingRequestLinePhase = 0;
            parsingRequestLineEol = false;
            parsingRequestLineStart = 0;
            return true;
        }
        throw new IllegalStateException("Invalid request line parse phase:" + 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;
    }

    /**
     * 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);
        }
    }

    /**
     * Available bytes in the buffers (note that due to encoding, this may not
     * correspond).
     */
    int available(boolean read) {
        int available = byteBuffer.remaining();
        if ((available == 0) && (lastActiveFilter >= 0)) {
            for (int i = 0; (available == 0) && (i <= lastActiveFilter); i++) {
                available = activeFilters[i].available();
            }
        }
        if (available > 0 || !read) {
            return available;
        }
        try {
            if (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() > 0 primarily because of having to handle
     * faking non-blocking reads with the blocking IO connector.
     */
    boolean isFinished() {
        if (byteBuffer.limit() > byteBuffer.position()) {
            // Data to read in the buffer so not finished
            return false;
        }
        /*
         * Don't use fill(false) here because in the following cirreplacedstances
         * BIO will block - possibly indefinitely
         * - client is using keep-alive and connection is still open
         * - client has sent the complete request
         * - client has not sent any of the next request (i.e. no pipelining)
         * - application has read the complete request
         */
        // Check the InputFilters
        if (lastActiveFilter >= 0) {
            return activeFilters[lastActiveFilter].isFinished();
        } else {
            // No filters. replacedume 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;
        }
    }

    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 (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);
        }
        byteBuffer.mark();
        if (byteBuffer.position() < byteBuffer.limit()) {
            byteBuffer.position(byteBuffer.limit());
        }
        byteBuffer.limit(byteBuffer.capacity());
        int nRead = wrapper.read(block, byteBuffer);
        byteBuffer.limit(byteBuffer.position()).reset();
        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 false after reading a blank line (which indicates that the
     * HTTP header parsing is done
     */
    private HeaderParseStatus parseHeader() throws IOException {
        // 
        // Check for blank line
        // 
        byte chr = 0;
        while (headerParsePos == HeaderParsePosition.HEADER_START) {
            // Read new bytes if needed
            if (byteBuffer.position() >= byteBuffer.limit()) {
                if (!fill(false)) {
                    // parse header
                    headerParsePos = HeaderParsePosition.HEADER_START;
                    return HeaderParseStatus.NEED_MORE_DATA;
                }
            }
            chr = byteBuffer.get();
            if (chr == Constants.CR) {
            // Skip
            } else if (chr == Constants.LF) {
                return HeaderParseStatus.DONE;
            } else {
                byteBuffer.position(byteBuffer.position() - 1);
                break;
            }
        }
        if (headerParsePos == HeaderParsePosition.HEADER_START) {
            // Mark the current buffer position
            headerData.start = byteBuffer.position();
            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()) {
                // 解析http头的时候是不会阻塞的。
                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();
            }
            // 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();
        }
        // 
        // 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);
                        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;
                        }
                    }
                    chr = byteBuffer.get();
                    if (chr == Constants.CR) {
                    // Skip
                    } else if (chr == Constants.LF) {
                        eol = 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;
                }
            }
            chr = byteBuffer.get(byteBuffer.position());
            if (headerParsePos == HeaderParsePosition.HEADER_MULTI_LINE) {
                if ((chr != Constants.SP) && (chr != 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, chr);
                    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() throws IOException {
        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();
            byte chr = byteBuffer.get();
            if (chr == Constants.CR) {
            // Skip
            } else if (chr == Constants.LF) {
                eol = true;
            } else {
                headerData.lastSignificantChar = pos;
            }
        }
        if (rejectIllegalHeaderName || log.isDebugEnabled()) {
            String message = sm.getString("iib.invalidheader", new String(byteBuffer.array(), headerData.start, headerData.lastSignificantChar - headerData.start + 1, StandardCharsets.ISO_8859_1));
            if (rejectIllegalHeaderName) {
                throw new IllegalArgumentException(message);
            }
            log.debug(message);
        }
        headerParsePos = HeaderParsePosition.HEADER_START;
        return HeaderParseStatus.HAVE_MORE_HEADERS;
    }

    // ----------------------------------------------------------- Inner clreplacedes
    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 clreplaced HeaderParseData {

        /**
         * 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() {
            start = 0;
            realPos = 0;
            lastSignificantChar = 0;
            headerValue = null;
        }
    }

    // ------------------------------------- InputStreamInputBuffer Inner Clreplaced
    /**
     * This clreplaced is an input buffer which will read its data from an input
     * stream.
     */
    private clreplaced SocketInputBuffer implements InputBuffer {

        /**
         * @deprecated Unused. Will be removed in Tomcat 9. Use
         *             {@link #doRead(ApplicationBufferHandler)}
         */
        @Deprecated
        @Override
        public int doRead(ByteChunk chunk) throws IOException {
            if (byteBuffer.position() >= byteBuffer.limit()) {
                // The application is reading the HTTP request body which is
                // always a blocking operation.
                /**
                 * 读取HttpBody的时候是会阻塞的。
                 */
                if (!fill(true))
                    return -1;
            }
            int length = byteBuffer.remaining();
            chunk.setBytes(byteBuffer.array(), byteBuffer.position(), length);
            byteBuffer.position(byteBuffer.limit());
            return length;
        }

        @Override
        public int doRead(ApplicationBufferHandler handler) throws IOException {
            if (byteBuffer.position() >= byteBuffer.limit()) {
                // The application is reading the HTTP request body which is
                // always a blocking operation.
                if (!fill(true))
                    return -1;
            }
            int length = byteBuffer.remaining();
            handler.setByteBuffer(byteBuffer.duplicate());
            byteBuffer.position(byteBuffer.limit());
            return length;
        }
    }

    @Override
    public void setByteBuffer(ByteBuffer buffer) {
        byteBuffer = buffer;
    }

    @Override
    public ByteBuffer getByteBuffer() {
        return byteBuffer;
    }

    @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;
    }
}

15 View Complete Implementation : IdentityInputFilter.java
Copyright Apache License 2.0
Author : codefollower
// ---------------------------------------------------- InputBuffer Methods
/**
 * Read bytes.
 *
 * @return If the filter does request length control, this value is
 * significant; it should be the number of bytes consumed from the buffer,
 * up until the end of the current request body, or the buffer length,
 * whichever is greater. If the filter does not do request body length
 * control, the returned value should be -1.
 */
@Override
public int doRead(ByteChunk chunk, Request req) throws IOException {
    int result = -1;
    if (contentLength >= 0) {
        if (remaining > 0) {
            int nRead = buffer.doRead(chunk, req);
            if (nRead > remaining) {
                // The chunk is longer than the number of bytes remaining
                // in the body; changing the chunk length to the number
                // of bytes remaining
                chunk.setBytes(chunk.getBytes(), chunk.getStart(), (int) remaining);
                result = (int) remaining;
            } else {
                result = nRead;
            }
            remaining = remaining - nRead;
        } else {
            // No more bytes left to be read : return -1 and clear the
            // buffer
            chunk.recycle();
            result = -1;
        }
    }
    return result;
}

15 View Complete Implementation : IdentityInputFilter.java
Copyright Apache License 2.0
Author : how2j
// ---------------------------------------------------- InputBuffer Methods
/**
 * Read bytes.
 *
 * @return If the filter does request length control, this value is
 *         significant; it should be the number of bytes consumed from the
 *         buffer, up until the end of the current request body, or the
 *         buffer length, whichever is greater. If the filter does not do
 *         request body length control, the returned value should be -1.
 */
@Override
public int doRead(ByteChunk chunk, Request req) throws IOException {
    int result = -1;
    if (contentLength >= 0) {
        if (remaining > 0) {
            int nRead = buffer.doRead(chunk, req);
            if (nRead > remaining) {
                // The chunk is longer than the number of bytes remaining
                // in the body; changing the chunk length to the number
                // of bytes remaining
                chunk.setBytes(chunk.getBytes(), chunk.getStart(), (int) remaining);
                result = (int) remaining;
            } else {
                result = nRead;
            }
            if (nRead > 0) {
                remaining = remaining - nRead;
            }
        } else {
            // No more bytes left to be read : return -1 and clear the
            // buffer
            chunk.recycle();
            result = -1;
        }
    }
    return result;
}