/* * Copyright (C) 2010, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.http.server; import static org.eclipse.jgit.http.server.ServletUtils.acceptsGzipEncoding; import static org.eclipse.jgit.util.HttpSupport.ENCODING_GZIP; import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_ENCODING; import java.io.IOException; import java.io.OutputStream; import java.util.zip.GZIPOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jgit.util.TemporaryBuffer; /** * Buffers a response, trying to gzip it if the user agent supports that. *

* If the response overflows the buffer, gzip is skipped and the response is * streamed to the client as its produced, most likely using HTTP/1.1 chunked * encoding. This is useful for servlets that produce mixed-mode content, where * smaller payloads are primarily pure text that compresses well, while much * larger payloads are heavily compressed binary data. {@link UploadPackServlet} * is one such servlet. */ class SmartOutputStream extends TemporaryBuffer { private static final int LIMIT = 32 * 1024; private final HttpServletRequest req; private final HttpServletResponse rsp; private boolean compressStream; private boolean startedOutput; SmartOutputStream(final HttpServletRequest req, final HttpServletResponse rsp, boolean compressStream) { super(LIMIT); this.req = req; this.rsp = rsp; this.compressStream = compressStream; } /** {@inheritDoc} */ @Override protected OutputStream overflow() throws IOException { startedOutput = true; OutputStream out = rsp.getOutputStream(); if (compressStream && acceptsGzipEncoding(req)) { rsp.setHeader(HDR_CONTENT_ENCODING, ENCODING_GZIP); out = new GZIPOutputStream(out); } return out; } /** {@inheritDoc} */ @Override public void close() throws IOException { super.close(); if (!startedOutput) { // If output hasn't started yet, the entire thing fit into our // buffer. Try to use a proper Content-Length header, and also // deflate the response with gzip if it will be smaller. if (256 < this.length() && acceptsGzipEncoding(req)) { TemporaryBuffer gzbuf = new TemporaryBuffer.Heap(LIMIT); try { try (GZIPOutputStream gzip = new GZIPOutputStream(gzbuf)) { this.writeTo(gzip, null); } if (gzbuf.length() < this.length()) { rsp.setHeader(HDR_CONTENT_ENCODING, ENCODING_GZIP); writeResponse(gzbuf); return; } } catch (IOException err) { // Most likely caused by overflowing the buffer, meaning // its larger if it were compressed. Discard compressed // copy and use the original. } } writeResponse(this); } } private void writeResponse(TemporaryBuffer out) throws IOException { // The Content-Length cannot overflow when cast to an int, our // hardcoded LIMIT constant above assures us we wouldn't store // more than 2 GiB of content in memory. rsp.setContentLength((int) out.length()); try (OutputStream os = rsp.getOutputStream()) { out.writeTo(os, null); os.flush(); } } }