diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java index 9daa5235ee2..6fe4a4ff8c3 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java @@ -98,7 +98,6 @@ public class HttpChannel implements HttpParser.RequestHandler, 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 implements HttpParser.RequestHandler, Runnable { return _version; } - - BlockingCallback getWriteBlockingCallback() - { - return _writeblock; - } - /** * @return the number of requests handled by this connection */ @@ -725,29 +718,17 @@ public class HttpChannel implements HttpParser.RequestHandler, 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(); } - /** - *

Blocking write, committing the response if needed.

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

Non-Blocking write, committing the response if needed.

* diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java index c19acd32d13..4c41803fdce 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java @@ -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: diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java index 2814ded8cd6..9d2e6faeb8d 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java @@ -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() _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) diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java index 7afc3dcfdc3..c522b084cfb 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java @@ -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(); } }); diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletTester.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletTester.java index 2a152d089a8..a2e395c212c 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletTester.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletTester.java @@ -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); diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/AsyncGzipFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/AsyncGzipFilter.java new file mode 100644 index 00000000000..eb7df127944 --- /dev/null +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/AsyncGzipFilter.java @@ -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:
    + *
  • The filter is mapped to a matching path
  • + *
  • accept-encoding header is set to either gzip, deflate or a combination of those
  • + *
  • The response status code is >=200 and <300 + *
  • The content length is unknown or more than the minGzipSize initParameter or the minGzipSize is 0(default)
  • + *
  • If a list of mimeTypes is set by the mimeTypes init parameter, then the Content-Type is in the list.
  • + *
  • If no mimeType list is set, then the content-type is not in the list defined by excludedMimeTypes
  • + *
  • No content-encoding is specified by the resource
  • + *
+ * + *

+ * 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. + *

+ *

+ * This filter extends {@link UserAgentFilter} and if the the initParameter excludedAgents + * is set to a comma separated list of user agents, then these agents will be excluded from gzip content. + *

+ *

Init Parameters:

+ *
+ *
bufferSize
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)} + *
+ *
minGzipSize
Content will only be compressed if content length is either unknown or greater + * than minGzipSize. + *
+ *
deflateCompressionLevel
The compression level used for deflate compression. (0-9). + * See: {@link java.util.zip.Deflater#Deflater(int, boolean)} + *
+ *
deflateNoWrap
The noWrap setting for deflate compression. Defaults to true. (true/false) + * See: {@link java.util.zip.Deflater#Deflater(int, boolean)} + *
+ *
methods
Comma separated list of HTTP methods to compress. If not set, only GET requests are compressed. + *
+ *
mimeTypes
Comma separated list of mime types to compress. If it is not set, then the excludedMimeTypes list is used. + *
+ *
excludedMimeTypes
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. + *
+ + *
excludedAgents
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 + *
+ *
excludeAgentPatterns
Same as excludedAgents, but accepts regex patterns for more complex matching. + *
+ *
excludePaths
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 excludePathPatterns + * instead. + *
+ *
excludePathPatterns
Same as excludePath, but accepts regex patterns for more complex matching. + *
+ *
vary
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. + *
+ *
checkGzExists
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. + *
+ *
+ */ +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 _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 = new ThreadLocal(); + + protected final static ThreadLocal _buffer= new ThreadLocal(); + + protected final Set _methods=new HashSet(); + protected Set _excludedAgents; + protected Set _excludedAgentPatterns; + protected Set _excludedPaths; + protected Set _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(); + 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(); + 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(); + 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(); + 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 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; + } + + +} diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipFactory.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipFactory.java new file mode 100644 index 00000000000..474706f8c4b --- /dev/null +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipFactory.java @@ -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); + +} diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipHttpOutput.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipHttpOutput.java new file mode 100644 index 00000000000..29f969e6301 --- /dev/null +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipHttpOutput.java @@ -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 _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; + } + + } +} diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterContentLengthTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterContentLengthTest.java index 101eaafdb75..9e1d4aab145 100644 --- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterContentLengthTest.java +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterContentLengthTest.java @@ -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 testServlet, String compressionType) + public GzipFilterContentLengthTest(Class testFilter,Class 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 testFilter; private Class 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); diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterDefaultNoRecompressTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterDefaultNoRecompressTest.java index 0eee233bad7..92acd9e5ed2 100644 --- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterDefaultNoRecompressTest.java +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterDefaultNoRecompressTest.java @@ -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 testFilter; private String alreadyCompressedFilename; private String expectedContentType; private String compressionType; - public GzipFilterDefaultNoRecompressTest(String testFilename, String expectedContentType, String compressionType) + public GzipFilterDefaultNoRecompressTest(Class 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); diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterDefaultTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterDefaultTest.java index cdb0564c5d5..66200469185 100644 --- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterDefaultTest.java +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterDefaultTest.java @@ -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 data() + public static List 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 testFilter; private String compressionType; - public GzipFilterDefaultTest(String compressionType) + public GzipFilterDefaultTest(Class 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 = "

COMPRESSED

"; + public static final String COMPRESSED_CONTENT = "

COMPRESSABLE CONTENT

"+ + "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!"+ + ""; @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 diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/GzipTester.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/GzipTester.java index b4ecdc1af8c..8181e810582 100644 --- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/GzipTester.java +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/GzipTester.java @@ -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 gzipFilterClass = GzipFilter.class; + private Class 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 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 getGzipFilterClass() + public Class getGzipFilterClass() { return gzipFilterClass; } - public void setGzipFilterClass(Class gzipFilterClass) + public void setGzipFilterClass(Class gzipFilterClass) { this.gzipFilterClass = gzipFilterClass; } diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestServletBufferTypeLengthWrite.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestServletBufferTypeLengthWrite.java new file mode 100644 index 00000000000..0dff53c1b2d --- /dev/null +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestServletBufferTypeLengthWrite.java @@ -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: + * + *
+ *  1) get stream
+ *  2) set content type
+ *  2) set content length
+ *  4) write
+ * 
+ * + * @see http://bugs.eclipse.org/354014 + */ +@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()); + } +} diff --git a/jetty-servlets/src/test/resources/jetty-logging.properties b/jetty-servlets/src/test/resources/jetty-logging.properties index 9ef3d34faf8..f97be99cc87 100644 --- a/jetty-servlets/src/test/resources/jetty-logging.properties +++ b/jetty-servlets/src/test/resources/jetty-logging.properties @@ -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 diff --git a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/AbstractHTTPSPDYTest.java b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/AbstractHTTPSPDYTest.java index fe532865ece..346eb193a30 100644 --- a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/AbstractHTTPSPDYTest.java +++ b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/AbstractHTTPSPDYTest.java @@ -89,6 +89,7 @@ public abstract class AbstractHTTPSPDYTest server.addConnector(connector); server.setHandler(handler); server.start(); + server.dumpStdErr(); return new InetSocketAddress("localhost", connector.getLocalPort()); } diff --git a/jetty-spdy/spdy-http-server/src/test/resources/jetty-logging.properties b/jetty-spdy/spdy-http-server/src/test/resources/jetty-logging.properties index 25faad48a78..be1b7aa594d 100644 --- a/jetty-spdy/spdy-http-server/src/test/resources/jetty-logging.properties +++ b/jetty-spdy/spdy-http-server/src/test/resources/jetty-logging.properties @@ -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 +