diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpContent.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpContent.java index 1554f443ab0..3e401d96960 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpContent.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpContent.java @@ -23,6 +23,7 @@ import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.channels.ReadableByteChannel; +import org.eclipse.jetty.http.MimeTypes.Type; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.resource.Resource; @@ -33,13 +34,23 @@ import org.eclipse.jetty.util.resource.Resource; */ public interface HttpContent { - String getContentType(); - String getLastModified(); + HttpField getContentType(); + String getContentTypeValue(); + String getCharacterEncoding(); + Type getMimeType(); + + HttpField getContentLength(); + long getContentLengthValue(); + + HttpField getLastModified(); + String getLastModifiedValue(); + + HttpField getETag(); + String getETagValue(); + ByteBuffer getIndirectBuffer(); ByteBuffer getDirectBuffer(); - String getETag(); Resource getResource(); - long getContentLength(); InputStream getInputStream() throws IOException; ReadableByteChannel getReadableByteChannel() throws IOException; void release(); @@ -50,49 +61,79 @@ public interface HttpContent public class ResourceAsHttpContent implements HttpContent { final Resource _resource; - final String _mimeType; + final String _contentType; final int _maxBuffer; final String _etag; /* ------------------------------------------------------------ */ - public ResourceAsHttpContent(final Resource resource, final String mimeType) + public ResourceAsHttpContent(final Resource resource, final String contentType) { - this(resource,mimeType,-1,false); + this(resource,contentType,-1,false); } /* ------------------------------------------------------------ */ - public ResourceAsHttpContent(final Resource resource, final String mimeType, int maxBuffer) + public ResourceAsHttpContent(final Resource resource, final String contentType, int maxBuffer) { - this(resource,mimeType,maxBuffer,false); + this(resource,contentType,maxBuffer,false); } /* ------------------------------------------------------------ */ - public ResourceAsHttpContent(final Resource resource, final String mimeType, boolean etag) + public ResourceAsHttpContent(final Resource resource, final String contentType, boolean etag) { - this(resource,mimeType,-1,etag); + this(resource,contentType,-1,etag); } /* ------------------------------------------------------------ */ - public ResourceAsHttpContent(final Resource resource, final String mimeType, int maxBuffer, boolean etag) + public ResourceAsHttpContent(final Resource resource, final String contentType, int maxBuffer, boolean etag) { _resource=resource; - _mimeType=mimeType; + _contentType=contentType; _maxBuffer=maxBuffer; _etag=etag?resource.getWeakETag():null; } /* ------------------------------------------------------------ */ @Override - public String getContentType() + public String getContentTypeValue() { - return _mimeType; + return _contentType; + } + + /* ------------------------------------------------------------ */ + @Override + public HttpField getContentType() + { + return _contentType==null?null:new HttpField(HttpHeader.CONTENT_TYPE,_contentType); } /* ------------------------------------------------------------ */ @Override - public String getLastModified() + public String getCharacterEncoding() { - return null; + return _contentType==null?null:MimeTypes.getCharsetFromContentType(_contentType); + } + + /* ------------------------------------------------------------ */ + @Override + public Type getMimeType() + { + return _contentType==null?null:MimeTypes.CACHE.get(MimeTypes.getContentTypeWithoutCharset(_contentType)); + } + + /* ------------------------------------------------------------ */ + @Override + public HttpField getLastModified() + { + long lm = _resource.lastModified(); + return lm>=0?new HttpField(HttpHeader.LAST_MODIFIED,DateGenerator.formatDate(lm)):null; + } + + /* ------------------------------------------------------------ */ + @Override + public String getLastModifiedValue() + { + long lm = _resource.lastModified(); + return lm>=0?DateGenerator.formatDate(lm):null; } /* ------------------------------------------------------------ */ @@ -113,7 +154,14 @@ public interface HttpContent /* ------------------------------------------------------------ */ @Override - public String getETag() + public HttpField getETag() + { + return _etag==null?null:new HttpField(HttpHeader.ETAG,_etag); + } + + /* ------------------------------------------------------------ */ + @Override + public String getETagValue() { return _etag; } @@ -136,7 +184,15 @@ public interface HttpContent /* ------------------------------------------------------------ */ @Override - public long getContentLength() + public HttpField getContentLength() + { + long l=_resource.length(); + return l==-1?null:new HttpField.LongValueHttpField(HttpHeader.CONTENT_LENGTH,_resource.length()); + } + + /* ------------------------------------------------------------ */ + @Override + public long getContentLengthValue() { return _resource.length(); } @@ -169,10 +225,12 @@ public interface HttpContent _resource.close(); } + /* ------------------------------------------------------------ */ @Override public String toString() { return String.format("%s@%x{r=%s}",this.getClass().getSimpleName(),hashCode(),_resource); } } + } 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 9b6c83b1fb0..d5387ad6c5d 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 @@ -769,16 +769,29 @@ public class HttpGenerator break; case CONTENT_LENGTH: + { long content_length = _info.getContentLength(); if ((response!=null || content_length>0 || content_type ) && !_noContent) { - // known length but not actually set. header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace()); BufferUtil.putDecLong(header, content_length); header.put(HttpTokens.CRLF); } break; + } + case SELF_DEFINING_CONTENT: + { + // TODO - Should we do this? Why was it not required before? + long content_length = _info.getContentLength(); + if (content_length>0) + { + header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace()); + BufferUtil.putDecLong(header, content_length); + header.put(HttpTokens.CRLF); + } + break; + } case NO_CONTENT: if (response!=null && status >= 200 && status != 204 && status != 304) header.put(CONTENT_LENGTH_0); diff --git a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackContext.java b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackContext.java index 8bc7c725f01..a1d482f02df 100644 --- a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackContext.java +++ b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackContext.java @@ -177,8 +177,6 @@ public class HpackContext __headerEntryTable[h.ordinal()]=entry; } } - - private int _maxHeaderTableSizeInBytes; private int _headerTableSizeInBytes; @@ -302,6 +300,16 @@ public class HpackContext return _headerTable.index(entry)+__staticTable.length-1; } + public static int staticIndex(HttpHeader header) + { + if (header==null) + return 0; + Entry entry=__staticNameMap.get(header.asString()); + if (entry==null) + return 0; + return entry.getSlot(); + } + private void evict() { while (_headerTableSizeInBytes>_maxHeaderTableSizeInBytes) diff --git a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java index fed4aa5b82e..75d824eaf98 100644 --- a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java +++ b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java @@ -44,14 +44,14 @@ public class HpackEncoder private final static HttpField[] __status= new HttpField[599]; - private final static EnumSet __DO_NOT_HUFFMAN = + final static EnumSet __DO_NOT_HUFFMAN = EnumSet.of( HttpHeader.AUTHORIZATION, HttpHeader.CONTENT_MD5, HttpHeader.PROXY_AUTHENTICATE, HttpHeader.PROXY_AUTHORIZATION); - private final static EnumSet __DO_NOT_INDEX = + final static EnumSet __DO_NOT_INDEX = EnumSet.of( // HttpHeader.C_PATH, // TODO more data needed // HttpHeader.DATE, // TODO more data needed @@ -67,13 +67,13 @@ public class HpackEncoder HttpHeader.LOCATION, HttpHeader.RANGE, HttpHeader.RETRY_AFTER, - HttpHeader.EXPIRES, + // HttpHeader.EXPIRES, HttpHeader.LAST_MODIFIED, HttpHeader.SET_COOKIE, HttpHeader.SET_COOKIE2); - private final static EnumSet __NEVER_INDEX = + final static EnumSet __NEVER_INDEX = EnumSet.of( HttpHeader.AUTHORIZATION, HttpHeader.SET_COOKIE, @@ -82,7 +82,7 @@ public class HpackEncoder static { for (HttpStatus.Code code : HttpStatus.Code.values()) - __status[code.getCode()]=new HttpField(":status",Integer.toString(code.getCode())); + __status[code.getCode()]=new PreEncodedHttpField(HttpHeader.C_STATUS,Integer.toString(code.getCode())); } private final HpackContext _context; @@ -185,7 +185,7 @@ public class HpackEncoder _context.resize(maxHeaderTableSize); } - private void encode(ByteBuffer buffer, HttpField field) + public void encode(ByteBuffer buffer, HttpField field) { final int p=_debug?buffer.position():-1; @@ -216,7 +216,6 @@ public class HpackEncoder // Unknown field entry, so we will have to send literally. final boolean indexed; - // But do we know it's name? HttpHeader header = field.getHeader(); @@ -225,9 +224,18 @@ public class HpackEncoder { // Select encoding strategy for unknown header names Entry name = _context.get(field.getName()); - + + if (field instanceof PreEncodedHttpField) + { + int i=buffer.position(); + ((PreEncodedHttpField)field).putTo(buffer,HttpVersion.HTTP_2); + byte b=buffer.get(i); + indexed=b<0||b>=0x40; + if (_debug) + encoding=indexed?"PreEncodedIdx":"PreEncoded"; + } // has the custom header name been seen before? - if (name==null) + else if (name==null) { // unknown name and value, so let's index this just in case it is // the first time we have seen a custom name or a custom field. @@ -237,7 +245,6 @@ public class HpackEncoder encodeValue(buffer,true,field.getValue()); if (_debug) encoding="LitHuffNHuffVIdx"; - } else { @@ -255,7 +262,17 @@ public class HpackEncoder // Select encoding strategy for known header names Entry name = _context.get(header); - if (__DO_NOT_INDEX.contains(header)) + if (field instanceof PreEncodedHttpField) + { + // Preencoded field + int i=buffer.position(); + ((PreEncodedHttpField)field).putTo(buffer,HttpVersion.HTTP_2); + byte b=buffer.get(i); + indexed=b<0||b>=0x40; + if (_debug) + encoding=indexed?"PreEncodedIdx":"PreEncoded"; + } + else if (__DO_NOT_INDEX.contains(header)) { // Non indexed field indexed=false; @@ -272,22 +289,13 @@ public class HpackEncoder } else if (header==HttpHeader.CONTENT_LENGTH && field.getValue().length()>1) { - // Non indexed content length for non zero value + // Non indexed content length for 2 digits or more indexed=false; encodeName(buffer,(byte)0x00,4,header.asString(),name); encodeValue(buffer,true,field.getValue()); if (_debug) encoding="LitIdxNS"+(1+NBitInteger.octectsNeeded(4,_context.index(name)))+"HuffV!Idx"; } - else if (field instanceof PreEncodedHttpField) - { - // Preencoded field - indexed=true; - ((PreEncodedHttpField)field).putTo(buffer,HttpVersion.HTTP_2); - if (_debug) - encoding=((name==null)?"LitHuffN":("LitIdxN"+(name.isStatic()?"S":"")+(1+NBitInteger.octectsNeeded(6,_context.index(name)))))+ - "HuffVIdx"; - } else { // indexed @@ -332,7 +340,7 @@ public class HpackEncoder } } - private void encodeValue(ByteBuffer buffer, boolean huffman, String value) + static void encodeValue(ByteBuffer buffer, boolean huffman, String value) { if (huffman) { diff --git a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackFieldPreEncoder.java b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackFieldPreEncoder.java index 330cd2c932a..453c644258a 100644 --- a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackFieldPreEncoder.java +++ b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackFieldPreEncoder.java @@ -24,7 +24,6 @@ import java.nio.ByteBuffer; import org.eclipse.jetty.http.HttpFieldPreEncoder; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpVersion; -import org.eclipse.jetty.http2.hpack.HpackContext.Entry; import org.eclipse.jetty.util.BufferUtil; @@ -50,24 +49,48 @@ public class HpackFieldPreEncoder implements HttpFieldPreEncoder @Override public byte[] getEncodedField(HttpHeader header, String name, String value) { + boolean not_indexed=HpackEncoder.__DO_NOT_INDEX.contains(header); + ByteBuffer buffer = BufferUtil.allocate(name.length()+value.length()+10); BufferUtil.clearToFill(buffer); - buffer.put((byte)0x40); - Entry entry = header==null?null:HpackContext.getStatic(header); - if (entry==null) + boolean huffman; + int bits; + + if (not_indexed) + { + // Non indexed field + boolean never_index=HpackEncoder.__NEVER_INDEX.contains(header); + huffman=!HpackEncoder.__DO_NOT_HUFFMAN.contains(header); + buffer.put(never_index?(byte)0x10:(byte)0x00); + bits=4; + } + else if (header==HttpHeader.CONTENT_LENGTH && value.length()>1) + { + // Non indexed content length for 2 digits or more + buffer.put((byte)0x00); + huffman=true; + bits=4; + } + else + { + // indexed + buffer.put((byte)0x40); + huffman=!HpackEncoder.__DO_NOT_HUFFMAN.contains(header); + bits=6; + } + + int name_idx=HpackContext.staticIndex(header); + if (name_idx>0) + NBitInteger.encode(buffer,bits,name_idx); + else { buffer.put((byte)0x80); NBitInteger.encode(buffer,7,Huffman.octetsNeededLC(name)); Huffman.encodeLC(buffer,name); } - else - { - NBitInteger.encode(buffer,6,entry.getSlot()); - } - buffer.put((byte)0x80); - NBitInteger.encode(buffer,7,Huffman.octetsNeeded(value)); - Huffman.encode(buffer,value); + HpackEncoder.encodeValue(buffer,huffman,value); + BufferUtil.flipToFlush(buffer,0); return BufferUtil.toArray(buffer); } diff --git a/jetty-http2/http2-server/src/test/java/org/eclipse/jetty/http2/server/Http2Server.java b/jetty-http2/http2-server/src/test/java/org/eclipse/jetty/http2/server/Http2Server.java index a401b75a28c..c80f3585499 100644 --- a/jetty-http2/http2-server/src/test/java/org/eclipse/jetty/http2/server/Http2Server.java +++ b/jetty-http2/http2-server/src/test/java/org/eclipse/jetty/http2/server/Http2Server.java @@ -62,7 +62,7 @@ public class Http2Server context.addFilter(PushCacheFilter.class,"/*",EnumSet.of(DispatcherType.REQUEST)) .setInitParameter("ports","443,6443,8443"); context.addServlet(new ServletHolder(servlet), "/test/*"); - context.addServlet(DefaultServlet.class, "/"); + context.addServlet(DefaultServlet.class, "/").setInitParameter("maxCacheSize","81920"); server.setHandler(context); @@ -122,6 +122,10 @@ public class Http2Server @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + String code=request.getParameter("code"); + if (code!=null) + response.setStatus(Integer.parseInt(code)); + HttpSession session = request.getSession(true); if (session.isNew()) response.addCookie(new Cookie("bigcookie", diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java index 7952ba5320d..d08b1032efd 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java @@ -33,7 +33,11 @@ import java.util.concurrent.atomic.AtomicReference; import org.eclipse.jetty.http.DateGenerator; import org.eclipse.jetty.http.HttpContent; +import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.http.PreEncodedHttpField; +import org.eclipse.jetty.http.MimeTypes.Type; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -258,7 +262,7 @@ public class ResourceCache if (c1._lastAccessed>c2._lastAccessed) return 1; - if (c1._length _indirectBuffer=new AtomicReference(); @@ -340,18 +347,24 @@ public class ResourceCache _key=pathInContext; _resource=resource; - String mimeType = _mimeTypes.getMimeByExtension(_resource.toString()); - _contentType=(mimeType==null?null:BufferUtil.toBuffer(mimeType)); - boolean exists=resource.exists(); - _lastModified=exists?resource.lastModified():-1; - _lastModifiedBytes=_lastModified<0?null:BufferUtil.toBuffer(DateGenerator.formatDate(_lastModified)); + String contentType = _mimeTypes.getMimeByExtension(_resource.toString()); + _contentType=contentType==null?null:new PreEncodedHttpField(HttpHeader.CONTENT_TYPE,contentType); + _characterEncoding = _contentType==null?null:MimeTypes.getCharsetFromContentType(contentType); + _mimeType = _contentType==null?null:MimeTypes.CACHE.get(MimeTypes.getContentTypeWithoutCharset(contentType)); - _length=exists?(int)resource.length():0; - _cachedSize.addAndGet(_length); + boolean exists=resource.exists(); + _lastModifiedValue=exists?resource.lastModified():-1L; + _lastModified=_lastModifiedValue==-1?null + :new PreEncodedHttpField(HttpHeader.LAST_MODIFIED,DateGenerator.formatDate(_lastModifiedValue)); + + _contentLengthValue=exists?(int)resource.length():0; + _contentLength=new PreEncodedHttpField(HttpHeader.CONTENT_LENGTH,Long.toString(_contentLengthValue)); + + _cachedSize.addAndGet(_contentLengthValue); _cachedFiles.incrementAndGet(); _lastAccessed=System.currentTimeMillis(); - _etag=ResourceCache.this._etagSupported?resource.getWeakETag():null; + _etag=ResourceCache.this._etagSupported?new PreEncodedHttpField(HttpHeader.ETAG,resource.getWeakETag()):null; } @@ -382,15 +395,22 @@ public class ResourceCache /* ------------------------------------------------------------ */ @Override - public String getETag() + public HttpField getETag() { return _etag; } + + /* ------------------------------------------------------------ */ + @Override + public String getETagValue() + { + return _etag.getValue(); + } /* ------------------------------------------------------------ */ boolean isValid() { - if (_lastModified==_resource.lastModified() && _length==_resource.length()) + if (_lastModifiedValue==_resource.lastModified() && _contentLengthValue==_resource.length()) { _lastAccessed=System.currentTimeMillis(); return true; @@ -405,25 +425,55 @@ public class ResourceCache protected void invalidate() { // Invalidate it - _cachedSize.addAndGet(-_length); + _cachedSize.addAndGet(-_contentLengthValue); _cachedFiles.decrementAndGet(); _resource.close(); } /* ------------------------------------------------------------ */ @Override - public String getLastModified() + public HttpField getLastModified() { - return BufferUtil.toString(_lastModifiedBytes); + return _lastModified; + } + + /* ------------------------------------------------------------ */ + @Override + public String getLastModifiedValue() + { + return _lastModified==null?null:_lastModified.getValue(); + } + + + /* ------------------------------------------------------------ */ + @Override + public HttpField getContentType() + { + return _contentType; + } + + /* ------------------------------------------------------------ */ + @Override + public String getContentTypeValue() + { + return _contentType==null?null:_contentType.getValue(); + } + + /* ------------------------------------------------------------ */ + @Override + public String getCharacterEncoding() + { + return _characterEncoding; } /* ------------------------------------------------------------ */ @Override - public String getContentType() + public Type getMimeType() { - return BufferUtil.toString(_contentType); + return _mimeType; } + /* ------------------------------------------------------------ */ @Override public void release() @@ -473,12 +523,19 @@ public class ResourceCache return null; return buffer.asReadOnlyBuffer(); } + + /* ------------------------------------------------------------ */ + @Override + public HttpField getContentLength() + { + return _contentLength; + } /* ------------------------------------------------------------ */ @Override - public long getContentLength() + public long getContentLengthValue() { - return _length; + return _contentLengthValue; } /* ------------------------------------------------------------ */ @@ -504,7 +561,7 @@ public class ResourceCache @Override public String toString() { - return String.format("CachedContent@%x{r=%s,e=%b,lm=%s,ct=%s}",hashCode(),_resource,_resource.exists(),BufferUtil.toString(_lastModifiedBytes),_contentType); + return String.format("CachedContent@%x{r=%s,e=%b,lm=%s,ct=%s}",hashCode(),_resource,_resource.exists(),_lastModified,_contentType); } } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java index fa628693b6b..aead87cfb05 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java @@ -28,6 +28,7 @@ import java.util.Enumeration; import java.util.Iterator; import java.util.Locale; import java.util.concurrent.atomic.AtomicInteger; + import javax.servlet.RequestDispatcher; import javax.servlet.ServletOutputStream; import javax.servlet.http.Cookie; @@ -48,6 +49,7 @@ import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.http.PreEncodedHttpField; import org.eclipse.jetty.io.RuntimeIOException; import org.eclipse.jetty.server.handler.ErrorHandler; import org.eclipse.jetty.util.ByteArrayISO8859Writer; @@ -66,6 +68,7 @@ public class Response implements HttpServletResponse private static final String __COOKIE_DELIM="\",;\\ \t"; private final static String __01Jan1970_COOKIE = DateGenerator.formatCookieDate(0).trim(); private final static int __MIN_BUFFER_SIZE = 1; + private final static HttpField __EXPIRES_01JAN1970 = new PreEncodedHttpField(HttpHeader.EXPIRES,DateGenerator.__01Jan1970); // Cookie building buffer. Reduce garbage for cookie using applications @@ -146,31 +149,6 @@ public class Response implements HttpServletResponse _fields.clear(); _explicitEncoding=false; } - - public void setHeaders(HttpContent httpContent) - { - Response response = _channel.getResponse(); - String contentType = httpContent.getContentType(); - if (contentType != null && !response.getHttpFields().containsKey(HttpHeader.CONTENT_TYPE.asString())) - setContentType(contentType); - - if (httpContent.getContentLength() > 0) - setLongContentLength(httpContent.getContentLength()); - - String lm = httpContent.getLastModified(); - if (lm != null) - response.getHttpFields().put(HttpHeader.LAST_MODIFIED, lm); - else if (httpContent.getResource() != null) - { - long lml = httpContent.getResource().lastModified(); - if (lml != -1) - response.getHttpFields().putDateField(HttpHeader.LAST_MODIFIED, lml); - } - - String etag=httpContent.getETag(); - if (etag!=null) - response.getHttpFields().put(HttpHeader.ETAG,etag); - } public HttpOutput getHttpOutput() { @@ -376,7 +354,7 @@ public class Response implements HttpServletResponse _fields.add(HttpHeader.SET_COOKIE.toString(), buf.toString()); // Expire responses with set-cookie headers so they do not get cached. - _fields.put(HttpHeader.EXPIRES.toString(), DateGenerator.__01Jan1970); + _fields.put(__EXPIRES_01JAN1970); } @@ -1110,7 +1088,7 @@ public class Response implements HttpServletResponse } } } - + @Override public void setContentType(String contentType) { @@ -1366,4 +1344,67 @@ public class Response implements HttpServletResponse out=_httpWriter; } } + + public void putHeaders(HttpContent content,long contentLength, boolean etag) + { + + HttpField lm = content.getLastModified(); + if (lm!=null) + _fields.put(lm); + + if (contentLength==0) + { + _fields.put(content.getContentLength()); + _contentLength=content.getContentLengthValue(); + } + else if (contentLength>0) + { + _fields.putLongField(HttpHeader.CONTENT_LENGTH,contentLength); + _contentLength=contentLength; + } + + HttpField ct=content.getContentType(); + if (ct!=null) + { + _fields.put(ct); + _contentType=ct.getValue(); + _characterEncoding=content.getCharacterEncoding(); + _mimeType=content.getMimeType(); + } + + if (etag) + { + HttpField et = content.getETag(); + if (et!=null) + _fields.put(et); + } + } + + public static void putHeaders(HttpServletResponse response, HttpContent content, long contentLength, boolean etag) + { + long lml=content.getResource().lastModified(); + if (lml>=0) + response.setDateHeader(HttpHeader.LAST_MODIFIED.asString(),lml); + + if (contentLength==0) + contentLength=content.getContentLengthValue(); + if (contentLength >=0) + { + if (contentLength0;) + { + HttpField field=fields.getField(i); + switch (field.getHeader()) + { + case IF_MATCH: + ifm=field.getValue(); + break; + case IF_NONE_MATCH: + ifnm=field.getValue(); + break; + case IF_MODIFIED_SINCE: + ifms=field.getValue(); + break; + case IF_UNMODIFIED_SINCE: + ifums=DateParser.parseDate(field.getValue()); + break; + default: + } + } + } + else + { + ifm=request.getHeader(HttpHeader.IF_MATCH.asString()); + ifnm=request.getHeader(HttpHeader.IF_NONE_MATCH.asString()); + ifms=request.getHeader(HttpHeader.IF_MODIFIED_SINCE.asString()); + ifums=request.getDateHeader(HttpHeader.IF_UNMODIFIED_SINCE.asString()); + } + if (!HttpMethod.HEAD.is(request.getMethod())) { if (_etags) { - String ifm=request.getHeader(HttpHeader.IF_MATCH.asString()); if (ifm!=null) { boolean match=false; - if (content.getETag()!=null) + if (content.getETagValue()!=null) { QuotedStringTokenizer quoted = new QuotedStringTokenizer(ifm,", ",false,true); while (!match && quoted.hasMoreTokens()) { String tag = quoted.nextToken(); - if (content.getETag().equals(tag)) + if (content.getETagValue().equals(tag)) match=true; } } @@ -729,34 +767,33 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory } } - String if_non_match_etag=request.getHeader(HttpHeader.IF_NONE_MATCH.asString()); - if (if_non_match_etag!=null && content.getETag()!=null) + if (ifnm!=null && content.getETagValue()!=null) { // Look for GzipFiltered version of etag - if (content.getETag().equals(request.getAttribute("o.e.j.s.GzipFilter.ETag"))) + if (content.getETagValue().equals(request.getAttribute("o.e.j.s.GzipFilter.ETag"))) { response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); - response.setHeader(HttpHeader.ETAG.asString(),if_non_match_etag); + response.setHeader(HttpHeader.ETAG.asString(),ifnm); return false; } // Handle special case of exact match. - if (content.getETag().equals(if_non_match_etag)) + if (content.getETagValue().equals(ifnm)) { response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); - response.setHeader(HttpHeader.ETAG.asString(),content.getETag()); + response.setHeader(HttpHeader.ETAG.asString(),content.getETagValue()); return false; } // Handle list of tags - QuotedStringTokenizer quoted = new QuotedStringTokenizer(if_non_match_etag,", ",false,true); + QuotedStringTokenizer quoted = new QuotedStringTokenizer(ifnm,", ",false,true); while (quoted.hasMoreTokens()) { String tag = quoted.nextToken(); - if (content.getETag().equals(tag)) + if (content.getETagValue().equals(tag)) { response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); - response.setHeader(HttpHeader.ETAG.asString(),content.getETag()); + response.setHeader(HttpHeader.ETAG.asString(),content.getETagValue()); return false; } } @@ -767,16 +804,15 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory } // Handle if modified since - String ifms=request.getHeader(HttpHeader.IF_MODIFIED_SINCE.asString()); if (ifms!=null) { //Get jetty's Response impl - String mdlm=content.getLastModified(); + String mdlm=content.getLastModifiedValue(); if (mdlm!=null && ifms.equals(mdlm)) { response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); if (_etags) - response.setHeader(HttpHeader.ETAG.asString(),content.getETag()); + response.setHeader(HttpHeader.ETAG.asString(),content.getETagValue()); response.flushBuffer(); return false; } @@ -786,15 +822,14 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory { response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); if (_etags) - response.setHeader(HttpHeader.ETAG.asString(),content.getETag()); + response.setHeader(HttpHeader.ETAG.asString(),content.getETagValue()); response.flushBuffer(); return false; } } // Parse the if[un]modified dates and compare to resource - long date=request.getDateHeader(HttpHeader.IF_UNMODIFIED_SINCE.asString()); - if (date!=-1 && resource.lastModified()/1000 > date/1000) + if (ifums!=-1 && resource.lastModified()/1000 > ifums/1000) { response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED); return false; @@ -862,7 +897,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory Enumeration reqRanges) throws IOException { - final long content_length = (content==null)?resource.length():content.getContentLength(); + final long content_length = (content==null)?resource.length():content.getContentLengthValue(); // Get the output stream (or writer) OutputStream out =null; @@ -890,13 +925,14 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory // if there were no ranges, send entire entity if (include) { + // write without headers resource.writeTo(out,0,content_length); } // else if we can't do a bypass write because of wrapping else if (content==null || written || !(out instanceof HttpOutput)) { // write normally - writeHeaders(response,content,written?-1:content_length); + putHeaders(response,content,written?-1:0); ByteBuffer buffer = (content==null)?null:content.getIndirectBuffer(); if (buffer!=null) BufferUtil.writeTo(buffer,out); @@ -907,14 +943,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory else { // write the headers - if (response instanceof Response) - { - Response r = (Response)response; - writeOptionHeaders(r.getHttpFields()); - r.setHeaders(content); - } - else - writeHeaders(response,content,content_length); + putHeaders(response,content,0); // write the content asynchronously if supported if (request.isAsyncSupported()) @@ -961,7 +990,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory // if there are no satisfiable ranges, send 416 response if (ranges==null || ranges.size()==0) { - writeHeaders(response, content, content_length); + putHeaders(response,content,0); response.setStatus(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE); response.setHeader(HttpHeader.CONTENT_RANGE.asString(), InclusiveByteRange.to416HeaderRangeString(content_length)); @@ -975,7 +1004,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory { InclusiveByteRange singleSatisfiableRange = ranges.get(0); long singleLength = singleSatisfiableRange.getSize(content_length); - writeHeaders(response,content,singleLength ); + putHeaders(response,content,singleLength); response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); if (!response.containsHeader(HttpHeader.DATE.asString())) response.addDateHeader(HttpHeader.DATE.asString(),System.currentTimeMillis()); @@ -989,8 +1018,8 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory // 216 response which does not require an overall // content-length header // - writeHeaders(response,content,-1); - String mimetype=(content==null?null:content.getContentType()); + putHeaders(response,content,-1); + String mimetype=(content==null?null:content.getContentTypeValue()); if (mimetype==null) LOG.warn("Unknown mimetype for "+request.getRequestURI()); MultiPartOutputStream multi = new MultiPartOutputStream(out); @@ -1066,82 +1095,30 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory } /* ------------------------------------------------------------ */ - protected void writeHeaders(HttpServletResponse response,HttpContent content,long count) + protected void putHeaders(HttpServletResponse response,HttpContent content, long contentLength) { - if (content == null) - { - // No content, then no headers to process - // This is possible during bypass write because of wrapping - // See .sendData() for more details. - return; - } - - if (content.getContentType()!=null && response.getContentType()==null) - response.setContentType(content.getContentType().toString()); - if (response instanceof Response) { - Response r=(Response)response; - HttpFields fields = r.getHttpFields(); + Response r = (Response)response; + r.putHeaders(content,contentLength,_etags); + HttpFields f = r.getHttpFields(); + if (_acceptRanges) + f.put(ACCEPT_RANGES); - if (content.getLastModified()!=null) - fields.put(HttpHeader.LAST_MODIFIED,content.getLastModified()); - else if (content.getResource()!=null) - { - long lml=content.getResource().lastModified(); - if (lml!=-1) - fields.putDateField(HttpHeader.LAST_MODIFIED,lml); - } - - if (count != -1) - r.setLongContentLength(count); - - writeOptionHeaders(fields); - - if (_etags) - fields.put(HttpHeader.ETAG,content.getETag()); + if (_cacheControl!=null) + f.put(_cacheControl); } else { - long lml=content.getResource().lastModified(); - if (lml>=0) - response.setDateHeader(HttpHeader.LAST_MODIFIED.asString(),lml); + Response.putHeaders(response,content,contentLength,_etags); + if (_acceptRanges) + response.setHeader(ACCEPT_RANGES.getName(),ACCEPT_RANGES.getValue()); - if (count != -1) - { - if (count