diff options
Diffstat (limited to 'jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java')
-rw-r--r-- | jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java | 482 |
1 files changed, 187 insertions, 295 deletions
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java index 6f7ead3ec3..79e5a892bd 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java @@ -21,7 +21,6 @@ package org.eclipse.jetty.http; import java.io.IOException; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.HashSet; import java.util.Set; @@ -32,28 +31,26 @@ import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; -/* ------------------------------------------------------------ */ /** * HttpGenerator. Builds HTTP Messages. - * + * <p> * If the system property "org.eclipse.jetty.http.HttpGenerator.STRICT" is set to true, * then the generator will strictly pass on the exact strings received from methods and header * fields. Otherwise a fast case insensitive string lookup is used that may alter the * case and white space of some methods/headers - * </p> */ public class HttpGenerator { private final static Logger LOG = Log.getLogger(HttpGenerator.class); - public final static boolean __STRICT=Boolean.getBoolean("org.eclipse.jetty.http.HttpGenerator.STRICT"); + public final static boolean __STRICT=Boolean.getBoolean("org.eclipse.jetty.http.HttpGenerator.STRICT"); private final static byte[] __colon_space = new byte[] {':',' '}; private final static HttpHeaderValue[] CLOSE = {HttpHeaderValue.CLOSE}; - public static final ResponseInfo CONTINUE_100_INFO = new ResponseInfo(HttpVersion.HTTP_1_1,null,-1,100,null,false); - public static final ResponseInfo PROGRESS_102_INFO = new ResponseInfo(HttpVersion.HTTP_1_1,null,-1,102,null,false); - public final static ResponseInfo RESPONSE_500_INFO = - new ResponseInfo(HttpVersion.HTTP_1_1,new HttpFields(){{put(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE);}},0,HttpStatus.INTERNAL_SERVER_ERROR_500,null,false); + public static final MetaData.Response CONTINUE_100_INFO = new MetaData.Response(HttpVersion.HTTP_1_1,100,null,null,-1); + public static final MetaData.Response PROGRESS_102_INFO = new MetaData.Response(HttpVersion.HTTP_1_1,102,null,null,-1); + public final static MetaData.Response RESPONSE_500_INFO = + new MetaData.Response(HttpVersion.HTTP_1_1,HttpStatus.INTERNAL_SERVER_ERROR_500,null,new HttpFields(){{put(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE);}},0); // states public enum State { START, COMMITTED, COMPLETING, COMPLETING_1XX, END } @@ -92,7 +89,7 @@ public class HttpGenerator { this(false,false); } - + /* ------------------------------------------------------------------------------- */ public HttpGenerator(boolean sendServerVersion,boolean sendXPoweredBy) { @@ -165,7 +162,7 @@ public class HttpGenerator { return _noContent; } - + /* ------------------------------------------------------------ */ public void setPersistent(boolean persistent) { @@ -202,7 +199,7 @@ public class HttpGenerator } /* ------------------------------------------------------------ */ - public Result generateRequest(RequestInfo info, ByteBuffer header, ByteBuffer chunk, ByteBuffer content, boolean last) throws IOException + public Result generateRequest(MetaData.Request info, ByteBuffer header, ByteBuffer chunk, ByteBuffer content, boolean last) throws IOException { switch(_state) { @@ -211,13 +208,16 @@ public class HttpGenerator if (info==null) return Result.NEED_INFO; - // Do we need a request header if (header==null) return Result.NEED_HEADER; // If we have not been told our persistence, set the default if (_persistent==null) - _persistent=(info.getHttpVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal()); + { + _persistent=info.getVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal(); + if (!_persistent && HttpMethod.CONNECT.is(info.getMethod())) + _persistent=true; + } // prepare the header int pos=BufferUtil.flipToFill(header); @@ -226,12 +226,12 @@ public class HttpGenerator // generate ResponseLine generateRequestLine(info,header); - if (info.getHttpVersion()==HttpVersion.HTTP_0_9) - _noContent=true; - else - generateHeaders(info,header,content,last); + if (info.getVersion()==HttpVersion.HTTP_0_9) + throw new IllegalArgumentException("HTTP/0.9 not supported"); + + generateHeaders(info,header,content,last); - boolean expect100 = info.getHttpFields().contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString()); + boolean expect100 = info.getFields().contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString()); if (expect100) { @@ -254,7 +254,7 @@ public class HttpGenerator } catch(Exception e) { - String message= (e instanceof BufferOverflowException)?"Response header too large":e.getMessage(); + String message= (e instanceof BufferOverflowException)?"Request header too large":e.getMessage(); throw new IOException(message,e); } finally @@ -283,12 +283,9 @@ public class HttpGenerator } if (last) - { _state=State.COMPLETING; - return len>0?Result.FLUSH:Result.CONTINUE; - } - return Result.FLUSH; + return len>0?Result.FLUSH:Result.CONTINUE; } case COMPLETING: @@ -331,7 +328,13 @@ public class HttpGenerator } /* ------------------------------------------------------------ */ - public Result generateResponse(ResponseInfo info, ByteBuffer header, ByteBuffer chunk, ByteBuffer content, boolean last) throws IOException + public Result generateResponse(MetaData.Response info, ByteBuffer header, ByteBuffer chunk, ByteBuffer content, boolean last) throws IOException + { + return generateResponse(info,false,header,chunk,content,last); + } + + /* ------------------------------------------------------------ */ + public Result generateResponse(MetaData.Response info, boolean head, ByteBuffer header, ByteBuffer chunk, ByteBuffer content, boolean last) throws IOException { switch(_state) { @@ -339,26 +342,27 @@ public class HttpGenerator { if (info==null) return Result.NEED_INFO; - - // Handle 0.9 - if (info.getHttpVersion() == HttpVersion.HTTP_0_9) + + switch(info.getVersion()) { - _persistent = false; - _endOfContent=EndOfContent.EOF_CONTENT; - if (BufferUtil.hasContent(content)) - _contentPrepared+=content.remaining(); - _state = last?State.COMPLETING:State.COMMITTED; - return Result.FLUSH; + case HTTP_1_0: + if (_persistent==null) + _persistent=Boolean.FALSE; + break; + + case HTTP_1_1: + if (_persistent==null) + _persistent=Boolean.TRUE; + break; + + default: + throw new IllegalArgumentException(info.getVersion()+" not supported"); } - + // Do we need a response header if (header==null) return Result.NEED_HEADER; - // If we have not been told our persistence, set the default - if (_persistent==null) - _persistent=(info.getHttpVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal()); - // prepare the header int pos=BufferUtil.flipToFill(header); try @@ -391,7 +395,7 @@ public class HttpGenerator if (len>0) { _contentPrepared+=len; - if (isChunking() && !info.isHead()) + if (isChunking() && !head) prepareChunk(header,len); } _state = last?State.COMPLETING:State.COMMITTED; @@ -506,26 +510,18 @@ public class HttpGenerator } /* ------------------------------------------------------------ */ - private void generateRequestLine(RequestInfo request,ByteBuffer header) + private void generateRequestLine(MetaData.Request request,ByteBuffer header) { header.put(StringUtil.getBytes(request.getMethod())); header.put((byte)' '); - header.put(StringUtil.getBytes(request.getUri())); - switch(request.getHttpVersion()) - { - case HTTP_1_0: - case HTTP_1_1: - header.put((byte)' '); - header.put(request.getHttpVersion().toBytes()); - break; - default: - throw new IllegalStateException(); - } + header.put(StringUtil.getBytes(request.getURIString())); + header.put((byte)' '); + header.put(request.getVersion().toBytes()); header.put(HttpTokens.CRLF); } /* ------------------------------------------------------------ */ - private void generateResponseLine(ResponseInfo response, ByteBuffer header) + private void generateResponseLine(MetaData.Response response, ByteBuffer header) { // Look for prepared response line int status=response.getStatus(); @@ -575,10 +571,10 @@ public class HttpGenerator } /* ------------------------------------------------------------ */ - private void generateHeaders(Info _info,ByteBuffer header,ByteBuffer content,boolean last) + private void generateHeaders(MetaData _info,ByteBuffer header,ByteBuffer content,boolean last) { - final RequestInfo request=(_info instanceof RequestInfo)?(RequestInfo)_info:null; - final ResponseInfo response=(_info instanceof ResponseInfo)?(ResponseInfo)_info:null; + final MetaData.Request request=(_info instanceof MetaData.Request)?(MetaData.Request)_info:null; + final MetaData.Response response=(_info instanceof MetaData.Response)?(MetaData.Response)_info:null; // default field values int send=_send; @@ -587,122 +583,132 @@ public class HttpGenerator boolean close=false; boolean content_type=false; StringBuilder connection = null; + long content_length = _info.getContentLength(); // Generate fields - if (_info.getHttpFields() != null) + HttpFields fields = _info.getFields(); + if (fields != null) { - for (HttpField field : _info.getHttpFields()) + int n=fields.size(); + for (int f=0;f<n;f++) { - HttpHeader h = field.getHeader(); + HttpField field = fields.getField(f); + String v = field.getValue(); + if (v==null || v.length()==0) + continue; // rfc7230 does not allow no value - switch (h==null?HttpHeader.UNKNOWN:h) + HttpHeader h = field.getHeader(); + if (h==null) + putTo(field,header); + else { - case CONTENT_LENGTH: - // handle specially below - if (_info.getContentLength()>=0) - _endOfContent=EndOfContent.CONTENT_LENGTH; - break; - - case CONTENT_TYPE: + switch (h) { - if (field.getValue().startsWith(MimeTypes.Type.MULTIPART_BYTERANGES.toString())) - _endOfContent=EndOfContent.SELF_DEFINING_CONTENT; - - // write the field to the header - content_type=true; - putTo(field,header); - break; - } - - case TRANSFER_ENCODING: - { - if (_info.getHttpVersion() == HttpVersion.HTTP_1_1) - transfer_encoding = field; - // Do NOT add yet! - break; - } + case CONTENT_LENGTH: + _endOfContent=EndOfContent.CONTENT_LENGTH; + if (content_length<0) + content_length=Long.valueOf(field.getValue()); + // handle setting the field specially below + break; - case CONNECTION: - { - if (request!=null) + case CONTENT_TYPE: + { + // write the field to the header + content_type=true; putTo(field,header); + break; + } - // Lookup and/or split connection value field - HttpHeaderValue[] values = HttpHeaderValue.CLOSE.is(field.getValue())?CLOSE:new HttpHeaderValue[]{HttpHeaderValue.CACHE.get(field.getValue())}; - String[] split = null; - - if (values[0]==null) + case TRANSFER_ENCODING: { - split = StringUtil.csvSplit(field.getValue()); - if (split.length>0) - { - values=new HttpHeaderValue[split.length]; - for (int i=0;i<split.length;i++) - values[i]=HttpHeaderValue.CACHE.get(split[i]); - } + if (_info.getVersion() == HttpVersion.HTTP_1_1) + transfer_encoding = field; + // Do NOT add yet! + break; } - // Handle connection values - for (int i=0;i<values.length;i++) + case CONNECTION: { - HttpHeaderValue value=values[i]; - switch (value==null?HttpHeaderValue.UNKNOWN:value) + if (request!=null) + putTo(field,header); + + // Lookup and/or split connection value field + HttpHeaderValue[] values = HttpHeaderValue.CLOSE.is(field.getValue())?CLOSE:new HttpHeaderValue[]{HttpHeaderValue.CACHE.get(field.getValue())}; + String[] split = null; + + if (values[0]==null) { - case UPGRADE: + split = StringUtil.csvSplit(field.getValue()); + if (split.length>0) { - // special case for websocket connection ordering - header.put(HttpHeader.CONNECTION.getBytesColonSpace()).put(HttpHeader.UPGRADE.getBytes()); - header.put(CRLF); - break; + values=new HttpHeaderValue[split.length]; + for (int i=0;i<split.length;i++) + values[i]=HttpHeaderValue.CACHE.get(split[i]); } + } - case CLOSE: + // Handle connection values + for (int i=0;i<values.length;i++) + { + HttpHeaderValue value=values[i]; + switch (value==null?HttpHeaderValue.UNKNOWN:value) { - close=true; - if (response!=null) + case UPGRADE: { - _persistent=false; - if (_endOfContent == EndOfContent.UNKNOWN_CONTENT) - _endOfContent=EndOfContent.EOF_CONTENT; + // special case for websocket connection ordering + header.put(HttpHeader.CONNECTION.getBytesColonSpace()).put(HttpHeader.UPGRADE.getBytes()); + header.put(CRLF); + break; } - break; - } - case KEEP_ALIVE: - { - if (_info.getHttpVersion() == HttpVersion.HTTP_1_0) + case CLOSE: { - keep_alive = true; + close=true; + _persistent=false; if (response!=null) - _persistent=true; + { + if (_endOfContent == EndOfContent.UNKNOWN_CONTENT) + _endOfContent=EndOfContent.EOF_CONTENT; + } + break; } - break; - } - default: - { - if (connection==null) - connection=new StringBuilder(); - else - connection.append(','); - connection.append(split==null?field.getValue():split[i]); + case KEEP_ALIVE: + { + if (_info.getVersion() == HttpVersion.HTTP_1_0) + { + keep_alive = true; + if (response!=null) + _persistent=true; + } + break; + } + + default: + { + if (connection==null) + connection=new StringBuilder(); + else + connection.append(','); + connection.append(split==null?field.getValue():split[i]); + } } } + + // Do NOT add yet! + break; } - // Do NOT add yet! - break; - } + case SERVER: + { + send=send&~SEND_SERVER; + putTo(field,header); + break; + } - case SERVER: - { - send=send&~SEND_SERVER; - putTo(field,header); - break; + default: + putTo(field,header); } - - default: - putTo(field,header); } } } @@ -710,13 +716,15 @@ public class HttpGenerator // Calculate how to end _content and connection, _content length and transfer encoding // settings. + // From http://tools.ietf.org/html/rfc7230#section-3.3.3 // From RFC 2616 4.4: // 1. No body for 1xx, 204, 304 & HEAD response - // 2. Force _content-length? - // 3. If Transfer-Encoding!=identity && HTTP/1.1 && !HttpConnection==close then chunk - // 4. Content-Length - // 5. multipart/byteranges - // 6. close + // 3. If Transfer-Encoding==(.*,)?chunked && HTTP/1.1 && !HttpConnection==close then chunk + // 5. Content-Length without Transfer-Encoding + // 6. Request and none over the above, then Content-Length=0 if POST/PUT + // 7. close + + int status=response!=null?response.getStatus():-1; switch (_endOfContent) { @@ -725,13 +733,12 @@ public class HttpGenerator // written yet? // Response known not to have a body - if (_contentPrepared == 0 && response!=null && (status < 200 || status == 204 || status == 304)) + if (_contentPrepared == 0 && response!=null && _noContent) _endOfContent=EndOfContent.NO_CONTENT; else if (_info.getContentLength()>0) { // we have been given a content length _endOfContent=EndOfContent.CONTENT_LENGTH; - long content_length = _info.getContentLength(); if ((response!=null || content_length>0 || content_type ) && !_noContent) { // known length but not actually set. @@ -744,20 +751,13 @@ public class HttpGenerator { // we have seen all the _content there is, so we can be content-length limited. _endOfContent=EndOfContent.CONTENT_LENGTH; - long content_length = _contentPrepared+BufferUtil.length(content); + long actual_length = _contentPrepared+BufferUtil.length(content); + if (content_length>=0 && content_length!=actual_length) + throw new IllegalArgumentException("Content-Length header("+content_length+") != actual("+actual_length+")"); + // Do we need to tell the headers about it - if (content_length>0) - { - header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace()); - BufferUtil.putDecLong(header, content_length); - header.put(HttpTokens.CRLF); - } - else if (!_noContent) - { - if (content_type || response!=null || (request!=null && __assumedContentMethods.contains(request.getMethod()))) - header.put(CONTENT_LENGTH_0); - } + putContentLength(header,actual_length,content_type,request,response); } else { @@ -767,25 +767,16 @@ public class HttpGenerator // For a request with HTTP 1.0 & Connection: keep-alive // we *must* close the connection, otherwise the client // has no way to detect the end of the content. - if (!isPersistent() || _info.getHttpVersion().ordinal() < HttpVersion.HTTP_1_1.ordinal()) + if (!isPersistent() || _info.getVersion().ordinal() < HttpVersion.HTTP_1_1.ordinal()) _endOfContent = EndOfContent.EOF_CONTENT; } break; case CONTENT_LENGTH: - long content_length = _info.getContentLength(); - if (content_length>0) - { - header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace()); - BufferUtil.putDecLong(header, content_length); - header.put(HttpTokens.CRLF); - } - else if (!_noContent) - { - if (content_type || response!=null || (request!=null && __assumedContentMethods.contains(request.getMethod()))) - header.put(CONTENT_LENGTH_0); - } + { + putContentLength(header,content_length,content_type,request,response); break; + } case NO_CONTENT: throw new IllegalStateException(); @@ -827,7 +818,7 @@ public class HttpGenerator // If this is a response, work out persistence if (response!=null) { - if (!isPersistent() && (close || _info.getHttpVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal())) + if (!isPersistent() && (close || _info.getVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal())) { if (connection==null) header.put(CONNECTION_CLOSE); @@ -867,6 +858,22 @@ public class HttpGenerator } /* ------------------------------------------------------------------------------- */ + private void putContentLength(ByteBuffer header, long contentLength, boolean contentType, MetaData.Request request, MetaData.Response response) + { + if (contentLength>0) + { + header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace()); + BufferUtil.putDecLong(header, contentLength); + header.put(HttpTokens.CRLF); + } + else if (!_noContent) + { + if (contentType || response!=null || (request!=null && __assumedContentMethods.contains(request.getMethod()))) + header.put(CONTENT_LENGTH_0); + } + } + + /* ------------------------------------------------------------------------------- */ public static byte[] getReasonBuffer(int code) { PreparedResponse status = code<__preprepared.length?__preprepared[code]:null; @@ -879,8 +886,9 @@ public class HttpGenerator @Override public String toString() { - return String.format("%s{s=%s}", + return String.format("%s@%x{s=%s}", getClass().getSimpleName(), + hashCode(), _state); } @@ -942,110 +950,13 @@ public class HttpGenerator } } - public static class Info - { - final HttpVersion _httpVersion; - final HttpFields _httpFields; - final long _contentLength; - - private Info(HttpVersion httpVersion, HttpFields httpFields, long contentLength) - { - _httpVersion = httpVersion; - _httpFields = httpFields; - _contentLength = contentLength; - } - - public HttpVersion getHttpVersion() - { - return _httpVersion; - } - public HttpFields getHttpFields() - { - return _httpFields; - } - public long getContentLength() - { - return _contentLength; - } - } - - public static class RequestInfo extends Info - { - private final String _method; - private final String _uri; - - public RequestInfo(HttpVersion httpVersion, HttpFields httpFields, long contentLength, String method, String uri) - { - super(httpVersion,httpFields,contentLength); - _method = method; - _uri = uri; - } - - public String getMethod() - { - return _method; - } - - public String getUri() - { - return _uri; - } - - @Override - public String toString() - { - return String.format("RequestInfo{%s %s %s,%d}",_method,_uri,_httpVersion,_contentLength); - } - } - - public static class ResponseInfo extends Info - { - private final int _status; - private final String _reason; - private final boolean _head; - - public ResponseInfo(HttpVersion httpVersion, HttpFields httpFields, long contentLength, int status, String reason, boolean head) - { - super(httpVersion,httpFields,contentLength); - _status = status; - _reason = reason; - _head = head; - } - - public boolean isInformational() - { - return _status>=100 && _status<200; - } - - public int getStatus() - { - return _status; - } - - public String getReason() - { - return _reason; - } - - public boolean isHead() - { - return _head; - } - - @Override - public String toString() - { - return String.format("ResponseInfo{%s %s %s,%d,%b}",_httpVersion,_status,_reason,_contentLength,_head); - } - } - private static void putSanitisedName(String s,ByteBuffer buffer) { int l=s.length(); for (int i=0;i<l;i++) { char c=s.charAt(i); - + if (c<0 || c>0xff || c=='\r' || c=='\n'|| c==':') buffer.put((byte)'?'); else @@ -1059,7 +970,7 @@ public class HttpGenerator for (int i=0;i<l;i++) { char c=s.charAt(i); - + if (c<0 || c>0xff || c=='\r' || c=='\n') buffer.put((byte)' '); else @@ -1069,9 +980,9 @@ public class HttpGenerator public static void putTo(HttpField field, ByteBuffer bufferInFillMode) { - if (field instanceof CachedHttpField) + if (field instanceof PreEncodedHttpField) { - ((CachedHttpField)field).putTo(bufferInFillMode); + ((PreEncodedHttpField)field).putTo(bufferInFillMode,HttpVersion.HTTP_1_0); } else { @@ -1092,7 +1003,7 @@ public class HttpGenerator } } - public static void putTo(HttpFields fields, ByteBuffer bufferInFillMode) + public static void putTo(HttpFields fields, ByteBuffer bufferInFillMode) { for (HttpField field : fields) { @@ -1101,23 +1012,4 @@ public class HttpGenerator } BufferUtil.putCRLF(bufferInFillMode); } - - public static class CachedHttpField extends HttpField - { - private final byte[] _bytes; - public CachedHttpField(HttpHeader header,String value) - { - super(header,value); - int cbl=header.getBytesColonSpace().length; - _bytes=Arrays.copyOf(header.getBytesColonSpace(), cbl+value.length()+2); - System.arraycopy(value.getBytes(StandardCharsets.ISO_8859_1),0,_bytes,cbl,value.length()); - _bytes[_bytes.length-2]=(byte)'\r'; - _bytes[_bytes.length-1]=(byte)'\n'; - } - - public void putTo(ByteBuffer bufferInFillMode) - { - bufferInFillMode.put(_bytes); - } - } } |