1 package com.bradmcevoy.http;
2
3 import com.bradmcevoy.http.http11.DefaultHttp11ResponseHandler;
4 import com.bradmcevoy.http.exceptions.BadRequestException;
5 import java.io.OutputStream;
6 import java.util.Map;
7 import java.util.zip.GZIPOutputStream;
8
9 import org.slf4j.Logger;
10 import org.slf4j.LoggerFactory;
11
12 import com.bradmcevoy.http.exceptions.NotAuthorizedException;
13 import com.bradmcevoy.http.http11.CacheControlHelper;
14 import com.bradmcevoy.http.http11.DefaultCacheControlHelper;
15 import com.bradmcevoy.http.webdav.WebDavResponseHandler;
16 import com.bradmcevoy.io.BufferingOutputStream;
17 import com.bradmcevoy.io.FileUtils;
18 import com.bradmcevoy.io.ReadingException;
19 import com.bradmcevoy.io.StreamUtils;
20 import com.bradmcevoy.io.WritingException;
21 import java.io.InputStream;
22 import java.util.Date;
23 import org.apache.commons.io.IOUtils;
24
25
26
27
28
29
30
31
32
33 public class CompressingResponseHandler extends AbstractWrappingResponseHandler {
34
35 private static final Logger log = LoggerFactory.getLogger( CompressingResponseHandler.class );
36
37
38
39 private int maxMemorySize = 100000;
40 private CacheControlHelper cacheControlHelper = new DefaultCacheControlHelper();
41
42 public CompressingResponseHandler() {
43 }
44
45 public CompressingResponseHandler( WebDavResponseHandler wrapped ) {
46 super( wrapped );
47 }
48
49
50
51
52
53 public CacheControlHelper getCacheControlHelper() {
54 return cacheControlHelper;
55 }
56
57 public void setCacheControlHelper( CacheControlHelper cacheControlHelper ) {
58 this.cacheControlHelper = cacheControlHelper;
59 }
60
61 @Override
62 public void respondContent( Resource resource, Response response, Request request, Map<String, String> params ) throws NotAuthorizedException, BadRequestException {
63 if( resource instanceof GetableResource ) {
64 GetableResource r = (GetableResource) resource;
65
66 String acceptableContentTypes = request.getAcceptHeader();
67 String contentType = r.getContentType( acceptableContentTypes );
68
69 if( canCompress( r, contentType, request.getAcceptEncodingHeader() ) ) {
70 log.trace( "respondContent: compressable" );
71
72
73
74 BufferingOutputStream tempOut = new BufferingOutputStream( maxMemorySize );
75 try {
76 OutputStream gzipOut = new GZIPOutputStream( tempOut );
77 r.sendContent( gzipOut, null, params, contentType );
78 gzipOut.flush();
79 gzipOut.close();
80 tempOut.flush();
81 } catch( Exception ex ) {
82 tempOut.deleteTempFileIfExists();
83 throw new RuntimeException( ex );
84 } finally {
85 FileUtils.close( tempOut );
86 }
87
88 log.trace( "respondContent-compressed: " + resource.getClass() );
89 setRespondContentCommonHeaders( response, resource, Response.Status.SC_OK, request.getAuthorization() );
90 response.setContentEncodingHeader( Response.ContentEncoding.GZIP );
91 response.setVaryHeader( "Accept-Encoding" );
92 Long contentLength = tempOut.getSize();
93 response.setContentLengthHeader( contentLength );
94 response.setContentTypeHeader( contentType );
95 cacheControlHelper.setCacheControl( r, response, request.getAuthorization() );
96 InputStream in = tempOut.getInputStream();
97 try {
98 StreamUtils.readTo( in, response.getOutputStream() );
99 } catch( ReadingException ex ) {
100 throw new RuntimeException( ex );
101 } catch( WritingException ex ) {
102 log.warn( "exception writing, client probably closed connection", ex );
103 } finally {
104 IOUtils.closeQuietly(in);
105 }
106 return;
107 } else {
108 log.trace( "respondContent: not compressable" );
109
110
111 wrapped.respondContent( resource, response, request, params );
112 }
113 } else {
114 throw new RuntimeException( "Cant generate content for non-Getable resource: " + resource.getClass() );
115 }
116 }
117
118 protected void setRespondContentCommonHeaders( Response response, Resource resource, Response.Status status, Auth auth ) {
119 response.setStatus( status );
120 response.setDateHeader( new Date() );
121 String etag = wrapped.generateEtag( resource );
122 if( etag != null ) {
123 response.setEtag( etag );
124 }
125 DefaultHttp11ResponseHandler.setModifiedDate( response, resource, auth );
126 }
127
128 private boolean canCompress( GetableResource r, String contentType, String acceptableEncodings ) {
129 log.trace( "canCompress: contentType: " + contentType + " acceptable-encodings: " + acceptableEncodings );
130 if( contentType != null ) {
131 contentType = contentType.toLowerCase();
132 boolean contentIsCompressable = contentType.contains( "text" ) || contentType.contains( "css" ) || contentType.contains( "js" ) || contentType.contains( "javascript" );
133 if( contentIsCompressable ) {
134 boolean supportsGzip = ( acceptableEncodings != null && acceptableEncodings.toLowerCase().indexOf( "gzip" ) > -1 );
135 log.trace( "supports gzip: " + supportsGzip );
136 return supportsGzip;
137 }
138 }
139 return false;
140 }
141
142 public void setMaxMemorySize( int maxMemorySize ) {
143 this.maxMemorySize = maxMemorySize;
144 }
145
146 public int getMaxMemorySize() {
147 return maxMemorySize;
148 }
149 }