Improve HTTP header pre-encoding

The HttpContent class has been reworked to store HttpField instances, we may be generated on
demand or instances of PreEncodedHttpField.

The encoding of HTTP2 fields has been generalized to handle both indexed and literal fields, selected
by header enum set.

Default servlet and response classes have been cleaned up in how they set response headers.
This commit is contained in:
Greg Wilkins 2014-10-16 12:29:08 +11:00
parent 61ec3efd65
commit ec79a6f88e
12 changed files with 402 additions and 208 deletions

View File

@ -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);
}
}
}

View File

@ -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);

View File

@ -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)

View File

@ -44,14 +44,14 @@ public class HpackEncoder
private final static HttpField[] __status= new HttpField[599];
private final static EnumSet<HttpHeader> __DO_NOT_HUFFMAN =
final static EnumSet<HttpHeader> __DO_NOT_HUFFMAN =
EnumSet.of(
HttpHeader.AUTHORIZATION,
HttpHeader.CONTENT_MD5,
HttpHeader.PROXY_AUTHENTICATE,
HttpHeader.PROXY_AUTHORIZATION);
private final static EnumSet<HttpHeader> __DO_NOT_INDEX =
final static EnumSet<HttpHeader> __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<HttpHeader> __NEVER_INDEX =
final static EnumSet<HttpHeader> __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)
{

View File

@ -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);
}

View File

@ -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",

View File

@ -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<c2._length)
if (c1._contentLengthValue<c2._contentLengthValue)
return -1;
return c1._key.compareTo(c2._key);
@ -322,13 +326,16 @@ public class ResourceCache
*/
public class Content implements HttpContent
{
final Resource _resource;
final int _length;
final String _key;
final long _lastModified;
final ByteBuffer _lastModifiedBytes;
final ByteBuffer _contentType;
final String _etag;
final Resource _resource;
final int _contentLengthValue;
final HttpField _contentType;
final String _characterEncoding;
final MimeTypes.Type _mimeType;
final HttpField _contentLength;
final HttpField _lastModified;
final long _lastModifiedValue;
final HttpField _etag;
volatile long _lastAccessed;
AtomicReference<ByteBuffer> _indirectBuffer=new AtomicReference<ByteBuffer>();
@ -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);
}
}
}

View File

@ -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 (contentLength<Integer.MAX_VALUE)
response.setContentLength((int)contentLength);
else
response.setHeader(HttpHeader.CONTENT_LENGTH.asString(),Long.toString(contentLength));
}
String ct=content.getContentTypeValue();
if (ct!=null && response.getContentType()==null)
response.setContentType(content.getContentTypeValue());
if (etag)
{
String et=content.getETagValue();
if (et!=null)
response.setHeader(HttpHeader.ETAG.asString(),et);
}
}
}

View File

@ -141,7 +141,7 @@ public class ResourceCacheTest
HttpContent content;
content=cache.lookup(names[8]);
assertTrue(content!=null);
assertEquals(80,content.getContentLength());
assertEquals(80,content.getContentLengthValue());
assertEquals(80,cache.getCachedSize());
assertEquals(1,cache.getCachedFiles());

View File

@ -39,6 +39,7 @@ import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.DateParser;
import org.eclipse.jetty.http.PreEncodedHttpField;
import org.eclipse.jetty.http.HttpContent;
import org.eclipse.jetty.http.HttpField;
@ -50,6 +51,7 @@ import org.eclipse.jetty.http.PathMap.MappedEntry;
import org.eclipse.jetty.io.WriterOutputStream;
import org.eclipse.jetty.server.HttpOutput;
import org.eclipse.jetty.server.InclusiveByteRange;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.ResourceCache;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.handler.ContextHandler;
@ -703,21 +705,57 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
{
try
{
String ifm=null;
String ifnm=null;
String ifms=null;
long ifums=-1;
if (request instanceof Request)
{
HttpFields fields = ((Request)request).getHttpFields();
for (int i=fields.size();i-->0;)
{
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<String> 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<Integer.MAX_VALUE)
response.setContentLength((int)count);
else
response.setHeader(HttpHeader.CONTENT_LENGTH.asString(),Long.toString(count));
}
writeOptionHeaders(response);
if (_etags)
response.setHeader(HttpHeader.ETAG.asString(),content.getETag());
if (_cacheControl!=null)
response.setHeader(_cacheControl.getName(),_cacheControl.getValue());
}
}
/* ------------------------------------------------------------ */
protected void writeOptionHeaders(HttpFields fields)
{
if (_acceptRanges)
fields.put(ACCEPT_RANGES);
if (_cacheControl!=null)
fields.put(_cacheControl);
}
/* ------------------------------------------------------------ */
protected void writeOptionHeaders(HttpServletResponse response)
{
if (_acceptRanges)
response.setHeader(HttpHeader.ACCEPT_RANGES.asString(),"bytes");
if (_cacheControl!=null)
response.setHeader(HttpHeader.CACHE_CONTROL.asString(),_cacheControl.getValue());
}
/* ------------------------------------------------------------ */
/*
* @see javax.servlet.Servlet#destroy()

View File

@ -662,16 +662,21 @@ public class DefaultServletTest
assertResponseContains("Content-Length: 12", response);
assertResponseNotContains("Extra Info", response);
server.stop();
context.addFilter(OutputFilter.class,"/*",EnumSet.of(DispatcherType.REQUEST));
server.start();
response = connector.getResponses("GET /context/data0.txt HTTP/1.0\r\n\r\n");
assertResponseContains("Content-Length: 2", response); // 20 something long
assertResponseContains("Extra Info", response);
assertResponseNotContains("Content-Length: 12", response);
server.stop();
context.getServletHandler().setFilterMappings(new FilterMapping[]{});
context.getServletHandler().setFilters(new FilterHolder[]{});
context.addFilter(WriterFilter.class,"/*",EnumSet.of(DispatcherType.REQUEST));
server.start();
response = connector.getResponses("GET /context/data0.txt HTTP/1.0\r\n\r\n");
assertResponseContains("Content-Length: 2", response); // 20 something long
assertResponseContains("Extra Info", response);

View File

@ -53,7 +53,7 @@ public class GzipFilterDefaultTest
{
return Arrays.asList(new Object[][]
{
{ AsyncGzipFilter.class, GzipFilter.GZIP },
{ AsyncGzipFilter.class, AsyncGzipFilter.GZIP },
{ GzipFilter.class, GzipFilter.GZIP },
{ GzipFilter.class, GzipFilter.DEFLATE },
});