You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SmartOutputStream.java 3.3KB

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