423392 - GzipFilter without wrapping or blocking

I Added AsyncGzipFilter, which uses a modified HttpOutput instance to provide gzip compression without wrapping or blocking.
Does not currently handle deflate.
This commit is contained in:
Greg Wilkins 2013-12-06 22:51:50 +11:00
parent 5f204b8812
commit cd05751ff7
17 changed files with 1275 additions and 229 deletions

View File

@ -98,7 +98,6 @@ public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable
private final HttpChannelState _state;
private final Request _request;
private final Response _response;
private final BlockingCallback _writeblock=new BlockingCallback();
private HttpVersion _version = HttpVersion.HTTP_1_1;
private boolean _expect = false;
private boolean _expect100Continue = false;
@ -127,12 +126,6 @@ public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable
{
return _version;
}
BlockingCallback getWriteBlockingCallback()
{
return _writeblock;
}
/**
* @return the number of requests handled by this connection
*/
@ -725,29 +718,17 @@ public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable
protected boolean sendResponse(ResponseInfo info, ByteBuffer content, boolean complete) throws IOException
{
boolean committing=sendResponse(info,content,complete,_writeblock);
_writeblock.block();
BlockingCallback writeBlock = _response.getHttpOutput().getWriteBlockingCallback();
boolean committing=sendResponse(info,content,complete,writeBlock);
writeBlock.block();
return committing;
}
protected boolean isCommitted()
public boolean isCommitted()
{
return _committed.get();
}
/**
* <p>Blocking write, committing the response if needed.</p>
*
* @param content the content buffer to write
* @param complete whether the content is complete for the response
* @throws IOException if the write fails
*/
protected void write(ByteBuffer content, boolean complete) throws IOException
{
sendResponse(null,content,complete,_writeblock);
_writeblock.block();
}
/**
* <p>Non-Blocking write, committing the response if needed.</p>
*

View File

@ -509,6 +509,8 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
case NEED_HEADER:
{
// Look for optimisation to avoid allocating a _header buffer
/*
Cannot use this optimisation unless we work out how not to overwrite data in user passed arrays.
if (_lastContent && _content!=null && !_content.isReadOnly() && _content.hasArray() && BufferUtil.space(_content)>_config.getResponseHeaderSize() )
{
// use spare space in content buffer for header buffer
@ -522,7 +524,9 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
_content.limit(l);
}
else
*/
_header = _bufferPool.acquire(_config.getResponseHeaderSize(), HEADER_BUFFER_DIRECT);
continue;
}
case NEED_CHUNK:

View File

@ -55,6 +55,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
{
private static Logger LOG = Log.getLogger(HttpOutput.class);
private final HttpChannel<?> _channel;
private final BlockingCallback _writeblock=new BlockingCallback();
private long _written;
private ByteBuffer _aggregate;
private int _bufferSize;
@ -80,7 +81,12 @@ public class HttpOutput extends ServletOutputStream implements Runnable
_bufferSize = _channel.getHttpConfiguration().getOutputBufferSize();
_commitSize=_bufferSize/4;
}
public HttpChannel<?> getHttpChannel()
{
return _channel;
}
public boolean isWritten()
{
return _written > 0;
@ -107,6 +113,22 @@ public class HttpOutput extends ServletOutputStream implements Runnable
return _channel.getResponse().isAllContentWritten(_written);
}
protected BlockingCallback getWriteBlockingCallback()
{
return _writeblock;
}
protected void write(ByteBuffer content, boolean complete) throws IOException
{
write(content,complete,_writeblock);
_writeblock.block();
}
protected void write(ByteBuffer content, boolean complete, Callback callback)
{
_channel.write(content,complete,callback);
}
@Override
public void close()
{
@ -117,10 +139,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
{
try
{
if (BufferUtil.hasContent(_aggregate))
_channel.write(_aggregate, !_channel.getResponse().isIncluding());
else
_channel.write(BufferUtil.EMPTY_BUFFER, !_channel.getResponse().isIncluding());
write(BufferUtil.hasContent(_aggregate)?_aggregate:BufferUtil.EMPTY_BUFFER,!_channel.getResponse().isIncluding());
}
catch(IOException e)
{
@ -180,10 +199,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
switch(_state.get())
{
case OPEN:
if (BufferUtil.hasContent(_aggregate))
_channel.write(_aggregate, false);
else
_channel.write(BufferUtil.EMPTY_BUFFER, false);
write(BufferUtil.hasContent(_aggregate)?_aggregate:BufferUtil.EMPTY_BUFFER, false);
return;
case ASYNC:
@ -290,7 +306,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
// flush any content from the aggregate
if (BufferUtil.hasContent(_aggregate))
{
_channel.write(_aggregate, complete && len==0);
write(_aggregate, complete && len==0);
// should we fill aggregate again from the buffer?
if (len>0 && !complete && len<=_commitSize)
@ -303,26 +319,25 @@ public class HttpOutput extends ServletOutputStream implements Runnable
// write any remaining content in the buffer directly
if (len>0)
{
// pass as readonly to avoid space stealing optimisation in HttpConnection
ByteBuffer wrap = ByteBuffer.wrap(b, off, len);
ByteBuffer readonly = wrap.asReadOnlyBuffer();
ByteBuffer view = wrap.duplicate();
// write a buffer capacity at a time to avoid JVM pooling large direct buffers
// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6210541
while (len>getBufferSize())
{
int p=readonly.position();
int p=view.position();
int l=p+getBufferSize();
readonly.limit(p+getBufferSize());
_channel.write(readonly,false);
view.limit(p+getBufferSize());
write(view,false);
len-=getBufferSize();
readonly.limit(l+Math.min(len,getBufferSize()));
readonly.position(l);
view.limit(l+Math.min(len,getBufferSize()));
view.position(l);
}
_channel.write(readonly,complete);
write(view,complete);
}
else if (complete)
_channel.write(BufferUtil.EMPTY_BUFFER,complete);
write(BufferUtil.EMPTY_BUFFER,complete);
if (complete)
closed();
@ -370,13 +385,13 @@ public class HttpOutput extends ServletOutputStream implements Runnable
// flush any content from the aggregate
if (BufferUtil.hasContent(_aggregate))
_channel.write(_aggregate, complete && len==0);
write(_aggregate, complete && len==0);
// write any remaining content in the buffer directly
if (len>0)
_channel.write(buffer, complete);
write(buffer, complete);
else if (complete)
_channel.write(BufferUtil.EMPTY_BUFFER,complete);
write(BufferUtil.EMPTY_BUFFER,complete);
if (complete)
closed();
@ -401,9 +416,8 @@ public class HttpOutput extends ServletOutputStream implements Runnable
// Check if all written or full
if (complete || BufferUtil.isFull(_aggregate))
{
BlockingCallback callback = _channel.getWriteBlockingCallback();
_channel.write(_aggregate, complete, callback);
callback.block();
write(_aggregate, complete, _writeblock);
_writeblock.block();
if (complete)
closed();
}
@ -443,8 +457,6 @@ public class HttpOutput extends ServletOutputStream implements Runnable
}
}
@Override
public void print(String s) throws IOException
{
@ -461,11 +473,8 @@ public class HttpOutput extends ServletOutputStream implements Runnable
*/
public void sendContent(ByteBuffer content) throws IOException
{
final BlockingCallback callback =_channel.getWriteBlockingCallback();
if (content.hasArray()&&content.limit()<content.capacity())
content=content.asReadOnlyBuffer();
_channel.write(content,true,callback);
callback.block();
write(content,true,_writeblock);
_writeblock.block();
}
/* ------------------------------------------------------------ */
@ -475,9 +484,8 @@ public class HttpOutput extends ServletOutputStream implements Runnable
*/
public void sendContent(InputStream in) throws IOException
{
final BlockingCallback callback =_channel.getWriteBlockingCallback();
new InputStreamWritingCB(in,callback).iterate();
callback.block();
new InputStreamWritingCB(in,_writeblock).iterate();
_writeblock.block();
}
/* ------------------------------------------------------------ */
@ -487,9 +495,8 @@ public class HttpOutput extends ServletOutputStream implements Runnable
*/
public void sendContent(ReadableByteChannel in) throws IOException
{
final BlockingCallback callback =_channel.getWriteBlockingCallback();
new ReadableByteChannelWritingCB(in,callback).iterate();
callback.block();
new ReadableByteChannelWritingCB(in,_writeblock).iterate();
_writeblock.block();
}
@ -500,9 +507,8 @@ public class HttpOutput extends ServletOutputStream implements Runnable
*/
public void sendContent(HttpContent content) throws IOException
{
final BlockingCallback callback =_channel.getWriteBlockingCallback();
sendContent(content,callback);
callback.block();
sendContent(content,_writeblock);
_writeblock.block();
}
/* ------------------------------------------------------------ */
@ -512,9 +518,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
*/
public void sendContent(ByteBuffer content, final Callback callback)
{
if (content.hasArray()&&content.limit()<content.capacity())
content=content.asReadOnlyBuffer();
_channel.write(content,true,new Callback()
write(content,true,new Callback()
{
@Override
public void succeeded()
@ -759,14 +763,14 @@ public class HttpOutput extends ServletOutputStream implements Runnable
if (BufferUtil.hasContent(_aggregate))
{
_flushed=true;
_channel.write(_aggregate, false, this);
write(_aggregate, false, this);
return State.SCHEDULED;
}
if (!_flushed)
{
_flushed=true;
_channel.write(BufferUtil.EMPTY_BUFFER,false,this);
write(BufferUtil.EMPTY_BUFFER,false,this);
return State.SCHEDULED;
}
@ -789,7 +793,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
_buffer=ByteBuffer.wrap(b, off, len);
_len=len;
// always use a view for large byte arrays to avoid JVM pooling large direct buffers
_slice=_len<getBufferSize()?null:_buffer.asReadOnlyBuffer();
_slice=_len<getBufferSize()?null:_buffer.duplicate();
_complete=complete;
}
@ -798,7 +802,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
_buffer=buffer;
_len=buffer.remaining();
// Use a slice buffer for large indirect to avoid JVM pooling large direct buffers
_slice=_buffer.isDirect()||_len<getBufferSize()?null:_buffer.asReadOnlyBuffer();
_slice=_buffer.isDirect()||_len<getBufferSize()?null:_buffer.duplicate();
_complete=complete;
}
@ -809,7 +813,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
if (BufferUtil.hasContent(_aggregate))
{
_completed=_len==0;
_channel.write(_aggregate, _complete && _completed, this);
write(_aggregate, _complete && _completed, this);
return State.SCHEDULED;
}
@ -827,7 +831,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
if (_slice==null)
{
_completed=true;
_channel.write(_buffer, _complete, this);
write(_buffer, _complete, this);
return State.SCHEDULED;
}
@ -839,7 +843,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
_buffer.position(pl);
_slice.position(p);
_completed=!_buffer.hasRemaining();
_channel.write(_slice, _complete && _completed, this);
write(_slice, _complete && _completed, this);
return State.SCHEDULED;
}
@ -850,7 +854,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
if (!_completed)
{
_completed=true;
_channel.write(BufferUtil.EMPTY_BUFFER, _complete, this);
write(BufferUtil.EMPTY_BUFFER, _complete, this);
return State.SCHEDULED;
}
closed();
@ -910,7 +914,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
// write what we have
_buffer.position(0);
_buffer.limit(len);
_channel.write(_buffer,_eof,this);
write(_buffer,_eof,this);
return State.SCHEDULED;
}
@ -973,7 +977,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
// write what we have
_buffer.flip();
_channel.write(_buffer,_eof,this);
write(_buffer,_eof,this);
return State.SCHEDULED;
}

View File

@ -109,9 +109,9 @@ public class Response implements HttpServletResponse
public final static String HTTP_ONLY_COMMENT = "__HTTP_ONLY__";
private final HttpChannel<?> _channel;
private final HttpOutput _out;
private final HttpFields _fields = new HttpFields();
private final AtomicInteger _include = new AtomicInteger();
private HttpOutput _out;
private int _status = HttpStatus.NOT_SET_000;
private String _reason;
private Locale _locale;
@ -179,6 +179,11 @@ public class Response implements HttpServletResponse
{
return _out;
}
public void setHttpOutput(HttpOutput out)
{
_out=out;
}
public boolean isIncluding()
{
@ -988,15 +993,14 @@ public class Response implements HttpServletResponse
if (isCommitted() || isIncluding())
return;
long written = _out.getWritten();
if (written > len)
throw new IllegalArgumentException("setContentLength(" + len + ") when already written " + written);
_contentLength = len;
_fields.putLongField(HttpHeader.CONTENT_LENGTH.toString(), len);
if (_contentLength > 0)
{
long written = _out.getWritten();
if (written > len)
throw new IllegalArgumentException("setContentLength(" + len + ") when already written " + written);
_fields.putLongField(HttpHeader.CONTENT_LENGTH, len);
if (isAllContentWritten(written))
{
try
@ -1009,6 +1013,20 @@ public class Response implements HttpServletResponse
}
}
}
else if (_contentLength==0)
{
long written = _out.getWritten();
if (written > 0)
throw new IllegalArgumentException("setContentLength(0) when already written " + written);
_fields.put(HttpHeader.CONTENT_LENGTH, "0");
}
else
_fields.remove(HttpHeader.CONTENT_LENGTH);
}
public long getContentLength()
{
return _contentLength;
}
public boolean isAllContentWritten(long written)

View File

@ -882,7 +882,10 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
@Override
public void failed(Throwable x)
{
LOG.debug(x);
if (x instanceof IOException)
LOG.debug(x);
else
LOG.warn(x);
context.complete();
}
});

