org.mortbay.jetty.HttpParser Maven / Gradle / Ivy
// ========================================================================
// Copyright 2004-2006 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// Licensed 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.mortbay.jetty;
import java.io.IOException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletResponse;
import org.mortbay.io.Buffer;
import org.mortbay.io.BufferUtil;
import org.mortbay.io.Buffers;
import org.mortbay.io.ByteArrayBuffer;
import org.mortbay.io.EndPoint;
import org.mortbay.io.View;
import org.mortbay.io.BufferCache.CachedBuffer;
import org.mortbay.log.Log;
/* ------------------------------------------------------------------------------- */
/**
* @author gregw
*/
public class HttpParser implements Parser
{
// States
public static final int STATE_START=-11;
public static final int STATE_FIELD0=-10;
public static final int STATE_SPACE1=-9;
public static final int STATE_FIELD1=-8;
public static final int STATE_SPACE2=-7;
public static final int STATE_END0=-6;
public static final int STATE_END1=-5;
public static final int STATE_FIELD2=-4;
public static final int STATE_HEADER=-3;
public static final int STATE_HEADER_NAME=-2;
public static final int STATE_HEADER_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;
private Buffers _buffers; // source of buffers
private 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 View _contentView=new View(); // View of the content in the buffer for {@link Input}
private int _headerBufferSize;
private int _contentBufferSize;
private EventHandler _handler;
private CachedBuffer _cached;
private View.CaseInsensitive _tok0; // Saved token: header name, request method or response version
private 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 Input _input;
/* ------------------------------------------------------------------------------- */
protected int _state=STATE_START;
protected byte _eol;
protected int _length;
protected long _contentLength;
protected long _contentPosition;
protected int _chunkLength;
protected int _chunkPosition;
/* ------------------------------------------------------------------------------- */
/**
* Constructor.
*/
public HttpParser(Buffer buffer, EventHandler handler)
{
this._header=buffer;
this._buffer=buffer;
this._handler=handler;
if (buffer != null)
{
_tok0=new View.CaseInsensitive(buffer);
_tok1=new View.CaseInsensitive(buffer);
_tok0.setPutIndex(_tok0.getIndex());
_tok1.setPutIndex(_tok1.getIndex());
}
}
/* ------------------------------------------------------------------------------- */
/**
* Constructor.
* @param headerBufferSize size in bytes of header buffer
* @param contentBufferSize size in bytes of content buffer
*/
public HttpParser(Buffers buffers, EndPoint endp, EventHandler handler, int headerBufferSize, int contentBufferSize)
{
_buffers=buffers;
_endp=endp;
_handler=handler;
_headerBufferSize=headerBufferSize;
_contentBufferSize=contentBufferSize;
}
/* ------------------------------------------------------------------------------- */
public long getContentLength()
{
return _contentLength;
}
/* ------------------------------------------------------------------------------- */
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
{
if ( _header!=null && _header.hasContent() ||
_body!=null && _body.hasContent())
return true;
return false;
}
/* ------------------------------------------------------------------------------- */
public boolean isState(int state)
{
return _state == state;
}
/* ------------------------------------------------------------------------------- */
/**
* 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(false);
if (_state!=STATE_START)
throw new IllegalStateException("!START");
// continue parsing
while (_state != STATE_END)
parseNext();
}
/* ------------------------------------------------------------------------------- */
/**
* 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 long parseAvailable() throws IOException
{
long len = parseNext();
long total=len>0?len:0;
// continue parsing
while (!isComplete() && _buffer!=null && _buffer.length()>0)
{
len = parseNext();
if (len>0)
total+=len;
}
return total;
}
/* ------------------------------------------------------------------------------- */
/**
* Parse until next Event.
* @returns number of bytes filled from endpoint or -1 if fill never called.
*/
public long parseNext() throws IOException
{
long total_filled=-1;
if (_state == STATE_END)
return -1;
if (_buffer==null)
{
if (_header == null)
{
_header=_buffers.getBuffer(_headerBufferSize);
}
_buffer=_header;
_tok0=new View.CaseInsensitive(_header);
_tok1=new View.CaseInsensitive(_header);
_tok0.setPutIndex(_tok0.getIndex());
_tok1.setPutIndex(_tok1.getIndex());
}
if (_state == STATE_CONTENT && _contentPosition == _contentLength)
{
_state=STATE_END;
_handler.messageComplete(_contentPosition);
return total_filled;
}
int length=_buffer.length();
// Fill buffer if we can
if (length == 0)
{
int filled=-1;
if (_body!=null && _buffer!=_body)
{
_buffer=_body;
filled=_buffer.length();
}
if (_buffer.markIndex() == 0 && _buffer.putIndex() == _buffer.capacity())
throw new HttpException(HttpStatus.ORDINAL_413_Request_Entity_Too_Large, "FULL");
IOException ioex=null;
if (_endp != null && filled<=0)
{
// Compress buffer if handling _content buffer
// TODO check this is not moving data too much
if (_buffer == _body)
_buffer.compact();
if (_buffer.space() == 0)
throw new HttpException(HttpStatus.ORDINAL_413_Request_Entity_Too_Large, "FULL "+(_buffer==_body?"body":"head"));
try
{
if (total_filled<0)
total_filled=0;
filled=_endp.fill(_buffer);
if (filled>0)
total_filled+=filled;
}
catch(IOException e)
{
Log.debug(e);
ioex=e;
filled=-1;
}
}
if (filled < 0)
{
if ( _state == STATE_EOF_CONTENT)
{
if (_buffer.length()>0)
{
// TODO should we do this here or fall down to main loop?
Buffer chunk=_buffer.get(_buffer.length());
_contentPosition += chunk.length();
_contentView.update(chunk);
_handler.content(chunk); // May recurse here
}
_state=STATE_END;
_handler.messageComplete(_contentPosition);
return total_filled;
}
reset(true);
throw new EofException(ioex);
}
length=_buffer.length();
}
// EventHandler header
byte ch;
byte[] array=_buffer.array();
while (_state0)
{
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);
_state=STATE_SPACE1;
continue;
}
else if (ch < HttpTokens.SPACE && ch>=0)
{
throw new HttpException(HttpServletResponse.SC_BAD_REQUEST);
}
break;
case STATE_SPACE1:
if (ch > HttpTokens.SPACE || ch<0)
{
_buffer.mark();
_state=STATE_FIELD1;
}
else if (ch < HttpTokens.SPACE)
{
throw new HttpException(HttpServletResponse.SC_BAD_REQUEST);
}
break;
case STATE_FIELD1:
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);
_state=STATE_END;
_handler.headerComplete();
_handler.messageComplete(_contentPosition);
return total_filled;
}
break;
case STATE_SPACE2:
if (ch > HttpTokens.SPACE || ch<0)
{
_buffer.mark();
_state=STATE_FIELD2;
}
else if (ch < HttpTokens.SPACE)
{
// HTTP/0.9
_handler.startRequest(HttpMethods.CACHE.lookup(_tok0), _tok1, null);
_state=STATE_END;
_handler.headerComplete();
_handler.messageComplete(_contentPosition);
return total_filled;
}
break;
case STATE_FIELD2:
if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED)
{
byte digit=_tok1.peek(_tok1.getIndex());
if (digit>='1'&&digit<='5')
{
_responseStatus = BufferUtil.toInt(_tok1);
_handler.startResponse(HttpVersions.CACHE.lookup(_tok0), _responseStatus,_buffer.sliceFromMark());
}
else
_handler.startRequest(HttpMethods.CACHE.lookup(_tok0), _tok1,HttpVersions.CACHE.lookup(_buffer.sliceFromMark()));
_eol=ch;
_state=STATE_HEADER;
_tok0.setPutIndex(_tok0.getIndex());
_tok1.setPutIndex(_tok1.getIndex());
_multiLineValue=null;
return total_filled;
}
break;
case STATE_HEADER:
if (ch == HttpTokens.COLON || ch == HttpTokens.SPACE || ch == HttpTokens.TAB)
{
// header value without name - continuation?
_length=-1;
_state=STATE_HEADER_VALUE;
}
else
{
// 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 ? (Buffer) _tok1 : (Buffer) new ByteArrayBuffer(_multiLineValue);
int ho=HttpHeaders.CACHE.getOrdinal(header);
if (ho >= 0)
{
int vo=-1;
switch (ho)
{
case HttpHeaders.CONTENT_LENGTH_ORDINAL:
if (_contentLength != HttpTokens.CHUNKED_CONTENT)
{
_contentLength=BufferUtil.toLong(value);
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();
if (c.endsWith(HttpHeaderValues.CHUNKED))
_contentLength=HttpTokens.CHUNKED_CONTENT;
else if (c.indexOf(HttpHeaderValues.CHUNKED) >= 0)
throw new HttpException(400,null);
}
break;
}
}
_handler.parsedHeader(header, value);
_tok0.setPutIndex(_tok0.getIndex());
_tok1.setPutIndex(_tok1.getIndex());
_multiLineValue=null;
}
// now handle ch
if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED)
{
// End of header
// 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;
// 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;
if(_body==null && _buffers!=null)
_body=_buffers.getBuffer(_contentBufferSize);
_handler.headerComplete(); // May recurse here !
break;
case HttpTokens.CHUNKED_CONTENT:
_state=STATE_CHUNKED_CONTENT;
if (_body==null && _buffers!=null)
_body=_buffers.getBuffer(_contentBufferSize);
_handler.headerComplete(); // May recurse here !
break;
case HttpTokens.NO_CONTENT:
_state=STATE_END;
_handler.headerComplete();
_handler.messageComplete(_contentPosition);
break;
default:
_state=STATE_CONTENT;
if(_forceContentBuffer ||
(_buffers!=null && _body==null && _buffer==_header && _contentLength>=(_header.capacity()-_header.getIndex())))
_body=_buffers.getBuffer(_contentBufferSize);
_handler.headerComplete(); // May recurse here !
break;
}
return total_filled;
}
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:
if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED)
{
if (_length > 0)
_tok0.update(_buffer.markIndex(), _buffer.markIndex() + _length);
_eol=ch;
_state=STATE_HEADER;
}
else if (ch == HttpTokens.COLON)
{
if (_length > 0 && _cached==null)
_tok0.update(_buffer.markIndex(), _buffer.markIndex() + _length);
_length=-1;
_state=STATE_HEADER_VALUE;
}
else if (ch != HttpTokens.SPACE && ch != HttpTokens.TAB)
{
// Drag the mark
_cached=null;
if (_length == -1) _buffer.mark();
_length=_buffer.getIndex() - _buffer.markIndex();
}
break;
case STATE_HEADER_VALUE:
if (ch == HttpTokens.CARRIAGE_RETURN || ch == 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();
_tok1.update(_buffer.markIndex(), _buffer.markIndex() + _length);
_multiLineValue += " " + _tok1.toString();
}
}
_eol=ch;
_state=STATE_HEADER;
}
else if (ch != HttpTokens.SPACE && ch != HttpTokens.TAB)
{
if (_length == -1) _buffer.mark();
_length=_buffer.getIndex() - _buffer.markIndex();
}
break;
}
} // end of HEADER states loop
// ==========================
// Handle _content
length=_buffer.length();
if (_input!=null)
_input._contentView=_contentView;
Buffer chunk;
while (_state > STATE_END && length > 0)
{
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 total_filled;
case STATE_CONTENT:
{
long remaining=_contentLength - _contentPosition;
if (remaining == 0)
{
_state=STATE_END;
_handler.messageComplete(_contentPosition);
return total_filled;
}
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=STATE_END;
_handler.messageComplete(_contentPosition);
}
// TODO adjust the _buffer to keep unconsumed content
return total_filled;
}
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)
{
_state=STATE_END;
_handler.messageComplete(_contentPosition);
return total_filled;
}
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)
{
_state=STATE_END;
_handler.messageComplete(_contentPosition);
return total_filled;
}
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 total_filled;
}
}
length=_buffer.length();
}
return total_filled;
}
/* ------------------------------------------------------------------------------- */
/** fill the buffers from the endpoint
*
*/
public long fill() throws IOException
{
if (_buffer==null)
{
_buffer=_header=getHeaderBuffer();
_tok0=new View.CaseInsensitive(_buffer);
_tok1=new View.CaseInsensitive(_buffer);
}
if (_body!=null && _buffer!=_body)
_buffer=_body;
if (_buffer == _body)
_buffer.compact();
int space=_buffer.space();
// Fill buffer if we can
if (space == 0)
throw new HttpException(HttpStatus.ORDINAL_413_Request_Entity_Too_Large, "FULL "+(_buffer==_body?"body":"head"));
else
{
int filled=-1;
if (_endp != null )
{
try
{
filled=_endp.fill(_buffer);
}
catch(IOException e)
{
Log.debug(e);
reset(true);
throw (e instanceof EofException) ? e:new EofException(e);
}
}
return filled;
}
}
/* ------------------------------------------------------------------------------- */
/** Skip any CRLFs in buffers
*
*/
public void skipCRLF()
{
while (_header!=null && _header.length()>0)
{
byte ch = _header.peek();
if (ch==HttpTokens.CARRIAGE_RETURN || ch==HttpTokens.LINE_FEED)
{
_eol=ch;
_header.skip(1);
}
else
break;
}
while (_body!=null && _body.length()>0)
{
byte ch = _body.peek();
if (ch==HttpTokens.CARRIAGE_RETURN || ch==HttpTokens.LINE_FEED)
{
_eol=ch;
_body.skip(1);
}
else
break;
}
}
/* ------------------------------------------------------------------------------- */
public void reset(boolean returnBuffers)
{
synchronized (this)
{
if (_input!=null && _contentView.length()>0)
_input._contentView=_contentView.duplicate(Buffer.READWRITE);
_state=STATE_START;
_contentLength=HttpTokens.UNKNOWN_CONTENT;
_contentPosition=0;
_length=0;
_responseStatus=0;
if (_buffer!=null && _buffer.length()>0 && _eol == HttpTokens.CARRIAGE_RETURN && _buffer.peek() == HttpTokens.LINE_FEED)
{
_buffer.skip(1);
_eol=HttpTokens.LINE_FEED;
}
if (_body!=null)
{
if (_body.hasContent())
{
_header.setMarkIndex(-1);
_header.compact();
// TODO if pipelined requests received after big input - maybe this is not good?.
_body.skip(_header.put(_body));
}
if (_body.length()==0)
{
if (_buffers!=null && returnBuffers)
_buffers.returnBuffer(_body);
_body=null;
}
else
{
_body.setMarkIndex(-1);
_body.compact();
}
}
if (_header!=null)
{
_header.setMarkIndex(-1);
if (!_header.hasContent() && _buffers!=null && returnBuffers)
{
_buffers.returnBuffer(_header);
_header=null;
_buffer=null;
}
else
{
_header.compact();
_tok0.update(_header);
_tok0.update(0,0);
_tok1.update(_header);
_tok1.update(0,0);
}
}
_buffer=_header;
}
}
/* ------------------------------------------------------------------------------- */
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();
}
/* ------------------------------------------------------------------------------- */
public String toString()
{
return "state=" + _state + " length=" + _length + " len=" + _contentLength;
}
/* ------------------------------------------------------------ */
public Buffer getHeaderBuffer()
{
if (_header == null)
{
_header=_buffers.getBuffer(_headerBufferSize);
}
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 static abstract class EventHandler
{
public abstract void content(Buffer ref) throws IOException;
public void headerComplete() throws IOException
{
}
public void messageComplete(long contextLength) 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 static class Input extends ServletInputStream
{
protected HttpParser _parser;
protected EndPoint _endp;
protected long _maxIdleTime;
protected Buffer _contentView;
/* ------------------------------------------------------------ */
public Input(HttpParser parser, long maxIdleTime)
{
_parser=parser;
_endp=parser._endp;
_maxIdleTime=maxIdleTime;
_contentView=_parser._contentView;
_parser._input=this;
}
/* ------------------------------------------------------------ */
/*
* @see java.io.InputStream#read()
*/
public int read() throws IOException
{
int c=-1;
if (blockForContent())
c= 0xff & _contentView.get();
return c;
}
/* ------------------------------------------------------------ */
/*
* @see java.io.InputStream#read(byte[], int, int)
*/
public int read(byte[] b, int off, int len) throws IOException
{
int l=-1;
if (blockForContent())
l= _contentView.get(b, off, len);
return l;
}
/* ------------------------------------------------------------ */
private boolean blockForContent() throws IOException
{
if (_contentView.length()>0)
return true;
if (_parser.getState() <= HttpParser.STATE_END)
return false;
// Handle simple end points.
if (_endp==null)
_parser.parseNext();
// Handle blocking end points
else if (_endp.isBlocking())
{
try
{
_parser.parseNext();
// parse until some progress is made (or IOException thrown for timeout)
while(_contentView.length() == 0 && !_parser.isState(HttpParser.STATE_END))
{
// Try to get more _parser._content
_parser.parseNext();
}
}
catch(IOException e)
{
_endp.close();
throw e;
}
}
else // Handle non-blocking end point
{
_parser.parseNext();
// parse until some progress is made (or IOException thrown for timeout)
while(_contentView.length() == 0 && !_parser.isState(HttpParser.STATE_END))
{
if (!_endp.blockReadable(_maxIdleTime))
{
_endp.close();
throw new EofException("timeout");
}
// Try to get more _parser._content
_parser.parseNext();
}
}
return _contentView.length()>0;
}
/* ------------------------------------------------------------ */
/* (non-Javadoc)
* @see java.io.InputStream#available()
*/
public int available() throws IOException
{
if (_contentView!=null && _contentView.length()>0)
return _contentView.length();
if (!_endp.isBlocking())
_parser.parseNext();
return _contentView==null?0:_contentView.length();
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy