xref: /JGit/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartOutputStream.java (revision db019c473edcf8cc96456f8c4ac07e42a7d2eefa)
1c0f09389SShawn O. Pearce /*
25c5f7c6bSMatthias Sohn  * Copyright (C) 2010, Google Inc. and others
3c0f09389SShawn O. Pearce  *
45c5f7c6bSMatthias Sohn  * This program and the accompanying materials are made available under the
55c5f7c6bSMatthias Sohn  * terms of the Eclipse Distribution License v. 1.0 which is available at
65c5f7c6bSMatthias Sohn  * https://www.eclipse.org/org/documents/edl-v10.php.
7c0f09389SShawn O. Pearce  *
85c5f7c6bSMatthias Sohn  * SPDX-License-Identifier: BSD-3-Clause
9c0f09389SShawn O. Pearce  */
10c0f09389SShawn O. Pearce 
11c0f09389SShawn O. Pearce package org.eclipse.jgit.http.server;
12c0f09389SShawn O. Pearce 
13c0f09389SShawn O. Pearce import static org.eclipse.jgit.http.server.ServletUtils.acceptsGzipEncoding;
14c0f09389SShawn O. Pearce import static org.eclipse.jgit.util.HttpSupport.ENCODING_GZIP;
15c0f09389SShawn O. Pearce import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_ENCODING;
16c0f09389SShawn O. Pearce 
17c0f09389SShawn O. Pearce import java.io.IOException;
18c0f09389SShawn O. Pearce import java.io.OutputStream;
19c0f09389SShawn O. Pearce import java.util.zip.GZIPOutputStream;
20c0f09389SShawn O. Pearce 
21c0f09389SShawn O. Pearce import javax.servlet.http.HttpServletRequest;
22c0f09389SShawn O. Pearce import javax.servlet.http.HttpServletResponse;
23c0f09389SShawn O. Pearce 
24c0f09389SShawn O. Pearce import org.eclipse.jgit.util.TemporaryBuffer;
25c0f09389SShawn O. Pearce 
26c0f09389SShawn O. Pearce /**
27c0f09389SShawn O. Pearce  * Buffers a response, trying to gzip it if the user agent supports that.
28c0f09389SShawn O. Pearce  * <p>
29c0f09389SShawn O. Pearce  * If the response overflows the buffer, gzip is skipped and the response is
30c0f09389SShawn O. Pearce  * streamed to the client as its produced, most likely using HTTP/1.1 chunked
31c0f09389SShawn O. Pearce  * encoding. This is useful for servlets that produce mixed-mode content, where
32c0f09389SShawn O. Pearce  * smaller payloads are primarily pure text that compresses well, while much
33c0f09389SShawn O. Pearce  * larger payloads are heavily compressed binary data. {@link UploadPackServlet}
34c0f09389SShawn O. Pearce  * is one such servlet.
35c0f09389SShawn O. Pearce  */
36c0f09389SShawn O. Pearce class SmartOutputStream extends TemporaryBuffer {
37c0f09389SShawn O. Pearce 	private static final int LIMIT = 32 * 1024;
38c0f09389SShawn O. Pearce 
39c0f09389SShawn O. Pearce 	private final HttpServletRequest req;
40c0f09389SShawn O. Pearce 	private final HttpServletResponse rsp;
41f93a6a72SShawn O. Pearce 	private boolean compressStream;
42c0f09389SShawn O. Pearce 	private boolean startedOutput;
43c0f09389SShawn O. Pearce 
SmartOutputStream(final HttpServletRequest req, final HttpServletResponse rsp, boolean compressStream)44c0f09389SShawn O. Pearce 	SmartOutputStream(final HttpServletRequest req,
45f93a6a72SShawn O. Pearce 			final HttpServletResponse rsp,
46f93a6a72SShawn O. Pearce 			boolean compressStream) {
47c0f09389SShawn O. Pearce 		super(LIMIT);
48c0f09389SShawn O. Pearce 		this.req = req;
49c0f09389SShawn O. Pearce 		this.rsp = rsp;
50f93a6a72SShawn O. Pearce 		this.compressStream = compressStream;
51c0f09389SShawn O. Pearce 	}
52c0f09389SShawn O. Pearce 
533b00041cSMatthias Sohn 	/** {@inheritDoc} */
54c0f09389SShawn O. Pearce 	@Override
overflow()55c0f09389SShawn O. Pearce 	protected OutputStream overflow() throws IOException {
56c0f09389SShawn O. Pearce 		startedOutput = true;
57f93a6a72SShawn O. Pearce 
58f93a6a72SShawn O. Pearce 		OutputStream out = rsp.getOutputStream();
59f93a6a72SShawn O. Pearce 		if (compressStream && acceptsGzipEncoding(req)) {
60f93a6a72SShawn O. Pearce 			rsp.setHeader(HDR_CONTENT_ENCODING, ENCODING_GZIP);
61f93a6a72SShawn O. Pearce 			out = new GZIPOutputStream(out);
62f93a6a72SShawn O. Pearce 		}
63f93a6a72SShawn O. Pearce 		return out;
64c0f09389SShawn O. Pearce 	}
65c0f09389SShawn O. Pearce 
663b00041cSMatthias Sohn 	/** {@inheritDoc} */
677ac182f4SDavid Pursehouse 	@Override
close()68c0f09389SShawn O. Pearce 	public void close() throws IOException {
69c0f09389SShawn O. Pearce 		super.close();
70c0f09389SShawn O. Pearce 
71c0f09389SShawn O. Pearce 		if (!startedOutput) {
72c0f09389SShawn O. Pearce 			// If output hasn't started yet, the entire thing fit into our
73c0f09389SShawn O. Pearce 			// buffer. Try to use a proper Content-Length header, and also
74c0f09389SShawn O. Pearce 			// deflate the response with gzip if it will be smaller.
75*4209a0f8SMatthias Sohn 			if (256 < this.length() && acceptsGzipEncoding(req)) {
76c0f09389SShawn O. Pearce 				TemporaryBuffer gzbuf = new TemporaryBuffer.Heap(LIMIT);
77c0f09389SShawn O. Pearce 				try {
785c70be00SDavid Pursehouse 					try (GZIPOutputStream gzip = new GZIPOutputStream(gzbuf)) {
79*4209a0f8SMatthias Sohn 						this.writeTo(gzip, null);
800d61707fSShawn O. Pearce 					}
81*4209a0f8SMatthias Sohn 					if (gzbuf.length() < this.length()) {
82c0f09389SShawn O. Pearce 						rsp.setHeader(HDR_CONTENT_ENCODING, ENCODING_GZIP);
83*4209a0f8SMatthias Sohn 						writeResponse(gzbuf);
84*4209a0f8SMatthias Sohn 						return;
85c0f09389SShawn O. Pearce 					}
86c0f09389SShawn O. Pearce 				} catch (IOException err) {
87c0f09389SShawn O. Pearce 					// Most likely caused by overflowing the buffer, meaning
88c0f09389SShawn O. Pearce 					// its larger if it were compressed. Discard compressed
89c0f09389SShawn O. Pearce 					// copy and use the original.
90c0f09389SShawn O. Pearce 				}
91c0f09389SShawn O. Pearce 			}
92*4209a0f8SMatthias Sohn 			writeResponse(this);
93*4209a0f8SMatthias Sohn 		}
94*4209a0f8SMatthias Sohn 	}
95c0f09389SShawn O. Pearce 
writeResponse(TemporaryBuffer out)96*4209a0f8SMatthias Sohn 	private void writeResponse(TemporaryBuffer out) throws IOException {
97c0f09389SShawn O. Pearce 		// The Content-Length cannot overflow when cast to an int, our
98c0f09389SShawn O. Pearce 		// hardcoded LIMIT constant above assures us we wouldn't store
99c0f09389SShawn O. Pearce 		// more than 2 GiB of content in memory.
100c0f09389SShawn O. Pearce 		rsp.setContentLength((int) out.length());
1015c70be00SDavid Pursehouse 		try (OutputStream os = rsp.getOutputStream()) {
102c0f09389SShawn O. Pearce 			out.writeTo(os, null);
103c0f09389SShawn O. Pearce 			os.flush();
104c0f09389SShawn O. Pearce 		}
105c0f09389SShawn O. Pearce 	}
106c0f09389SShawn O. Pearce }
107