View File

@ -41,6 +41,17 @@ public class ServletTester extends ContainerLifeCycle
private final Server _server=new Server();
private final LocalConnector _connector=new LocalConnector(_server);
private final ServletContextHandler _context;
public Server getServer()
{
return _server;
}
public LocalConnector getConnector()
{
return _connector;
}
public void setVirtualHosts(String[] vhosts)
{
_context.setVirtualHosts(vhosts);

View File

@ -0,0 +1,535 @@
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.servlets;
import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.regex.Pattern;
import java.util.zip.Deflater;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpGenerator;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.server.HttpChannel;
import org.eclipse.jetty.server.HttpOutput;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.servlets.gzip.GzipFactory;
import org.eclipse.jetty.servlets.gzip.GzipHttpOutput;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/* ------------------------------------------------------------ */
/** Async GZIP Filter
* This filter is a gzip filter using jetty internal mechanism to apply gzip compression
* to output that is compatible with async IO and does not need to wrap the response nor output stream.
* The filter will gzip the content of a response if: <ul>
* <li>The filter is mapped to a matching path</li>
* <li>accept-encoding header is set to either gzip, deflate or a combination of those</li>
* <li>The response status code is >=200 and <300
* <li>The content length is unknown or more than the <code>minGzipSize</code> initParameter or the minGzipSize is 0(default)</li>
* <li>If a list of mimeTypes is set by the <code>mimeTypes</code> init parameter, then the Content-Type is in the list.</li>
* <li>If no mimeType list is set, then the content-type is not in the list defined by <code>excludedMimeTypes</code></li>
* <li>No content-encoding is specified by the resource</li>
* </ul>
*
* <p>
* Compressing the content can greatly improve the network bandwidth usage, but at a cost of memory and
* CPU cycles. If this filter is mapped for static content, then use of efficient direct NIO may be
* prevented, thus use of the gzip mechanism of the {@link org.eclipse.jetty.servlet.DefaultServlet} is
* advised instead.
* </p>
* <p>
* This filter extends {@link UserAgentFilter} and if the the initParameter <code>excludedAgents</code>
* is set to a comma separated list of user agents, then these agents will be excluded from gzip content.
* </p>
* <p>Init Parameters:</p>
* <dl>
* <dt>bufferSize</dt> <dd>The output buffer size. Defaults to 8192. Be careful as values <= 0 will lead to an
* {@link IllegalArgumentException}.
* See: {@link java.util.zip.GZIPOutputStream#GZIPOutputStream(java.io.OutputStream, int)}
* and: {@link java.util.zip.DeflaterOutputStream#DeflaterOutputStream(java.io.OutputStream, Deflater, int)}
* </dd>
* <dt>minGzipSize</dt> <dd>Content will only be compressed if content length is either unknown or greater
* than <code>minGzipSize</code>.
* </dd>
* <dt>deflateCompressionLevel</dt> <dd>The compression level used for deflate compression. (0-9).
* See: {@link java.util.zip.Deflater#Deflater(int, boolean)}
* </dd>
* <dt>deflateNoWrap</dt> <dd>The noWrap setting for deflate compression. Defaults to true. (true/false)
* See: {@link java.util.zip.Deflater#Deflater(int, boolean)}
* </dd>
* <dt>methods</dt> <dd>Comma separated list of HTTP methods to compress. If not set, only GET requests are compressed.
* </dd>
* <dt>mimeTypes</dt> <dd>Comma separated list of mime types to compress. If it is not set, then the excludedMimeTypes list is used.
* </dd>
* <dt>excludedMimeTypes</dt> <dd>Comma separated list of mime types to never compress. If not set, then the default is the commonly known
* image, video, audio and compressed types.
* </dd>
* <dt>excludedAgents</dt> <dd>Comma separated list of user agents to exclude from compression. Does a
* {@link String#contains(CharSequence)} to check if the excluded agent occurs
* in the user-agent header. If it does -> no compression
* </dd>
* <dt>excludeAgentPatterns</dt> <dd>Same as excludedAgents, but accepts regex patterns for more complex matching.
* </dd>
* <dt>excludePaths</dt> <dd>Comma separated list of paths to exclude from compression.
* Does a {@link String#startsWith(String)} comparison to check if the path matches.
* If it does match -> no compression. To match subpaths use <code>excludePathPatterns</code>
* instead.
* </dd>
* <dt>excludePathPatterns</dt> <dd>Same as excludePath, but accepts regex patterns for more complex matching.
* </dd>
* <dt>vary</dt> <dd>Set to the value of the Vary header sent with responses that could be compressed. By default it is
* set to 'Vary: Accept-Encoding, User-Agent' since IE6 is excluded by default from the excludedAgents.
* If user-agents are not to be excluded, then this can be set to 'Vary: Accept-Encoding'. Note also
* that shared caches may cache copies of a resource that is varied by User-Agent - one per variation of
* the User-Agent, unless the cache does some normalization of the UA string.
* </dd>
* <dt>checkGzExists</dt> <dd>If set to true, the filter check if a static resource with ".gz" appended exists. If so then
* the normal processing is done so that the default servlet can send the pre existing gz content.
* </dd>
* </dl>
*/
public class AsyncGzipFilter extends UserAgentFilter implements GzipFactory
{
private static final Logger LOG = Log.getLogger(GzipFilter.class);
public final static String GZIP = "gzip";
public static final String DEFLATE = "deflate";
public final static String ETAG = "o.e.j.s.GzipFilter.ETag";
public final static int DEFAULT_MIN_GZIP_SIZE=256;
protected ServletContext _context;
protected final Set<String> _mimeTypes=new HashSet<>();
protected boolean _excludeMimeTypes;
protected int _bufferSize=8192;
protected int _minGzipSize=DEFAULT_MIN_GZIP_SIZE;
protected int _deflateCompressionLevel=Deflater.DEFAULT_COMPRESSION;
protected boolean _deflateNoWrap = true;
protected boolean _checkGzExists = true;
// non-static, as other GzipFilter instances may have different configurations
protected final ThreadLocal<Deflater> _deflater = new ThreadLocal<Deflater>();
protected final static ThreadLocal<byte[]> _buffer= new ThreadLocal<byte[]>();
protected final Set<String> _methods=new HashSet<String>();
protected Set<String> _excludedAgents;
protected Set<Pattern> _excludedAgentPatterns;
protected Set<String> _excludedPaths;
protected Set<Pattern> _excludedPathPatterns;
protected HttpField _vary=new HttpGenerator.CachedHttpField(HttpHeader.VARY,HttpHeader.ACCEPT_ENCODING+", "+HttpHeader.USER_AGENT);
/* ------------------------------------------------------------ */
/**
* @see org.eclipse.jetty.servlets.UserAgentFilter#init(javax.servlet.FilterConfig)
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException
{
super.init(filterConfig);
_context=filterConfig.getServletContext();
String tmp=filterConfig.getInitParameter("bufferSize");
if (tmp!=null)
_bufferSize=Integer.parseInt(tmp);
LOG.debug("{} bufferSize={}",this,_bufferSize);
tmp=filterConfig.getInitParameter("minGzipSize");
if (tmp!=null)
_minGzipSize=Integer.parseInt(tmp);
LOG.debug("{} minGzipSize={}",this,_minGzipSize);
tmp=filterConfig.getInitParameter("deflateCompressionLevel");
if (tmp!=null)
_deflateCompressionLevel=Integer.parseInt(tmp);
LOG.debug("{} deflateCompressionLevel={}",this,_deflateCompressionLevel);
tmp=filterConfig.getInitParameter("deflateNoWrap");
if (tmp!=null)
_deflateNoWrap=Boolean.parseBoolean(tmp);
LOG.debug("{} deflateNoWrap={}",this,_deflateNoWrap);
tmp=filterConfig.getInitParameter("checkGzExists");
if (tmp!=null)
_checkGzExists=Boolean.parseBoolean(tmp);
LOG.debug("{} checkGzExists={}",this,_checkGzExists);
tmp=filterConfig.getInitParameter("methods");
if (tmp!=null)
{
StringTokenizer tok = new StringTokenizer(tmp,",",false);
while (tok.hasMoreTokens())
_methods.add(tok.nextToken().trim().toUpperCase());
}
else
_methods.add(HttpMethod.GET.asString());
LOG.debug("{} methods={}",this,_methods);
tmp=filterConfig.getInitParameter("mimeTypes");
if (tmp==null)
{
_excludeMimeTypes=true;
tmp=filterConfig.getInitParameter("excludedMimeTypes");
if (tmp==null)
{
for (String type:MimeTypes.getKnownMimeTypes())
{
if (type.startsWith("image/")||
type.startsWith("audio/")||
type.startsWith("video/"))
_mimeTypes.add(type);
_mimeTypes.add("application/compress");
_mimeTypes.add("application/zip");
_mimeTypes.add("application/gzip");
}
}
else
{
StringTokenizer tok = new StringTokenizer(tmp,",",false);
while (tok.hasMoreTokens())
_mimeTypes.add(tok.nextToken().trim());
}
}
else
{
StringTokenizer tok = new StringTokenizer(tmp,",",false);
while (tok.hasMoreTokens())
_mimeTypes.add(tok.nextToken().trim());
}
LOG.debug("{} mimeTypes={}",this,_mimeTypes);
LOG.debug("{} excludeMimeTypes={}",this,_excludeMimeTypes);
tmp=filterConfig.getInitParameter("excludedAgents");
if (tmp!=null)
{
_excludedAgents=new HashSet<String>();
StringTokenizer tok = new StringTokenizer(tmp,",",false);
while (tok.hasMoreTokens())
_excludedAgents.add(tok.nextToken().trim());
}
LOG.debug("{} excludedAgents={}",this,_excludedAgents);
tmp=filterConfig.getInitParameter("excludeAgentPatterns");
if (tmp!=null)
{
_excludedAgentPatterns=new HashSet<Pattern>();
StringTokenizer tok = new StringTokenizer(tmp,",",false);
while (tok.hasMoreTokens())
_excludedAgentPatterns.add(Pattern.compile(tok.nextToken().trim()));
}
LOG.debug("{} excludedAgentPatterns={}",this,_excludedAgentPatterns);
tmp=filterConfig.getInitParameter("excludePaths");
if (tmp!=null)
{
_excludedPaths=new HashSet<String>();
StringTokenizer tok = new StringTokenizer(tmp,",",false);
while (tok.hasMoreTokens())
_excludedPaths.add(tok.nextToken().trim());
}
LOG.debug("{} excludedPaths={}",this,_excludedPaths);
tmp=filterConfig.getInitParameter("excludePathPatterns");
if (tmp!=null)
{
_excludedPathPatterns=new HashSet<Pattern>();
StringTokenizer tok = new StringTokenizer(tmp,",",false);
while (tok.hasMoreTokens())
_excludedPathPatterns.add(Pattern.compile(tok.nextToken().trim()));
}
LOG.debug("{} excludedPathPatterns={}",this,_excludedPathPatterns);
tmp=filterConfig.getInitParameter("vary");
if (tmp!=null)
_vary=new HttpGenerator.CachedHttpField(HttpHeader.VARY,tmp);
LOG.debug("{} vary={}",this,_vary);
}
/* ------------------------------------------------------------ */
/**
* @see org.eclipse.jetty.servlets.UserAgentFilter#destroy()
*/
@Override
public void destroy()
{
}
/* ------------------------------------------------------------ */
/**
* @see org.eclipse.jetty.servlets.UserAgentFilter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
*/
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException
{
HttpServletRequest request=(HttpServletRequest)req;
HttpServletResponse response=(HttpServletResponse)res;
// If not a supported method or it is an Excluded URI or an excluded UA - no Vary because no matter what client, this URI is always excluded
String requestURI = request.getRequestURI();
if (!_methods.contains(request.getMethod()))
{
LOG.debug("{} excluded by method {}",this,request);
super.doFilter(request,response,chain);
return;
}
if (isExcludedPath(requestURI))
{
LOG.debug("{} excluded by path {}",this,request);
super.doFilter(request,response,chain);
return;
}
// Exclude non compressible mime-types known from URI extension. - no Vary because no matter what client, this URI is always excluded
if (_mimeTypes.size()>0)
{
String mimeType = _context.getMimeType(request.getRequestURI());
if (mimeType!=null && _mimeTypes.contains(mimeType)==_excludeMimeTypes)
{
LOG.debug("{} excluded by path suffix {}",this,request);
// handle normally without setting vary header
super.doFilter(request,response,chain);
return;
}
}
if (_checkGzExists && request.getServletContext()!=null)
{
String path=request.getServletContext().getRealPath(URIUtil.addPaths(request.getServletPath(),request.getPathInfo()));
if (path!=null)
{
File gz=new File(path+".gz");
if (gz.exists())
{
LOG.debug("{} gzip exists {}",this,request);
// allow default servlet to handle
super.doFilter(request,response,chain);
return;
}
}
}
// Special handling for etags
String etag = request.getHeader("If-None-Match");
if (etag!=null)
{
int dd=etag.indexOf("--");
if (dd>0)
request.setAttribute(ETAG,etag.substring(0,dd)+(etag.endsWith("\"")?"\"":""));
}
HttpChannel<?> channel = HttpChannel.getCurrentHttpChannel();
HttpOutput out = channel.getResponse().getHttpOutput();
if (!(out instanceof GzipHttpOutput))
{
if (out.getClass()!=HttpOutput.class)
throw new IllegalStateException();
channel.getResponse().setHttpOutput(out = new GzipHttpOutput(channel));
}
GzipHttpOutput cout=(GzipHttpOutput)out;
boolean exceptional=true;
try
{
cout.mightCompress(this);
super.doFilter(request,response,chain);
exceptional=false;
}
finally
{
LOG.debug("{} excepted {}",this,request);
if (exceptional && !response.isCommitted())
{
cout.resetBuffer();
cout.noCompression();
}
}
}
/**
* Checks to see if the userAgent is excluded
*
* @param ua
* the user agent
* @return boolean true if excluded
*/
private boolean isExcludedAgent(String ua)
{
if (ua == null)
return false;
if (_excludedAgents != null)
{
if (_excludedAgents.contains(ua))
{
return true;
}
}
if (_excludedAgentPatterns != null)
{
for (Pattern pattern : _excludedAgentPatterns)
{
if (pattern.matcher(ua).matches())
{
return true;
}
}
}
return false;
}
/**
* Checks to see if the path is excluded
*
* @param requestURI
* the request uri
* @return boolean true if excluded
*/
private boolean isExcludedPath(String requestURI)
{
if (requestURI == null)
return false;
if (_excludedPaths != null)
{
for (String excludedPath : _excludedPaths)
{
if (requestURI.startsWith(excludedPath))
{
return true;
}
}
}
if (_excludedPathPatterns != null)
{
for (Pattern pattern : _excludedPathPatterns)
{
if (pattern.matcher(requestURI).matches())
{
return true;
}
}
}
return false;
}
@Override
public HttpField getVaryField()
{
return _vary;
}
@Override
public Deflater getDeflater(Request request, long content_length)
{
String ua = getUserAgent(request);
if (ua!=null && isExcludedAgent(ua))
{
LOG.debug("{} excluded user agent {}",this,request);
return null;
}
if (content_length>=0 && content_length<_minGzipSize)
{
LOG.debug("{} excluded minGzipSize {}",this,request);
return null;
}
String accept = request.getHttpFields().get(HttpHeader.ACCEPT_ENCODING);
if (accept==null)
{
LOG.debug("{} excluded !accept {}",this,request);
return null;
}
boolean gzip=false;
if (GZIP.equals(accept) || accept.startsWith("gzip,"))
gzip=true;
else
{
List<String> list=HttpFields.qualityList(request.getHttpFields().getValues(HttpHeader.ACCEPT_ENCODING.asString(),","));
for (String a:list)
{
if (GZIP.equalsIgnoreCase(HttpFields.valueParameters(a,null)))
{
gzip=true;
break;
}
}
}
if (!gzip)
{
LOG.debug("{} excluded not gzip accept {}",this,request);
return null;
}
Deflater df = _deflater.get();
if (df==null)
df=new Deflater(_deflateCompressionLevel,_deflateNoWrap);
else
_deflater.set(null);
return df;
}
@Override
public void recycle(Deflater deflater)
{
if (_deflater.get()==null)
_deflater.set(deflater);
}
@Override
public boolean isExcludedMimeType(String mimetype)
{
return _mimeTypes.contains(mimetype) == _excludeMimeTypes;
}
@Override
public int getBufferSize()
{
return _bufferSize;
}
}

View File

@ -0,0 +1,38 @@
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.servlets.gzip;
import java.util.zip.Deflater;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.server.Request;
public interface GzipFactory
{
int getBufferSize();
HttpField getVaryField();
Deflater getDeflater(Request request, long content_length);
boolean isExcludedMimeType(String asciiToLowerCase);
void recycle(Deflater deflater);
}

View File

@ -0,0 +1,353 @@
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.servlets.gzip;
import java.nio.ByteBuffer;
import java.nio.channels.WritePendingException;
import java.util.concurrent.atomic.AtomicReference;
import java.util.zip.CRC32;
import java.util.zip.Deflater;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpGenerator;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.server.HttpChannel;
import org.eclipse.jetty.server.HttpOutput;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IteratingNestedCallback;
import org.eclipse.jetty.util.StringUtil;
public class GzipHttpOutput extends HttpOutput
{
private final static HttpGenerator.CachedHttpField CONTENT_ENCODING_GZIP=new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_ENCODING,"gzip");
private final static byte[] GZIP_HEADER = new byte[] { (byte)0x1f, (byte)0x8b, Deflater.DEFLATED, 0, 0, 0, 0, 0, 0, 0 };
private enum GZState { NOT_COMPRESSING, MIGHT_COMPRESS, COMMITTING, COMPRESSING, FINISHED};
private final AtomicReference<GZState> _state = new AtomicReference<>(GZState.NOT_COMPRESSING);
private final CRC32 _crc = new CRC32();
private Deflater _deflater;
private GzipFactory _factory;
private ByteBuffer _buffer;
public GzipHttpOutput(HttpChannel<?> channel)
{
super(channel);
}
@Override
public void reset()
{
super.reset();
}
@Override
protected void write(ByteBuffer content, boolean complete, Callback callback)
{
switch (_state.get())
{
case NOT_COMPRESSING:
super.write(content,complete,callback);
return;
case MIGHT_COMPRESS:
commit(content,complete,callback);
break;
case COMMITTING:
throw new WritePendingException();
case COMPRESSING:
gzip(content,complete,callback);
break;
case FINISHED:
throw new IllegalStateException();
}
}
private void superWrite(ByteBuffer content, boolean complete, Callback callback)
{
super.write(content,complete,callback);
}
private void addTrailer()
{
int i=_buffer.limit();
_buffer.limit(i+8);
int v=(int)_crc.getValue();
_buffer.put(i++,(byte)(v & 0xFF));
_buffer.put(i++,(byte)((v>>>8) & 0xFF));
_buffer.put(i++,(byte)((v>>>16) & 0xFF));
_buffer.put(i++,(byte)((v>>>24) & 0xFF));
v=_deflater.getTotalIn();
_buffer.put(i++,(byte)(v & 0xFF));
_buffer.put(i++,(byte)((v>>>8) & 0xFF));
_buffer.put(i++,(byte)((v>>>16) & 0xFF));
_buffer.put(i++,(byte)((v>>>24) & 0xFF));
}
private void gzip(ByteBuffer content, boolean complete, final Callback callback)
{
if (content.hasRemaining())
{
if (content.hasArray())
new GzipArrayCB(content,complete,callback).iterate();
else
new GzipBufferCB(content,complete,callback).iterate();
}
else if (complete)
{
_deflater.finish();
int produced=_deflater.deflate(_buffer.array(),_buffer.arrayOffset()+_buffer.limit(),_buffer.capacity()-_buffer.limit(),Deflater.NO_FLUSH);
_buffer.limit(_buffer.limit()+produced);
addTrailer();
superWrite(_buffer,complete,new Callback()
{
@Override
public void succeeded()
{
getHttpChannel().getByteBufferPool().release(_buffer);
_buffer=null;
callback.succeeded();
}
@Override
public void failed(Throwable x)
{
callback.failed(x);
}
});
}
}
protected void commit(ByteBuffer content, boolean complete, Callback callback)
{
// Are we excluding because of status?
Response response=getHttpChannel().getResponse();
int sc = response.getStatus();
if (sc>0 && (sc<200 || sc==204 || sc==205 || sc>=300))
{
noCompression();
super.write(content,complete,callback);
return;
}
// Are we excluding because of mime-type?
String ct = getHttpChannel().getResponse().getContentType();
if (ct!=null)
{
ct=MimeTypes.getContentTypeWithoutCharset(ct);
if (_factory.isExcludedMimeType(StringUtil.asciiToLowerCase(ct)))
{
noCompression();
super.write(content,complete,callback);
return;
}
}
// Are we the thread that commits?
if (_state.compareAndSet(GZState.MIGHT_COMPRESS,GZState.COMMITTING))
{
// We are varying the response due to accept encoding header.
HttpFields fields = response.getHttpFields();
fields.add(_factory.getVaryField());
long content_length = response.getContentLength();
if (content_length<0 && complete)
content_length=content.remaining();
_deflater = content.isDirect()?null:_factory.getDeflater(getHttpChannel().getRequest(),content_length);
if (_deflater==null)
{
_state.set(GZState.NOT_COMPRESSING);
super.write(content,complete,callback);
return;
}
fields.put(CONTENT_ENCODING_GZIP);
_crc.reset();
_buffer=getHttpChannel().getByteBufferPool().acquire(_factory.getBufferSize(),false);
BufferUtil.fill(_buffer,GZIP_HEADER,0,GZIP_HEADER.length);
// Adjust headers
response.setContentLength(-1);
String etag=fields.get(HttpHeader.ETAG);
if (etag!=null)
fields.put(HttpHeader.ETAG,etag.substring(0,etag.length()-1)+"--gzip\"");
_state.set(GZState.COMPRESSING);
gzip(content,complete,callback);
}
}
public void noCompression()
{
while (true)
{
switch (_state.get())
{
case NOT_COMPRESSING:
return;
case MIGHT_COMPRESS:
if (_state.compareAndSet(GZState.MIGHT_COMPRESS,GZState.NOT_COMPRESSING))
return;
break;
default:
throw new IllegalStateException(_state.get().toString());
}
}
}
public void mightCompress(GzipFactory factory)
{
while (true)
{
switch (_state.get())
{
case NOT_COMPRESSING:
_factory=factory;
if (_state.compareAndSet(GZState.NOT_COMPRESSING,GZState.MIGHT_COMPRESS))
return;
_factory=null;
break;
default:
throw new IllegalStateException();
}
}
}
private class GzipArrayCB extends IteratingNestedCallback
{
private final boolean _complete;
public GzipArrayCB(ByteBuffer content, boolean complete, Callback callback)
{
super(callback);
_complete=complete;
byte[] array=content.array();
int off=content.arrayOffset()+content.position();
int len=content.remaining();
_crc.update(array,off,len);
_deflater.setInput(array,off,len);
if (complete)
_deflater.finish();
content.position(content.limit());
}
@Override
protected State process() throws Exception
{
if (_deflater.needsInput())
{
if (_deflater.finished())
{
_factory.recycle(_deflater);
_deflater=null;
getHttpChannel().getByteBufferPool().release(_buffer);
_buffer=null;
}
return State.SUCCEEDED;
}
int off=_buffer.arrayOffset()+_buffer.limit();
int len=_buffer.capacity()-_buffer.limit()- (_complete?8:0);
int produced=_deflater.deflate(_buffer.array(),off,len,Deflater.NO_FLUSH);
_buffer.limit(_buffer.limit()+produced);
boolean complete=_deflater.finished();
if (complete)
{
addTrailer();
_deflater.end(); // TODO recycle
}
superWrite(_buffer,complete,this);
return State.SCHEDULED;
}
}
private class GzipBufferCB extends IteratingNestedCallback
{
private final ByteBuffer _input;
private final ByteBuffer _content;
private final boolean _complete;
public GzipBufferCB(ByteBuffer content, boolean complete, Callback callback)
{
super(callback);
_input=getHttpChannel().getByteBufferPool().acquire(Math.min(_factory.getBufferSize(),content.remaining()),false);
_content=content;
_complete=complete;
}
@Override
protected State process() throws Exception
{
if (_deflater.needsInput())
{
if (BufferUtil.isEmpty(_content))
{
if (_deflater.finished())
{
_factory.recycle(_deflater);
_deflater=null;
getHttpChannel().getByteBufferPool().release(_buffer);
_buffer=null;
}
return State.SUCCEEDED;
}
BufferUtil.clearToFill(_input);
BufferUtil.put(_content,_input);
BufferUtil.flipToFlush(_input,0);
byte[] array=_input.array();
int off=_input.arrayOffset()+_input.position();
int len=_input.remaining();
_crc.update(array,off,len);
_deflater.setInput(array,off,len);
if (_complete && BufferUtil.isEmpty(_content))
_deflater.finish();
}
int off=_buffer.arrayOffset()+_buffer.limit();
int len=_buffer.capacity()-_buffer.limit() - (_complete?8:0);
int produced=_deflater.deflate(_buffer.array(),off,len,Deflater.NO_FLUSH);
_buffer.limit(_buffer.limit()+produced);
boolean complete=_deflater.finished();
if (complete)
addTrailer();
superWrite(_buffer,complete,this);
return State.SCHEDULED;
}
}
}

