/* * � Copyright IBM Corp. 2011 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied. See the License for the specific language governing * permissions and limitations under the License. */ package com.ibm.domino.das.servlet; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.util.Enumeration; import java.util.List; import java.util.zip.DeflaterOutputStream; import java.util.zip.GZIPOutputStream; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.ext.RuntimeDelegate; import javax.ws.rs.ext.RuntimeDelegate.HeaderDelegate; import org.apache.wink.common.internal.http.AcceptEncoding; import com.ibm.commons.util.io.ByteStreamCache; import com.ibm.commons.util.io.StreamUtil; import com.ibm.domino.osgi.core.context.ContextInfo; /** * This class is based on ContentEncodingResponseFilter from org.apache.wink and modified for DAS Wink. * * Original Class: org.apache.wink.server.internal.servlet.contentencode.ContentEncodingResponseFilter * It wraps the original ServletResponse with gzip/deflate encoding for the OutputStream support * Note: Call the finishResponse() to close the EncodedOutputStream which is required by DeflaterOutputStream and * * sauriemma: Added support for Content-Length using the ByteStreamCache. * This feature is required for persistent connections. * * @author Mao Chuan * */ public class DasHttpResponseWrapper extends HttpServletResponseWrapper { public final static String ENCODER_GZIP = "gzip"; // $NON-NLS-1$ public final static String ENCODER_DEFLATE = "deflate"; // $NON-NLS-1$ public final static String ENV_VAR_DAS_PREVENT_GZIP = "DASPreventGzip"; // $NON-NLS-1$ public final static String ENV_VAR_DAS_PREVENT_CACHE = "DASPreventCache"; // $NON-NLS-1$ private final static HeaderDelegate<AcceptEncoding> acceptEncodingHeaderDelegate = RuntimeDelegate.getInstance().createHeaderDelegate(AcceptEncoding.class); private final HttpServletResponse httpServletResponse; private final HttpServletRequest httpServletRequest; private EncodedOutputStream encodedOutputStream; private PrintWriter servletWriter; private String encoder = ""; private boolean preventCache; private boolean preventGzip; public DasHttpResponseWrapper(HttpServletRequest request, HttpServletResponse response) { super(response); this.httpServletResponse = response; this.httpServletRequest = request; } public boolean isPreventGzip() { return preventGzip; } public void setPreventGzip(boolean preventGzip) { this.preventGzip = preventGzip; } public boolean isPreventCache() { return preventCache; } public void setPreventCache(boolean preventCache) { this.preventCache = preventCache; } private AcceptEncoding getAcceptEncodingHeader() { Enumeration<String> acceptEncodingEnum = httpServletRequest.getHeaders(HttpHeaders.ACCEPT_ENCODING); StringBuilder sb = new StringBuilder(); if (acceptEncodingEnum.hasMoreElements()) { sb.append(acceptEncodingEnum.nextElement()); while (acceptEncodingEnum.hasMoreElements()) { sb.append(","); //$NON-NLS-1$ sb.append(acceptEncodingEnum.nextElement()); } String acceptEncodingHeader = sb.toString(); return acceptEncodingHeaderDelegate.fromString(acceptEncodingHeader); } return null; } @Override public ServletOutputStream getOutputStream() throws IOException { if(servletWriter != null){ throw new IllegalStateException("Servlet writer is open."); //$NON-NLS-1$ } return this.getEncodedOutputStream(); } @Override public PrintWriter getWriter() throws IOException { if(servletWriter == null){ ServletOutputStream outputStream = getOutputStream(); OutputStreamWriter outWriter = new OutputStreamWriter(outputStream); servletWriter = new PrintWriter(outWriter); } return servletWriter; } // private ServletOutputStream getServletOutputStream() throws IOException{ // // return super.getOutputStream(); // } private EncodedOutputStream getEncodedOutputStream() throws IOException{ //preventCache = new Boolean(ContextInfo.getEnvironmentString(ENV_VAR_DAS_PREVENT_CACHE)); preventGzip = new Boolean(ContextInfo.getEnvironmentString(ENV_VAR_DAS_PREVENT_GZIP)); if (this.encodedOutputStream == null) { if(!preventGzip) { AcceptEncoding acceptEncoding = this.getAcceptEncodingHeader(); if (acceptEncoding != null) { List<String> acceptableEncodings = acceptEncoding.getAcceptableEncodings(); for (String encoding : acceptableEncodings) { if (ENCODER_GZIP.equalsIgnoreCase(encoding)) { encoder = ENCODER_GZIP; break; } else if (ENCODER_DEFLATE.equalsIgnoreCase(encoding)) { encoder = ENCODER_DEFLATE; break; } } if (acceptEncoding.isAnyEncodingAllowed() && !acceptEncoding.getBannedEncodings().contains(ENCODER_GZIP)) { encoder = ENCODER_GZIP; } if (encoder != null) { this.addHeader(HttpHeaders.CONTENT_ENCODING, encoder); this.addHeader(HttpHeaders.VARY, HttpHeaders.ACCEPT_ENCODING); } } } this.encodedOutputStream = new EncodedOutputStream(httpServletResponse, encoder, preventCache); } return this.encodedOutputStream; } // public void finishResponse() throws IOException { // // if(this.encodedOutputStream != null){ // this.encodedOutputStream.close(); // } // // if(this.servletWriter != null){ // this.servletWriter.close(); // } // } /** * Inner class EncodedOutputStream used to support encodings like gzip and deflate. * Cache is require to emit the Content-Length and used to support persistent connections. **/ private static class EncodedOutputStream extends ServletOutputStream { final private HttpServletResponse httpServletResponse; private ByteStreamCache byteStreamCache = null; private OutputStream outputStream = null; private boolean streamOpened = false; private boolean preventCache = false; private String encoder = null; /** * @param httpServletResponse * @param encoder * @param preventCache */ public EncodedOutputStream(HttpServletResponse httpServletResponse, String encoder, boolean preventCache) { this.httpServletResponse = httpServletResponse; this.preventCache = preventCache; this.encoder = encoder; } @Override public void write(int b) throws IOException { if (!streamOpened) { this.createEncoderOutputStream(); streamOpened = true; } outputStream.write(b); } @Override public void write(byte[] b, int off, int len) throws IOException { if (!streamOpened) { this.createEncoderOutputStream(); streamOpened = true; } outputStream.write(b, off, len); } @Override public void write(byte[] b) throws IOException { if (!streamOpened) { this.createEncoderOutputStream(); streamOpened = true; } outputStream.write(b); } @Override public void flush() throws IOException { if (!streamOpened) { this.createEncoderOutputStream(); streamOpened = true; } this.finish(); if (!preventCache) { httpServletResponse.setContentLength((int)byteStreamCache.getLength()); // And copy the entire content InputStream is = byteStreamCache.getInputStream(); OutputStream out = httpServletResponse.getOutputStream(); StreamUtil.copyStream(is, out); out.flush(); byteStreamCache.clear(); } outputStream.flush(); } @Override public void close() throws IOException { flush(); outputStream.close(); } public void finish() throws IOException { if (outputStream instanceof DeflaterOutputStream) { ((DeflaterOutputStream) outputStream).finish(); } } private void createEncoderOutputStream() throws IOException { OutputStream os = null; if(preventCache) { os = httpServletResponse.getOutputStream(); } else { byteStreamCache = new ByteStreamCache(); os = byteStreamCache.getOutputStream(); } if (this.encoder.equalsIgnoreCase(ENCODER_GZIP)) { outputStream = new GZIPOutputStream(os); } else if (this.encoder.equalsIgnoreCase(ENCODER_DEFLATE)) { outputStream = new DeflaterOutputStream(os); } else { outputStream = os; } } } }