diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeader.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeader.java index 140e5427abe..2749ad91910 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeader.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeader.java @@ -14,6 +14,10 @@ package org.eclipse.jetty.http; import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; import org.eclipse.jetty.util.StringMap; import org.eclipse.jetty.util.StringUtil; @@ -114,7 +118,72 @@ public enum HttpHeader if (header!=UNKNOWN) CACHE.put(header.toString(),header); } + + /* ------------------------------------------------------------ */ + private final static HttpHeader[] __hashed= new HttpHeader[4096]; + private final static int __maxHashed; + static + { + // This hash function has been picked to have no collisions for + // the known header values. This allows a very quick lookup. + int max=0; + Map hashes=new HashMap<>(); + for (HttpHeader header : HttpHeader.values()) + { + String s=header.asString(); + max=Math.max(max,s.length()); + int h=0; + for (char c:s.toCharArray()) + h = 31*h + ((c>='a')?(c-'a'+'A'):c); + int hash=h%__hashed.length; + if (hash<0)hash=-hash; + if (hashes.containsKey(hash)) + { + // This should not happen with known headers. + System.err.println("Duplicate hash "+header+" "+hashes.get(hash)); + System.exit(1); + } + hashes.put(hash,header); + __hashed[hash]=header; + } + __maxHashed=max; + } + public static HttpHeader lookAheadGet(byte[] bytes, int position, int limit) + { + int h=0; + byte b=0; + limit=Math.min(position+__maxHashed,limit); + for (int i=position;i='a')?(b-'a'+'A'):b); + } + if (b!=':'&&b!=' ') + return null; + + int hash=h%__hashed.length; + if (hash<0)hash=-hash; + HttpHeader header=__hashed[hash]; + + if (header!=null) + { + String s=header.asString(); + for (int i=s.length();i-->0;) + { + b=bytes[position+i]; + char c=s.charAt(i); + if (c!=b && Character.toUpperCase(c)!=(b>='a'?(b-'a'+'A'):b)) + return null; + } + } + + return header; + } + + private final String _string; private final byte[] _bytes; private final byte[] _bytesColonSpace; diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeaderValue.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeaderValue.java index 5ceb71ba017..69ca9840ecb 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeaderValue.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeaderValue.java @@ -63,6 +63,12 @@ public enum HttpHeaderValue return _buffer.asReadOnlyBuffer(); } + /* ------------------------------------------------------------ */ + public String asString() + { + return _string; + } + /* ------------------------------------------------------------ */ @Override public String toString() diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpMethod.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpMethod.java index 07658774941..ca2e0d24c09 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpMethod.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpMethod.java @@ -34,6 +34,81 @@ public enum HttpMethod CONNECT, MOVE; + /* ------------------------------------------------------------ */ + /** + * Optimised lookup to find a method name and trailing space in a byte array. + * @param bytes Array containing ISO-8859-1 characters + * @param position The first valid index + * @param limit The first non valid index + * @return A HttpMethod if a match or null if no easy match. + */ + public static HttpMethod lookAheadGet(byte[] bytes, int position, int limit) + { + int length=limit-position; + if (length<4) + return null; + switch(bytes[position]) + { + case 'G': + if (bytes[position+1]=='E' && bytes[position+2]=='T' && bytes[position+3]==' ') + return GET; + break; + case 'P': + if (bytes[position+1]=='O' && bytes[position+2]=='S' && bytes[position+3]=='T' && length>=5 && bytes[position+4]==' ') + return POST; + if (bytes[position+1]=='U' && bytes[position+2]=='T' && bytes[position+3]==' ') + return PUT; + break; + case 'H': + if (bytes[position+1]=='E' && bytes[position+2]=='A' && bytes[position+3]=='D' && length>=5 && bytes[position+4]==' ') + return HEAD; + break; + case 'O': + if (bytes[position+1]=='O' && bytes[position+2]=='T' && bytes[position+3]=='I' && length>=8 && + bytes[position+4]=='O' && bytes[position+5]=='N' && bytes[position+6]=='S' && bytes[position+7]==' ' ) + return OPTIONS; + break; + case 'D': + if (bytes[position+1]=='E' && bytes[position+2]=='L' && bytes[position+3]=='E' && length>=7 && + bytes[position+4]=='T' && bytes[position+5]=='E' && bytes[position+6]==' ' ) + return DELETE; + break; + case 'T': + if (bytes[position+1]=='R' && bytes[position+2]=='A' && bytes[position+3]=='C' && length>=6 && + bytes[position+4]=='E' && bytes[position+5]==' ' ) + return TRACE; + break; + case 'C': + if (bytes[position+1]=='O' && bytes[position+2]=='N' && bytes[position+3]=='N' && length>=8 && + bytes[position+4]=='E' && bytes[position+5]=='C' && bytes[position+6]=='T' && bytes[position+7]==' ' ) + return CONNECT; + break; + case 'M': + if (bytes[position+1]=='O' && bytes[position+2]=='V' && bytes[position+3]=='E' && bytes[position+4]==' ') + return MOVE; + break; + + default: + break; + } + return null; + } + + /* ------------------------------------------------------------ */ + /** + * Optimised lookup to find a method name and trailing space in a byte array. + * @param bytes Array containing ISO-8859-1 characters + * @param position The first valid index + * @param limit The first non valid index + * @return A HttpMethod if a match or null if no easy match. + */ + public static HttpMethod lookAheadGet(ByteBuffer buffer) + { + if (buffer.hasArray()) + return lookAheadGet(buffer.array(),buffer.arrayOffset()+buffer.position(),buffer.arrayOffset()+buffer.limit()); + return null; + } + /* ------------------------------------------------------------ */ public final static StringMap CACHE= new StringMap(true); static diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java index f003c7a72c7..9e6cecea770 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java @@ -20,6 +20,7 @@ import org.eclipse.jetty.http.HttpTokens.EndOfContent; import org.eclipse.jetty.io.EofException; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.Utf8StringBuilder; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -58,14 +59,18 @@ public class HttpParser private final RequestHandler _requestHandler; private final ResponseHandler _responseHandler; private HttpHeader _header; + private String _headerString; private HttpHeaderValue _value; + private String _valueString; private int _responseStatus; private boolean _persistent; /* ------------------------------------------------------------------------------- */ private State _state=State.START; - private String _field0; - private String _field1; + private HttpMethod _method; + private String _methodString; + private HttpVersion _version; + private String _uri; private byte _eol; private EndOfContent _endOfContent; private long _contentLength; @@ -74,6 +79,10 @@ public class HttpParser private int _chunkPosition; private boolean _headResponse; private ByteBuffer _contentChunk; + + private int _length; + private final StringBuilder _string=new StringBuilder(); + private final Utf8StringBuilder _utf8=new Utf8StringBuilder(); /* ------------------------------------------------------------------------------- */ /** @@ -203,7 +212,639 @@ public class HttpParser } } + /* ------------------------------------------------------------------------------- */ + /* Quick lookahead for the start state looking for a request method or a HTTP version, + * otherwise skip white space until something else to parse. + */ + private void quickStart(ByteBuffer buffer) + { + // Quick start look + while (_state==State.START && buffer.hasRemaining()) + { + if (_requestHandler!=null) + { + _method = HttpMethod.lookAheadGet(buffer); + if (_method!=null) + { + _methodString = _method.asString(); + buffer.position(buffer.position()+_methodString.length()+1); + _state=State.SPACE1; + return; + } + } + else if (_responseHandler!=null) + { + _version = HttpVersion.lookAheadGet(buffer); + if (_version!=null) + { + buffer.position(buffer.position()+_version.asString().length()+1); + _state=State.SPACE1; + return; + } + } + byte ch=buffer.get(); + + if (_eol == HttpTokens.CARRIAGE_RETURN && ch == HttpTokens.LINE_FEED) + { + _eol=HttpTokens.LINE_FEED; + continue; + } + _eol=0; + + if (ch > HttpTokens.SPACE || ch<0) + { + _string.setLength(0); + _string.append((char)ch); + _state=_requestHandler!=null?State.METHOD:State.RESPONSE_VERSION; + return; + } + } + } + + private String takeString() + { + String s =_string.toString(); + _string.setLength(0); + return s; + } + + private String takeLengthString() + { + _string.setLength(_length); + String s =_string.toString(); + _string.setLength(0); + _length=-1; + return s; + } + + /* ------------------------------------------------------------------------------- */ + /* Parse a request or response line + */ + private boolean parseLine(ByteBuffer buffer) throws IOException + { + boolean return_from_parse=false; + + // Process headers + while (_state.ordinal()=0) + { + throw new HttpException(HttpStatus.BAD_REQUEST_400); + } + else + _string.append((char)ch); + break; + + case RESPONSE_VERSION: + if (ch == HttpTokens.SPACE) + { + String version=takeString(); + _version=HttpVersion.CACHE.get(version); + if (_version==null) + throw new HttpException(HttpStatus.BAD_REQUEST_400); + _persistent=HttpVersion.HTTP_1_1==_version; + _state=State.SPACE1; + } + else if (ch < HttpTokens.SPACE && ch>=0) + { + throw new HttpException(HttpStatus.BAD_REQUEST_400); + } + else + _string.append((char)ch); + break; + + case SPACE1: + if (ch > HttpTokens.SPACE || ch<0) + { + if (_responseHandler!=null) + { + _state=State.STATUS; + _responseStatus=ch-'0'; + } + else + { + _state=State.URI; + _utf8.reset(); + _utf8.append(ch); + } + } + else if (ch < HttpTokens.SPACE) + { + throw new HttpException(HttpStatus.BAD_REQUEST_400); + } + break; + + case STATUS: + if (ch == HttpTokens.SPACE) + { + _state=State.SPACE2; + } + else if (ch>='0' && ch<='9') + { + _responseStatus=_responseStatus*10+(ch-'0'); + } + else if (ch < HttpTokens.SPACE && ch>=0) + { + return_from_parse|=_responseHandler.startResponse(_version, _responseStatus, null); + _eol=ch; + _state=State.HEADER; + } + else + { + throw new IllegalStateException(); + } + break; + + case URI: + if (ch == HttpTokens.SPACE) + { + _uri=_utf8.toString(); + _utf8.reset(); + _state=State.SPACE2; + } + else if (ch < HttpTokens.SPACE && ch>=0) + { + // HTTP/0.9 + _uri=_utf8.toString(); + _utf8.reset(); + return_from_parse|=_requestHandler.startRequest(_method,_methodString,_uri,null); + _persistent=false; + _state=State.SEEKING_EOF; + return_from_parse|=_handler.headerComplete(false,false); + return_from_parse|=_handler.messageComplete(_contentPosition); + } + else + _utf8.append(ch); + break; + + case SPACE2: + if (ch > HttpTokens.SPACE || ch<0) + { + _string.setLength(0); + _string.append((char)ch); + if (_responseHandler!=null) + { + _length=1; + _state=State.REASON; + } + else + { + _state=State.REQUEST_VERSION; + + // try quick look ahead + if (buffer.position()>0 && buffer.hasArray()) + { + _version=HttpVersion.lookAheadGet(buffer.array(),buffer.arrayOffset()+buffer.position()-1,buffer.arrayOffset()+buffer.limit()); + if (_version!=null) + { + _string.setLength(0); + buffer.position(buffer.position()+_version.asString().length()-1); + _eol=buffer.get(); + _persistent=HttpVersion.HTTP_1_1==_version; + _state=State.HEADER; + return_from_parse|=_requestHandler.startRequest(_method,_methodString, _uri, _version); + } + } + } + } + else if (ch < HttpTokens.SPACE) + { + if (_responseHandler!=null) + { + return_from_parse|=_responseHandler.startResponse(_version, _responseStatus, null); + _eol=ch; + _state=State.HEADER; + } + else + { + // HTTP/0.9 + return_from_parse|=_requestHandler.startRequest(_method,_methodString, _uri, null); + _persistent=false; + _state=State.SEEKING_EOF; + return_from_parse|=_handler.headerComplete(false,false); + return_from_parse|=_handler.messageComplete(_contentPosition); + } + } + break; + + case REQUEST_VERSION: + if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED) + { + String version = takeString(); + _version=HttpVersion.CACHE.get(version); + if (_version==null) + throw new HttpException(HttpStatus.BAD_REQUEST_400); + + _eol=ch; + _persistent=HttpVersion.HTTP_1_1==_version; + _state=State.HEADER; + return_from_parse|=_requestHandler.startRequest(_method,_methodString, _uri, _version); + continue; + } + else + _string.append((char)ch); + + break; + + case REASON: + if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED) + { + String reason=takeLengthString(); + + _eol=ch; + _state=State.HEADER; + return_from_parse|=_responseHandler.startResponse(_version, _responseStatus, reason); + continue; + } + else + { + _string.append((char)ch); + if (ch!=' '&&ch!='\t') + _length=_string.length(); + } + break; + + default: + throw new IllegalStateException(_state.toString()); + + } + } + + return return_from_parse; + } + + /* ------------------------------------------------------------------------------- */ + /* + * Parse the message headers and return true if the handler has signaled for a return + */ + private boolean parseHeaders(ByteBuffer buffer) throws IOException + { + boolean return_from_parse=false; + + // Process headers + while (_state.ordinal()=200)) + { + try + { + _contentLength=Long.parseLong(_valueString); + } + catch(NumberFormatException e) + { + LOG.ignore(e); + throw new HttpException(HttpStatus.BAD_REQUEST_400); + } + if (_contentLength <= 0) + _endOfContent=EndOfContent.NO_CONTENT; + else + _endOfContent=EndOfContent.CONTENT_LENGTH; + } + break; + + case TRANSFER_ENCODING: + if (_value==HttpHeaderValue.CHUNKED) + _endOfContent=EndOfContent.CHUNKED_CONTENT; + else + { + if (_valueString.endsWith(HttpHeaderValue.CHUNKED.toString())) + _endOfContent=EndOfContent.CHUNKED_CONTENT; + else if (_valueString.indexOf(HttpHeaderValue.CHUNKED.toString()) >= 0) + throw new HttpException(400,null); + } + break; + + case CONNECTION: + switch(_value==null?HttpHeaderValue.UNKNOWN:_value) + { + case CLOSE: + _persistent=false; + break; + + case KEEP_ALIVE: + _persistent=true; + break; + + default: // No match, may be multi valued + { + for (String v : _valueString.toString().split(",")) + { + switch(HttpHeaderValue.CACHE.get(v.trim())) + { + case CLOSE: + _persistent=false; + break; + + case KEEP_ALIVE: + _persistent=true; + break; + } + } + break; + } + } + } + } + + return_from_parse|=_handler.parsedHeader(_header, _headerString, _valueString); + } + _headerString=_valueString=null; + _header=null; + _value=null; + + + // now handle the ch + if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED) + { + _eol=ch; + _contentPosition=0; + + // End of headers! + // so work out the _content demarcation + if (_endOfContent == EndOfContent.UNKNOWN_CONTENT) + { + if (_responseStatus == 0 // request + || _responseStatus == 304 // not-modified response + || _responseStatus == 204 // no-content response + || _responseStatus < 200) // 1xx response + _endOfContent=EndOfContent.NO_CONTENT; + else + _endOfContent=EndOfContent.EOF_CONTENT; + } + + // How is the message ended? + switch (_endOfContent) + { + case EOF_CONTENT: + _state=State.EOF_CONTENT; + return_from_parse|=_handler.headerComplete(true,false); + break; + + case CHUNKED_CONTENT: + _state=State.CHUNKED_CONTENT; + return_from_parse|=_handler.headerComplete(true,_persistent); + break; + + case NO_CONTENT: + return_from_parse|=_handler.headerComplete(false,_persistent); + _state=_persistent||(_responseStatus>=100&&_responseStatus<200)?State.END:State.SEEKING_EOF; + return_from_parse|=_handler.messageComplete(_contentPosition); + break; + + default: + _state=State.CONTENT; + return_from_parse|=_handler.headerComplete(true,_persistent); + break; + } + } + else + { + if (buffer.remaining()>6 && buffer.hasArray()) + { + // Try a look ahead for the known headers. + _header=HttpHeader.lookAheadGet(buffer.array(),buffer.arrayOffset()+buffer.position()-1,buffer.arrayOffset()+buffer.limit()); + + if (_header!=null) + { + _headerString=_header.asString(); + buffer.position(buffer.position()+_headerString.length()); + _state=buffer.get(buffer.position()-1)==':'?State.HEADER_VALUE:State.HEADER_NAME; + break; + } + } + + // New header + _state=State.HEADER_NAME; + _string.setLength(0); + _string.append((char)ch); + _length=1; + } + } + } + + break; + + case HEADER_NAME: + switch(ch) + { + case HttpTokens.CARRIAGE_RETURN: + case HttpTokens.LINE_FEED: + _eol=ch; + _headerString=takeLengthString(); + _header=HttpHeader.CACHE.get(_headerString); + _state=State.HEADER; + + break; + + case HttpTokens.COLON: + if (_headerString==null) + { + _headerString=takeLengthString(); + _header=HttpHeader.CACHE.get(_headerString); + } + _state=State.HEADER_VALUE; + break; + case HttpTokens.SPACE: + case HttpTokens.TAB: + _string.append((char)ch); + break; + default: + { + if (_header!=null) + { + _string.setLength(0); + _string.append(_header.asString()); + _string.append(' '); + _length=_string.length(); + _header=null; + _headerString=null; + } + _string.append((char)ch); + _length=_string.length(); + _state=State.HEADER_IN_NAME; + } + } + + break; + + case HEADER_IN_NAME: + switch(ch) + { + case HttpTokens.CARRIAGE_RETURN: + case HttpTokens.LINE_FEED: + _eol=ch; + _headerString=takeString(); + _length=-1; + _header=HttpHeader.CACHE.get(_headerString); + _state=State.HEADER; + break; + + case HttpTokens.COLON: + if (_headerString==null) + { + _headerString=takeString(); + _header=HttpHeader.CACHE.get(_headerString); + } + _length=-1; + _state=State.HEADER_VALUE; + break; + case HttpTokens.SPACE: + case HttpTokens.TAB: + _state=State.HEADER_NAME; + _string.append((char)ch); + break; + default: + _string.append((char)ch); + _length++; + } + break; + + case HEADER_VALUE: + switch(ch) + { + case HttpTokens.CARRIAGE_RETURN: + case HttpTokens.LINE_FEED: + _eol=ch; + if (_length > 0) + { + if (_valueString!=null) + { + // multi line value! + _value=null; + _valueString+=" "+takeLengthString(); + } + else if (HttpHeaderValue.hasKnownValues(_header)) + { + _valueString=takeLengthString(); + _value=HttpHeaderValue.CACHE.get(_valueString); + } + else + { + _value=null; + _valueString=takeLengthString(); + } + } + _state=State.HEADER; + break; + case HttpTokens.SPACE: + case HttpTokens.TAB: + break; + default: + { + _string.append((char)ch); + _length=_string.length(); + _state=State.HEADER_IN_VALUE; + } + } + break; + + case HEADER_IN_VALUE: + switch(ch) + { + case HttpTokens.CARRIAGE_RETURN: + case HttpTokens.LINE_FEED: + _eol=ch; + if (_length > 0) + { + if (_valueString!=null) + { + // multi line value! + _value=null; + _valueString+=" "+takeString(); + } + else if (HttpHeaderValue.hasKnownValues(_header)) + { + _valueString=takeString(); + _value=HttpHeaderValue.CACHE.get(_valueString); + } + else + { + _value=null; + _valueString=takeString(); + } + _length=-1; + } + _state=State.HEADER; + break; + case HttpTokens.SPACE: + case HttpTokens.TAB: + _string.append((char)ch); + _state=State.HEADER_VALUE; + break; + default: + _string.append((char)ch); + _length++; + } + break; + + default: + throw new IllegalStateException(_state.toString()); + + } + } + + return return_from_parse; + } + /* ------------------------------------------------------------------------------- */ /** * Parse until next Event. @@ -216,9 +857,9 @@ public class HttpParser try { + // process end states if (_state == State.END) return false; - if (_state == State.CONTENT && _contentPosition == _contentLength) { _state=State.END; @@ -226,485 +867,38 @@ public class HttpParser return true; } - // Handle header states - byte ch; - int length=-1; - boolean return_from_parse=false; - - while (_state.ordinal() HttpTokens.SPACE || ch<0) - { - start=buffer.position()-1; - startState=_state; - _state=_requestHandler!=null?State.METHOD:State.RESPONSE_VERSION; - } - break; - - case METHOD: - if (ch == HttpTokens.SPACE) - { - HttpMethod method=HttpMethod.CACHE.get(buffer,start,buffer.position()-start-1); - _field0=method==null?BufferUtil.toString(buffer,start,buffer.position()-start-1,StringUtil.__ISO_8859_1_CHARSET):method.toString(); - _state=State.SPACE1; - } - else if (ch < HttpTokens.SPACE && ch>=0) - { - throw new HttpException(HttpStatus.BAD_REQUEST_400); - } - break; - - case RESPONSE_VERSION: - if (ch == HttpTokens.SPACE) - { - HttpVersion v=HttpVersion.CACHE.get(buffer,start,buffer.position()-start-1); - _field0=v==null?BufferUtil.toString(buffer,start,buffer.position()-start-1,StringUtil.__ISO_8859_1_CHARSET):v.toString(); - start=-1; - _persistent=HttpVersion.HTTP_1_1==v; - _state=State.SPACE1; - } - else if (ch < HttpTokens.SPACE && ch>=0) - { - throw new HttpException(HttpStatus.BAD_REQUEST_400); - } - break; - - case SPACE1: - if (ch > HttpTokens.SPACE || ch<0) - { - if (_responseHandler!=null) - { - _state=State.STATUS; - _responseStatus=ch-'0'; - } - else - { - start=buffer.position()-1; - startState=_state; - _state=State.URI; - } - } - else if (ch < HttpTokens.SPACE) - { - throw new HttpException(HttpStatus.BAD_REQUEST_400); - } - break; - - case STATUS: - if (ch == HttpTokens.SPACE) - { - _state=State.SPACE2; - } - else if (ch>='0' && ch<='9') - { - _responseStatus=_responseStatus*10+(ch-'0'); - } - else if (ch < HttpTokens.SPACE && ch>=0) - { - return_from_parse|=_responseHandler.startResponse(_field0, _responseStatus, null); - - _eol=ch; - _state=State.HEADER; - _field0=_field1=null; - } - else - { - throw new IllegalStateException(); - } - break; - - case URI: - if (ch == HttpTokens.SPACE) - { - _field1=BufferUtil.toString(buffer,start,buffer.position()-start-1,StringUtil.__UTF8_CHARSET); - start=-1; - _state=State.SPACE2; - } - else if (ch < HttpTokens.SPACE && ch>=0) - { - // HTTP/0.9 - _field1=BufferUtil.toString(buffer,start,buffer.position()-start-1,StringUtil.__UTF8_CHARSET); - start=-1; - return_from_parse|=_requestHandler.startRequest(_field0,_field1,null); - _persistent=false; - _state=State.SEEKING_EOF; - return_from_parse|=_handler.headerComplete(false,false); - return_from_parse|=_handler.messageComplete(_contentPosition); - } - break; - - case SPACE2: - if (ch > HttpTokens.SPACE || ch<0) - { - start=buffer.position()-1; - startState=_state; - _state=_requestHandler!=null?State.REQUEST_VERSION:State.REASON; - } - else if (ch < HttpTokens.SPACE) - { - if (_responseHandler!=null) - { - return_from_parse|=_responseHandler.startResponse(_field0, _responseStatus, null); - _eol=ch; - _state=State.HEADER; - _field0=_field1=null; - } - else - { - // HTTP/0.9 - return_from_parse|=_requestHandler.startRequest(_field0, _field1, null); - _persistent=false; - _state=State.SEEKING_EOF; - return_from_parse|=_handler.headerComplete(false,false); - return_from_parse|=_handler.messageComplete(_contentPosition); - } - } - break; - - case REQUEST_VERSION: - if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED) - { - HttpVersion v=HttpVersion.CACHE.get(buffer,start,buffer.position()-start-1); - String version=v==null?BufferUtil.toString(buffer,start,buffer.position()-start-1,StringUtil.__ISO_8859_1_CHARSET):v.toString(); - start=-1; - - return_from_parse|=_requestHandler.startRequest(_field0, _field1, version); - _eol=ch; - _persistent=HttpVersion.HTTP_1_1==v; - _state=State.HEADER; - _field0=_field1=null; - continue; - } - break; - - case REASON: - if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED) - { - String reason=BufferUtil.toString(buffer,start,buffer.position()-start-1,StringUtil.__ISO_8859_1_CHARSET); - start=-1; - - return_from_parse|=_responseHandler.startResponse(_field0, _responseStatus, reason); - _eol=ch; - _state=State.HEADER; - _field0=_field1=null; - continue; - } - break; - - case HEADER: - switch(ch) - { - case HttpTokens.COLON: - case HttpTokens.SPACE: - case HttpTokens.TAB: - { - // header value without name - continuation? - length=-1; - _state=State.HEADER_VALUE; - break; - } - - default: - { - // handler last header if any - if (_field0!=null || _field1!=null) - { - // Handle known headers - if (_header!=null) - { - switch (_header) - { - case CONTENT_LENGTH: - if (_endOfContent != EndOfContent.CHUNKED_CONTENT && _responseStatus!=304 && _responseStatus!=204 && (_responseStatus<100 || _responseStatus>=200)) - { - try - { - _contentLength=Long.parseLong(_field1); - } - catch(NumberFormatException e) - { - LOG.ignore(e); - throw new HttpException(HttpStatus.BAD_REQUEST_400); - } - if (_contentLength <= 0) - _endOfContent=EndOfContent.NO_CONTENT; - else - _endOfContent=EndOfContent.CONTENT_LENGTH; - } - break; - - case TRANSFER_ENCODING: - if (_value==HttpHeaderValue.CHUNKED) - _endOfContent=EndOfContent.CHUNKED_CONTENT; - else - { - if (_field1.endsWith(HttpHeaderValue.CHUNKED.toString())) - _endOfContent=EndOfContent.CHUNKED_CONTENT; - else if (_field1.indexOf(HttpHeaderValue.CHUNKED.toString()) >= 0) - throw new HttpException(400,null); - } - break; - - case CONNECTION: - switch(_value==null?HttpHeaderValue.UNKNOWN:_value) - { - case CLOSE: - _persistent=false; - break; - - case KEEP_ALIVE: - _persistent=true; - break; - - default: // No match, may be multi valued - { - for (String v : _field1.toString().split(",")) - { - switch(HttpHeaderValue.CACHE.get(v.trim())) - { - case CLOSE: - _persistent=false; - break; - - case KEEP_ALIVE: - _persistent=true; - break; - } - } - break; - } - } - } - } - - return_from_parse|=_handler.parsedHeader(_header, _field0, _field1); - } - _field0=_field1=null; - _header=null; - _value=null; - - - // now handle ch - if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED) - { - _eol=ch; - _contentPosition=0; - - // End of headers! - // work out the _content demarcation - if (_endOfContent == EndOfContent.UNKNOWN_CONTENT) - { - if (_responseStatus == 0 // request - || _responseStatus == 304 // not-modified response - || _responseStatus == 204 // no-content response - || _responseStatus < 200) // 1xx response - _endOfContent=EndOfContent.NO_CONTENT; - else - _endOfContent=EndOfContent.EOF_CONTENT; - } - - // How is the message ended? - switch (_endOfContent) - { - case EOF_CONTENT: - _state=State.EOF_CONTENT; - return_from_parse|=_handler.headerComplete(true,false); - break; - - case CHUNKED_CONTENT: - _state=State.CHUNKED_CONTENT; - return_from_parse|=_handler.headerComplete(true,_persistent); - break; - - case NO_CONTENT: - return_from_parse|=_handler.headerComplete(false,_persistent); - _state=_persistent||(_responseStatus>=100&&_responseStatus<200)?State.END:State.SEEKING_EOF; - return_from_parse|=_handler.messageComplete(_contentPosition); - break; - - default: - _state=State.CONTENT; - return_from_parse|=_handler.headerComplete(true,_persistent); - break; - } - } - else - { - // New header - start=buffer.position()-1; - startState=_state; - length=1; - _state=State.HEADER_NAME; - } - } - } - - break; - - case HEADER_NAME: - switch(ch) - { - case HttpTokens.CARRIAGE_RETURN: - case HttpTokens.LINE_FEED: - _eol=ch; - _header=HttpHeader.CACHE.get(buffer,start,length); - _field0=_header==null?BufferUtil.toString(buffer,start,length,StringUtil.__ISO_8859_1_CHARSET):_header.toString(); - start=length=-1; - _state=State.HEADER; - break; - - case HttpTokens.COLON: - _header=HttpHeader.CACHE.get(buffer,start,length); - _field0=_header==null?BufferUtil.toString(buffer,start,length,StringUtil.__ISO_8859_1_CHARSET):_header.toString(); - start=length=-1; - _state=State.HEADER_VALUE; - break; - case HttpTokens.SPACE: - case HttpTokens.TAB: - break; - default: - { - length=buffer.position()-start; - _state=State.HEADER_IN_NAME; - } - } - - break; - - case HEADER_IN_NAME: - switch(ch) - { - case HttpTokens.CARRIAGE_RETURN: - case HttpTokens.LINE_FEED: - _eol=ch; - _header=HttpHeader.CACHE.get(buffer,start,length); - _field0=_header==null?BufferUtil.toString(buffer,start,length,StringUtil.__ISO_8859_1_CHARSET):_header.toString(); - start=length=-1; - _state=State.HEADER; - break; - - case HttpTokens.COLON: - _header=HttpHeader.CACHE.get(buffer,start,length); - _field0=_header==null?BufferUtil.toString(buffer,start,length,StringUtil.__ISO_8859_1_CHARSET):_header.toString(); - start=length=-1; - _state=State.HEADER_VALUE; - break; - case HttpTokens.SPACE: - case HttpTokens.TAB: - _state=State.HEADER_NAME; - break; - default: - length++; - } - break; - - case HEADER_VALUE: - switch(ch) - { - case HttpTokens.CARRIAGE_RETURN: - case HttpTokens.LINE_FEED: - _eol=ch; - if (length > 0) - { - if (_field1!=null) - { - // multi line value! - _value=null; - _field1+=" "+BufferUtil.toString(buffer,start,length,StringUtil.__ISO_8859_1_CHARSET); - } - else if (HttpHeaderValue.hasKnownValues(_header)) - { - _value=HttpHeaderValue.CACHE.get(buffer,start,length); - _field1=_value.toString(); - } - else - { - _field1=BufferUtil.toString(buffer,start,length,StringUtil.__ISO_8859_1_CHARSET); - } - start=length=-1; - } - _state=State.HEADER; - break; - case HttpTokens.SPACE: - case HttpTokens.TAB: - break; - default: - { - if (start==-1) - { - start=buffer.position()-1; - startState=_state; - } - length=buffer.position()-start; - _state=State.HEADER_IN_VALUE; - } - } - break; - - case HEADER_IN_VALUE: - switch(ch) - { - case HttpTokens.CARRIAGE_RETURN: - case HttpTokens.LINE_FEED: - _eol=ch; - if (length > 0) - { - if (_field1!=null) - { - // multi line value! - _value=null; - _field1+=" "+BufferUtil.toString(buffer,start,length,StringUtil.__ISO_8859_1_CHARSET); - } - else if (HttpHeaderValue.hasKnownValues(_header)) - { - _value=HttpHeaderValue.CACHE.get(buffer,start,length); - _field1=_value!=null?_value.toString():BufferUtil.toString(buffer,start,length,StringUtil.__ISO_8859_1_CHARSET); - } - else - { - _field1=BufferUtil.toString(buffer,start,length,StringUtil.__ISO_8859_1_CHARSET); - } - start=length=-1; - } - _state=State.HEADER; - break; - case HttpTokens.SPACE: - case HttpTokens.TAB: - _state=State.HEADER_VALUE; - break; - default: - length++; - } - break; - } - } - // end of HEADER states loop - // ========================== - + _version=null; + _method=null; + _methodString=null; + _uri=null; + _endOfContent=EndOfContent.UNKNOWN_CONTENT; + _header=null; + quickStart(buffer); + } + + // Request/response line + if (_state.ordinal()0 && _headResponse) { _state=_persistent||(_responseStatus>=100&&_responseStatus<200)?State.END:State.SEEKING_EOF; - return_from_parse|=_handler.messageComplete(_contentLength); + if (_handler.messageComplete(_contentLength)) + return true; } - // ========================== // Handle _content + byte ch; while (_state.ordinal() > State.END.ordinal() && buffer.hasRemaining()) { if (_eol == HttpTokens.CARRIAGE_RETURN && buffer.get(buffer.position()) == HttpTokens.LINE_FEED) @@ -720,7 +914,8 @@ public class HttpParser _contentChunk=buffer.asReadOnlyBuffer(); _contentPosition += _contentChunk.remaining(); buffer.position(buffer.position()+_contentChunk.remaining()); - return_from_parse|=_handler.content(_contentChunk); + if (_handler.content(_contentChunk)) + return true; break; case CONTENT: @@ -729,7 +924,8 @@ public class HttpParser if (remaining == 0) { _state=_persistent?State.END:State.SEEKING_EOF; - return_from_parse|=_handler.messageComplete(_contentPosition); + if (_handler.messageComplete(_contentPosition)) + return true; } else { @@ -745,12 +941,15 @@ public class HttpParser _contentPosition += _contentChunk.remaining(); buffer.position(buffer.position()+_contentChunk.remaining()); - return_from_parse|=_handler.content(_contentChunk); + + if (_handler.content(_contentChunk)) + return true; if(_contentPosition == _contentLength) { _state=_persistent?State.END:State.SEEKING_EOF; - return_from_parse|=_handler.messageComplete(_contentPosition); + if (_handler.messageComplete(_contentPosition)) + return true; } } break; @@ -784,7 +983,8 @@ public class HttpParser if (_eol==HttpTokens.CARRIAGE_RETURN && buffer.hasRemaining() && buffer.get(buffer.position())==HttpTokens.LINE_FEED) _eol=buffer.get(); _state=_persistent?State.END:State.SEEKING_EOF; - return_from_parse|=_handler.messageComplete(_contentPosition); + if (_handler.messageComplete(_contentPosition)) + return true; } else _state=State.CHUNK; @@ -813,7 +1013,8 @@ public class HttpParser if (_eol==HttpTokens.CARRIAGE_RETURN && buffer.hasRemaining() && buffer.get(buffer.position())==HttpTokens.LINE_FEED) _eol=buffer.get(); _state=_persistent?State.END:State.SEEKING_EOF; - return_from_parse|=_handler.messageComplete(_contentPosition); + if (_handler.messageComplete(_contentPosition)) + return true; } else _state=State.CHUNK; @@ -839,7 +1040,8 @@ public class HttpParser _contentPosition += remaining; _chunkPosition += remaining; buffer.position(buffer.position()+remaining); - return_from_parse|=_handler.content(_contentChunk); + if (_handler.content(_contentChunk)) + return true; } break; } @@ -850,10 +1052,9 @@ public class HttpParser break; } } - } - return return_from_parse; + return false; } catch(HttpException e) { @@ -865,6 +1066,7 @@ public class HttpParser { if (start>=0) { + _string.setLength(0); buffer.position(start); _state=startState; } @@ -964,7 +1166,7 @@ public class HttpParser /** * This is the method called by parser when the HTTP request line is parsed */ - public abstract boolean startRequest(String method, String uri, String version) + public abstract boolean startRequest(HttpMethod method, String methodString, String uri, HttpVersion version) throws IOException; } @@ -973,7 +1175,7 @@ public class HttpParser /** * This is the method called by parser when the HTTP request line is parsed */ - public abstract boolean startResponse(String version, int status, String reason) + public abstract boolean startResponse(HttpVersion version, int status, String reason) throws IOException; } diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpVersion.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpVersion.java index daf69216a99..e403696a388 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpVersion.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpVersion.java @@ -24,7 +24,8 @@ public enum HttpVersion { HTTP_0_9("HTTP/0.9",9), HTTP_1_0("HTTP/1.0",10), - HTTP_1_1("HTTP/1.1",11); + HTTP_1_1("HTTP/1.1",11), + HTTP_2_0("HTTP/2.0",20); /* ------------------------------------------------------------ */ public final static StringMap CACHE= new StringMap(true); @@ -34,6 +35,64 @@ public enum HttpVersion CACHE.put(version.toString(),version); } + /* ------------------------------------------------------------ */ + /** + * Optimised lookup to find a Http Version and whitespace in a byte array. + * @param bytes Array containing ISO-8859-1 characters + * @param position The first valid index + * @param limit The first non valid index + * @return A HttpMethod if a match or null if no easy match. + */ + public static HttpVersion lookAheadGet(byte[] bytes, int position, int limit) + { + int length=limit-position; + if (length<9) + return null; + + if (bytes[position+4]=='/' && bytes[position+6]=='.' && Character.isWhitespace((char)bytes[position+8]) && + ((bytes[position]=='H' && bytes[position+1]=='T' && bytes[position+2]=='T' && bytes[position+3]=='P') || + (bytes[position]=='h' && bytes[position+1]=='t' && bytes[position+2]=='t' && bytes[position+3]=='p'))) + { + switch(bytes[position+5]) + { + case '1': + switch(bytes[position+7]) + { + case '0': + return HTTP_1_0; + case '1': + return HTTP_1_1; + } + break; + case '2': + switch(bytes[position+7]) + { + case '0': + return HTTP_2_0; + } + break; + } + } + + return null; + } + + /* ------------------------------------------------------------ */ + /** + * Optimised lookup to find a HTTP Version and trailing white space in a byte array. + * @param bytes Array containing ISO-8859-1 characters + * @param position The first valid index + * @param limit The first non valid index + * @return A HttpVersion if a match or null if no easy match. + */ + public static HttpVersion lookAheadGet(ByteBuffer buffer) + { + if (buffer.hasArray()) + return lookAheadGet(buffer.array(),buffer.arrayOffset()+buffer.position(),buffer.arrayOffset()+buffer.limit()); + return null; + } + + private final String _string; private final byte[] _bytes; private final ByteBuffer _buffer; @@ -72,6 +131,12 @@ public enum HttpVersion return _string.equalsIgnoreCase(s); } + /* ------------------------------------------------------------ */ + public String asString() + { + return _string; + } + /* ------------------------------------------------------------ */ @Override public String toString() diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorServerTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorServerTest.java index b25416021be..05aa204c7ba 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorServerTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorServerTest.java @@ -23,7 +23,6 @@ import static org.junit.matchers.JUnitMatchers.containsString; import static org.junit.matchers.JUnitMatchers.either; import java.io.IOException; -import java.nio.Buffer; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; @@ -81,7 +80,7 @@ public class HttpGeneratorServerTest } @Override - public boolean startResponse(String version, int status, String reason) throws IOException + public boolean startResponse(HttpVersion version, int status, String reason) throws IOException { _version=version; _status=status; @@ -273,7 +272,7 @@ public class HttpGeneratorServerTest private int _status; private final List _val=new ArrayList<>(); - private String _version; + private HttpVersion _version; private final TR[] tr = { @@ -309,15 +308,15 @@ public class HttpGeneratorServerTest for (int c=0;c<(v==11?connect.length:(connect.length-1));c++) { String t="v="+v+",chunks="+chunks+",connect="+connect[c]+",tr="+r+"="+tr[r]; - System.err.println("\n=========================================="); - System.err.println(t); + // System.err.println("\n=========================================="); + // System.err.println(t); gen.reset(); tr[r].getHttpFields().clear(); String response=tr[r].build(v,gen,"OK\r\nTest",connect[c],null,chunks); - System.err.println("---\n"+t+"\n"+response+(gen.isPersistent()?"...":"===")); + // System.err.println("---\n"+t+"\n"+response+(gen.isPersistent()?"...":"===")); if (v==9) { diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java index d688da87402..488638b4d9d 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java @@ -120,12 +120,13 @@ public class HttpParserTest "GET / HTTP/1.0\015\012" + "Host: localhost\015\012" + "Header1: value1\015\012" + - "Header2 : value 2a \015\012" + + "Header 2 : value 2a \015\012" + " value 2b \015\012" + "Header3: \015\012" + "Header4 \015\012" + " value4\015\012" + - "Server5: notServer\015\012" + + "Server5 : notServer\015\012" + + "Host Header: notHost\015\012" + "\015\012"); Handler handler = new Handler(); HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler); @@ -138,7 +139,7 @@ public class HttpParserTest assertEquals("localhost", val[0]); assertEquals("Header1", hdr[1]); assertEquals("value1", val[1]); - assertEquals("Header2", hdr[2]); + assertEquals("Header 2", hdr[2]); assertEquals("value 2a value 2b", val[2]); assertEquals("Header3", hdr[3]); assertEquals(null, val[3]); @@ -146,23 +147,25 @@ public class HttpParserTest assertEquals("value4", val[4]); assertEquals("Server5", hdr[5]); assertEquals("notServer", val[5]); - assertEquals(5, h); + assertEquals("Host Header", hdr[6]); + assertEquals("notHost", val[6]); + assertEquals(6, h); } @Test public void testSplitHeaderParse() throws Exception { ByteBuffer buffer= BufferUtil.toBuffer( - "XXXXGET / HTTP/1.0\015\012" + - "Host: localhost\015\012" + - "Header1: value1\015\012" + - "Header2 : value 2a \015\012" + - " value 2b \015\012" + - "Header3: \015\012" + - "Header4 \015\012" + - " value4\015\012" + - "Server5: notServer\015\012" + - "\015\012ZZZZ"); + "XXXXSPLIT / HTTP/1.0\015\012" + + "Host: localhost\015\012" + + "Header1: value1\015\012" + + "Header2 : value 2a \015\012" + + " value 2b \015\012" + + "Header3: \015\012" + + "Header4 \015\012" + + " value4\015\012" + + "Server5: notServer\015\012" + + "\015\012ZZZZ"); buffer.position(2); buffer.limit(buffer.capacity()-2); buffer=buffer.slice(); @@ -177,11 +180,15 @@ public class HttpParserTest if (!parser.parseNext(buffer)) { + // consumed all + assertEquals(0,buffer.remaining()); + + // parse the rest buffer.limit(buffer.capacity()-2); parser.parseNext(buffer); } - assertEquals("GET", f0); + assertEquals("SPLIT", f0); assertEquals("/", f1); assertEquals("HTTP/1.0", f2); assertEquals("Host", hdr[0]); @@ -478,7 +485,7 @@ public class HttpParserTest } @Override - public boolean startRequest(String method, String uri, String version) + public boolean startRequest(HttpMethod httpMethod, String method, String uri, HttpVersion version) { request=true; h= -1; @@ -486,7 +493,7 @@ public class HttpParserTest val= new String[9]; f0= method; f1= uri; - f2= version; + f2= version==null?null:version.asString(); fields=new HttpFields(); messageCompleted = false; @@ -530,10 +537,10 @@ public class HttpParserTest } @Override - public boolean startResponse(String version, int status, String reason) + public boolean startResponse(HttpVersion version, int status, String reason) { request=false; - f0 = version.toString(); + f0 = version.asString(); f1 = Integer.toString(status); f2 = reason==null?null:reason.toString(); diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java index 9f9579c0059..4c0ffd8daae 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java @@ -297,6 +297,8 @@ public abstract class HttpChannel _responseFields.clear(); _response.recycle(); _uri.clear(); + if (_out!=null) + _out.reset(); synchronized (_inputQ) { _inputEOF=false; @@ -437,8 +439,13 @@ public abstract class HttpChannel _response.setStatus(status,reason); if (close) _responseFields.add(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE); - ByteBuffer buffer=BufferUtil.toBuffer(content,StringUtil.__UTF8_CHARSET); - _response.setContentLength(buffer.remaining()); + + ByteBuffer buffer=null; + if (content!=null) + { + buffer=BufferUtil.toBuffer(content,StringUtil.__UTF8_CHARSET); + _response.setContentLength(buffer.remaining()); + } HttpGenerator.ResponseInfo info = _handler.commit(); commit(info,buffer); @@ -555,7 +562,7 @@ public abstract class HttpChannel private class RequestHandler implements EventHandler { @Override - public boolean startRequest(String method, String uri, String version) throws IOException + public boolean startRequest(HttpMethod httpMethod,String method, String uri, HttpVersion version) throws IOException { _host = false; _expect = false; @@ -564,30 +571,18 @@ public abstract class HttpChannel if(_request.getTimeStamp()==0) _request.setTimeStamp(System.currentTimeMillis()); - HttpMethod m = HttpMethod.CACHE.get(method); - _request.setMethod(m,method); + _request.setMethod(httpMethod,method); try { - if (m==HttpMethod.CONNECT) + if (httpMethod==HttpMethod.CONNECT) _uri.parseConnect(uri); else _uri.parse(uri); _request.setUri(_uri); - - if (version==null) - { - _request.setHttpVersion(HttpVersion.HTTP_0_9); - _version=HttpVersion.HTTP_0_9; - } - else - { - _version= HttpVersion.CACHE.get(version); - if (_version==null) - throw new HttpException(HttpStatus.BAD_REQUEST_400,null); - _request.setHttpVersion(_version); - } + _version=version==null?HttpVersion.HTTP_0_9:version; + _request.setHttpVersion(_version); } catch (Exception e) { @@ -713,7 +708,10 @@ public abstract class HttpChannel @Override public boolean content(ByteBuffer ref) throws IOException { - _inputQ.add(ref); + synchronized (_inputQ.lock()) + { + _inputQ.add(ref); + } return true; } @@ -826,9 +824,7 @@ public abstract class HttpChannel public abstract HttpConnector getHttpConnector(); protected abstract void blockForContent() throws IOException; - - protected abstract void contentConsumed(); - + protected abstract int write(ByteBuffer content) throws IOException; protected abstract void commit(ResponseInfo info, ByteBuffer content) throws IOException; diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java index 7e2deb375fa..828dcaa6d6c 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java @@ -200,8 +200,15 @@ public class HttpConnection extends AbstractAsyncConnection ?_bufferPool.acquire(_connector.getRequestBufferSize(),false) :_bufferPool.acquire(_connector.getRequestHeaderSize(),false); - int filled=getEndPoint().fill(_requestBuffer); - LOG.debug("{} filled {}",this,filled); + // Only fill if buffer is fully consumed + if (BufferUtil.isEmpty(_requestBuffer)) + { + // TODO this is still dangerous as a suspended request might have references + // to the buffer still for unconsumed input. We need a callback to say + // all content is consumed and we can read again. + int filled=getEndPoint().fill(_requestBuffer); + LOG.debug("{} filled {}",this,filled); + } // If we parse to an event, call the connection if (BufferUtil.hasContent(_requestBuffer) && _parser.parseNext(_requestBuffer)) @@ -220,6 +227,9 @@ public class HttpConnection extends AbstractAsyncConnection getEndPoint().setCheckForIdle(true); } } + else if (BufferUtil.hasContent(_requestBuffer)) + throw new IllegalStateException("parser should consume all content OR return true"); + } catch (HttpException e) @@ -565,6 +575,7 @@ public class HttpConnection extends AbstractAsyncConnection protected void blockForContent() throws IOException { // While progress and the connection has not changed + boolean parsed_event=false; while (getEndPoint().isOpen()) { try @@ -578,8 +589,13 @@ public class HttpConnection extends AbstractAsyncConnection if (_requestBuffer==null) _requestBuffer=_bufferPool.acquire(_connector.getRequestBufferSize(),false); + int filled=getEndPoint().fill(_requestBuffer); + LOG.debug("{} filled {}",this,filled); + // If we parse to an event, return - if (BufferUtil.hasContent(_requestBuffer) && _parser.parseNext(_requestBuffer)) + while (BufferUtil.hasContent(_requestBuffer) && _parser.inContentState()) + parsed_event|=_parser.parseNext(_requestBuffer); + if (parsed_event) return; } catch (InterruptedException e) @@ -603,13 +619,6 @@ public class HttpConnection extends AbstractAsyncConnection } } - @Override - protected void contentConsumed() - { - // TODO Auto-generated method stub - - } - @Override protected void commit(ResponseInfo info, ByteBuffer content) throws IOException { diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/StringMap.java b/jetty-util/src/main/java/org/eclipse/jetty/util/StringMap.java index 29a54ff8924..1ff5eee6e8d 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/StringMap.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/StringMap.java @@ -71,7 +71,6 @@ public class StringMap extends AbstractMap s1=o1.toString(); String s2=(String)o2; - int n1 = s1==null?b1.remaining():s1.length(); int n2 = s2.length(); int min = Math.min(n1, n2); @@ -79,7 +78,6 @@ public class StringMap extends AbstractMap char c1 = s1==null?(char)b1.get(b1.position()+i):s1.charAt(i); char c2 = s2.charAt(i); if (c1 != c2) { - if (ignoreCase) { c1 = Character.toUpperCase(c1); @@ -98,11 +96,8 @@ public class StringMap extends AbstractMap } } return n1 - n2; - } }); - - } /* ------------------------------------------------------------ */ @@ -138,17 +133,11 @@ public class StringMap extends AbstractMap } /* ------------------------------------------------------------ */ - public O get(ByteBuffer buffer, int position, int length) + public O get(ByteBuffer buffer) { - ByteBuffer ro=buffer.asReadOnlyBuffer(); - ro.limit(ro.capacity()); - ro.position(position); - ro.limit(position+length); - return _map.get(ro); + return _map.get(buffer); } - - /* ------------------------------------------------------------ */ @Override public O remove(Object key) @@ -156,7 +145,6 @@ public class StringMap extends AbstractMap return _map.remove(key); } - /* ------------------------------------------------------------ */ public O remove(String key) { diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java b/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java index 45ec954d6e7..bce1318dd4a 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java @@ -88,7 +88,11 @@ public class StringUtil */ public static String normalizeCharset(ByteBuffer b,int position,int length) { - String n=CHARSETS.get(b,position,length); + ByteBuffer ro=b.asReadOnlyBuffer(); + ro.limit(ro.capacity()); + ro.position(position); + ro.limit(position+length); + String n=CHARSETS.get(ro); if (n!=null) return n; ByteBuffer slice = b.slice();