diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java index 5200eff13b6..6c0a584211e 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java @@ -18,8 +18,6 @@ package org.eclipse.jetty.server.handler.gzip; -import static org.eclipse.jetty.http.GzipHttpContent.ETAG_GZIP_QUOTE; - import java.io.File; import java.io.IOException; import java.util.Set; @@ -47,9 +45,8 @@ import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; - /** - * A Handler that can dynamically GZIP compress responses. Unlike + * A Handler that can dynamically GZIP compress responses. Unlike * previous and 3rd party GzipFilters, this mechanism works with asynchronously * generated responses and does not need to wrap the response or it's output * stream. Instead it uses the efficient {@link org.eclipse.jetty.server.HttpOutput.Interceptor} mechanism. @@ -69,22 +66,22 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory private int _compressionLevel=Deflater.DEFAULT_COMPRESSION; private boolean _checkGzExists = true; private boolean _syncFlush = false; - + // non-static, as other GzipHandler instances may have different configurations - private final ThreadLocal _deflater = new ThreadLocal(); + private final ThreadLocal _deflater = new ThreadLocal<>(); private final IncludeExclude _agentPatterns=new IncludeExclude<>(RegexSet.class); private final IncludeExclude _methods = new IncludeExclude<>(); private final IncludeExclude _paths = new IncludeExclude<>(PathSpecSet.class); private final IncludeExclude _mimeTypes = new IncludeExclude<>(); - + private HttpField _vary; /* ------------------------------------------------------------ */ /** * Instantiates a new gzip handler. - * The excluded Mime Types are initialized to common known + * The excluded Mime Types are initialized to common known * images, audio, video and other already compressed types. * The included methods is initialized to GET. * The excluded agent patterns are set to exclude MSIE 6.0 @@ -107,7 +104,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory _mimeTypes.exclude("application/bzip2"); _mimeTypes.exclude("application/x-rar-compressed"); LOG.debug("{} mime types {}",this,_mimeTypes); - + _agentPatterns.exclude(".*MSIE 6.0.*"); } @@ -145,7 +142,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory /* ------------------------------------------------------------ */ /** - * @param pathspecs Path specs (as per servlet spec) to exclude. If a + * @param pathspecs Path specs (as per servlet spec) to exclude. If a * ServletContext is available, the paths are relative to the context path, * otherwise they are absolute. * For backward compatibility the pathspecs may be comma separated strings, but this @@ -165,7 +162,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory { _agentPatterns.include(patterns); } - + /* ------------------------------------------------------------ */ /** * @param methods The methods to include in compression @@ -214,7 +211,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory /* ------------------------------------------------------------ */ /** * Add path specs to include. Inclusion takes precedence over exclusion. - * @param pathspecs Path specs (as per servlet spec) to include. If a + * @param pathspecs Path specs (as per servlet spec) to include. If a * ServletContext is available, the paths are relative to the context path, * otherwise they are absolute * For backward compatibility the pathspecs may be comma separated strings, but this @@ -225,7 +222,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory for (String p : pathspecs) _paths.include(StringUtil.csvSplit(p)); } - + /* ------------------------------------------------------------ */ @Override protected void doStart() throws Exception @@ -245,7 +242,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory { return _compressionLevel; } - + /* ------------------------------------------------------------ */ @Override public Deflater getDeflater(Request request, long content_length) @@ -256,7 +253,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory LOG.debug("{} excluded user agent {}",this,request); return null; } - + if (content_length>=0 && content_length<_minGzipSize) { LOG.debug("{} excluded minGzipSize {}",this,request); @@ -281,16 +278,16 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory return null; } } - + Deflater df = _deflater.get(); if (df==null) - df=new Deflater(_compressionLevel,true); + df=new Deflater(_compressionLevel,true); else _deflater.set(null); - + return df; } - + /* ------------------------------------------------------------ */ public String[] getExcludedAgentPatterns() { @@ -325,7 +322,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory Set includes=_agentPatterns.getIncluded(); return includes.toArray(new String[includes.size()]); } - + /* ------------------------------------------------------------ */ public String[] getIncludedMethods() { @@ -365,6 +362,11 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory return _minGzipSize; } + protected HttpField getVaryField() + { + return _vary; + } + /* ------------------------------------------------------------ */ /** * @see org.eclipse.jetty.server.handler.HandlerWrapper#handle(java.lang.String, org.eclipse.jetty.server.Request, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) @@ -375,8 +377,8 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory ServletContext context = baseRequest.getServletContext(); String path = context==null?baseRequest.getRequestURI():URIUtil.addPaths(baseRequest.getServletPath(),baseRequest.getPathInfo()); LOG.debug("{} handle {} in {}",this,baseRequest,context); - - HttpOutput out = baseRequest.getResponse().getHttpOutput(); + + HttpOutput out = baseRequest.getResponse().getHttpOutput(); // Are we already being gzipped? HttpOutput.Interceptor interceptor = out.getInterceptor(); while (interceptor!=null) @@ -389,7 +391,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory } interceptor=interceptor.getNextInterceptor(); } - + // If not a supported method - no Vary because no matter what client, this URI is always excluded if (!_methods.matches(baseRequest.getMethod())) { @@ -397,7 +399,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory _handler.handle(target,baseRequest, request, response); return; } - + // If not a supported URI- no Vary because no matter what client, this URI is always excluded // Use pathInfo because this is be if (!isPathGzipable(path)) @@ -420,7 +422,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory return; } } - + if (_checkGzExists && context!=null) { String realpath=request.getServletContext().getRealPath(path); @@ -436,25 +438,26 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory } } } - + // Special handling for etags - String etag = baseRequest.getHttpFields().get(HttpHeader.IF_NONE_MATCH); + String etag = baseRequest.getHttpFields().get(HttpHeader.IF_NONE_MATCH); if (etag!=null) { - int i=etag.indexOf(ETAG_GZIP_QUOTE); + int i=etag.indexOf(GzipHttpContent.ETAG_GZIP_QUOTE); if (i>0) { while (i>=0) { etag=etag.substring(0,i)+etag.substring(i+GzipHttpContent.ETAG_GZIP.length()); - i=etag.indexOf(ETAG_GZIP_QUOTE,i); + i=etag.indexOf(GzipHttpContent.ETAG_GZIP_QUOTE,i); } baseRequest.getHttpFields().put(new HttpField(HttpHeader.IF_NONE_MATCH,etag)); } } // install interceptor and handle - out.setInterceptor(new GzipHttpOutputInterceptor(this,_vary,baseRequest.getHttpChannel(),out.getInterceptor(),_syncFlush)); + out.setInterceptor(new GzipHttpOutputInterceptor(this,getVaryField(),baseRequest.getHttpChannel(),out.getInterceptor(),isSyncFlush())); + if (_handler!=null) _handler.handle(target,baseRequest, request, response); } @@ -470,7 +473,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory { if (ua == null) return false; - + return _agentPatterns.matches(ua); } @@ -483,7 +486,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory /* ------------------------------------------------------------ */ /** - * Checks to see if the path is included or not excluded + * Checks to see if the path is included or not excluded * * @param requestURI * the request uri @@ -493,7 +496,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory { if (requestURI == null) return true; - + return _paths.matches(requestURI); } @@ -515,7 +518,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory { _checkGzExists = checkGzExists; } - + /* ------------------------------------------------------------ */ /** * @param compressionLevel The compression level to use to initialize {@link Deflater#setLevel(int)} @@ -558,7 +561,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory /* ------------------------------------------------------------ */ /** - * @param pathspecs Path specs (as per servlet spec) to exclude. If a + * @param pathspecs Path specs (as per servlet spec) to exclude. If a * ServletContext is available, the paths are relative to the context path, * otherwise they are absolute. */ @@ -577,7 +580,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory _agentPatterns.getIncluded().clear(); addIncludedAgentPatterns(patterns); } - + /* ------------------------------------------------------------ */ /** * @param methods The methods to include in compression @@ -587,7 +590,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory _methods.getIncluded().clear(); _methods.include(methods); } - + /* ------------------------------------------------------------ */ /** * Set included mime types. Inclusion takes precedence over @@ -603,7 +606,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory /* ------------------------------------------------------------ */ /** * Set the path specs to include. Inclusion takes precedence over exclusion. - * @param pathspecs Path specs (as per servlet spec) to include. If a + * @param pathspecs Path specs (as per servlet spec) to include. If a * ServletContext is available, the paths are relative to the context path, * otherwise they are absolute */ diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHttpOutputInterceptor.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHttpOutputInterceptor.java index f717c2656af..17a1e25fb46 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHttpOutputInterceptor.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHttpOutputInterceptor.java @@ -32,6 +32,7 @@ import org.eclipse.jetty.http.MimeTypes; import org.eclipse.jetty.http.PreEncodedHttpField; 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; @@ -46,7 +47,7 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor public final static HttpField VARY_ACCEPT_ENCODING_USER_AGENT=new PreEncodedHttpField(HttpHeader.VARY,HttpHeader.ACCEPT_ENCODING+", "+HttpHeader.USER_AGENT); public final static HttpField VARY_ACCEPT_ENCODING=new PreEncodedHttpField(HttpHeader.VARY,HttpHeader.ACCEPT_ENCODING.asString()); - + private enum GZState { MIGHT_COMPRESS, NOT_COMPRESSING, COMMITTING, COMPRESSING, FINISHED}; private final AtomicReference _state = new AtomicReference<>(GZState.MIGHT_COMPRESS); private final CRC32 _crc = new CRC32(); @@ -57,7 +58,7 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor private final HttpField _vary; private final int _bufferSize; private final boolean _syncFlush; - + private Deflater _deflater; private ByteBuffer _buffer; @@ -65,12 +66,12 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor { this(factory,VARY_ACCEPT_ENCODING_USER_AGENT,channel.getHttpConfiguration().getOutputBufferSize(),channel,next,syncFlush); } - + public GzipHttpOutputInterceptor(GzipFactory factory, HttpField vary, HttpChannel channel, HttpOutput.Interceptor next,boolean syncFlush) { this(factory,vary,channel.getHttpConfiguration().getOutputBufferSize(),channel,next,syncFlush); } - + public GzipHttpOutputInterceptor(GzipFactory factory, HttpField vary, int bufferSize, HttpChannel channel, HttpOutput.Interceptor next,boolean syncFlush) { _factory=factory; @@ -85,14 +86,14 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor { return _interceptor; } - + @Override public boolean isOptimizedForDirectBuffers() { return false; // No point as deflator is in user space. } - - + + @Override public void write(ByteBuffer content, boolean complete, Callback callback) { @@ -101,11 +102,11 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor case MIGHT_COMPRESS: commit(content,complete,callback); break; - + case NOT_COMPRESSING: _interceptor.write(content, complete, callback); return; - + case COMMITTING: callback.failed(new WritePendingException()); break; @@ -124,21 +125,21 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor { 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() || complete) @@ -150,7 +151,8 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor protected void commit(ByteBuffer content, boolean complete, Callback callback) { // Are we excluding because of status? - int sc = _channel.getResponse().getStatus(); + Response response = _channel.getResponse(); + int sc = response.getStatus(); if (sc>0 && (sc<200 || sc==204 || sc==205 || sc>=300)) { LOG.debug("{} exclude by status {}",this,sc); @@ -158,9 +160,9 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor _interceptor.write(content, complete, callback); return; } - + // Are we excluding because of mime-type? - String ct = _channel.getResponse().getContentType(); + String ct = response.getContentType(); if (ct!=null) { ct=MimeTypes.getContentTypeWithoutCharset(ct); @@ -172,9 +174,9 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor return; } } - + // Has the Content-Encoding header already been set? - String ce=_channel.getResponse().getHeader("Content-Encoding"); + String ce=response.getHeader("Content-Encoding"); if (ce != null) { LOG.debug("{} exclude by content-encoding {}",this,ce); @@ -182,20 +184,21 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor _interceptor.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 = _channel.getResponse().getHttpFields(); - fields.add(_vary); + HttpFields fields = response.getHttpFields(); + if (_vary != null) + fields.add(_vary); - long content_length = _channel.getResponse().getContentLength(); + long content_length = response.getContentLength(); if (content_length<0 && complete) content_length=content.remaining(); - + _deflater = _factory.getDeflater(_channel.getRequest(),content_length); - + if (_deflater==null) { LOG.debug("{} exclude no deflater",this); @@ -210,7 +213,7 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor BufferUtil.fill(_buffer,GZIP_HEADER,0,GZIP_HEADER.length); // Adjust headers - _channel.getResponse().setContentLength(-1); + response.setContentLength(-1); String etag=fields.get(HttpHeader.ETAG); if (etag!=null) { @@ -218,10 +221,10 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor etag=(etag.charAt(end)=='"')?etag.substring(0,end)+GzipHttpContent.ETAG_GZIP+'"':etag+GzipHttpContent.ETAG_GZIP; fields.put(HttpHeader.ETAG,etag); } - + LOG.debug("{} compressing {}",this,_deflater); _state.set(GZState.COMPRESSING); - + gzip(content,complete,callback); } else @@ -268,14 +271,14 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor } } } - + public boolean mightCompress() { return _state.get()==GZState.MIGHT_COMPRESS; } - + private class GzipBufferCB extends IteratingNestedCallback - { + { private ByteBuffer _copy; private final ByteBuffer _content; private final boolean _last; @@ -291,11 +294,11 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor { if (_deflater==null) return Action.SUCCEEDED; - + if (_deflater.needsInput()) - { + { if (BufferUtil.isEmpty(_content)) - { + { if (_deflater.finished()) { _factory.recycle(_deflater); @@ -309,12 +312,12 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor } return Action.SUCCEEDED; } - + if (!_last) { return Action.SUCCEEDED; } - + _deflater.finish(); } else if (_content.hasArray()) @@ -323,9 +326,9 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor int off=_content.arrayOffset()+_content.position(); int len=_content.remaining(); BufferUtil.clear(_content); - + _crc.update(array,off,len); - _deflater.setInput(array,off,len); + _deflater.setInput(array,off,len); if (_last) _deflater.finish(); } @@ -338,13 +341,13 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor BufferUtil.flipToFlush(_copy,0); if (took==0) throw new IllegalStateException(); - + byte[] array=_copy.array(); int off=_copy.arrayOffset()+_copy.position(); int len=_copy.remaining(); _crc.update(array,off,len); - _deflater.setInput(array,off,len); + _deflater.setInput(array,off,len); if (_last && BufferUtil.isEmpty(_content)) _deflater.finish(); } @@ -359,10 +362,10 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor _buffer.limit(_buffer.limit()+produced); } boolean finished=_deflater.finished(); - + if (finished) addTrailer(); - + _interceptor.write(_buffer,finished,this); return Action.SCHEDULED; }