Merged branch 'jetty-9.4.x' into 'master'.
This commit is contained in:
commit
f5d215bb1d
|
@ -271,6 +271,12 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
|
|||
return !proceed || async;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contentComplete()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean messageComplete()
|
||||
{
|
||||
|
|
|
@ -284,6 +284,12 @@ public class ResponseContentParser extends StreamContentParser
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contentComplete()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean messageComplete()
|
||||
{
|
||||
|
|
|
@ -175,6 +175,7 @@ public class ServerFCGIConnection extends AbstractConnection
|
|||
LOG.debug("Request {} end on {}", request, channel);
|
||||
if (channel != null)
|
||||
{
|
||||
channel.onContentComplete();
|
||||
channel.onRequestComplete();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
@ -160,6 +165,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;
|
||||
|
@ -491,7 +497,8 @@ public class HttpParser
|
|||
_cr=true;
|
||||
if (buffer.hasRemaining())
|
||||
{
|
||||
if(_maxHeaderBytes>0 && _state.ordinal()<State.END.ordinal())
|
||||
// Don't count the CRs and LFs of the chunked encoding.
|
||||
if (_maxHeaderBytes>0 && (_state == State.HEADER || _state == State.TRAILER))
|
||||
_headerBytes++;
|
||||
return next(buffer);
|
||||
}
|
||||
|
@ -503,7 +510,6 @@ public class HttpParser
|
|||
case LEGAL:
|
||||
if (_cr)
|
||||
throw new BadMessageException("Bad EOL");
|
||||
|
||||
}
|
||||
|
||||
return ch;
|
||||
|
@ -582,6 +588,24 @@ public class HttpParser
|
|||
_length=-1;
|
||||
return s;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
private boolean handleHeaderContentMessage()
|
||||
{
|
||||
boolean handle_header = _handler.headerComplete();
|
||||
_headerComplete = true;
|
||||
boolean handle_content = _handler.contentComplete();
|
||||
boolean handle_message = _handler.messageComplete();
|
||||
return handle_header || handle_content || handle_message;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
private boolean handleContentMessage()
|
||||
{
|
||||
boolean handle_content = _handler.contentComplete();
|
||||
boolean handle_message = _handler.messageComplete();
|
||||
return handle_content || handle_message;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
/* Parse a request or response line
|
||||
|
@ -730,10 +754,7 @@ public class HttpParser
|
|||
handle=_requestHandler.startRequest(_methodString,_uri.toString(), HttpVersion.HTTP_0_9);
|
||||
setState(State.END);
|
||||
BufferUtil.clear(buffer);
|
||||
handle=_handler.headerComplete()||handle;
|
||||
_headerComplete=true;
|
||||
handle=_handler.messageComplete()||handle;
|
||||
return handle;
|
||||
handle= handleHeaderContentMessage() || handle;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -801,10 +822,7 @@ public class HttpParser
|
|||
handle=_requestHandler.startRequest(_methodString,_uri.toString(), HttpVersion.HTTP_0_9);
|
||||
setState(State.END);
|
||||
BufferUtil.clear(buffer);
|
||||
handle=_handler.headerComplete()||handle;
|
||||
_headerComplete=true;
|
||||
handle=_handler.messageComplete()||handle;
|
||||
return handle;
|
||||
handle= handleHeaderContentMessage() || handle;
|
||||
}
|
||||
}
|
||||
else if (ch<0)
|
||||
|
@ -861,7 +879,6 @@ public class HttpParser
|
|||
|
||||
default:
|
||||
throw new IllegalStateException(_state.toString());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -956,6 +973,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
|
||||
|
@ -973,12 +1002,10 @@ 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())
|
||||
{
|
||||
// process each character
|
||||
byte ch=next(buffer);
|
||||
|
@ -987,13 +1014,16 @@ public class HttpParser
|
|||
|
||||
if (_maxHeaderBytes>0 && ++_headerBytes>_maxHeaderBytes)
|
||||
{
|
||||
LOG.warn("Header is too large >"+_maxHeaderBytes);
|
||||
throw new BadMessageException(HttpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE_431);
|
||||
boolean header = _state == State.HEADER;
|
||||
LOG.warn("{} is too large {}>{}", header ? "Header" : "Trailer", _headerBytes, _maxHeaderBytes);
|
||||
throw new BadMessageException(header ?
|
||||
HttpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE_431 :
|
||||
HttpStatus.PAYLOAD_TOO_LARGE_413);
|
||||
}
|
||||
|
||||
switch (_state)
|
||||
switch (_fieldState)
|
||||
{
|
||||
case HEADER:
|
||||
case FIELD:
|
||||
switch(ch)
|
||||
{
|
||||
case HttpTokens.COLON:
|
||||
|
@ -1016,19 +1046,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)
|
||||
{
|
||||
|
@ -1058,29 +1096,31 @@ public class HttpParser
|
|||
switch (_endOfContent)
|
||||
{
|
||||
case EOF_CONTENT:
|
||||
{
|
||||
setState(State.EOF_CONTENT);
|
||||
handle=_handler.headerComplete()||handle;
|
||||
boolean handle=_handler.headerComplete();
|
||||
_headerComplete=true;
|
||||
return handle;
|
||||
|
||||
}
|
||||
case CHUNKED_CONTENT:
|
||||
{
|
||||
setState(State.CHUNKED_CONTENT);
|
||||
handle=_handler.headerComplete()||handle;
|
||||
boolean handle=_handler.headerComplete();
|
||||
_headerComplete=true;
|
||||
return handle;
|
||||
|
||||
}
|
||||
case NO_CONTENT:
|
||||
{
|
||||
setState(State.END);
|
||||
handle=_handler.headerComplete()||handle;
|
||||
_headerComplete=true;
|
||||
handle=_handler.messageComplete()||handle;
|
||||
return handle;
|
||||
|
||||
return handleHeaderContentMessage();
|
||||
}
|
||||
default:
|
||||
{
|
||||
setState(State.CONTENT);
|
||||
handle=_handler.headerComplete()||handle;
|
||||
boolean handle=_handler.headerComplete();
|
||||
_headerComplete=true;
|
||||
return handle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1091,7 +1131,10 @@ public class HttpParser
|
|||
throw new BadMessageException();
|
||||
|
||||
// process previous header
|
||||
parsedHeader();
|
||||
if (_state==State.HEADER)
|
||||
parsedHeader();
|
||||
else
|
||||
parsedTrailer();
|
||||
|
||||
// handle new header
|
||||
if (buffer.hasRemaining())
|
||||
|
@ -1132,7 +1175,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);
|
||||
|
@ -1148,7 +1191,7 @@ public class HttpParser
|
|||
{
|
||||
_field=field;
|
||||
_valueString=v;
|
||||
setState(State.HEADER_IN_VALUE);
|
||||
setState(FieldState.IN_VALUE);
|
||||
|
||||
if (b==HttpTokens.CARRIAGE_RETURN)
|
||||
{
|
||||
|
@ -1161,7 +1204,7 @@ public class HttpParser
|
|||
}
|
||||
else
|
||||
{
|
||||
setState(State.HEADER_IN_VALUE);
|
||||
setState(FieldState.IN_VALUE);
|
||||
setString(v);
|
||||
buffer.position(pos);
|
||||
break;
|
||||
|
@ -1171,7 +1214,7 @@ public class HttpParser
|
|||
}
|
||||
|
||||
// New header
|
||||
setState(State.HEADER_IN_NAME);
|
||||
setState(FieldState.IN_NAME);
|
||||
_string.setLength(0);
|
||||
_string.append((char)ch);
|
||||
_length=1;
|
||||
|
@ -1180,7 +1223,7 @@ public class HttpParser
|
|||
}
|
||||
break;
|
||||
|
||||
case HEADER_IN_NAME:
|
||||
case IN_NAME:
|
||||
if (ch==HttpTokens.COLON)
|
||||
{
|
||||
if (_headerString==null)
|
||||
|
@ -1190,7 +1233,7 @@ public class HttpParser
|
|||
}
|
||||
_length=-1;
|
||||
|
||||
setState(State.HEADER_VALUE);
|
||||
setState(FieldState.VALUE);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -1221,18 +1264,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;
|
||||
}
|
||||
|
||||
|
@ -1246,12 +1289,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)
|
||||
|
@ -1274,7 +1317,7 @@ public class HttpParser
|
|||
_valueString=takeString();
|
||||
_length=-1;
|
||||
}
|
||||
setState(State.HEADER);
|
||||
setState(FieldState.FIELD);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -1286,7 +1329,7 @@ public class HttpParser
|
|||
}
|
||||
}
|
||||
|
||||
return handle;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
|
@ -1321,20 +1364,20 @@ 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)
|
||||
{
|
||||
setState(State.END);
|
||||
return _handler.messageComplete();
|
||||
return handleContentMessage();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1343,6 +1386,13 @@ public class HttpParser
|
|||
}
|
||||
}
|
||||
|
||||
// parse headers
|
||||
if (_state==State.TRAILER)
|
||||
{
|
||||
if (parseFields(buffer))
|
||||
return true;
|
||||
}
|
||||
|
||||
// handle end states
|
||||
if (_state==State.END)
|
||||
{
|
||||
|
@ -1350,22 +1400,7 @@ public class HttpParser
|
|||
while (buffer.remaining()>0 && buffer.get(buffer.position())<=HttpTokens.SPACE)
|
||||
buffer.get();
|
||||
}
|
||||
else if (_state==State.CLOSE)
|
||||
{
|
||||
// Seeking EOF
|
||||
if (BufferUtil.hasContent(buffer))
|
||||
{
|
||||
// Just ignore data when closed
|
||||
_headerBytes+=buffer.remaining();
|
||||
BufferUtil.clear(buffer);
|
||||
if (_maxHeaderBytes>0 && _headerBytes>_maxHeaderBytes)
|
||||
{
|
||||
// Don't want to waste time reading data of a closed request
|
||||
throw new IllegalStateException("too much data seeking EOF");
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (_state==State.CLOSED)
|
||||
else if (isClose() || isClosed())
|
||||
{
|
||||
BufferUtil.clear(buffer);
|
||||
}
|
||||
|
@ -1389,16 +1424,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 handleContentMessage();
|
||||
}
|
||||
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;
|
||||
|
@ -1407,55 +1448,33 @@ public class HttpParser
|
|||
if (DEBUG)
|
||||
LOG.debug("{} EOF in {}",this,_state);
|
||||
setState(State.CLOSED);
|
||||
_handler.badMessage(400,null);
|
||||
_handler.badMessage(HttpStatus.BAD_REQUEST_400,null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(BadMessageException e)
|
||||
catch(BadMessageException x)
|
||||
{
|
||||
BufferUtil.clear(buffer);
|
||||
|
||||
Throwable cause = e.getCause();
|
||||
boolean stack = LOG.isDebugEnabled() ||
|
||||
(!(cause instanceof NumberFormatException ) && (cause instanceof RuntimeException || cause instanceof Error));
|
||||
|
||||
if (stack)
|
||||
LOG.warn("bad HTTP parsed: "+e._code+(e.getReason()!=null?" "+e.getReason():"")+" for "+_handler,e);
|
||||
else
|
||||
LOG.warn("bad HTTP parsed: "+e._code+(e.getReason()!=null?" "+e.getReason():"")+" for "+_handler);
|
||||
setState(State.CLOSE);
|
||||
_handler.badMessage(e.getCode(), e.getReason());
|
||||
badMessage(x);
|
||||
}
|
||||
catch(NumberFormatException|IllegalStateException e)
|
||||
catch(Throwable x)
|
||||
{
|
||||
BufferUtil.clear(buffer);
|
||||
LOG.warn("parse exception: {} in {} for {}",e.toString(),_state,_handler);
|
||||
if (DEBUG)
|
||||
LOG.debug(e);
|
||||
badMessage();
|
||||
|
||||
}
|
||||
catch(Exception|Error e)
|
||||
{
|
||||
BufferUtil.clear(buffer);
|
||||
LOG.warn("parse exception: "+e.toString()+" for "+_handler,e);
|
||||
badMessage();
|
||||
badMessage(new BadMessageException(HttpStatus.BAD_REQUEST_400, _requestHandler != null ? "Bad Request" : "Bad Response", x));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void badMessage()
|
||||
protected void badMessage(BadMessageException x)
|
||||
{
|
||||
if (DEBUG)
|
||||
LOG.debug("Parse exception: " + this + " for " + _handler, x);
|
||||
setState(State.CLOSE);
|
||||
if (_headerComplete)
|
||||
{
|
||||
_handler.earlyEOF();
|
||||
}
|
||||
else if (_state!=State.CLOSED)
|
||||
{
|
||||
setState(State.CLOSE);
|
||||
_handler.badMessage(400,_requestHandler!=null?"Bad Request":"Bad Response");
|
||||
}
|
||||
else
|
||||
_handler.badMessage(x._code, x._reason);
|
||||
}
|
||||
|
||||
protected boolean parseContent(ByteBuffer buffer)
|
||||
|
@ -1467,13 +1486,13 @@ public class HttpParser
|
|||
if (content == 0)
|
||||
{
|
||||
setState(State.END);
|
||||
return _handler.messageComplete();
|
||||
return handleContentMessage();
|
||||
}
|
||||
}
|
||||
|
||||
// Handle _content
|
||||
byte ch;
|
||||
while (_state.ordinal() < State.END.ordinal() && remaining>0)
|
||||
while (_state.ordinal() < State.TRAILER.ordinal() && remaining>0)
|
||||
{
|
||||
switch (_state)
|
||||
{
|
||||
|
@ -1491,7 +1510,7 @@ public class HttpParser
|
|||
if (content == 0)
|
||||
{
|
||||
setState(State.END);
|
||||
return _handler.messageComplete();
|
||||
return handleContentMessage();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1514,7 +1533,7 @@ public class HttpParser
|
|||
if(_contentPosition == _contentLength)
|
||||
{
|
||||
setState(State.END);
|
||||
return _handler.messageComplete();
|
||||
return handleContentMessage();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -1541,7 +1560,11 @@ public class HttpParser
|
|||
if (ch == HttpTokens.LINE_FEED)
|
||||
{
|
||||
if (_chunkLength == 0)
|
||||
setState(State.CHUNK_END);
|
||||
{
|
||||
setState(State.TRAILER);
|
||||
if (_handler.contentComplete())
|
||||
return true;
|
||||
}
|
||||
else
|
||||
setState(State.CHUNK);
|
||||
}
|
||||
|
@ -1558,7 +1581,11 @@ public class HttpParser
|
|||
if (ch == HttpTokens.LINE_FEED)
|
||||
{
|
||||
if (_chunkLength == 0)
|
||||
setState(State.CHUNK_END);
|
||||
{
|
||||
setState(State.TRAILER);
|
||||
if (_handler.contentComplete())
|
||||
return true;
|
||||
}
|
||||
else
|
||||
setState(State.CHUNK);
|
||||
}
|
||||
|
@ -1589,31 +1616,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);
|
||||
|
@ -1686,6 +1688,14 @@ public class HttpParser
|
|||
_state=state;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
protected void setState(FieldState state)
|
||||
{
|
||||
if (DEBUG)
|
||||
LOG.debug("{}:{} --> {}",_state,_field,state);
|
||||
_fieldState=state;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
public Trie<HttpField> getFieldCache()
|
||||
{
|
||||
|
@ -1736,6 +1746,8 @@ public class HttpParser
|
|||
|
||||
public boolean headerComplete();
|
||||
|
||||
public boolean contentComplete();
|
||||
|
||||
public boolean messageComplete();
|
||||
|
||||
/**
|
||||
|
@ -1743,6 +1755,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
|
||||
|
|
|
@ -231,6 +231,12 @@ public class HttpGeneratorServerHTTPTest
|
|||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contentComplete()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean messageComplete()
|
||||
{
|
||||
|
|
|
@ -18,10 +18,6 @@
|
|||
|
||||
package org.eclipse.jetty.http;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
|
@ -124,7 +120,7 @@ public class HttpParserTest
|
|||
Assert.assertEquals("/999", _uriOrStatus);
|
||||
Assert.assertEquals("HTTP/0.9", _versionOrReason);
|
||||
Assert.assertEquals(-1, _headers);
|
||||
assertThat(_complianceViolation,containsString("0.9"));
|
||||
Assert.assertThat(_complianceViolation, Matchers.containsString("0.9"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -153,7 +149,7 @@ public class HttpParserTest
|
|||
Assert.assertEquals("/222", _uriOrStatus);
|
||||
Assert.assertEquals("HTTP/0.9", _versionOrReason);
|
||||
Assert.assertEquals(-1, _headers);
|
||||
assertThat(_complianceViolation,containsString("0.9"));
|
||||
Assert.assertThat(_complianceViolation, Matchers.containsString("0.9"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -269,7 +265,7 @@ public class HttpParserTest
|
|||
Assert.assertEquals("Name", _hdr[1]);
|
||||
Assert.assertEquals("value extra", _val[1]);
|
||||
Assert.assertEquals(1, _headers);
|
||||
assertThat(_complianceViolation,containsString("folding"));
|
||||
Assert.assertThat(_complianceViolation, Matchers.containsString("folding"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -379,7 +375,7 @@ public class HttpParserTest
|
|||
Assert.assertEquals("Other", _hdr[2]);
|
||||
Assert.assertEquals("value", _val[2]);
|
||||
Assert.assertEquals(2, _headers);
|
||||
assertThat(_complianceViolation,containsString("name only"));
|
||||
Assert.assertThat(_complianceViolation, Matchers.containsString("name only"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -612,7 +608,7 @@ public class HttpParserTest
|
|||
parser.parseNext(buffer);
|
||||
}
|
||||
|
||||
assertThat(BufferUtil.toUTF8String(buffer), is("FOOGRADE"));
|
||||
Assert.assertThat(BufferUtil.toUTF8String(buffer), Matchers.is("FOOGRADE"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -716,7 +712,7 @@ public class HttpParserTest
|
|||
Assert.assertEquals("cOnNeCtIoN", _hdr[1]);
|
||||
Assert.assertEquals("ClOsE", _val[1]);
|
||||
Assert.assertEquals(1, _headers);
|
||||
assertThat(_complianceViolation,containsString("case sensitive"));
|
||||
Assert.assertThat(_complianceViolation, Matchers.containsString("case sensitive"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -828,6 +824,48 @@ public class HttpParserTest
|
|||
Assert.assertEquals("Header1", _hdr[0]);
|
||||
Assert.assertEquals("value1", _val[0]);
|
||||
Assert.assertEquals("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", _content);
|
||||
Assert.assertEquals(1, _trailers.size());
|
||||
HttpField trailer1 = _trailers.get(0);
|
||||
Assert.assertEquals("Trailer", trailer1.getName());
|
||||
Assert.assertEquals("value", trailer1.getValue());
|
||||
|
||||
Assert.assertTrue(_headerCompleted);
|
||||
Assert.assertTrue(_messageCompleted);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChunkParseTrailers() throws Exception
|
||||
{
|
||||
ByteBuffer buffer = BufferUtil.toBuffer(
|
||||
"GET /chunk HTTP/1.0\r\n"
|
||||
+ "Transfer-Encoding: chunked\r\n"
|
||||
+ "\r\n"
|
||||
+ "a;\r\n"
|
||||
+ "0123456789\r\n"
|
||||
+ "1a\r\n"
|
||||
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ\r\n"
|
||||
+ "0\r\n"
|
||||
+ "Trailer: value\r\n"
|
||||
+ "Foo: bar\r\n"
|
||||
+ "\r\n");
|
||||
HttpParser.RequestHandler handler = new Handler();
|
||||
HttpParser parser = new HttpParser(handler);
|
||||
parseAll(parser, buffer);
|
||||
|
||||
Assert.assertEquals("GET", _methodOrVersion);
|
||||
Assert.assertEquals("/chunk", _uriOrStatus);
|
||||
Assert.assertEquals("HTTP/1.0", _versionOrReason);
|
||||
Assert.assertEquals(0, _headers);
|
||||
Assert.assertEquals("Transfer-Encoding", _hdr[0]);
|
||||
Assert.assertEquals("chunked", _val[0]);
|
||||
Assert.assertEquals("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", _content);
|
||||
Assert.assertEquals(2, _trailers.size());
|
||||
HttpField trailer1 = _trailers.get(0);
|
||||
Assert.assertEquals("Trailer", trailer1.getName());
|
||||
Assert.assertEquals("value", trailer1.getValue());
|
||||
HttpField trailer2 = _trailers.get(1);
|
||||
Assert.assertEquals("Foo", trailer2.getName());
|
||||
Assert.assertEquals("bar", trailer2.getValue());
|
||||
|
||||
Assert.assertTrue(_headerCompleted);
|
||||
Assert.assertTrue(_messageCompleted);
|
||||
|
@ -1938,6 +1976,7 @@ public class HttpParserTest
|
|||
private String _uriOrStatus;
|
||||
private String _versionOrReason;
|
||||
private List<HttpField> _fields = new ArrayList<>();
|
||||
private List<HttpField> _trailers = new ArrayList<>();
|
||||
private String[] _hdr;
|
||||
private String[] _val;
|
||||
private int _headers;
|
||||
|
@ -1948,8 +1987,6 @@ public class HttpParserTest
|
|||
|
||||
private class Handler implements HttpParser.RequestHandler, HttpParser.ResponseHandler, HttpParser.ComplianceHandler
|
||||
{
|
||||
private HttpFields fields;
|
||||
|
||||
@Override
|
||||
public boolean content(ByteBuffer ref)
|
||||
{
|
||||
|
@ -1965,14 +2002,13 @@ public class HttpParserTest
|
|||
public boolean startRequest(String method, String uri, HttpVersion version)
|
||||
{
|
||||
_fields.clear();
|
||||
_trailers.clear();
|
||||
_headers = -1;
|
||||
_hdr = new String[10];
|
||||
_val = new String[10];
|
||||
_methodOrVersion = method;
|
||||
_uriOrStatus = uri;
|
||||
_versionOrReason = version == null ? null : version.asString();
|
||||
|
||||
fields = new HttpFields();
|
||||
_messageCompleted = false;
|
||||
_headerCompleted = false;
|
||||
_early = false;
|
||||
|
@ -1998,17 +2034,22 @@ public class HttpParserTest
|
|||
public boolean headerComplete()
|
||||
{
|
||||
_content = null;
|
||||
String s0 = fields.toString();
|
||||
String s1 = fields.toString();
|
||||
if (!s0.equals(s1))
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
_headerCompleted = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parsedTrailer(HttpField field)
|
||||
{
|
||||
_trailers.add(field);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contentComplete()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean messageComplete()
|
||||
{
|
||||
|
@ -2026,14 +2067,12 @@ public class HttpParserTest
|
|||
public boolean startResponse(HttpVersion version, int status, String reason)
|
||||
{
|
||||
_fields.clear();
|
||||
_trailers.clear();
|
||||
_methodOrVersion = version.asString();
|
||||
_uriOrStatus = Integer.toString(status);
|
||||
_versionOrReason = reason;
|
||||
|
||||
fields = new HttpFields();
|
||||
_hdr = new String[9];
|
||||
_val = new String[9];
|
||||
|
||||
_messageCompleted = false;
|
||||
_headerCompleted = false;
|
||||
return false;
|
||||
|
|
|
@ -329,6 +329,12 @@ public class HttpTester
|
|||
add(field.getName(),field.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contentComplete()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean messageComplete()
|
||||
{
|
||||
|
|
|
@ -18,9 +18,17 @@
|
|||
|
||||
package org.eclipse.jetty.http2.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletInputStream;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
|
@ -28,7 +36,9 @@ import org.eclipse.jetty.http.MetaData;
|
|||
import org.eclipse.jetty.http2.api.Session;
|
||||
import org.eclipse.jetty.http2.api.Stream;
|
||||
import org.eclipse.jetty.http2.api.server.ServerSessionListener;
|
||||
import org.eclipse.jetty.http2.frames.DataFrame;
|
||||
import org.eclipse.jetty.http2.frames.HeadersFrame;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.FuturePromise;
|
||||
import org.eclipse.jetty.util.Promise;
|
||||
|
@ -83,6 +93,77 @@ public class TrailersTest extends AbstractTest
|
|||
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServletRequestTrailers() throws Exception
|
||||
{
|
||||
CountDownLatch trailerLatch = new CountDownLatch(1);
|
||||
start(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
Request jettyRequest = (Request)request;
|
||||
// No trailers yet.
|
||||
Assert.assertNull(jettyRequest.getTrailers());
|
||||
|
||||
trailerLatch.countDown();
|
||||
|
||||
// Read the content.
|
||||
ServletInputStream input = jettyRequest.getInputStream();
|
||||
while (true)
|
||||
{
|
||||
int read = input.read();
|
||||
if (read < 0)
|
||||
break;
|
||||
}
|
||||
|
||||
// Now we have the trailers.
|
||||
HttpFields trailers = jettyRequest.getTrailers();
|
||||
Assert.assertNotNull(trailers);
|
||||
Assert.assertNotNull(trailers.get("X-Trailer"));
|
||||
}
|
||||
});
|
||||
|
||||
Session session = newClient(new Session.Listener.Adapter());
|
||||
|
||||
HttpFields requestFields = new HttpFields();
|
||||
requestFields.put("X-Request", "true");
|
||||
MetaData.Request request = newRequest("GET", requestFields);
|
||||
HeadersFrame requestFrame = new HeadersFrame(request, null, false);
|
||||
FuturePromise<Stream> streamPromise = new FuturePromise<>();
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
session.newStream(requestFrame, streamPromise, new Stream.Listener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onHeaders(Stream stream, HeadersFrame frame)
|
||||
{
|
||||
MetaData.Response response = (MetaData.Response)frame.getMetaData();
|
||||
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
if (frame.isEndStream())
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
Stream stream = streamPromise.get(5, TimeUnit.SECONDS);
|
||||
|
||||
// Send some data.
|
||||
Callback.Completable callback = new Callback.Completable();
|
||||
stream.data(new DataFrame(stream.getId(), ByteBuffer.allocate(16), false), callback);
|
||||
|
||||
Assert.assertTrue(trailerLatch.await(5, TimeUnit.SECONDS));
|
||||
|
||||
// Send the trailers.
|
||||
callback.thenRun(() ->
|
||||
{
|
||||
HttpFields trailerFields = new HttpFields();
|
||||
trailerFields.put("X-Trailer", "true");
|
||||
MetaData trailers = new MetaData(HttpVersion.HTTP_2, trailerFields);
|
||||
HeadersFrame trailerFrame = new HeadersFrame(stream.getId(), trailers, null, true);
|
||||
stream.headers(trailerFrame, Callback.NOOP);
|
||||
});
|
||||
|
||||
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTrailersSentByServer() throws Exception
|
||||
{
|
||||
|
|
|
@ -167,6 +167,15 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection
|
|||
}
|
||||
}
|
||||
|
||||
public void onTrailers(IStream stream, HeadersFrame frame)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Processing trailers {} on {}", frame, stream);
|
||||
HttpChannelOverHTTP2 channel = (HttpChannelOverHTTP2)stream.getAttribute(IStream.CHANNEL_ATTRIBUTE);
|
||||
if (channel != null)
|
||||
channel.onRequestTrailers(frame);
|
||||
}
|
||||
|
||||
public boolean onStreamTimeout(IStream stream, Throwable failure)
|
||||
{
|
||||
HttpChannelOverHTTP2 channel = (HttpChannelOverHTTP2)stream.getAttribute(IStream.CHANNEL_ATTRIBUTE);
|
||||
|
|
|
@ -142,8 +142,10 @@ public class HTTP2ServerConnectionFactory extends AbstractHTTP2ServerConnectionF
|
|||
@Override
|
||||
public void onHeaders(Stream stream, HeadersFrame frame)
|
||||
{
|
||||
// Servers do not receive responses.
|
||||
close(stream, "response_headers");
|
||||
if (frame.isEndStream())
|
||||
getConnection().onTrailers((IStream)stream, frame);
|
||||
else
|
||||
close(stream, "invalid_trailers");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -114,7 +114,10 @@ public class HttpChannelOverHTTP2 extends HttpChannel
|
|||
|
||||
boolean endStream = frame.isEndStream();
|
||||
if (endStream)
|
||||
{
|
||||
onContentComplete();
|
||||
onRequestComplete();
|
||||
}
|
||||
|
||||
_delayedUntilContent = getHttpConfiguration().isDelayDispatchUntilContent() &&
|
||||
!endStream && !_expect100Continue;
|
||||
|
@ -150,6 +153,7 @@ public class HttpChannelOverHTTP2 extends HttpChannel
|
|||
{
|
||||
onRequest(request);
|
||||
getRequest().setAttribute("org.eclipse.jetty.pushed", Boolean.TRUE);
|
||||
onContentComplete();
|
||||
onRequestComplete();
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
|
@ -255,7 +259,11 @@ public class HttpChannelOverHTTP2 extends HttpChannel
|
|||
|
||||
boolean endStream = frame.isEndStream();
|
||||
if (endStream)
|
||||
handle |= onRequestComplete();
|
||||
{
|
||||
boolean handle_content = onContentComplete();
|
||||
boolean handle_request = onRequestComplete();
|
||||
handle |= handle_content | handle_request;
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
|
@ -274,6 +282,20 @@ public class HttpChannelOverHTTP2 extends HttpChannel
|
|||
return handle || wasDelayed ? this : null;
|
||||
}
|
||||
|
||||
public void onRequestTrailers(HeadersFrame frame)
|
||||
{
|
||||
HttpFields trailers = frame.getMetaData().getFields();
|
||||
onTrailers(trailers);
|
||||
onRequestComplete();
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
Stream stream = getStream();
|
||||
LOG.debug("HTTP2 Request #{}/{}, trailers:{}{}",
|
||||
stream.getId(), Integer.toHexString(stream.getSession().hashCode()),
|
||||
System.lineSeparator(), trailers);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isRequestHandled()
|
||||
{
|
||||
return _handled;
|
||||
|
|
|
@ -581,11 +581,25 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
public boolean onContent(HttpInput.Content content)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} content {}", this, content);
|
||||
LOG.debug("{} onContent {}", this, content);
|
||||
|
||||
return _request.getHttpInput().addContent(content);
|
||||
}
|
||||
|
||||
public boolean onContentComplete()
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} onContentComplete", this);
|
||||
return false;
|
||||
}
|
||||
|
||||
public void onTrailers(HttpFields trailers)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} onTrailers {}", this, trailers);
|
||||
_request.setTrailers(trailers);
|
||||
}
|
||||
|
||||
public boolean onRequestComplete()
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
|
|
|
@ -62,6 +62,7 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque
|
|||
private boolean _expect100Continue = false;
|
||||
private boolean _expect102Processing = false;
|
||||
private List<String> _complianceViolations;
|
||||
private HttpFields _trailers;
|
||||
|
||||
public HttpChannelOverHttp(HttpConnection httpConnection, Connector connector, HttpConfiguration config, EndPoint endPoint, HttpTransport transport)
|
||||
{
|
||||
|
@ -87,6 +88,7 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque
|
|||
_connection = null;
|
||||
_fields.clear();
|
||||
_upgrade = null;
|
||||
_trailers = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -184,6 +186,14 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque
|
|||
_fields.add(field);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parsedTrailer(HttpField field)
|
||||
{
|
||||
if (_trailers == null)
|
||||
_trailers = new HttpFields();
|
||||
_trailers.add(field);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the associated response has the Expect header set to 100 Continue,
|
||||
* then accessing the input stream indicates that the handler/servlet
|
||||
|
@ -455,13 +465,21 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean messageComplete()
|
||||
public boolean contentComplete()
|
||||
{
|
||||
boolean handle = onRequestComplete() || _delayedForContent;
|
||||
boolean handle = onContentComplete() || _delayedForContent;
|
||||
_delayedForContent = false;
|
||||
return handle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean messageComplete()
|
||||
{
|
||||
if (_trailers != null)
|
||||
onTrailers(_trailers);
|
||||
return onRequestComplete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeaderCacheSize()
|
||||
{
|
||||
|
|
|
@ -413,10 +413,13 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
|||
|
||||
// Reset the channel, parsers and generator
|
||||
_channel.recycle();
|
||||
if (_generator.isPersistent() && !_parser.isClosed())
|
||||
_parser.reset();
|
||||
else
|
||||
_parser.close();
|
||||
if (!_parser.isClosed())
|
||||
{
|
||||
if (_generator.isPersistent())
|
||||
_parser.reset();
|
||||
else
|
||||
_parser.close();
|
||||
}
|
||||
|
||||
// Not in a race here with onFillable, because it has given up control before calling handle.
|
||||
// in a slight race with #completed, but not sure what to do with that anyway.
|
||||
|
|
|
@ -421,6 +421,12 @@ public class LocalConnector extends AbstractConnector
|
|||
public void parsedHeader(HttpField field)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contentComplete()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean messageComplete()
|
||||
|
|
|
@ -169,15 +169,12 @@ public class Request implements HttpServletRequest
|
|||
private final HttpChannel _channel;
|
||||
private final List<ServletRequestAttributeListener> _requestAttributeListeners=new ArrayList<>();
|
||||
private final HttpInput _input;
|
||||
|
||||
private MetaData.Request _metaData;
|
||||
private String _originalUri;
|
||||
|
||||
private String _contextPath;
|
||||
private String _servletPath;
|
||||
private String _pathInfo;
|
||||
private PathSpec _pathSpec;
|
||||
|
||||
private boolean _secure;
|
||||
private String _asyncNotSupportedSource = null;
|
||||
private boolean _newContext;
|
||||
|
@ -206,6 +203,7 @@ public class Request implements HttpServletRequest
|
|||
private long _timeStamp;
|
||||
private MultiPartInputStreamParser _multiPartInputStream; //if the request is a multi-part mime
|
||||
private AsyncContextState _async;
|
||||
private HttpFields _trailers;
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public Request(HttpChannel channel, HttpInput input)
|
||||
|
@ -221,6 +219,11 @@ public class Request implements HttpServletRequest
|
|||
return metadata==null?null:metadata.getFields();
|
||||
}
|
||||
|
||||
public HttpFields getTrailers()
|
||||
{
|
||||
return _trailers;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public HttpInput getHttpInput()
|
||||
{
|
||||
|
@ -1791,6 +1794,7 @@ public class Request implements HttpServletRequest
|
|||
_multiPartInputStream = null;
|
||||
_remote=null;
|
||||
_input.recycle();
|
||||
_trailers = null;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
|
@ -2160,6 +2164,11 @@ public class Request implements HttpServletRequest
|
|||
_scope = scope;
|
||||
}
|
||||
|
||||
public void setTrailers(HttpFields trailers)
|
||||
{
|
||||
_trailers = trailers;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
@Override
|
||||
public AsyncContext startAsync() throws IllegalStateException
|
||||
|
|
|
@ -21,6 +21,7 @@ package org.eclipse.jetty.server.session;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
@ -651,7 +652,28 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements
|
|||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("SessionDataStore checking expiration on {}", candidates);
|
||||
return _sessionDataStore.getExpired(candidates);
|
||||
Set<String> allCandidates = _sessionDataStore.getExpired(candidates);
|
||||
Set<String> sessionsInUse = new HashSet<>();
|
||||
if (allCandidates != null)
|
||||
{
|
||||
for (String c:allCandidates)
|
||||
{
|
||||
Session s = doGet(c);
|
||||
if (s != null && s.getRequests() > 0) //if the session is in my cache, check its not in use first
|
||||
sessionsInUse.add(c);
|
||||
}
|
||||
try
|
||||
{
|
||||
allCandidates.removeAll(sessionsInUse);
|
||||
}
|
||||
catch (UnsupportedOperationException e)
|
||||
{
|
||||
Set<String> tmp = new HashSet<>(allCandidates);
|
||||
tmp.removeAll(sessionsInUse);
|
||||
allCandidates = tmp;
|
||||
}
|
||||
}
|
||||
return allCandidates;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -149,7 +149,7 @@ public class Session implements SessionHandler.SessionIf
|
|||
@Override
|
||||
public void setIdleTimeout(long idleTimeout)
|
||||
{
|
||||
if (LOG.isDebugEnabled()) LOG.debug("setIdleTimeout called: "+idleTimeout);
|
||||
if (LOG.isDebugEnabled()) LOG.debug("setIdleTimeout called: old="+getIdleTimeout()+" new="+idleTimeout);
|
||||
super.setIdleTimeout(idleTimeout);
|
||||
}
|
||||
|
||||
|
@ -231,6 +231,8 @@ public class Session implements SessionHandler.SessionIf
|
|||
return false;
|
||||
_newSession=false;
|
||||
long lastAccessed = _sessionData.getAccessed();
|
||||
if (_sessionInactivityTimer != null)
|
||||
_sessionInactivityTimer.notIdle();
|
||||
_sessionData.setAccessed(time);
|
||||
_sessionData.setLastAccessed(lastAccessed);
|
||||
_sessionData.calcAndSetExpiry(time);
|
||||
|
@ -889,6 +891,16 @@ public class Session implements SessionHandler.SessionIf
|
|||
{
|
||||
return _lock.lock();
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------- */
|
||||
/** Grab the lock on the session if it isn't locked already
|
||||
* @return the lock
|
||||
*/
|
||||
public Lock lockIfNotHeld ()
|
||||
{
|
||||
return _lock.lockIfNotHeld();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------- */
|
||||
/** Call HttpSessionAttributeListeners as part of invalidating
|
||||
|
|
|
@ -1292,15 +1292,18 @@ public class SessionHandler extends ScopedHandler
|
|||
{
|
||||
if (session == null)
|
||||
return;
|
||||
|
||||
|
||||
|
||||
//check if the session is:
|
||||
//1. valid
|
||||
//2. expired
|
||||
//3. idle
|
||||
boolean expired = false;
|
||||
try (Lock lock = session.lock())
|
||||
try (Lock lock = session.lockIfNotHeld())
|
||||
{
|
||||
if (session.getRequests() > 0)
|
||||
return; //session can't expire or be idle if there is a request in it
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Inspecting session {}, valid={}", session.getId(), session.isValid());
|
||||
|
||||
|
|
|
@ -328,6 +328,66 @@ public class HttpConnectionTest
|
|||
checkContains(response,offset,"/R1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChunk() throws Exception
|
||||
{
|
||||
String response=connector.getResponse("GET /R1 HTTP/1.1\r\n"+
|
||||
"Host: localhost\r\n"+
|
||||
"Transfer-Encoding: chunked\r\n"+
|
||||
"Content-Type: text/plain\r\n"+
|
||||
"Connection: close\r\n"+
|
||||
"\r\n"+
|
||||
"A\r\n" +
|
||||
"0123456789\r\n"+
|
||||
"0\r\n" +
|
||||
"\r\n");
|
||||
|
||||
int offset=0;
|
||||
offset = checkContains(response,offset,"HTTP/1.1 200");
|
||||
offset = checkContains(response,offset,"/R1");
|
||||
checkContains(response,offset,"0123456789");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChunkTrailer() throws Exception
|
||||
{
|
||||
String response=connector.getResponse("GET /R1 HTTP/1.1\r\n"+
|
||||
"Host: localhost\r\n"+
|
||||
"Transfer-Encoding: chunked\r\n"+
|
||||
"Content-Type: text/plain\r\n"+
|
||||
"Connection: close\r\n"+
|
||||
"\r\n"+
|
||||
"A\r\n" +
|
||||
"0123456789\r\n"+
|
||||
"0\r\n" +
|
||||
"Trailer: ignored\r\n" +
|
||||
"\r\n");
|
||||
|
||||
int offset=0;
|
||||
offset = checkContains(response,offset,"HTTP/1.1 200");
|
||||
offset = checkContains(response,offset,"/R1");
|
||||
checkContains(response,offset,"0123456789");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChunkNoTrailer() throws Exception
|
||||
{
|
||||
String response=connector.getResponse("GET /R1 HTTP/1.1\r\n"+
|
||||
"Host: localhost\r\n"+
|
||||
"Transfer-Encoding: chunked\r\n"+
|
||||
"Content-Type: text/plain\r\n"+
|
||||
"Connection: close\r\n"+
|
||||
"\r\n"+
|
||||
"A\r\n" +
|
||||
"0123456789\r\n"+
|
||||
"0\r\n");
|
||||
|
||||
int offset=0;
|
||||
offset = checkContains(response,offset,"HTTP/1.1 200");
|
||||
offset = checkContains(response,offset,"/R1");
|
||||
checkContains(response,offset,"0123456789");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHead() throws Exception
|
||||
{
|
||||
|
|
|
@ -0,0 +1,163 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2017 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.server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletInputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.HttpTester;
|
||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class HttpTrailersTest
|
||||
{
|
||||
private Server server;
|
||||
private ServerConnector connector;
|
||||
|
||||
private void start(Handler handler) throws Exception
|
||||
{
|
||||
server = new Server();
|
||||
connector = new ServerConnector(server);
|
||||
server.addConnector(connector);
|
||||
server.setHandler(handler);
|
||||
server.start();
|
||||
}
|
||||
|
||||
@After
|
||||
public void dispose() throws Exception
|
||||
{
|
||||
if (server != null)
|
||||
server.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServletRequestTrailers() throws Exception
|
||||
{
|
||||
String trailerName = "Trailer";
|
||||
String trailerValue = "value";
|
||||
start(new AbstractHandler.ErrorDispatchHandler()
|
||||
{
|
||||
@Override
|
||||
protected void doNonErrorHandle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
{
|
||||
jettyRequest.setHandled(true);
|
||||
|
||||
// Read the content first.
|
||||
ServletInputStream input = jettyRequest.getInputStream();
|
||||
while (true)
|
||||
{
|
||||
int read = input.read();
|
||||
if (read < 0)
|
||||
break;
|
||||
}
|
||||
|
||||
// Now the trailers can be accessed.
|
||||
HttpFields trailers = jettyRequest.getTrailers();
|
||||
Assert.assertNotNull(trailers);
|
||||
Assert.assertEquals(trailerValue, trailers.get(trailerName));
|
||||
}
|
||||
});
|
||||
|
||||
try (Socket client = new Socket("localhost", connector.getLocalPort()))
|
||||
{
|
||||
client.setSoTimeout(5000);
|
||||
|
||||
String request = "" +
|
||||
"GET / HTTP/1.1\r\n" +
|
||||
"Host: localhost\r\n" +
|
||||
"Transfer-Encoding: chunked\r\n" +
|
||||
"\r\n" +
|
||||
"0\r\n" +
|
||||
trailerName + ": " + trailerValue + "\r\n" +
|
||||
"\r\n";
|
||||
OutputStream output = client.getOutputStream();
|
||||
output.write(request.getBytes(StandardCharsets.UTF_8));
|
||||
output.flush();
|
||||
|
||||
HttpTester.Response response = HttpTester.parseResponse(HttpTester.from(client.getInputStream()));
|
||||
Assert.assertNotNull(response);
|
||||
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHugeTrailer() throws Exception
|
||||
{
|
||||
start(new AbstractHandler.ErrorDispatchHandler()
|
||||
{
|
||||
@Override
|
||||
protected void doNonErrorHandle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
{
|
||||
jettyRequest.setHandled(true);
|
||||
|
||||
try
|
||||
{
|
||||
// EOF will not be reached because of the huge trailer.
|
||||
ServletInputStream input = jettyRequest.getInputStream();
|
||||
while (true)
|
||||
{
|
||||
int read = input.read();
|
||||
if (read < 0)
|
||||
break;
|
||||
}
|
||||
Assert.fail();
|
||||
}
|
||||
catch (IOException x)
|
||||
{
|
||||
// Expected.
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
char[] huge = new char[1024 * 1024];
|
||||
Arrays.fill(huge, 'X');
|
||||
try (Socket client = new Socket("localhost", connector.getLocalPort()))
|
||||
{
|
||||
client.setSoTimeout(5000);
|
||||
|
||||
String request = "" +
|
||||
"GET / HTTP/1.1\r\n" +
|
||||
"Host: localhost\r\n" +
|
||||
"Transfer-Encoding: chunked\r\n" +
|
||||
"\r\n" +
|
||||
"0\r\n" +
|
||||
"Trailer: " + new String(huge) + "\r\n" +
|
||||
"\r\n";
|
||||
OutputStream output = client.getOutputStream();
|
||||
output.write(request.getBytes(StandardCharsets.UTF_8));
|
||||
output.flush();
|
||||
|
||||
HttpTester.Response response = HttpTester.parseResponse(HttpTester.from(client.getInputStream()));
|
||||
Assert.assertNotNull(response);
|
||||
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -46,15 +46,339 @@ import org.junit.Test;
|
|||
public class ModifyMaxInactiveIntervalTest
|
||||
{
|
||||
|
||||
public static int __inactive = 4;
|
||||
|
||||
public static int newMaxInactive = 20;
|
||||
public static int __scavenge = 1;
|
||||
|
||||
|
||||
@Test
|
||||
public void testReduceMaxInactiveInterval() throws Exception
|
||||
{
|
||||
int oldMaxInactive = 3;
|
||||
int newMaxInactive = 1;
|
||||
int sleep = (int)(oldMaxInactive * 0.8);
|
||||
|
||||
|
||||
AbstractTestServer server = new JdbcTestServer(0, oldMaxInactive, 1, SessionCache.NEVER_EVICT);
|
||||
ServletContextHandler ctxA = server.addContext("/mod");
|
||||
ctxA.addServlet(TestModServlet.class, "/test");
|
||||
|
||||
server.start();
|
||||
int port=server.getPort();
|
||||
try
|
||||
{
|
||||
HttpClient client = new HttpClient();
|
||||
client.start();
|
||||
try
|
||||
{
|
||||
// Perform a request to create a session
|
||||
ContentResponse response = client.GET("http://localhost:" + port + "/mod/test?action=create");
|
||||
|
||||
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
|
||||
String sessionCookie = response.getHeaders().get("Set-Cookie");
|
||||
assertTrue(sessionCookie != null);
|
||||
// Mangle the cookie, replacing Path with $Path, etc.
|
||||
sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
|
||||
|
||||
//do another request to reduce the maxinactive interval
|
||||
Request request = client.newRequest("http://localhost:" + port + "/mod/test?action=change&val="+newMaxInactive+"&wait="+sleep);
|
||||
request.header("Cookie", sessionCookie);
|
||||
response = request.send();
|
||||
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
|
||||
|
||||
//do another request using the cookie to ensure the session is still there
|
||||
request= client.newRequest("http://localhost:" + port + "/mod/test?action=test&val="+newMaxInactive);
|
||||
request.header("Cookie", sessionCookie);
|
||||
response = request.send();
|
||||
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
client.stop();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
server.stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testIncreaseMaxInactiveInterval() throws Exception
|
||||
{
|
||||
|
||||
int oldMaxInactive = 3;
|
||||
int newMaxInactive = 5;
|
||||
int sleep = (int)(oldMaxInactive * 0.8);
|
||||
|
||||
AbstractTestServer server = new JdbcTestServer(0, oldMaxInactive, 1, SessionCache.NEVER_EVICT);
|
||||
ServletContextHandler ctxA = server.addContext("/mod");
|
||||
ctxA.addServlet(TestModServlet.class, "/test");
|
||||
|
||||
server.start();
|
||||
int port=server.getPort();
|
||||
try
|
||||
{
|
||||
HttpClient client = new HttpClient();
|
||||
client.start();
|
||||
try
|
||||
{
|
||||
// Perform a request to create a session
|
||||
ContentResponse response = client.GET("http://localhost:" + port + "/mod/test?action=create");
|
||||
|
||||
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
|
||||
String sessionCookie = response.getHeaders().get("Set-Cookie");
|
||||
assertTrue(sessionCookie != null);
|
||||
// Mangle the cookie, replacing Path with $Path, etc.
|
||||
sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
|
||||
|
||||
//do another request to increase the maxinactive interval, first waiting until the old expiration should have passed
|
||||
Request request = client.newRequest("http://localhost:" + port + "/mod/test?action=change&val="+newMaxInactive+"&wait="+sleep);
|
||||
request.header("Cookie", sessionCookie);
|
||||
response = request.send();
|
||||
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
|
||||
|
||||
//do another request using the cookie to ensure the session is still there
|
||||
request= client.newRequest("http://localhost:" + port + "/mod/test?action=test&val="+newMaxInactive);
|
||||
request.header("Cookie", sessionCookie);
|
||||
response = request.send();
|
||||
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
client.stop();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
server.stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSetMaxInactiveIntervalWithImmortalSessionAndEviction() throws Exception
|
||||
{
|
||||
int oldMaxInactive = -1;
|
||||
int newMaxInactive = 120; //2min
|
||||
int evict = 2;
|
||||
int sleep = evict;
|
||||
|
||||
AbstractTestServer server = new JdbcTestServer(0, oldMaxInactive, 1, evict);
|
||||
|
||||
|
||||
ServletContextHandler ctxA = server.addContext("/mod");
|
||||
ctxA.addServlet(TestModServlet.class, "/test");
|
||||
|
||||
server.start();
|
||||
int port=server.getPort();
|
||||
try
|
||||
{
|
||||
HttpClient client = new HttpClient();
|
||||
client.start();
|
||||
try
|
||||
{
|
||||
// Perform a request to create a session
|
||||
ContentResponse response = client.GET("http://localhost:" + port + "/mod/test?action=create");
|
||||
|
||||
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
|
||||
String sessionCookie = response.getHeaders().get("Set-Cookie");
|
||||
assertTrue(sessionCookie != null);
|
||||
// Mangle the cookie, replacing Path with $Path, etc.
|
||||
sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
|
||||
|
||||
//do another request to reduce the maxinactive interval
|
||||
Request request = client.newRequest("http://localhost:" + port + "/mod/test?action=change&val="+newMaxInactive+"&wait="+sleep);
|
||||
request.header("Cookie", sessionCookie);
|
||||
response = request.send();
|
||||
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
|
||||
|
||||
//do another request using the cookie to ensure the session is still there
|
||||
request= client.newRequest("http://localhost:" + port + "/mod/test?action=test&val="+newMaxInactive);
|
||||
request.header("Cookie", sessionCookie);
|
||||
response = request.send();
|
||||
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
client.stop();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
server.stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSetMaxInactiveIntervalWithNonImmortalSessionAndEviction() throws Exception
|
||||
{
|
||||
int oldMaxInactive = 10;
|
||||
int newMaxInactive = 2;
|
||||
int evict = 4;
|
||||
int sleep = evict;
|
||||
|
||||
AbstractTestServer server = new JdbcTestServer(0, oldMaxInactive, 1, evict);
|
||||
ServletContextHandler ctxA = server.addContext("/mod");
|
||||
ctxA.addServlet(TestModServlet.class, "/test");
|
||||
|
||||
server.start();
|
||||
int port=server.getPort();
|
||||
try
|
||||
{
|
||||
HttpClient client = new HttpClient();
|
||||
client.start();
|
||||
try
|
||||
{
|
||||
// Perform a request to create a session
|
||||
ContentResponse response = client.GET("http://localhost:" + port + "/mod/test?action=create");
|
||||
|
||||
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
|
||||
String sessionCookie = response.getHeaders().get("Set-Cookie");
|
||||
assertTrue(sessionCookie != null);
|
||||
// Mangle the cookie, replacing Path with $Path, etc.
|
||||
sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
|
||||
|
||||
//do another request to reduce the maxinactive interval
|
||||
Request request = client.newRequest("http://localhost:" + port + "/mod/test?action=change&val="+newMaxInactive+"&wait="+sleep);
|
||||
request.header("Cookie", sessionCookie);
|
||||
response = request.send();
|
||||
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
|
||||
|
||||
//do another request using the cookie to ensure the session is still there
|
||||
request= client.newRequest("http://localhost:" + port + "/mod/test?action=test&val="+newMaxInactive);
|
||||
request.header("Cookie", sessionCookie);
|
||||
response = request.send();
|
||||
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
client.stop();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
server.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChangeMaxInactiveIntervalForImmortalSessionNoEviction() throws Exception
|
||||
{
|
||||
int oldMaxInactive = -1;
|
||||
int newMaxInactive = 120;
|
||||
|
||||
AbstractTestServer server = new JdbcTestServer(0, oldMaxInactive, 1, SessionCache.NEVER_EVICT);
|
||||
|
||||
|
||||
ServletContextHandler ctxA = server.addContext("/mod");
|
||||
ctxA.addServlet(TestModServlet.class, "/test");
|
||||
|
||||
server.start();
|
||||
int port=server.getPort();
|
||||
try
|
||||
{
|
||||
HttpClient client = new HttpClient();
|
||||
client.start();
|
||||
try
|
||||
{
|
||||
// Perform a request to create a session
|
||||
ContentResponse response = client.GET("http://localhost:" + port + "/mod/test?action=create");
|
||||
|
||||
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
|
||||
String sessionCookie = response.getHeaders().get("Set-Cookie");
|
||||
assertTrue(sessionCookie != null);
|
||||
// Mangle the cookie, replacing Path with $Path, etc.
|
||||
sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
|
||||
|
||||
//do another request to change the maxinactive interval
|
||||
Request request = client.newRequest("http://localhost:" + port + "/mod/test?action=change&val="+newMaxInactive+"&wait="+2);
|
||||
request.header("Cookie", sessionCookie);
|
||||
response = request.send();
|
||||
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
|
||||
|
||||
//do another request using the cookie to ensure the session is still there
|
||||
request= client.newRequest("http://localhost:" + port + "/mod/test?action=test&val="+newMaxInactive);
|
||||
request.header("Cookie", sessionCookie);
|
||||
response = request.send();
|
||||
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
client.stop();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
server.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoExpireSessionInUse() throws Exception
|
||||
{
|
||||
int maxInactive = 3;
|
||||
int sleep = maxInactive + (int)(maxInactive * 0.8);
|
||||
|
||||
AbstractTestServer server = new JdbcTestServer(0, maxInactive, 1, SessionCache.NEVER_EVICT);
|
||||
|
||||
|
||||
ServletContextHandler ctxA = server.addContext("/mod");
|
||||
ctxA.addServlet(TestModServlet.class, "/test");
|
||||
|
||||
server.start();
|
||||
int port=server.getPort();
|
||||
try
|
||||
{
|
||||
HttpClient client = new HttpClient();
|
||||
client.start();
|
||||
try
|
||||
{
|
||||
// Perform a request to create a session
|
||||
|
||||
ContentResponse response = client.GET("http://localhost:" + port + "/mod/test?action=create");
|
||||
|
||||
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
|
||||
String sessionCookie = response.getHeaders().get("Set-Cookie");
|
||||
assertTrue(sessionCookie != null);
|
||||
// Mangle the cookie, replacing Path with $Path, etc.
|
||||
sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
|
||||
|
||||
//do another request that will sleep long enough for the session expiry time to have passed
|
||||
//before trying to access the session and ensure it is still there
|
||||
Request request = client.newRequest("http://localhost:" + port + "/mod/test?action=sleep&val="+sleep);
|
||||
request.header("Cookie", sessionCookie);
|
||||
response = request.send();
|
||||
|
||||
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
client.stop();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
server.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSessionExpiryAfterModifiedMaxInactiveInterval() throws Exception
|
||||
{
|
||||
AbstractTestServer server = new JdbcTestServer(0,__inactive,__scavenge, SessionCache.NEVER_EVICT);
|
||||
int oldMaxInactive = 4;
|
||||
int newMaxInactive = 20;
|
||||
int sleep = oldMaxInactive+(int)(oldMaxInactive * 0.8);
|
||||
|
||||
AbstractTestServer server = new JdbcTestServer(0, oldMaxInactive,__scavenge, SessionCache.NEVER_EVICT);
|
||||
|
||||
ServletContextHandler ctxA = server.addContext("/mod");
|
||||
ctxA.addServlet(TestModServlet.class, "/test");
|
||||
|
@ -85,13 +409,15 @@ public class ModifyMaxInactiveIntervalTest
|
|||
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
|
||||
|
||||
//wait for longer than the old inactive interval
|
||||
Thread.currentThread().sleep(10*1000L);
|
||||
Thread.currentThread().sleep(sleep*1000L);
|
||||
|
||||
//do another request using the cookie to ensure the session is still there
|
||||
request= client.newRequest("http://localhost:" + port + "/mod/test?action=test");
|
||||
request= client.newRequest("http://localhost:" + port + "/mod/test?action=test&val="+newMaxInactive);
|
||||
request.header("Cookie", sessionCookie);
|
||||
response = request.send();
|
||||
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
|
||||
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
@ -126,16 +452,51 @@ public class ModifyMaxInactiveIntervalTest
|
|||
|
||||
if ("change".equals(action))
|
||||
{
|
||||
//change the expiry time for the session, maybe sleeping before the change
|
||||
String tmp = request.getParameter("val");
|
||||
int interval = -1;
|
||||
interval = (tmp==null?-1:Integer.parseInt(tmp));
|
||||
|
||||
tmp = request.getParameter("wait");
|
||||
int wait = (tmp==null?0:Integer.parseInt(tmp));
|
||||
if (wait >0)
|
||||
{
|
||||
try { Thread.currentThread().sleep(wait*1000);}catch (Exception e) {throw new ServletException(e);}
|
||||
}
|
||||
HttpSession session = request.getSession(false);
|
||||
if (session == null)
|
||||
throw new ServletException("Session is null for action=change");
|
||||
|
||||
String tmp = request.getParameter("val");
|
||||
int interval = -1;
|
||||
interval = (tmp==null?-1:Integer.parseInt(tmp));
|
||||
|
||||
if (interval > 0)
|
||||
session.setMaxInactiveInterval(interval);
|
||||
session.setMaxInactiveInterval(interval);
|
||||
|
||||
session = request.getSession(false);
|
||||
if (session == null)
|
||||
throw new ServletException ("Null session after maxInactiveInterval change");
|
||||
return;
|
||||
}
|
||||
|
||||
if ("sleep".equals(action))
|
||||
{
|
||||
//sleep before trying to access the session
|
||||
|
||||
HttpSession session = request.getSession(false);
|
||||
if (session == null)
|
||||
throw new ServletException("Session is null for action=sleep");
|
||||
|
||||
String tmp = request.getParameter("val");
|
||||
int interval = 0;
|
||||
interval = (tmp==null?0:Integer.parseInt(tmp));
|
||||
|
||||
if (interval > 0)
|
||||
{
|
||||
try{Thread.currentThread().sleep(interval*1000);}catch (Exception e) {throw new ServletException(e);}
|
||||
}
|
||||
|
||||
session = request.getSession(false);
|
||||
if (session == null)
|
||||
throw new ServletException("Session null after sleep");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -144,7 +505,11 @@ public class ModifyMaxInactiveIntervalTest
|
|||
HttpSession session = request.getSession(false);
|
||||
if (session == null)
|
||||
throw new ServletException("Session does not exist");
|
||||
assertEquals(ModifyMaxInactiveIntervalTest.newMaxInactive, session.getMaxInactiveInterval());
|
||||
String tmp = request.getParameter("val");
|
||||
int interval = 0;
|
||||
interval = (tmp==null?0:Integer.parseInt(tmp));
|
||||
|
||||
assertEquals(interval, session.getMaxInactiveInterval());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue