Issue #612 Http Trailers

Added support for parsing Http Trailers.
Currently ignored in HttpParser.HttpHandler
This commit is contained in:
Greg Wilkins 2017-01-11 14:44:43 +11:00
parent 7e3d3604ac
commit 5011f071ff
1 changed files with 88 additions and 60 deletions

View File

@ -108,6 +108,15 @@ public class HttpParser
*/
public final static Trie<HttpField> CACHE = new ArrayTrie<>(2048);
// States
public enum FieldState
{
FIELD,
IN_NAME,
VALUE,
IN_VALUE,
}
// States
public enum State
{
@ -122,17 +131,13 @@ public class HttpParser
REASON,
PROXY,
HEADER,
HEADER_IN_NAME,
HEADER_VALUE,
HEADER_IN_VALUE,
CONTENT,
EOF_CONTENT,
CHUNKED_CONTENT,
CHUNK_SIZE,
CHUNK_PARAMS,
CHUNK,
CHUNK_TRAILER,
CHUNK_END,
TRAILER,
END,
CLOSE, // The associated stream/endpoint should be closed
CLOSED // The associated stream/endpoint is at EOF
@ -159,6 +164,7 @@ public class HttpParser
/* ------------------------------------------------------------------------------- */
private volatile State _state=State.START;
private volatile FieldState _fieldState=FieldState.FIELD;
private volatile boolean _eof;
private HttpMethod _method;
private String _methodString;
@ -953,6 +959,18 @@ public class HttpParser
_field=null;
}
private void parsedTrailer()
{
// handler last header if any. Delayed to here just in case there was a continuation line (above)
if (_headerString!=null || _valueString!=null)
_handler.parsedTrailer(_field!=null?_field:new HttpField(_header,_headerString,_valueString));
_headerString=_valueString=null;
_header=null;
_value=null;
_field=null;
}
private long convertContentLength(String valueString)
{
try
@ -970,12 +988,12 @@ public class HttpParser
/*
* Parse the message headers and return true if the handler has signaled for a return
*/
protected boolean parseHeaders(ByteBuffer buffer)
protected boolean parseFields(ByteBuffer buffer)
{
boolean handle=false;
// Process headers
while (_state.ordinal()<State.CONTENT.ordinal() && buffer.hasRemaining() && !handle)
while ((_state==State.HEADER || _state==State.TRAILER) && buffer.hasRemaining() && !handle)
{
// process each character
byte ch=next(buffer);
@ -988,9 +1006,9 @@ public class HttpParser
throw new BadMessageException(HttpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE_431);
}
switch (_state)
switch (_fieldState)
{
case HEADER:
case FIELD:
switch(ch)
{
case HttpTokens.COLON:
@ -1013,19 +1031,27 @@ public class HttpParser
_length++;
_valueString=null;
}
setState(State.HEADER_VALUE);
setState(FieldState.VALUE);
break;
}
case HttpTokens.LINE_FEED:
{
// process previous header
parsedHeader();
if (_state==State.HEADER)
parsedHeader();
else
parsedTrailer();
_contentPosition=0;
// End of headers!
// End of headers or trailers?
if (_state==State.TRAILER)
{
setState(State.END);
return _handler.messageComplete();
}
// Was there a required host header?
if (!_host && _version==HttpVersion.HTTP_1_1 && _requestHandler!=null)
{
@ -1125,7 +1151,7 @@ public class HttpParser
if (v==null)
{
// Header only
setState(State.HEADER_VALUE);
setState(FieldState.VALUE);
_string.setLength(0);
_length=0;
buffer.position(buffer.position()+n.length()+1);
@ -1141,7 +1167,7 @@ public class HttpParser
{
_field=field;
_valueString=v;
setState(State.HEADER_IN_VALUE);
setState(FieldState.IN_VALUE);
if (b==HttpTokens.CARRIAGE_RETURN)
{
@ -1154,7 +1180,7 @@ public class HttpParser
}
else
{
setState(State.HEADER_IN_VALUE);
setState(FieldState.IN_VALUE);
setString(v);
buffer.position(pos);
break;
@ -1164,7 +1190,7 @@ public class HttpParser
}
// New header
setState(State.HEADER_IN_NAME);
setState(FieldState.IN_NAME);
_string.setLength(0);
_string.append((char)ch);
_length=1;
@ -1173,7 +1199,7 @@ public class HttpParser
}
break;
case HEADER_IN_NAME:
case IN_NAME:
if (ch==HttpTokens.COLON)
{
if (_headerString==null)
@ -1183,7 +1209,7 @@ public class HttpParser
}
_length=-1;
setState(State.HEADER_VALUE);
setState(FieldState.VALUE);
break;
}
@ -1214,18 +1240,18 @@ public class HttpParser
_valueString="";
_length=-1;
setState(State.HEADER);
setState(FieldState.FIELD);
break;
}
throw new IllegalCharacterException(_state,ch,buffer);
case HEADER_VALUE:
case VALUE:
if (ch>HttpTokens.SPACE || ch<0)
{
_string.append((char)(0xff&ch));
_length=_string.length();
setState(State.HEADER_IN_VALUE);
setState(FieldState.IN_VALUE);
break;
}
@ -1239,12 +1265,12 @@ public class HttpParser
_valueString="";
_length=-1;
setState(State.HEADER);
setState(FieldState.FIELD);
break;
}
throw new IllegalCharacterException(_state,ch,buffer);
case HEADER_IN_VALUE:
case IN_VALUE:
if (ch>=HttpTokens.SPACE || ch<0 || ch==HttpTokens.TAB)
{
if (_valueString!=null)
@ -1267,7 +1293,7 @@ public class HttpParser
_valueString=takeString();
_length=-1;
}
setState(State.HEADER);
setState(FieldState.FIELD);
break;
}
@ -1314,14 +1340,14 @@ public class HttpParser
}
// parse headers
if (_state.ordinal()>= State.HEADER.ordinal() && _state.ordinal()<State.CONTENT.ordinal())
if (_state== State.HEADER)
{
if (parseHeaders(buffer))
if (parseFields(buffer))
return true;
}
// parse content
if (_state.ordinal()>= State.CONTENT.ordinal() && _state.ordinal()<State.END.ordinal())
if (_state.ordinal()>= State.CONTENT.ordinal() && _state.ordinal()<State.TRAILER.ordinal())
{
// Handle HEAD response
if (_responseStatus>0 && _headResponse)
@ -1336,6 +1362,13 @@ public class HttpParser
}
}
// parse headers
if (_state==State.TRAILER)
{
if (parseFields(buffer))
return true;
}
// handle end states
if (_state==State.END)
{
@ -1382,16 +1415,22 @@ public class HttpParser
break;
case EOF_CONTENT:
case CHUNK_END:
case TRAILER:
if (_fieldState==FieldState.FIELD)
{
// Be forgiving of missing last CRLF
setState(State.CLOSED);
return _handler.messageComplete();
}
setState(State.CLOSED);
return _handler.messageComplete();
_handler.earlyEOF();
break;
case CONTENT:
case CHUNKED_CONTENT:
case CHUNK_SIZE:
case CHUNK_PARAMS:
case CHUNK:
case CHUNK_TRAILER:
setState(State.CLOSED);
_handler.earlyEOF();
break;
@ -1475,7 +1514,7 @@ public class HttpParser
// Handle _content
byte ch;
while (_state.ordinal() < State.END.ordinal() && remaining>0)
while (_state.ordinal() < State.TRAILER.ordinal() && remaining>0)
{
switch (_state)
{
@ -1543,7 +1582,7 @@ public class HttpParser
if (ch == HttpTokens.LINE_FEED)
{
if (_chunkLength == 0)
setState(State.CHUNK_END);
setState(State.TRAILER);
else
setState(State.CHUNK);
}
@ -1560,7 +1599,7 @@ public class HttpParser
if (ch == HttpTokens.LINE_FEED)
{
if (_chunkLength == 0)
setState(State.CHUNK_END);
setState(State.TRAILER);
else
setState(State.CHUNK);
}
@ -1591,31 +1630,6 @@ public class HttpParser
break;
}
case CHUNK_END:
{
ch=next(buffer);
if (ch==0)
break;
if (ch == HttpTokens.LINE_FEED)
{
setState(State.END);
return _handler.messageComplete();
}
setState(State.CHUNK_TRAILER);
break;
}
case CHUNK_TRAILER:
{
// TODO handle chunk trailer values
ch=next(buffer);
if (ch==0)
break;
if (ch == HttpTokens.LINE_FEED)
setState(State.CHUNK_END);
break;
}
case CLOSED:
{
BufferUtil.clear(buffer);
@ -1687,6 +1701,14 @@ public class HttpParser
_state=state;
}
/* ------------------------------------------------------------------------------- */
protected void setState(FieldState state)
{
if (DEBUG)
LOG.debug("{}:{} --> {}",_state,_field,state);
_fieldState=state;
}
/* ------------------------------------------------------------------------------- */
public Trie<HttpField> getFieldCache()
{
@ -1744,6 +1766,12 @@ public class HttpParser
* @param field The field parsed
*/
public void parsedHeader(HttpField field);
/**
* This is the method called by parser when a HTTP Trailer name and value is found
* @param field The field parsed
*/
public default void parsedTrailer(HttpField field) {}
/* ------------------------------------------------------------ */
/** Called to signal that an EOF was received unexpectedly