1 /* 2 * Copyright (C) 2010, Google Inc. 3 * and other copyright owners as documented in the project's IP log. 4 * 5 * This program and the accompanying materials are made available 6 * under the terms of the Eclipse Distribution License v1.0 which 7 * accompanies this distribution, is reproduced below, and is 8 * available at http://www.eclipse.org/org/documents/edl-v10.php 9 * 10 * All rights reserved. 11 * 12 * Redistribution and use in source and binary forms, with or 13 * without modification, are permitted provided that the following 14 * conditions are met: 15 * 16 * - Redistributions of source code must retain the above copyright 17 * notice, this list of conditions and the following disclaimer. 18 * 19 * - Redistributions in binary form must reproduce the above 20 * copyright notice, this list of conditions and the following 21 * disclaimer in the documentation and/or other materials provided 22 * with the distribution. 23 * 24 * - Neither the name of the Eclipse Foundation, Inc. nor the 25 * names of its contributors may be used to endorse or promote 26 * products derived from this software without specific prior 27 * written permission. 28 * 29 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 30 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 31 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 32 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 33 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 34 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 35 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 36 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 37 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 38 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 39 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 40 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 41 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 42 */ 43 44 package org.eclipse.jgit.http.server; 45 46 import static org.eclipse.jgit.http.server.ServletUtils.acceptsGzipEncoding; 47 import static org.eclipse.jgit.util.HttpSupport.ENCODING_GZIP; 48 import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_ENCODING; 49 50 import java.io.IOException; 51 import java.io.OutputStream; 52 import java.util.zip.GZIPOutputStream; 53 54 import javax.servlet.http.HttpServletRequest; 55 import javax.servlet.http.HttpServletResponse; 56 57 import org.eclipse.jgit.util.TemporaryBuffer; 58 59 /** 60 * Buffers a response, trying to gzip it if the user agent supports that. 61 * <p> 62 * If the response overflows the buffer, gzip is skipped and the response is 63 * streamed to the client as its produced, most likely using HTTP/1.1 chunked 64 * encoding. This is useful for servlets that produce mixed-mode content, where 65 * smaller payloads are primarily pure text that compresses well, while much 66 * larger payloads are heavily compressed binary data. {@link UploadPackServlet} 67 * is one such servlet. 68 */ 69 class SmartOutputStream extends TemporaryBuffer { 70 private static final int LIMIT = 32 * 1024; 71 72 private final HttpServletRequest req; 73 private final HttpServletResponse rsp; 74 private boolean compressStream; 75 private boolean startedOutput; 76 77 SmartOutputStream(final HttpServletRequest req, 78 final HttpServletResponse rsp, 79 boolean compressStream) { 80 super(LIMIT); 81 this.req = req; 82 this.rsp = rsp; 83 this.compressStream = compressStream; 84 } 85 86 /** {@inheritDoc} */ 87 @Override 88 protected OutputStream overflow() throws IOException { 89 startedOutput = true; 90 91 OutputStream out = rsp.getOutputStream(); 92 if (compressStream && acceptsGzipEncoding(req)) { 93 rsp.setHeader(HDR_CONTENT_ENCODING, ENCODING_GZIP); 94 out = new GZIPOutputStream(out); 95 } 96 return out; 97 } 98 99 /** {@inheritDoc} */ 100 @Override 101 public void close() throws IOException { 102 super.close(); 103 104 if (!startedOutput) { 105 // If output hasn't started yet, the entire thing fit into our 106 // buffer. Try to use a proper Content-Length header, and also 107 // deflate the response with gzip if it will be smaller. 108 TemporaryBuffer out = this; 109 110 if (256 < out.length() && acceptsGzipEncoding(req)) { 111 TemporaryBuffer gzbuf = new TemporaryBuffer.Heap(LIMIT); 112 try { 113 GZIPOutputStream gzip = new GZIPOutputStream(gzbuf); 114 try { 115 out.writeTo(gzip, null); 116 } finally { 117 gzip.close(); 118 } 119 if (gzbuf.length() < out.length()) { 120 out = gzbuf; 121 rsp.setHeader(HDR_CONTENT_ENCODING, ENCODING_GZIP); 122 } 123 } catch (IOException err) { 124 // Most likely caused by overflowing the buffer, meaning 125 // its larger if it were compressed. Discard compressed 126 // copy and use the original. 127 } 128 } 129 130 // The Content-Length cannot overflow when cast to an int, our 131 // hardcoded LIMIT constant above assures us we wouldn't store 132 // more than 2 GiB of content in memory. 133 rsp.setContentLength((int) out.length()); 134 final OutputStream os = rsp.getOutputStream(); 135 try { 136 out.writeTo(os, null); 137 os.flush(); 138 } finally { 139 os.close(); 140 } 141 } 142 } 143 }