// ======================================================================== // Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 // and Apache License v2.0 which accompanies this distribution. // The Eclipse Public License is available at // http://www.eclipse.org/legal/epl-v10.html // The Apache License v2.0 is available at // http://www.opensource.org/licenses/apache2.0.php // You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.http; import java.io.IOException; import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.BufferCache.CachedBuffer; import org.eclipse.jetty.io.BufferUtil; import org.eclipse.jetty.io.Buffers; import org.eclipse.jetty.io.ByteArrayBuffer; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.EofException; import org.eclipse.jetty.io.View; import org.eclipse.jetty.io.bio.StreamEndPoint; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; public class HttpParser implements Parser { private static final Logger LOG = Log.getLogger(HttpParser.class); // States public static final int STATE_START=-14; public static final int STATE_FIELD0=-13; public static final int STATE_SPACE1=-12; public static final int STATE_STATUS=-11; public static final int STATE_URI=-10; public static final int STATE_SPACE2=-9; public static final int STATE_END0=-8; public static final int STATE_END1=-7; public static final int STATE_FIELD2=-6; public static final int STATE_HEADER=-5; public static final int STATE_HEADER_NAME=-4; public static final int STATE_HEADER_IN_NAME=-3; public static final int STATE_HEADER_VALUE=-2; public static final int STATE_HEADER_IN_VALUE=-1; public static final int STATE_END=0; public static final int STATE_EOF_CONTENT=1; public static final int STATE_CONTENT=2; public static final int STATE_CHUNKED_CONTENT=3; public static final int STATE_CHUNK_SIZE=4; public static final int STATE_CHUNK_PARAMS=5; public static final int STATE_CHUNK=6; public static final int STATE_SEEKING_EOF=7; private final EventHandler _handler; private final Buffers _buffers; // source of buffers private final EndPoint _endp; private Buffer _header; // Buffer for header data (and small _content) private Buffer _body; // Buffer for large content private Buffer _buffer; // The current buffer in use (either _header or _content) private CachedBuffer _cached; private final View.CaseInsensitive _tok0; // Saved token: header name, request method or response version private final View.CaseInsensitive _tok1; // Saved token: header value, request URI or response code private String _multiLineValue; private int _responseStatus; // If >0 then we are parsing a response private boolean _forceContentBuffer; private boolean _persistent; /* ------------------------------------------------------------------------------- */ protected final View _contentView=new View(); // View of the content in the buffer for {@link Input} protected int _state=STATE_START; protected byte _eol; protected int _length; protected long _contentLength; protected long _contentPosition; protected int _chunkLength; protected int _chunkPosition; private boolean _headResponse; /* ------------------------------------------------------------------------------- */ /** * Constructor. */ public HttpParser(Buffer buffer, EventHandler handler) { _endp=null; _buffers=null; _header=buffer; _buffer=buffer; _handler=handler; _tok0=new View.CaseInsensitive(_header); _tok1=new View.CaseInsensitive(_header); } /* ------------------------------------------------------------------------------- */ /** * Constructor. * @param buffers the buffers to use * @param endp the endpoint * @param handler the even handler */ public HttpParser(Buffers buffers, EndPoint endp, EventHandler handler) { _buffers=buffers; _endp=endp; _handler=handler; _tok0=new View.CaseInsensitive(); _tok1=new View.CaseInsensitive(); } /* ------------------------------------------------------------------------------- */ public long getContentLength() { return _contentLength; } /* ------------------------------------------------------------ */ public long getContentRead() { return _contentPosition; } /* ------------------------------------------------------------ */ /** Set if a HEAD response is expected * @param head */ public void setHeadResponse(boolean head) { _headResponse=head; } /* ------------------------------------------------------------------------------- */ public int getState() { return _state; } /* ------------------------------------------------------------------------------- */ public boolean inContentState() { return _state > 0; } /* ------------------------------------------------------------------------------- */ public boolean inHeaderState() { return _state < 0; } /* ------------------------------------------------------------------------------- */ public boolean isChunking() { return _contentLength==HttpTokens.CHUNKED_CONTENT; } /* ------------------------------------------------------------ */ public boolean isIdle() { return isState(STATE_START); } /* ------------------------------------------------------------ */ public boolean isComplete() { return isState(STATE_END); } /* ------------------------------------------------------------ */ public boolean isMoreInBuffer() throws IOException { return ( _header!=null && _header.hasContent() || _body!=null && _body.hasContent()); } /* ------------------------------------------------------------------------------- */ public boolean isState(int state) { return _state == state; } /* ------------------------------------------------------------------------------- */ public boolean isPersistent() { return _persistent; } /* ------------------------------------------------------------------------------- */ public void setPersistent(boolean persistent) { _persistent = persistent; if (!_persistent &&(_state==STATE_END || _state==STATE_START)) _state=STATE_SEEKING_EOF; } /* ------------------------------------------------------------------------------- */ /** * Parse until {@link #STATE_END END} state. * If the parser is already in the END state, then it is {@link #reset reset} and re-parsed. * @throws IllegalStateException If the buffers have already been partially parsed. */ public void parse() throws IOException { if (_state==STATE_END) reset(); if (_state!=STATE_START) throw new IllegalStateException("!START"); // continue parsing while (_state != STATE_END) if (parseNext()<0) return; } /* ------------------------------------------------------------------------------- */ /** * Parse until END state. * This method will parse any remaining content in the current buffer. It does not care about the * {@link #getState current state} of the parser. * @see #parse * @see #parseNext */ public boolean parseAvailable() throws IOException { boolean progress=parseNext()>0; // continue parsing while (!isComplete() && _buffer!=null && _buffer.length()>0) { progress |= parseNext()>0; } return progress; } /* ------------------------------------------------------------------------------- */ /** * Parse until next Event. * @return an indication of progress <0 EOF, 0 no progress, >0 progress. */ public int parseNext() throws IOException { try { int progress=0; if (_state == STATE_END) return 0; if (_buffer==null) _buffer=getHeaderBuffer(); if (_state == STATE_CONTENT && _contentPosition == _contentLength) { _state=STATE_END; _handler.messageComplete(_contentPosition); returnBuffers(); return 1; } int length=_buffer.length(); // Fill buffer if we can if (length == 0) { int filled=-1; IOException ex=null; try { filled=fill(); LOG.debug("filled {}/{}",filled,_buffer.length()); } catch(IOException e) { LOG.debug(this.toString(),e); ex=e; } if (filled > 0 ) progress++; else if (filled < 0 ) { _persistent=false; // do we have content to deliver? if (_state>STATE_END) { if (_buffer.length()>0 && !_headResponse) { Buffer chunk=_buffer.get(_buffer.length()); _contentPosition += chunk.length(); _contentView.update(chunk); _handler.content(chunk); // May recurse here } } // was this unexpected? switch(_state) { case STATE_END: case STATE_SEEKING_EOF: _state=STATE_END; break; case STATE_EOF_CONTENT: _state=STATE_END; _handler.messageComplete(_contentPosition); break; default: _state=STATE_END; if (!_headResponse) _handler.earlyEOF(); _handler.messageComplete(_contentPosition); } if (ex!=null) throw ex; if (!isComplete() && !isIdle()) throw new EofException(); returnBuffers(); return -1; } length=_buffer.length(); } // Handle header states byte ch; byte[] array=_buffer.array(); int last=_state; while (_state<STATE_END && length-->0) { if (last!=_state) { progress++; last=_state; } ch=_buffer.get(); if (_eol == HttpTokens.CARRIAGE_RETURN && ch == HttpTokens.LINE_FEED) { _eol=HttpTokens.LINE_FEED; continue; } _eol=0; switch (_state) { case STATE_START: _contentLength=HttpTokens.UNKNOWN_CONTENT; _cached=null; if (ch > HttpTokens.SPACE || ch<0) { _buffer.mark(); _state=STATE_FIELD0; } break; case STATE_FIELD0: if (ch == HttpTokens.SPACE) { _tok0.update(_buffer.markIndex(), _buffer.getIndex() - 1); _responseStatus=HttpVersions.CACHE.get(_tok0)==null?-1:0; _state=STATE_SPACE1; continue; } else if (ch < HttpTokens.SPACE && ch>=0) { throw new HttpException(HttpStatus.BAD_REQUEST_400); } break; case STATE_SPACE1: if (ch > HttpTokens.SPACE || ch<0) { _buffer.mark(); if (_responseStatus>=0) { _state=STATE_STATUS; _responseStatus=ch-'0'; } else _state=STATE_URI; } else if (ch < HttpTokens.SPACE) { throw new HttpException(HttpStatus.BAD_REQUEST_400); } break; case STATE_STATUS: if (ch == HttpTokens.SPACE) { _tok1.update(_buffer.markIndex(), _buffer.getIndex() - 1); _state=STATE_SPACE2; continue; } else if (ch>='0' && ch<='9') { _responseStatus=_responseStatus*10+(ch-'0'); continue; } else if (ch < HttpTokens.SPACE && ch>=0) { _handler.startResponse(HttpMethods.CACHE.lookup(_tok0), _responseStatus, null); _eol=ch; _state=STATE_HEADER; _tok0.setPutIndex(_tok0.getIndex()); _tok1.setPutIndex(_tok1.getIndex()); _multiLineValue=null; continue; } // not a digit, so must be a URI _state=STATE_URI; _responseStatus=-1; break; case STATE_URI: if (ch == HttpTokens.SPACE) { _tok1.update(_buffer.markIndex(), _buffer.getIndex() - 1); _state=STATE_SPACE2; continue; } else if (ch < HttpTokens.SPACE && ch>=0) { // HTTP/0.9 _handler.startRequest(HttpMethods.CACHE.lookup(_tok0), _buffer.sliceFromMark(), null); _persistent=false; _state=STATE_SEEKING_EOF; _handler.headerComplete(); _handler.messageComplete(_contentPosition); returnBuffers(); return 1; } break; case STATE_SPACE2: if (ch > HttpTokens.SPACE || ch<0) { _buffer.mark(); _state=STATE_FIELD2; } else if (ch < HttpTokens.SPACE) { if (_responseStatus>0) { _handler.startResponse(HttpMethods.CACHE.lookup(_tok0), _responseStatus, null); _eol=ch; _state=STATE_HEADER; _tok0.setPutIndex(_tok0.getIndex()); _tok1.setPutIndex(_tok1.getIndex()); _multiLineValue=null; } else { // HTTP/0.9 _handler.startRequest(HttpMethods.CACHE.lookup(_tok0), _tok1, null); _persistent=false; _state=STATE_SEEKING_EOF; _handler.headerComplete(); _handler.messageComplete(_contentPosition); returnBuffers(); return 1; } } break; case STATE_FIELD2: if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED) { Buffer version; if (_responseStatus>0) _handler.startResponse(version=HttpVersions.CACHE.lookup(_tok0), _responseStatus,_buffer.sliceFromMark()); else _handler.startRequest(HttpMethods.CACHE.lookup(_tok0), _tok1, version=HttpVersions.CACHE.lookup(_buffer.sliceFromMark())); _eol=ch; _persistent=HttpVersions.CACHE.getOrdinal(version)>=HttpVersions.HTTP_1_1_ORDINAL; _state=STATE_HEADER; _tok0.setPutIndex(_tok0.getIndex()); _tok1.setPutIndex(_tok1.getIndex()); _multiLineValue=null; continue; } break; case STATE_HEADER: switch(ch) { case HttpTokens.COLON: case HttpTokens.SPACE: case HttpTokens.TAB: { // header value without name - continuation? _length=-1; _state=STATE_HEADER_VALUE; break; } default: { // handler last header if any if (_cached!=null || _tok0.length() > 0 || _tok1.length() > 0 || _multiLineValue != null) { Buffer header=_cached!=null?_cached:HttpHeaders.CACHE.lookup(_tok0); _cached=null; Buffer value=_multiLineValue == null ? _tok1 : new ByteArrayBuffer(_multiLineValue); int ho=HttpHeaders.CACHE.getOrdinal(header); if (ho >= 0) { int vo; switch (ho) { case HttpHeaders.CONTENT_LENGTH_ORDINAL: if (_contentLength != HttpTokens.CHUNKED_CONTENT && _responseStatus!=304 && _responseStatus!=204 && (_responseStatus<100 || _responseStatus>=200)) { try { _contentLength=BufferUtil.toLong(value); } catch(NumberFormatException e) { LOG.ignore(e); throw new HttpException(HttpStatus.BAD_REQUEST_400); } if (_contentLength <= 0) _contentLength=HttpTokens.NO_CONTENT; } break; case HttpHeaders.TRANSFER_ENCODING_ORDINAL: value=HttpHeaderValues.CACHE.lookup(value); vo=HttpHeaderValues.CACHE.getOrdinal(value); if (HttpHeaderValues.CHUNKED_ORDINAL == vo) _contentLength=HttpTokens.CHUNKED_CONTENT; else { String c=value.toString(StringUtil.__ISO_8859_1); if (c.endsWith(HttpHeaderValues.CHUNKED)) _contentLength=HttpTokens.CHUNKED_CONTENT; else if (c.indexOf(HttpHeaderValues.CHUNKED) >= 0) throw new HttpException(400,null); } break; case HttpHeaders.CONNECTION_ORDINAL: switch(HttpHeaderValues.CACHE.getOrdinal(value)) { case HttpHeaderValues.CLOSE_ORDINAL: _persistent=false; break; case HttpHeaderValues.KEEP_ALIVE_ORDINAL: _persistent=true; break; case -1: // No match, may be multi valued { for (String v : value.toString().split(",")) { switch(HttpHeaderValues.CACHE.getOrdinal(v.trim())) { case HttpHeaderValues.CLOSE_ORDINAL: _persistent=false; break; case HttpHeaderValues.KEEP_ALIVE_ORDINAL: _persistent=true; break; } } break; } } } } _handler.parsedHeader(header, value); _tok0.setPutIndex(_tok0.getIndex()); _tok1.setPutIndex(_tok1.getIndex()); _multiLineValue=null; } _buffer.setMarkIndex(-1); // now handle ch if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED) { // work out the _content demarcation if (_contentLength == HttpTokens.UNKNOWN_CONTENT) { if (_responseStatus == 0 // request || _responseStatus == 304 // not-modified response || _responseStatus == 204 // no-content response || _responseStatus < 200) // 1xx response _contentLength=HttpTokens.NO_CONTENT; else _contentLength=HttpTokens.EOF_CONTENT; } _contentPosition=0; _eol=ch; if (_eol==HttpTokens.CARRIAGE_RETURN && _buffer.hasContent() && _buffer.peek()==HttpTokens.LINE_FEED) _eol=_buffer.get(); // We convert _contentLength to an int for this switch statement because // we don't care about the amount of data available just whether there is some. switch (_contentLength > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) _contentLength) { case HttpTokens.EOF_CONTENT: _state=STATE_EOF_CONTENT; _handler.headerComplete(); // May recurse here ! break; case HttpTokens.CHUNKED_CONTENT: _state=STATE_CHUNKED_CONTENT; _handler.headerComplete(); // May recurse here ! break; case HttpTokens.NO_CONTENT: _handler.headerComplete(); _state=_persistent||(_responseStatus>=100&&_responseStatus<200)?STATE_END:STATE_SEEKING_EOF; _handler.messageComplete(_contentPosition); returnBuffers(); return 1; default: _state=STATE_CONTENT; _handler.headerComplete(); // May recurse here ! break; } return 1; } else { // New header _length=1; _buffer.mark(); _state=STATE_HEADER_NAME; // try cached name! if (array!=null) { _cached=HttpHeaders.CACHE.getBest(array, _buffer.markIndex(), length+1); if (_cached!=null) { _length=_cached.length(); _buffer.setGetIndex(_buffer.markIndex()+_length); length=_buffer.length(); } } } } } break; case STATE_HEADER_NAME: switch(ch) { case HttpTokens.CARRIAGE_RETURN: case HttpTokens.LINE_FEED: if (_length > 0) _tok0.update(_buffer.markIndex(), _buffer.markIndex() + _length); _eol=ch; _state=STATE_HEADER; break; case HttpTokens.COLON: if (_length > 0 && _cached==null) _tok0.update(_buffer.markIndex(), _buffer.markIndex() + _length); _length=-1; _state=STATE_HEADER_VALUE; break; case HttpTokens.SPACE: case HttpTokens.TAB: break; default: { _cached=null; if (_length == -1) _buffer.mark(); _length=_buffer.getIndex() - _buffer.markIndex(); _state=STATE_HEADER_IN_NAME; } } break; case STATE_HEADER_IN_NAME: switch(ch) { case HttpTokens.CARRIAGE_RETURN: case HttpTokens.LINE_FEED: if (_length > 0) _tok0.update(_buffer.markIndex(), _buffer.markIndex() + _length); _eol=ch; _state=STATE_HEADER; break; case HttpTokens.COLON: if (_length > 0 && _cached==null) _tok0.update(_buffer.markIndex(), _buffer.markIndex() + _length); _length=-1; _state=STATE_HEADER_VALUE; break; case HttpTokens.SPACE: case HttpTokens.TAB: _state=STATE_HEADER_NAME; break; default: { _cached=null; _length++; } } break; case STATE_HEADER_VALUE: switch(ch) { case HttpTokens.CARRIAGE_RETURN: case HttpTokens.LINE_FEED: if (_length > 0) { if (_tok1.length() == 0) _tok1.update(_buffer.markIndex(), _buffer.markIndex() + _length); else { // Continuation line! if (_multiLineValue == null) _multiLineValue=_tok1.toString(StringUtil.__ISO_8859_1); _tok1.update(_buffer.markIndex(), _buffer.markIndex() + _length); _multiLineValue += " " + _tok1.toString(StringUtil.__ISO_8859_1); } } _eol=ch; _state=STATE_HEADER; break; case HttpTokens.SPACE: case HttpTokens.TAB: break; default: { if (_length == -1) _buffer.mark(); _length=_buffer.getIndex() - _buffer.markIndex(); _state=STATE_HEADER_IN_VALUE; } } break; case STATE_HEADER_IN_VALUE: switch(ch) { case HttpTokens.CARRIAGE_RETURN: case HttpTokens.LINE_FEED: if (_length > 0) { if (_tok1.length() == 0) _tok1.update(_buffer.markIndex(), _buffer.markIndex() + _length); else { // Continuation line! if (_multiLineValue == null) _multiLineValue=_tok1.toString(StringUtil.__ISO_8859_1); _tok1.update(_buffer.markIndex(), _buffer.markIndex() + _length); _multiLineValue += " " + _tok1.toString(StringUtil.__ISO_8859_1); } } _eol=ch; _state=STATE_HEADER; break; case HttpTokens.SPACE: case HttpTokens.TAB: _state=STATE_HEADER_VALUE; break; default: _length++; } break; } } // end of HEADER states loop // ========================== // Handle HEAD response if (_responseStatus>0 && _headResponse) { _state=_persistent||(_responseStatus>=100&&_responseStatus<200)?STATE_END:STATE_SEEKING_EOF; _handler.messageComplete(_contentLength); } // ========================== // Handle _content length=_buffer.length(); Buffer chunk; last=_state; while (_state > STATE_END && length > 0) { if (last!=_state) { progress++; last=_state; } if (_eol == HttpTokens.CARRIAGE_RETURN && _buffer.peek() == HttpTokens.LINE_FEED) { _eol=_buffer.get(); length=_buffer.length(); continue; } _eol=0; switch (_state) { case STATE_EOF_CONTENT: chunk=_buffer.get(_buffer.length()); _contentPosition += chunk.length(); _contentView.update(chunk); _handler.content(chunk); // May recurse here // TODO adjust the _buffer to keep unconsumed content return 1; case STATE_CONTENT: { long remaining=_contentLength - _contentPosition; if (remaining == 0) { _state=_persistent?STATE_END:STATE_SEEKING_EOF; _handler.messageComplete(_contentPosition); returnBuffers(); return 1; } if (length > remaining) { // We can cast reamining to an int as we know that it is smaller than // or equal to length which is already an int. length=(int)remaining; } chunk=_buffer.get(length); _contentPosition += chunk.length(); _contentView.update(chunk); _handler.content(chunk); // May recurse here if(_contentPosition == _contentLength) { _state=_persistent?STATE_END:STATE_SEEKING_EOF; _handler.messageComplete(_contentPosition); returnBuffers(); } // TODO adjust the _buffer to keep unconsumed content return 1; } case STATE_CHUNKED_CONTENT: { ch=_buffer.peek(); if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED) _eol=_buffer.get(); else if (ch <= HttpTokens.SPACE) _buffer.get(); else { _chunkLength=0; _chunkPosition=0; _state=STATE_CHUNK_SIZE; } break; } case STATE_CHUNK_SIZE: { ch=_buffer.get(); if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED) { _eol=ch; if (_chunkLength == 0) { if (_eol==HttpTokens.CARRIAGE_RETURN && _buffer.hasContent() && _buffer.peek()==HttpTokens.LINE_FEED) _eol=_buffer.get(); _state=_persistent?STATE_END:STATE_SEEKING_EOF; _handler.messageComplete(_contentPosition); returnBuffers(); return 1; } else _state=STATE_CHUNK; } else if (ch <= HttpTokens.SPACE || ch == HttpTokens.SEMI_COLON) _state=STATE_CHUNK_PARAMS; else if (ch >= '0' && ch <= '9') _chunkLength=_chunkLength * 16 + (ch - '0'); else if (ch >= 'a' && ch <= 'f') _chunkLength=_chunkLength * 16 + (10 + ch - 'a'); else if (ch >= 'A' && ch <= 'F') _chunkLength=_chunkLength * 16 + (10 + ch - 'A'); else throw new IOException("bad chunk char: " + ch); break; } case STATE_CHUNK_PARAMS: { ch=_buffer.get(); if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED) { _eol=ch; if (_chunkLength == 0) { if (_eol==HttpTokens.CARRIAGE_RETURN && _buffer.hasContent() && _buffer.peek()==HttpTokens.LINE_FEED) _eol=_buffer.get(); _state=_persistent?STATE_END:STATE_SEEKING_EOF; _handler.messageComplete(_contentPosition); returnBuffers(); return 1; } else _state=STATE_CHUNK; } break; } case STATE_CHUNK: { int remaining=_chunkLength - _chunkPosition; if (remaining == 0) { _state=STATE_CHUNKED_CONTENT; break; } else if (length > remaining) length=remaining; chunk=_buffer.get(length); _contentPosition += chunk.length(); _chunkPosition += chunk.length(); _contentView.update(chunk); _handler.content(chunk); // May recurse here // TODO adjust the _buffer to keep unconsumed content return 1; } case STATE_SEEKING_EOF: { // Close if there is more data than CRLF if (_buffer.length()>2) { _state=STATE_END; _endp.close(); } else { // or if the data is not white space while (_buffer.length()>0) if (!Character.isWhitespace(_buffer.get())) { _state=STATE_END; _endp.close(); _buffer.clear(); } } _buffer.clear(); break; } } length=_buffer.length(); } return progress; } catch(HttpException e) { _persistent=false; _state=STATE_SEEKING_EOF; throw e; } } /* ------------------------------------------------------------------------------- */ /** fill the buffers from the endpoint * */ protected int fill() throws IOException { // Do we have a buffer? if (_buffer==null) _buffer=getHeaderBuffer(); // Is there unconsumed content in body buffer if (_state>STATE_END && _buffer==_header && _header!=null && !_header.hasContent() && _body!=null && _body.hasContent()) { _buffer=_body; return _buffer.length(); } // Shall we switch to a body buffer? if (_buffer==_header && _state>STATE_END && _header.length()==0 && (_forceContentBuffer || (_contentLength-_contentPosition)>_header.capacity()) && (_body!=null||_buffers!=null)) { if (_body==null) _body=_buffers.getBuffer(); _buffer=_body; } // Do we have somewhere to fill from? if (_endp != null ) { // Shall we compact the body? if (_buffer==_body || _state>STATE_END) { _buffer.compact(); } // Are we full? if (_buffer.space() == 0) { LOG.warn("Full {}",_buffer.toDetailString()); throw new HttpException(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413, "FULL "+(_buffer==_body?"body":"head")); } try { int filled = _endp.fill(_buffer); return filled; } catch(IOException e) { LOG.debug(e); throw (e instanceof EofException) ? e:new EofException(e); } } return -1; } /* ------------------------------------------------------------------------------- */ public void reset() { // reset state _contentView.setGetIndex(_contentView.putIndex()); _state=_persistent?STATE_START:(_endp.isInputShutdown()?STATE_END:STATE_SEEKING_EOF); _contentLength=HttpTokens.UNKNOWN_CONTENT; _contentPosition=0; _length=0; _responseStatus=0; // Consume LF if CRLF if (_eol == HttpTokens.CARRIAGE_RETURN && _buffer!=null && _buffer.hasContent() && _buffer.peek() == HttpTokens.LINE_FEED) _eol=_buffer.get(); if (_body!=null && _body.hasContent()) { // There is content in the body after the end of the request. // This is probably a pipelined header of the next request, so we need to // copy it to the header buffer. if (_header==null) getHeaderBuffer(); else { _header.setMarkIndex(-1); _header.compact(); } int take=_header.space(); if (take>_body.length()) take=_body.length(); _body.peek(_body.getIndex(),take); _body.skip(_header.put(_body.peek(_body.getIndex(),take))); } if (_header!=null) { _header.setMarkIndex(-1); _header.compact(); } if (_body!=null) _body.setMarkIndex(-1); _buffer=_header; returnBuffers(); } /* ------------------------------------------------------------------------------- */ public void returnBuffers() { if (_body!=null && !_body.hasContent() && _body.markIndex()==-1 && _buffers!=null) { if (_buffer==_body) _buffer=_header; if (_buffers!=null) _buffers.returnBuffer(_body); _body=null; } if (_header!=null && !_header.hasContent() && _header.markIndex()==-1 && _buffers!=null) { if (_buffer==_header) _buffer=null; _buffers.returnBuffer(_header); _header=null; } } /* ------------------------------------------------------------------------------- */ public void setState(int state) { this._state=state; _contentLength=HttpTokens.UNKNOWN_CONTENT; } /* ------------------------------------------------------------------------------- */ public String toString(Buffer buf) { return "state=" + _state + " length=" + _length + " buf=" + buf.hashCode(); } /* ------------------------------------------------------------------------------- */ @Override public String toString() { return String.format("%s{s=%d,l=%d,c=%d}", getClass().getSimpleName(), _state, _length, _contentLength); } /* ------------------------------------------------------------ */ public Buffer getHeaderBuffer() { if (_header == null) { _header=_buffers.getHeader(); _tok0.update(_header); _tok1.update(_header); } return _header; } /* ------------------------------------------------------------ */ public Buffer getBodyBuffer() { return _body; } /* ------------------------------------------------------------ */ /** * @param force True if a new buffer will be forced to be used for content and the header buffer will not be used. */ public void setForceContentBuffer(boolean force) { _forceContentBuffer=force; } /* ------------------------------------------------------------ */ public Buffer blockForContent(long maxIdleTime) throws IOException { if (_contentView.length()>0) return _contentView; if (getState() <= STATE_END || isState(STATE_SEEKING_EOF)) return null; try { parseNext(); // parse until some progress is made (or IOException thrown for timeout) while(_contentView.length() == 0 && !(isState(HttpParser.STATE_END)||isState(HttpParser.STATE_SEEKING_EOF)) && _endp!=null && _endp.isOpen()) { if (!_endp.isBlocking()) { if (parseNext()>0) continue; if (!_endp.blockReadable(maxIdleTime)) { _endp.close(); throw new EofException("timeout"); } } parseNext(); } } catch(IOException e) { // TODO is this needed? _endp.close(); throw e; } return _contentView.length()>0?_contentView:null; } /* ------------------------------------------------------------ */ /* (non-Javadoc) * @see java.io.InputStream#available() */ public int available() throws IOException { if (_contentView!=null && _contentView.length()>0) return _contentView.length(); if (_endp.isBlocking()) { if (_state>0 && _endp instanceof StreamEndPoint) return ((StreamEndPoint)_endp).getInputStream().available()>0?1:0; return 0; } parseNext(); return _contentView==null?0:_contentView.length(); } /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ public static abstract class EventHandler { public abstract void content(Buffer ref) throws IOException; public void headerComplete() throws IOException { } public void messageComplete(long contentLength) throws IOException { } /** * This is the method called by parser when a HTTP Header name and value is found */ public void parsedHeader(Buffer name, Buffer value) throws IOException { } /** * This is the method called by parser when the HTTP request line is parsed */ public abstract void startRequest(Buffer method, Buffer url, Buffer version) throws IOException; /** * This is the method called by parser when the HTTP request line is parsed */ public abstract void startResponse(Buffer version, int status, Buffer reason) throws IOException; public void earlyEOF() {} } }