| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.coyote.http11; |
| |
| import java.io.EOFException; |
| import java.io.IOException; |
| import java.nio.channels.Selector; |
| import java.nio.charset.Charset; |
| |
| import org.apache.coyote.InputBuffer; |
| import org.apache.coyote.Request; |
| import org.apache.tomcat.util.buf.ByteChunk; |
| import org.apache.tomcat.util.buf.MessageBytes; |
| import org.apache.tomcat.util.net.AbstractEndpoint; |
| import org.apache.tomcat.util.net.NioChannel; |
| import org.apache.tomcat.util.net.NioEndpoint; |
| import org.apache.tomcat.util.net.NioSelectorPool; |
| import org.apache.tomcat.util.net.SocketWrapper; |
| |
| /** |
| * Implementation of InputBuffer which provides HTTP request header parsing as |
| * well as transfer decoding. |
| * |
| * @author <a href="mailto:remm@apache.org">Remy Maucherat</a> |
| * @author Filip Hanik |
| */ |
| public class InternalNioInputBuffer extends AbstractInputBuffer<NioChannel> { |
| |
| private static final org.apache.juli.logging.Log log = |
| org.apache.juli.logging.LogFactory.getLog(InternalNioInputBuffer.class); |
| |
| private static final Charset DEFAULT_CHARSET = |
| Charset.forName("ISO-8859-1"); |
| |
| // -------------------------------------------------------------- Constants |
| |
| enum HeaderParseStatus { |
| DONE, HAVE_MORE_HEADERS, NEED_MORE_DATA |
| } |
| |
| 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 |
| } |
| |
| // ----------------------------------------------------------- Constructors |
| |
| |
| /** |
| * Alternate constructor. |
| */ |
| public InternalNioInputBuffer(Request request, int headerBufferSize) { |
| |
| this.request = request; |
| headers = request.getMimeHeaders(); |
| |
| this.headerBufferSize = headerBufferSize; |
| |
| inputStreamInputBuffer = new SocketInputBuffer(); |
| |
| 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; |
| headerData.recycle(); |
| swallowInput = true; |
| |
| } |
| |
| /** |
| * 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; |
| |
| /** |
| * Underlying socket. |
| */ |
| private NioChannel socket; |
| |
| /** |
| * Selector pool, for blocking reads and blocking writes |
| */ |
| private NioSelectorPool pool; |
| |
| |
| /** |
| * 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; |
| |
| |
| // --------------------------------------------------------- Public Methods |
| |
| /** |
| * Recycle the input buffer. This should be called when closing the |
| * connection. |
| */ |
| @Override |
| public void recycle() { |
| super.recycle(); |
| socket = null; |
| 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. |
| */ |
| @Override |
| public void nextRequest() { |
| super.nextRequest(); |
| 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 |
| */ |
| @Override |
| public boolean parseRequestLine(boolean useAvailableDataOnly) |
| throws IOException { |
| |
| //check state |
| if ( !parsingRequestLine ) return true; |
| // |
| // Skipping blank lines |
| // |
| if ( parsingRequestLinePhase == 0 ) { |
| byte chr = 0; |
| do { |
| |
| // Read new bytes if needed |
| if (pos >= lastValid) { |
| if (useAvailableDataOnly) { |
| return false; |
| } |
| // Do a simple read with a short timeout |
| if (!fill(true, false)) { |
| return false; |
| } |
| } |
| chr = buf[pos++]; |
| } while ((chr == Constants.CR) || (chr == Constants.LF)); |
| pos--; |
| |
| parsingRequestLineStart = pos; |
| parsingRequestLinePhase = 2; |
| if (log.isDebugEnabled()) { |
| log.debug("Received [" |
| + new String(buf, pos, lastValid - pos, DEFAULT_CHARSET) |
| + "]"); |
| } |
| } |
| if ( parsingRequestLinePhase == 2 ) { |
| // |
| // Reading the method name |
| // Method name is always US-ASCII |
| // |
| boolean space = false; |
| while (!space) { |
| // Read new bytes if needed |
| if (pos >= lastValid) { |
| if (!fill(true, false)) //request line parsing |
| return false; |
| } |
| // Spec says no CR or LF in method name |
| if (buf[pos] == Constants.CR || buf[pos] == Constants.LF) { |
| throw new IllegalArgumentException( |
| sm.getString("iib.invalidmethod")); |
| } |
| if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) { |
| space = true; |
| request.method().setBytes(buf, parsingRequestLineStart, pos - parsingRequestLineStart); |
| } |
| pos++; |
| } |
| parsingRequestLinePhase = 3; |
| } |
| if ( parsingRequestLinePhase == 3 ) { |
| // Spec says single SP but also be tolerant of multiple and/or HT |
| boolean space = true; |
| while (space) { |
| // Read new bytes if needed |
| if (pos >= lastValid) { |
| if (!fill(true, false)) //request line parsing |
| return false; |
| } |
| if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) { |
| pos++; |
| } else { |
| space = false; |
| } |
| } |
| parsingRequestLineStart = pos; |
| 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 (pos >= lastValid) { |
| if (!fill(true,false)) //request line parsing |
| return false; |
| } |
| if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) { |
| space = true; |
| end = pos; |
| } else if ((buf[pos] == Constants.CR) |
| || (buf[pos] == Constants.LF)) { |
| // HTTP/0.9 style request |
| parsingRequestLineEol = true; |
| space = true; |
| end = pos; |
| } else if ((buf[pos] == Constants.QUESTION) |
| && (parsingRequestLineQPos == -1)) { |
| parsingRequestLineQPos = pos; |
| } |
| pos++; |
| } |
| request.unparsedURI().setBytes(buf, parsingRequestLineStart, end - parsingRequestLineStart); |
| if (parsingRequestLineQPos >= 0) { |
| request.queryString().setBytes(buf, parsingRequestLineQPos + 1, |
| end - parsingRequestLineQPos - 1); |
| request.requestURI().setBytes(buf, parsingRequestLineStart, parsingRequestLineQPos - parsingRequestLineStart); |
| } else { |
| request.requestURI().setBytes(buf, 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 (pos >= lastValid) { |
| if (!fill(true, false)) //request line parsing |
| return false; |
| } |
| if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) { |
| pos++; |
| } else { |
| space = false; |
| } |
| } |
| parsingRequestLineStart = pos; |
| parsingRequestLinePhase = 6; |
| |
| // Mark the current buffer position |
| end = 0; |
| } |
| if (parsingRequestLinePhase == 6) { |
| // |
| // Reading the protocol |
| // Protocol is always US-ASCII |
| // |
| while (!parsingRequestLineEol) { |
| // Read new bytes if needed |
| if (pos >= lastValid) { |
| if (!fill(true, false)) //request line parsing |
| return false; |
| } |
| |
| if (buf[pos] == Constants.CR) { |
| end = pos; |
| } else if (buf[pos] == Constants.LF) { |
| if (end == 0) |
| end = pos; |
| parsingRequestLineEol = true; |
| } |
| pos++; |
| } |
| |
| if ( (end - parsingRequestLineStart) > 0) { |
| request.protocol().setBytes(buf, 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); |
| } |
| |
| private void expand(int newsize) { |
| if ( newsize > buf.length ) { |
| if (parsingHeader) { |
| throw new IllegalArgumentException( |
| sm.getString("iib.requestheadertoolarge.error")); |
| } |
| // Should not happen |
| log.warn("Expanding buffer size. Old size: " + buf.length |
| + ", new size: " + newsize, new Exception()); |
| byte[] tmp = new byte[newsize]; |
| System.arraycopy(buf,0,tmp,0,buf.length); |
| buf = tmp; |
| } |
| } |
| |
| /** |
| * Perform blocking read with a timeout if desired |
| * @param timeout boolean - if we want to use the timeout data |
| * @param block - true if the system should perform a blocking read, false otherwise |
| * @return boolean - true if data was read, false is no data read, EOFException if EOF is reached |
| * @throws IOException if a socket exception occurs |
| * @throws EOFException if end of stream is reached |
| */ |
| |
| private int readSocket(boolean timeout, boolean block) throws IOException { |
| int nRead = 0; |
| socket.getBufHandler().getReadBuffer().clear(); |
| if ( block ) { |
| Selector selector = null; |
| try { |
| selector = pool.get(); |
| } catch ( IOException x ) { |
| // Ignore |
| } |
| try { |
| NioEndpoint.KeyAttachment att = |
| (NioEndpoint.KeyAttachment) socket.getAttachment(false); |
| if (att == null) { |
| throw new IOException("Key must be cancelled."); |
| } |
| nRead = pool.read(socket.getBufHandler().getReadBuffer(), |
| socket, selector, |
| socket.getIOChannel().socket().getSoTimeout()); |
| } catch ( EOFException eof ) { |
| nRead = -1; |
| } finally { |
| if ( selector != null ) pool.put(selector); |
| } |
| } else { |
| nRead = socket.read(socket.getBufHandler().getReadBuffer()); |
| } |
| if (nRead > 0) { |
| socket.getBufHandler().getReadBuffer().flip(); |
| socket.getBufHandler().getReadBuffer().limit(nRead); |
| expand(nRead + pos); |
| socket.getBufHandler().getReadBuffer().get(buf, pos, nRead); |
| lastValid = pos + nRead; |
| return nRead; |
| } else if (nRead == -1) { |
| //return false; |
| throw new EOFException(sm.getString("iib.eof.error")); |
| } else { |
| return 0; |
| } |
| } |
| |
| /** |
| * Parse the HTTP headers. |
| */ |
| @Override |
| public 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 (pos > headerBufferSize |
| || buf.length - pos < socketReadBufferSize) { |
| throw new IllegalArgumentException( |
| sm.getString("iib.requestheadertoolarge.error")); |
| } |
| } while ( status == HeaderParseStatus.HAVE_MORE_HEADERS ); |
| if (status == HeaderParseStatus.DONE) { |
| parsingHeader = false; |
| end = pos; |
| return true; |
| } 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 (pos >= lastValid) { |
| if (!fill(true,false)) {//parse header |
| headerParsePos = HeaderParsePosition.HEADER_START; |
| return HeaderParseStatus.NEED_MORE_DATA; |
| } |
| } |
| |
| chr = buf[pos]; |
| |
| if (chr == Constants.CR) { |
| // Skip |
| } else if (chr == Constants.LF) { |
| pos++; |
| return HeaderParseStatus.DONE; |
| } else { |
| break; |
| } |
| |
| pos++; |
| |
| } |
| |
| if ( headerParsePos == HeaderParsePosition.HEADER_START ) { |
| // Mark the current buffer position |
| headerData.start = pos; |
| 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 (pos >= lastValid) { |
| if (!fill(true,false)) { //parse header |
| return HeaderParseStatus.NEED_MORE_DATA; |
| } |
| } |
| |
| chr = buf[pos]; |
| if (chr == Constants.COLON) { |
| headerParsePos = HeaderParsePosition.HEADER_VALUE_START; |
| headerData.headerValue = headers.addValue(buf, headerData.start, pos - headerData.start); |
| pos++; |
| // Mark the current buffer position |
| headerData.start = pos; |
| headerData.realPos = pos; |
| headerData.lastSignificantChar = pos; |
| break; |
| } else if (!HTTP_TOKEN_CHAR[chr]) { |
| // If a non-token header is detected, skip the line and |
| // ignore the header |
| headerData.lastSignificantChar = pos; |
| return skipLine(); |
| } |
| |
| // chr is next byte of header name. Convert to lowercase. |
| if ((chr >= Constants.A) && (chr <= Constants.Z)) { |
| buf[pos] = (byte) (chr - Constants.LC_OFFSET); |
| } |
| pos++; |
| } |
| |
| // 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 (pos >= lastValid) { |
| if (!fill(true,false)) {//parse header |
| //HEADER_VALUE_START |
| return HeaderParseStatus.NEED_MORE_DATA; |
| } |
| } |
| |
| chr = buf[pos]; |
| if (chr == Constants.SP || chr == Constants.HT) { |
| pos++; |
| } else { |
| headerParsePos = HeaderParsePosition.HEADER_VALUE; |
| 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 (pos >= lastValid) { |
| if (!fill(true,false)) {//parse header |
| //HEADER_VALUE |
| return HeaderParseStatus.NEED_MORE_DATA; |
| } |
| } |
| |
| chr = buf[pos]; |
| if (chr == Constants.CR) { |
| // Skip |
| } else if (chr == Constants.LF) { |
| eol = true; |
| } else if (chr == Constants.SP || chr == Constants.HT) { |
| buf[headerData.realPos] = chr; |
| headerData.realPos++; |
| } else { |
| buf[headerData.realPos] = chr; |
| headerData.realPos++; |
| headerData.lastSignificantChar = headerData.realPos; |
| } |
| |
| pos++; |
| } |
| |
| // 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 (pos >= lastValid) { |
| if (!fill(true,false)) {//parse header |
| |
| //HEADER_MULTI_LINE |
| return HeaderParseStatus.NEED_MORE_DATA; |
| } |
| } |
| |
| chr = buf[pos]; |
| 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) |
| buf[headerData.realPos] = chr; |
| headerData.realPos++; |
| headerParsePos = HeaderParsePosition.HEADER_VALUE_START; |
| } |
| } |
| } |
| // Set the header value |
| headerData.headerValue.setBytes(buf, headerData.start, |
| headerData.lastSignificantChar - headerData.start); |
| headerData.recycle(); |
| return HeaderParseStatus.HAVE_MORE_HEADERS; |
| } |
| |
| public int getParsingRequestLinePhase() { |
| return parsingRequestLinePhase; |
| } |
| |
| 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 (pos >= lastValid) { |
| if (!fill(true,false)) { |
| return HeaderParseStatus.NEED_MORE_DATA; |
| } |
| } |
| |
| if (buf[pos] == Constants.CR) { |
| // Skip |
| } else if (buf[pos] == Constants.LF) { |
| eol = true; |
| } else { |
| headerData.lastSignificantChar = pos; |
| } |
| |
| pos++; |
| } |
| if (log.isDebugEnabled()) { |
| log.debug(sm.getString("iib.invalidheader", new String(buf, |
| headerData.start, |
| headerData.lastSignificantChar - headerData.start + 1, |
| DEFAULT_CHARSET))); |
| } |
| |
| headerParsePos = HeaderParsePosition.HEADER_START; |
| return HeaderParseStatus.HAVE_MORE_HEADERS; |
| } |
| |
| private HeaderParseData headerData = new HeaderParseData(); |
| public static class 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; |
| } |
| } |
| |
| |
| // ------------------------------------------------------ Protected Methods |
| |
| @Override |
| protected void init(SocketWrapper<NioChannel> socketWrapper, |
| AbstractEndpoint endpoint) throws IOException { |
| |
| socket = socketWrapper.getSocket(); |
| socketReadBufferSize = |
| socket.getBufHandler().getReadBuffer().capacity(); |
| |
| int bufLength = headerBufferSize + socketReadBufferSize; |
| if (buf == null || buf.length < bufLength) { |
| buf = new byte[bufLength]; |
| } |
| |
| pool = ((NioEndpoint)endpoint).getSelectorPool(); |
| } |
| |
| |
| /** |
| * Fill the internal buffer using data from the underlying input stream. |
| * |
| * @return false if at end of stream |
| */ |
| @Override |
| protected boolean fill(boolean block) throws IOException, EOFException { |
| return fill(true,block); |
| } |
| |
| protected boolean fill(boolean timeout, boolean block) throws IOException, EOFException { |
| |
| |
| boolean read = false; |
| |
| if (parsingHeader) { |
| |
| if (lastValid > headerBufferSize) { |
| throw new IllegalArgumentException |
| (sm.getString("iib.requestheadertoolarge.error")); |
| } |
| |
| // Do a simple read with a short timeout |
| read = readSocket(timeout,block)>0; |
| } else { |
| lastValid = pos = end; |
| // Do a simple read with a short timeout |
| read = readSocket(timeout, block)>0; |
| } |
| return read; |
| } |
| |
| |
| // ------------------------------------- InputStreamInputBuffer Inner Class |
| |
| |
| /** |
| * This class is an input buffer which will read its data from an input |
| * stream. |
| */ |
| protected class SocketInputBuffer |
| implements InputBuffer { |
| |
| |
| /** |
| * Read bytes into the specified chunk. |
| */ |
| @Override |
| public int doRead(ByteChunk chunk, Request req ) |
| throws IOException { |
| |
| if (pos >= lastValid) { |
| if (!fill(true,true)) //read body, must be blocking, as the thread is inside the app |
| return -1; |
| } |
| |
| int length = lastValid - pos; |
| chunk.setBytes(buf, pos, length); |
| pos = lastValid; |
| |
| return (length); |
| } |
| } |
| } |