View File

@ -22,13 +22,15 @@ import java.io.File;
import java.util.Arrays;
import java.util.List;
import javax.servlet.Filter;
import javax.servlet.Servlet;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlets.gzip.CompressedResponseWrapper;
import org.eclipse.jetty.servlets.gzip.GzipTester;
import org.eclipse.jetty.servlets.gzip.TestServletBufferTypeLengthWrite;
import org.eclipse.jetty.servlets.gzip.TestServletLengthStreamTypeWrite;
import org.eclipse.jetty.servlets.gzip.TestServletLengthTypeStreamWrite;
import org.eclipse.jetty.servlets.gzip.TestServletStreamLengthTypeWrite;
@ -71,32 +73,45 @@ public class GzipFilterContentLengthTest
{
return Arrays.asList(new Object[][]
{
{ TestServletLengthStreamTypeWrite.class, GzipFilter.GZIP },
{ TestServletLengthTypeStreamWrite.class, GzipFilter.GZIP },
{ TestServletStreamLengthTypeWrite.class, GzipFilter.GZIP },
{ TestServletStreamLengthTypeWriteWithFlush.class, GzipFilter.GZIP },
{ TestServletStreamTypeLengthWrite.class, GzipFilter.GZIP },
{ TestServletTypeLengthStreamWrite.class, GzipFilter.GZIP },
{ TestServletTypeStreamLengthWrite.class, GzipFilter.GZIP },
{ TestServletLengthStreamTypeWrite.class, GzipFilter.DEFLATE },
{ TestServletLengthTypeStreamWrite.class, GzipFilter.DEFLATE },
{ TestServletStreamLengthTypeWrite.class, GzipFilter.DEFLATE },
{ TestServletStreamLengthTypeWriteWithFlush.class, GzipFilter.DEFLATE },
{ TestServletStreamTypeLengthWrite.class, GzipFilter.DEFLATE },
{ TestServletTypeLengthStreamWrite.class, GzipFilter.DEFLATE },
{ TestServletTypeStreamLengthWrite.class, GzipFilter.DEFLATE }
{ AsyncGzipFilter.class, TestServletLengthStreamTypeWrite.class, GzipFilter.GZIP },
{ AsyncGzipFilter.class, TestServletLengthTypeStreamWrite.class, GzipFilter.GZIP },
{ AsyncGzipFilter.class, TestServletStreamLengthTypeWrite.class, GzipFilter.GZIP },
{ AsyncGzipFilter.class, TestServletStreamLengthTypeWriteWithFlush.class, GzipFilter.GZIP },
{ AsyncGzipFilter.class, TestServletStreamTypeLengthWrite.class, GzipFilter.GZIP },
{ AsyncGzipFilter.class, TestServletTypeLengthStreamWrite.class, GzipFilter.GZIP },
{ AsyncGzipFilter.class, TestServletTypeStreamLengthWrite.class, GzipFilter.GZIP },
{ AsyncGzipFilter.class, TestServletBufferTypeLengthWrite.class, GzipFilter.GZIP },
{ GzipFilter.class, TestServletLengthStreamTypeWrite.class, GzipFilter.GZIP },
{ GzipFilter.class, TestServletLengthTypeStreamWrite.class, GzipFilter.GZIP },
{ GzipFilter.class, TestServletStreamLengthTypeWrite.class, GzipFilter.GZIP },
{ GzipFilter.class, TestServletStreamLengthTypeWriteWithFlush.class, GzipFilter.GZIP },
{ GzipFilter.class, TestServletStreamTypeLengthWrite.class, GzipFilter.GZIP },
{ GzipFilter.class, TestServletTypeLengthStreamWrite.class, GzipFilter.GZIP },
{ GzipFilter.class, TestServletTypeStreamLengthWrite.class, GzipFilter.GZIP },
{ GzipFilter.class, TestServletLengthStreamTypeWrite.class, GzipFilter.DEFLATE },
{ GzipFilter.class, TestServletLengthTypeStreamWrite.class, GzipFilter.DEFLATE },
{ GzipFilter.class, TestServletStreamLengthTypeWrite.class, GzipFilter.DEFLATE },
{ GzipFilter.class, TestServletStreamLengthTypeWriteWithFlush.class, GzipFilter.DEFLATE },
{ GzipFilter.class, TestServletStreamTypeLengthWrite.class, GzipFilter.DEFLATE },
{ GzipFilter.class, TestServletTypeLengthStreamWrite.class, GzipFilter.DEFLATE },
{ GzipFilter.class, TestServletTypeStreamLengthWrite.class, GzipFilter.DEFLATE },
});
}
private static final int LARGE = CompressedResponseWrapper.DEFAULT_BUFFER_SIZE * 8;
private static final int MEDIUM = CompressedResponseWrapper.DEFAULT_BUFFER_SIZE;
private static final int SMALL = CompressedResponseWrapper.DEFAULT_BUFFER_SIZE / 4;
private static final int TINY = CompressedResponseWrapper.DEFAULT_MIN_COMPRESS_SIZE/ 2;
private static final HttpConfiguration defaultHttp = new HttpConfiguration();
private static final int LARGE = defaultHttp.getOutputBufferSize() * 8;
private static final int MEDIUM = defaultHttp.getOutputBufferSize();
private static final int SMALL = defaultHttp.getOutputBufferSize() / 4;
private static final int TINY = AsyncGzipFilter.DEFAULT_MIN_GZIP_SIZE / 2;
private String compressionType;
public GzipFilterContentLengthTest(Class<? extends Servlet> testServlet, String compressionType)
public GzipFilterContentLengthTest(Class<? extends Filter> testFilter,Class<? extends Servlet> testServlet, String compressionType)
{
this.testFilter = testFilter;
this.testServlet = testServlet;
this.compressionType = compressionType;
}
@ -104,11 +119,13 @@ public class GzipFilterContentLengthTest
@Rule
public TestingDir testingdir = new TestingDir();
private Class<? extends Filter> testFilter;
private Class<? extends Servlet> testServlet;
private void assertIsGzipCompressed(String filename, int filesize) throws Exception
{
GzipTester tester = new GzipTester(testingdir, compressionType);
tester.setGzipFilterClass(testFilter);
File testfile = tester.prepareServerFile(testServlet.getSimpleName() + "-" + filename,filesize);
@ -129,6 +146,7 @@ public class GzipFilterContentLengthTest
private void assertIsNotGzipCompressed(String filename, int filesize) throws Exception
{
GzipTester tester = new GzipTester(testingdir, compressionType);
tester.setGzipFilterClass(testFilter);
File testfile = tester.prepareServerFile(testServlet.getSimpleName() + "-" + filename,filesize);

View File

@ -23,6 +23,8 @@ import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import javax.servlet.Filter;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlets.gzip.GzipTester;
@ -49,56 +51,61 @@ public class GzipFilterDefaultNoRecompressTest
return Arrays.asList(new Object[][]
{
// Some already compressed files
{ "test_quotes.gz", "application/gzip", GzipFilter.GZIP },
{ "test_quotes.bz2", "application/bzip2", GzipFilter.GZIP },
{ "test_quotes.zip", "application/zip", GzipFilter.GZIP },
{ "test_quotes.rar", "application/octet-stream", GzipFilter.GZIP },
{ GzipFilter.class, "test_quotes.gz", "application/gzip", GzipFilter.GZIP },
{ GzipFilter.class, "test_quotes.bz2", "application/bzip2", GzipFilter.GZIP },
{ GzipFilter.class, "test_quotes.zip", "application/zip", GzipFilter.GZIP },
{ GzipFilter.class, "test_quotes.rar", "application/octet-stream", GzipFilter.GZIP },
// Some images (common first)
{ "jetty_logo.png", "image/png", GzipFilter.GZIP },
{ "jetty_logo.gif", "image/gif", GzipFilter.GZIP },
{ "jetty_logo.jpeg", "image/jpeg", GzipFilter.GZIP },
{ "jetty_logo.jpg", "image/jpeg", GzipFilter.GZIP },
{ GzipFilter.class, "jetty_logo.png", "image/png", GzipFilter.GZIP },
{ GzipFilter.class, "jetty_logo.gif", "image/gif", GzipFilter.GZIP },
{ GzipFilter.class, "jetty_logo.jpeg", "image/jpeg", GzipFilter.GZIP },
{ GzipFilter.class, "jetty_logo.jpg", "image/jpeg", GzipFilter.GZIP },
// Lesser encountered images (usually found being requested from non-browser clients)
{ "jetty_logo.bmp", "image/bmp", GzipFilter.GZIP },
{ "jetty_logo.tga", "application/tga", GzipFilter.GZIP },
{ "jetty_logo.tif", "image/tiff", GzipFilter.GZIP },
{ "jetty_logo.tiff", "image/tiff", GzipFilter.GZIP },
{ "jetty_logo.xcf", "image/xcf", GzipFilter.GZIP },
{ "jetty_logo.jp2", "image/jpeg2000", GzipFilter.GZIP },
{ GzipFilter.class, "jetty_logo.bmp", "image/bmp", GzipFilter.GZIP },
{ GzipFilter.class, "jetty_logo.tga", "application/tga", GzipFilter.GZIP },
{ GzipFilter.class, "jetty_logo.tif", "image/tiff", GzipFilter.GZIP },
{ GzipFilter.class, "jetty_logo.tiff", "image/tiff", GzipFilter.GZIP },
{ GzipFilter.class, "jetty_logo.xcf", "image/xcf", GzipFilter.GZIP },
{ GzipFilter.class, "jetty_logo.jp2", "image/jpeg2000", GzipFilter.GZIP },
//qvalue disables compression
{ "test_quotes.txt", "text/plain", GzipFilter.GZIP+";q=0"},
{ "test_quotes.txt", "text/plain", GzipFilter.GZIP+"; q = 0 "},
// Same tests again for deflate
{ GzipFilter.class, "test_quotes.txt", "text/plain", GzipFilter.GZIP+";q=0"},
{ GzipFilter.class, "test_quotes.txt", "text/plain", GzipFilter.GZIP+"; q = 0 "},
// Some already compressed files
{ "test_quotes.gz", "application/gzip", GzipFilter.DEFLATE },
{ "test_quotes.bz2", "application/bzip2", GzipFilter.DEFLATE },
{ "test_quotes.zip", "application/zip", GzipFilter.DEFLATE },
{ "test_quotes.rar", "application/octet-stream", GzipFilter.DEFLATE },
{ AsyncGzipFilter.class, "test_quotes.gz", "application/gzip", GzipFilter.GZIP },
{ AsyncGzipFilter.class, "test_quotes.bz2", "application/bzip2", GzipFilter.GZIP },
{ AsyncGzipFilter.class, "test_quotes.zip", "application/zip", GzipFilter.GZIP },
{ AsyncGzipFilter.class, "test_quotes.rar", "application/octet-stream", GzipFilter.GZIP },
// Some images (common first)
{ "jetty_logo.png", "image/png", GzipFilter.DEFLATE },
{ "jetty_logo.gif", "image/gif", GzipFilter.DEFLATE },
{ "jetty_logo.jpeg", "image/jpeg", GzipFilter.DEFLATE },
{ "jetty_logo.jpg", "image/jpeg", GzipFilter.DEFLATE },
{ AsyncGzipFilter.class, "jetty_logo.png", "image/png", GzipFilter.GZIP },
{ AsyncGzipFilter.class, "jetty_logo.gif", "image/gif", GzipFilter.GZIP },
{ AsyncGzipFilter.class, "jetty_logo.jpeg", "image/jpeg", GzipFilter.GZIP },
{ AsyncGzipFilter.class, "jetty_logo.jpg", "image/jpeg", GzipFilter.GZIP },
// Lesser encountered images (usually found being requested from non-browser clients)
{ "jetty_logo.bmp", "image/bmp", GzipFilter.DEFLATE },
{ "jetty_logo.tga", "application/tga", GzipFilter.DEFLATE },
{ "jetty_logo.tif", "image/tiff", GzipFilter.DEFLATE },
{ "jetty_logo.tiff", "image/tiff", GzipFilter.DEFLATE },
{ "jetty_logo.xcf", "image/xcf", GzipFilter.DEFLATE },
{ "jetty_logo.jp2", "image/jpeg2000", GzipFilter.DEFLATE } });
{ AsyncGzipFilter.class, "jetty_logo.bmp", "image/bmp", GzipFilter.GZIP },
{ AsyncGzipFilter.class, "jetty_logo.tga", "application/tga", GzipFilter.GZIP },
{ AsyncGzipFilter.class, "jetty_logo.tif", "image/tiff", GzipFilter.GZIP },
{ AsyncGzipFilter.class, "jetty_logo.tiff", "image/tiff", GzipFilter.GZIP },
{ AsyncGzipFilter.class, "jetty_logo.xcf", "image/xcf", GzipFilter.GZIP },
{ AsyncGzipFilter.class, "jetty_logo.jp2", "image/jpeg2000", GzipFilter.GZIP },
//qvalue disables compression
{ AsyncGzipFilter.class, "test_quotes.txt", "text/plain", GzipFilter.GZIP+";q=0"},
{ AsyncGzipFilter.class, "test_quotes.txt", "text/plain", GzipFilter.GZIP+"; q = 0 "}
});
}
@Rule
public TestingDir testingdir = new TestingDir();
private Class<? extends Filter> testFilter;
private String alreadyCompressedFilename;
private String expectedContentType;
private String compressionType;
public GzipFilterDefaultNoRecompressTest(String testFilename, String expectedContentType, String compressionType)
public GzipFilterDefaultNoRecompressTest(Class<? extends Filter> testFilter,String testFilename, String expectedContentType, String compressionType)
{
this.testFilter = testFilter;
this.alreadyCompressedFilename = testFilename;
this.expectedContentType = expectedContentType;
this.compressionType = compressionType;
@ -108,6 +115,7 @@ public class GzipFilterDefaultNoRecompressTest
public void testNotGzipFiltered_Default_AlreadyCompressed() throws Exception
{
GzipTester tester = new GzipTester(testingdir, compressionType);
tester.setGzipFilterClass(testFilter);
copyTestFileToServer(alreadyCompressedFilename);

View File

@ -21,22 +21,22 @@ package org.eclipse.jetty.servlets;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import javax.servlet.Filter;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import junit.framework.Assert;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlets.gzip.CompressedResponseWrapper;
import org.eclipse.jetty.servlets.gzip.GzipTester;
import org.eclipse.jetty.toolchain.test.TestingDir;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -50,20 +50,22 @@ import org.junit.runners.Parameterized.Parameters;
public class GzipFilterDefaultTest
{
@Parameters
public static Collection<String[]> data()
public static List<Object[]> data()
{
String[][] data = new String[][]
{
{ GzipFilter.GZIP },
{ GzipFilter.DEFLATE } };
return Arrays.asList(data);
return Arrays.asList(new Object[][]
{
{ AsyncGzipFilter.class, GzipFilter.GZIP },
{ GzipFilter.class, GzipFilter.GZIP },
{ GzipFilter.class, GzipFilter.DEFLATE },
});
}
private Class<? extends Filter> testFilter;
private String compressionType;
public GzipFilterDefaultTest(String compressionType)
public GzipFilterDefaultTest(Class<? extends Filter> testFilter, String compressionType)
{
this.testFilter=testFilter;
this.compressionType = compressionType;
}
@ -105,7 +107,10 @@ public class GzipFilterDefaultTest
public static class HttpContentTypeWithEncoding extends HttpServlet
{
public static final String COMPRESSED_CONTENT = "<html><head></head><body><h1>COMPRESSED</h1></body></html>";
public static final String COMPRESSED_CONTENT = "<html><head></head><body><h1>COMPRESSABLE CONTENT</h1>"+
"This content must be longer than the default min gzip length, which is 256 bytes. "+
"The moon is blue to a fish in love. How now brown cow. The quick brown fox jumped over the lazy dog. A woman needs a man like a fish needs a bicycle!"+
"</body></html>";
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
@ -128,9 +133,9 @@ public class GzipFilterDefaultTest
public void testIsGzipByMethod() throws Exception
{
GzipTester tester = new GzipTester(testingdir, compressionType);
tester.setGzipFilterClass(testFilter);
// Test content that is smaller than the buffer.
int filesize = CompressedResponseWrapper.DEFAULT_BUFFER_SIZE * 2;
int filesize = tester.getOutputBufferSize() * 2;
tester.prepareServerFile("file.txt",filesize);
FilterHolder holder = tester.setContentServlet(GetServlet.class);
@ -177,8 +182,8 @@ public class GzipFilterDefaultTest
public void testIsGzipCompressedEmpty() throws Exception
{
GzipTester tester = new GzipTester(testingdir, compressionType);
tester.setGzipFilterClass(testFilter);
// Test content that is smaller than the buffer.
tester.prepareServerFile("empty.txt",0);
FilterHolder holder = tester.setContentServlet(org.eclipse.jetty.servlet.DefaultServlet.class);
@ -199,9 +204,9 @@ public class GzipFilterDefaultTest
public void testIsGzipCompressedTiny() throws Exception
{
GzipTester tester = new GzipTester(testingdir, compressionType);
tester.setGzipFilterClass(testFilter);
// Test content that is smaller than the buffer.
int filesize = CompressedResponseWrapper.DEFAULT_BUFFER_SIZE / 4;
int filesize = tester.getOutputBufferSize() / 4;
tester.prepareServerFile("file.txt",filesize);
FilterHolder holder = tester.setContentServlet(org.eclipse.jetty.servlet.DefaultServlet.class);
@ -218,62 +223,14 @@ public class GzipFilterDefaultTest
tester.stop();
}
}
@Test
public void testIsGzipCompressedTinyWithQ() throws Exception
{
GzipTester tester = new GzipTester(testingdir, compressionType+";q=0.5");
// Test content that is smaller than the buffer.
int filesize = CompressedResponseWrapper.DEFAULT_BUFFER_SIZE / 4;
tester.prepareServerFile("file.txt",filesize);
FilterHolder holder = tester.setContentServlet(org.eclipse.jetty.servlet.DefaultServlet.class);
holder.setInitParameter("mimeTypes","text/plain");
try
{
tester.start();
HttpTester.Response http = tester.assertIsResponseGzipCompressed("GET","file.txt");
Assert.assertEquals("Accept-Encoding",http.get("Vary"));
}
finally
{
tester.stop();
}
}
@Test
public void testIsGzipCompressedTinyWithBadQ() throws Exception
{
GzipTester tester = new GzipTester(testingdir, compressionType+";q=");
// Test content that is smaller than the buffer.
int filesize = CompressedResponseWrapper.DEFAULT_BUFFER_SIZE / 4;
tester.prepareServerFile("file.txt",filesize);
FilterHolder holder = tester.setContentServlet(org.eclipse.jetty.servlet.DefaultServlet.class);
holder.setInitParameter("mimeTypes","text/plain");
try
{
tester.start();
HttpTester.Response http = tester.assertIsResponseGzipCompressed("GET","file.txt");
Assert.assertEquals("Accept-Encoding",http.get("Vary"));
}
finally
{
tester.stop();
}
}
@Test
public void testIsGzipCompressedLarge() throws Exception
{
GzipTester tester = new GzipTester(testingdir, compressionType);
tester.setGzipFilterClass(testFilter);
// Test content that is smaller than the buffer.
int filesize = CompressedResponseWrapper.DEFAULT_BUFFER_SIZE * 4;
int filesize = tester.getOutputBufferSize() * 4;
tester.prepareServerFile("file.txt",filesize);
FilterHolder holder = tester.setContentServlet(org.eclipse.jetty.servlet.DefaultServlet.class);
@ -296,9 +253,9 @@ public class GzipFilterDefaultTest
public void testGzipedIfModified() throws Exception
{
GzipTester tester = new GzipTester(testingdir, compressionType);
tester.setGzipFilterClass(testFilter);
// Test content that is smaller than the buffer.
int filesize = CompressedResponseWrapper.DEFAULT_BUFFER_SIZE * 4;
int filesize = tester.getOutputBufferSize() * 4;
tester.prepareServerFile("file.txt",filesize);
FilterHolder holder = tester.setContentServlet(org.eclipse.jetty.servlet.DefaultServlet.class);
@ -321,9 +278,9 @@ public class GzipFilterDefaultTest
public void testNotGzipedIfNotModified() throws Exception
{
GzipTester tester = new GzipTester(testingdir, compressionType);
tester.setGzipFilterClass(testFilter);
// Test content that is smaller than the buffer.
int filesize = CompressedResponseWrapper.DEFAULT_BUFFER_SIZE * 4;
int filesize = tester.getOutputBufferSize() * 4;
tester.prepareServerFile("file.txt",filesize);
FilterHolder holder = tester.setContentServlet(org.eclipse.jetty.servlet.DefaultServlet.class);
@ -343,11 +300,12 @@ public class GzipFilterDefaultTest
@Test
public void testIsNotGzipCompressedWithQ() throws Exception
public void testIsNotGzipCompressedWithZeroQ() throws Exception
{
GzipTester tester = new GzipTester(testingdir, compressionType+"; q = 0");
GzipTester tester = new GzipTester(testingdir, compressionType+"; q=0");
tester.setGzipFilterClass(testFilter);
int filesize = CompressedResponseWrapper.DEFAULT_BUFFER_SIZE / 4;
int filesize = tester.getOutputBufferSize() / 4;
tester.prepareServerFile("file.txt",filesize);
FilterHolder holder = tester.setContentServlet(org.eclipse.jetty.servlet.DefaultServlet.class);
@ -364,13 +322,38 @@ public class GzipFilterDefaultTest
tester.stop();
}
}
@Test
public void testIsGzipCompressedWithQ() throws Exception
{
GzipTester tester = new GzipTester(testingdir, compressionType,"something;q=0.1,"+compressionType+";q=0.5");
tester.setGzipFilterClass(testFilter);
int filesize = tester.getOutputBufferSize() / 4;
tester.prepareServerFile("file.txt",filesize);
FilterHolder holder = tester.setContentServlet(org.eclipse.jetty.servlet.DefaultServlet.class);
holder.setInitParameter("mimeTypes","text/plain");
try
{
tester.start();
HttpTester.Response http = tester.assertIsResponseGzipCompressed("GET","file.txt");
Assert.assertEquals("Accept-Encoding",http.get("Vary"));
}
finally
{
tester.stop();
}
}
@Test
public void testIsNotGzipCompressedByContentType() throws Exception
{
GzipTester tester = new GzipTester(testingdir, compressionType);
tester.setGzipFilterClass(testFilter);
int filesize = CompressedResponseWrapper.DEFAULT_BUFFER_SIZE * 4;
int filesize = tester.getOutputBufferSize() * 4;
tester.prepareServerFile("file.mp3",filesize);
FilterHolder holder = tester.setContentServlet(org.eclipse.jetty.servlet.DefaultServlet.class);
@ -392,6 +375,7 @@ public class GzipFilterDefaultTest
public void testGzipCompressedByContentTypeWithEncoding() throws Exception
{
GzipTester tester = new GzipTester(testingdir, compressionType);
tester.setGzipFilterClass(testFilter);
FilterHolder holder = tester.setContentServlet(HttpContentTypeWithEncoding.class);
holder.setInitParameter("mimeTypes","text/plain");
try
@ -399,8 +383,6 @@ public class GzipFilterDefaultTest
tester.start();
HttpTester.Response http = tester.assertNonStaticContentIsResponseGzipCompressed("GET","xxx", HttpContentTypeWithEncoding.COMPRESSED_CONTENT);
Assert.assertEquals("Accept-Encoding",http.get("Vary"));
System.err.println(http.get("Content-Type"));
System.err.println(http.get("Content-Encoding"));
}
finally
{
@ -413,8 +395,9 @@ public class GzipFilterDefaultTest
public void testIsNotGzipCompressedByDeferredContentType() throws Exception
{
GzipTester tester = new GzipTester(testingdir, compressionType);
tester.setGzipFilterClass(testFilter);
int filesize = CompressedResponseWrapper.DEFAULT_BUFFER_SIZE * 4;
int filesize = tester.getOutputBufferSize() * 4;
tester.prepareServerFile("file.mp3.deferred",filesize);
FilterHolder holder = tester.setContentServlet(GetServlet.class);
@ -436,6 +419,7 @@ public class GzipFilterDefaultTest
public void testIsNotGzipCompressedHttpStatus() throws Exception
{
GzipTester tester = new GzipTester(testingdir, compressionType);
tester.setGzipFilterClass(testFilter);
// Test error code 204
FilterHolder holder = tester.setContentServlet(HttpStatusServlet.class);
@ -457,6 +441,7 @@ public class GzipFilterDefaultTest
public void testIsNotGzipCompressedHttpBadRequestStatus() throws Exception
{
GzipTester tester = new GzipTester(testingdir, compressionType);
tester.setGzipFilterClass(testFilter);
// Test error code 400
FilterHolder holder = tester.setContentServlet(HttpErrorServlet.class);
@ -478,12 +463,13 @@ public class GzipFilterDefaultTest
public void testUserAgentExclusion() throws Exception
{
GzipTester tester = new GzipTester(testingdir,compressionType);
tester.setGzipFilterClass(testFilter);
FilterHolder holder = tester.setContentServlet(DefaultServlet.class);
holder.setInitParameter("excludedAgents","bar, foo");
tester.setUserAgent("foo");
int filesize = CompressedResponseWrapper.DEFAULT_BUFFER_SIZE * 4;
int filesize = tester.getOutputBufferSize() * 4;
tester.prepareServerFile("file.txt",filesize);
try
@ -501,13 +487,14 @@ public class GzipFilterDefaultTest
public void testUserAgentExclusionByExcludedAgentPatterns() throws Exception
{
GzipTester tester = new GzipTester(testingdir,compressionType);
tester.setGzipFilterClass(testFilter);
FilterHolder holder = tester.setContentServlet(DefaultServlet.class);
holder.setInitParameter("excludedAgents","bar");
holder.setInitParameter("excludeAgentPatterns","fo.*");
tester.setUserAgent("foo");
int filesize = CompressedResponseWrapper.DEFAULT_BUFFER_SIZE * 4;
int filesize = tester.getOutputBufferSize() * 4;
tester.prepareServerFile("file.txt",filesize);
try
@ -525,11 +512,12 @@ public class GzipFilterDefaultTest
public void testExcludePaths() throws Exception
{
GzipTester tester = new GzipTester(testingdir,compressionType);
tester.setGzipFilterClass(testFilter);
FilterHolder holder = tester.setContentServlet(DefaultServlet.class);
holder.setInitParameter("excludePaths","/bar/, /context/");
int filesize = CompressedResponseWrapper.DEFAULT_BUFFER_SIZE * 4;
int filesize = tester.getOutputBufferSize() * 4;
tester.prepareServerFile("file.txt",filesize);
try
@ -547,11 +535,12 @@ public class GzipFilterDefaultTest
public void testExcludePathPatterns() throws Exception
{
GzipTester tester = new GzipTester(testingdir,compressionType);
tester.setGzipFilterClass(testFilter);
FilterHolder holder = tester.setContentServlet(DefaultServlet.class);
holder.setInitParameter("excludePathPatterns","/cont.*");
int filesize = CompressedResponseWrapper.DEFAULT_BUFFER_SIZE * 4;
int filesize = tester.getOutputBufferSize() * 4;
tester.prepareServerFile("file.txt",filesize);
try

View File

@ -44,12 +44,14 @@ import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import javax.servlet.Servlet;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.DateGenerator;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlet.ServletTester;
@ -62,21 +64,33 @@ import org.junit.Assert;
public class GzipTester
{
private Class<? extends GzipFilter> gzipFilterClass = GzipFilter.class;
private Class<? extends Filter> gzipFilterClass = GzipFilter.class;
private String encoding = "ISO8859_1";
private String userAgent = null;
private ServletTester tester;
private final ServletTester tester = new ServletTester();;
private TestingDir testdir;
private String accept;
private String compressionType;
public GzipTester(TestingDir testingdir, String compressionType, String accept)
{
this.testdir = testingdir;
this.compressionType = compressionType;
this.accept=accept;
}
public GzipTester(TestingDir testingdir, String compressionType)
{
this.testdir = testingdir;
this.compressionType = compressionType;
// Make sure we start with a clean testing directory.
// DOES NOT WORK IN WINDOWS - this.testdir.ensureEmpty();
this.accept=compressionType;
}
public int getOutputBufferSize()
{
return tester.getConnector().getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().getOutputBufferSize();
}
public HttpTester.Response assertIsResponseGzipCompressed(String method, String filename) throws Exception
{
return assertIsResponseGzipCompressed(method,filename,filename,-1);
@ -101,7 +115,7 @@ public class GzipTester
request.setMethod(method);
request.setVersion("HTTP/1.0");
request.setHeader("Host","tester");
request.setHeader("Accept-Encoding",compressionType);
request.setHeader("Accept-Encoding",accept);
if (this.userAgent != null)
request.setHeader("User-Agent", this.userAgent);
@ -569,7 +583,6 @@ public class GzipTester
*/
public FilterHolder setContentServlet(Class<? extends Servlet> servletClass) throws IOException
{
tester = new ServletTester();
tester.setContextPath("/context");
tester.setResourceBase(testdir.getDir().getCanonicalPath());
ServletHolder servletHolder = tester.addServlet(servletClass,"/");
@ -580,12 +593,12 @@ public class GzipTester
return holder;
}
public Class<? extends GzipFilter> getGzipFilterClass()
public Class<? extends Filter> getGzipFilterClass()
{
return gzipFilterClass;
}
public void setGzipFilterClass(Class<? extends GzipFilter> gzipFilterClass)
public void setGzipFilterClass(Class<? extends Filter> gzipFilterClass)
{
this.gzipFilterClass = gzipFilterClass;
}

View File

@ -0,0 +1,68 @@
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.servlets.gzip;
import java.io.IOException;
import java.nio.ByteBuffer;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.HttpOutput;
import org.eclipse.jetty.servlets.GzipFilter;
/**
* A sample servlet to serve static content, using a order of construction that has caused problems for
* {@link GzipFilter} in the past.
*
* Using a real-world pattern of:
*
* <pre>
* 1) get stream
* 2) set content type
* 2) set content length
* 4) write
* </pre>
*
* @see <a href="Eclipse Bug 354014">http://bugs.eclipse.org/354014</a>
*/
@SuppressWarnings("serial")
public class TestServletBufferTypeLengthWrite extends TestDirContentServlet
{
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
String fileName = request.getServletPath();
byte[] dataBytes = loadContentFileBytes(fileName);
ServletOutputStream out = response.getOutputStream();
if (fileName.endsWith("txt"))
response.setContentType("text/plain");
else if (fileName.endsWith("mp3"))
response.setContentType("audio/mpeg");
response.setHeader("ETag","W/etag-"+fileName);
response.setContentLength(dataBytes.length);
((HttpOutput)out).write(ByteBuffer.wrap(dataBytes).asReadOnlyBuffer());
}
}

View File

@ -1,3 +1,4 @@
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
#org.eclipse.jetty.LEVEL=DEBUG
#org.eclipse.jetty.servlets.LEVEL=DEBUG
#org.eclipse.jetty.servlets.GzipFilter.LEVEL=DEBUG

View File

@ -89,6 +89,7 @@ public abstract class AbstractHTTPSPDYTest
server.addConnector(connector);
server.setHandler(handler);
server.start();
server.dumpStdErr();
return new InetSocketAddress("localhost", connector.getLocalPort());
}

View File

@ -7,3 +7,4 @@ org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
#org.eclipse.jetty.spdy.server.http.ReferrerPushStrategy.LEVEL=DEBUG
#org.eclipse.jetty.spdy.server.proxy.LEVEL=DEBUG
#org.mortbay.LEVEL=DEBUG