Move base64 response encoding into BinaryResult
Make it easy for any RestApiView to return binary data within a base64 wrapper by marking a BinaryResult with base64() before it is given to RestApiServlet. The servlet will process the base64 wrapping before gzip encoding, which may save on transfer cost. Change-Id: I5ed7b8cb2b034b60654cc2574e627159b11a4f27
This commit is contained in:
		@@ -61,6 +61,7 @@ public abstract class BinaryResult implements Closeable {
 | 
				
			|||||||
  private String characterEncoding;
 | 
					  private String characterEncoding;
 | 
				
			||||||
  private long contentLength = -1;
 | 
					  private long contentLength = -1;
 | 
				
			||||||
  private boolean gzip = true;
 | 
					  private boolean gzip = true;
 | 
				
			||||||
 | 
					  private boolean base64 = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /** @return the MIME type of the result, for HTTP clients. */
 | 
					  /** @return the MIME type of the result, for HTTP clients. */
 | 
				
			||||||
  public String getContentType() {
 | 
					  public String getContentType() {
 | 
				
			||||||
@@ -110,6 +111,17 @@ public abstract class BinaryResult implements Closeable {
 | 
				
			|||||||
    return this;
 | 
					    return this;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** @return true if the result must be base64 encoded. */
 | 
				
			||||||
 | 
					  public boolean isBase64() {
 | 
				
			||||||
 | 
					    return base64;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Wrap the binary data in base64 encoding. */
 | 
				
			||||||
 | 
					  public BinaryResult base64() {
 | 
				
			||||||
 | 
					    base64 = true;
 | 
				
			||||||
 | 
					    return this;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Write or copy the result onto the specified output stream.
 | 
					   * Write or copy the result onto the specified output stream.
 | 
				
			||||||
   *
 | 
					   *
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,6 +16,7 @@ package com.google.gerrit.httpd.restapi;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import static com.google.common.base.Charsets.UTF_8;
 | 
					import static com.google.common.base.Charsets.UTF_8;
 | 
				
			||||||
import static com.google.common.base.Preconditions.checkNotNull;
 | 
					import static com.google.common.base.Preconditions.checkNotNull;
 | 
				
			||||||
 | 
					import static java.math.RoundingMode.CEILING;
 | 
				
			||||||
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
 | 
					import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
 | 
				
			||||||
import static javax.servlet.http.HttpServletResponse.SC_CONFLICT;
 | 
					import static javax.servlet.http.HttpServletResponse.SC_CONFLICT;
 | 
				
			||||||
import static javax.servlet.http.HttpServletResponse.SC_CREATED;
 | 
					import static javax.servlet.http.HttpServletResponse.SC_CREATED;
 | 
				
			||||||
@@ -26,6 +27,7 @@ import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
 | 
				
			|||||||
import static javax.servlet.http.HttpServletResponse.SC_OK;
 | 
					import static javax.servlet.http.HttpServletResponse.SC_OK;
 | 
				
			||||||
import static javax.servlet.http.HttpServletResponse.SC_PRECONDITION_FAILED;
 | 
					import static javax.servlet.http.HttpServletResponse.SC_PRECONDITION_FAILED;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.google.common.base.Charsets;
 | 
				
			||||||
import com.google.common.base.Function;
 | 
					import com.google.common.base.Function;
 | 
				
			||||||
import com.google.common.base.Joiner;
 | 
					import com.google.common.base.Joiner;
 | 
				
			||||||
import com.google.common.base.Objects;
 | 
					import com.google.common.base.Objects;
 | 
				
			||||||
@@ -38,6 +40,8 @@ import com.google.common.collect.Lists;
 | 
				
			|||||||
import com.google.common.collect.Maps;
 | 
					import com.google.common.collect.Maps;
 | 
				
			||||||
import com.google.common.collect.Multimap;
 | 
					import com.google.common.collect.Multimap;
 | 
				
			||||||
import com.google.common.collect.Sets;
 | 
					import com.google.common.collect.Sets;
 | 
				
			||||||
 | 
					import com.google.common.io.BaseEncoding;
 | 
				
			||||||
 | 
					import com.google.common.math.IntMath;
 | 
				
			||||||
import com.google.common.net.HttpHeaders;
 | 
					import com.google.common.net.HttpHeaders;
 | 
				
			||||||
import com.google.gerrit.audit.AuditService;
 | 
					import com.google.gerrit.audit.AuditService;
 | 
				
			||||||
import com.google.gerrit.audit.HttpAuditEvent;
 | 
					import com.google.gerrit.audit.HttpAuditEvent;
 | 
				
			||||||
@@ -627,10 +631,28 @@ public class RestApiServlet extends HttpServlet {
 | 
				
			|||||||
      HttpServletResponse res,
 | 
					      HttpServletResponse res,
 | 
				
			||||||
      BinaryResult bin) throws IOException {
 | 
					      BinaryResult bin) throws IOException {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      res.setContentType(bin.getContentType());
 | 
					 | 
				
			||||||
      OutputStream dst = res.getOutputStream();
 | 
					      OutputStream dst = res.getOutputStream();
 | 
				
			||||||
      try {
 | 
					      try {
 | 
				
			||||||
        long len = bin.getContentLength();
 | 
					        long len = bin.getContentLength();
 | 
				
			||||||
 | 
					        if (bin.isBase64() && 0 <= len && len <= (10 << 20)) {
 | 
				
			||||||
 | 
					          final TemporaryBuffer.Heap buf = base64(bin);
 | 
				
			||||||
 | 
					          len = buf.length();
 | 
				
			||||||
 | 
					          base64(res, bin);
 | 
				
			||||||
 | 
					          bin = new BinaryResult() {
 | 
				
			||||||
 | 
					            @Override
 | 
				
			||||||
 | 
					            public void writeTo(OutputStream os) throws IOException {
 | 
				
			||||||
 | 
					              buf.writeTo(os, null);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }.setContentLength(len);
 | 
				
			||||||
 | 
					        } else if (bin.isBase64()) {
 | 
				
			||||||
 | 
					          len = -1;
 | 
				
			||||||
 | 
					          base64(res, bin);
 | 
				
			||||||
 | 
					          dst = BaseEncoding.base64().encodingStream(
 | 
				
			||||||
 | 
					              new OutputStreamWriter(dst, Charsets.ISO_8859_1));
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          res.setContentType(bin.getContentType());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        boolean gzip = bin.canGzip() && acceptsGzip(req);
 | 
					        boolean gzip = bin.canGzip() && acceptsGzip(req);
 | 
				
			||||||
        if (gzip && 256 <= len && len <= (10 << 20)) {
 | 
					        if (gzip && 256 <= len && len <= (10 << 20)) {
 | 
				
			||||||
          TemporaryBuffer.Heap buf = compress(bin);
 | 
					          TemporaryBuffer.Heap buf = compress(bin);
 | 
				
			||||||
@@ -656,6 +678,12 @@ public class RestApiServlet extends HttpServlet {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private static void base64(HttpServletResponse res, BinaryResult bin) {
 | 
				
			||||||
 | 
					    res.setContentType("text/plain; charset=ISO-8859-1");
 | 
				
			||||||
 | 
					    res.setHeader("X-FYI-Content-Encoding", "base64");
 | 
				
			||||||
 | 
					    res.setHeader("X-FYI-Content-Type", bin.getContentType());
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private static void replyUncompressed(HttpServletResponse res,
 | 
					  private static void replyUncompressed(HttpServletResponse res,
 | 
				
			||||||
      OutputStream dst, BinaryResult bin, long len) throws IOException {
 | 
					      OutputStream dst, BinaryResult bin, long len) throws IOException {
 | 
				
			||||||
    if (0 <= len && len < Integer.MAX_VALUE) {
 | 
					    if (0 <= len && len < Integer.MAX_VALUE) {
 | 
				
			||||||
@@ -842,6 +870,18 @@ public class RestApiServlet extends HttpServlet {
 | 
				
			|||||||
    return false;
 | 
					    return false;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private static TemporaryBuffer.Heap base64(BinaryResult bin)
 | 
				
			||||||
 | 
					      throws IOException {
 | 
				
			||||||
 | 
					    int len = (int) bin.getContentLength();
 | 
				
			||||||
 | 
					    int max = 4 * IntMath.divide(len, 3, CEILING);
 | 
				
			||||||
 | 
					    TemporaryBuffer.Heap buf = heap(max);
 | 
				
			||||||
 | 
					    OutputStream encoded = BaseEncoding.base64().encodingStream(
 | 
				
			||||||
 | 
					        new OutputStreamWriter(buf, Charsets.ISO_8859_1));
 | 
				
			||||||
 | 
					    bin.writeTo(encoded);
 | 
				
			||||||
 | 
					    encoded.close();
 | 
				
			||||||
 | 
					    return buf;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private static TemporaryBuffer.Heap compress(BinaryResult bin)
 | 
					  private static TemporaryBuffer.Heap compress(BinaryResult bin)
 | 
				
			||||||
      throws IOException {
 | 
					      throws IOException {
 | 
				
			||||||
    TemporaryBuffer.Heap buf = heap(20 << 20);
 | 
					    TemporaryBuffer.Heap buf = heap(20 << 20);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,11 +14,9 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
package com.google.gerrit.server.change;
 | 
					package com.google.gerrit.server.change;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import com.google.common.base.Charsets;
 | 
					import com.google.gerrit.extensions.restapi.BinaryResult;
 | 
				
			||||||
import com.google.common.io.BaseEncoding;
 | 
					 | 
				
			||||||
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 | 
					import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 | 
				
			||||||
import com.google.gerrit.extensions.restapi.RestReadView;
 | 
					import com.google.gerrit.extensions.restapi.RestReadView;
 | 
				
			||||||
import com.google.gerrit.extensions.restapi.StreamingResponse;
 | 
					 | 
				
			||||||
import com.google.gerrit.reviewdb.client.Project;
 | 
					import com.google.gerrit.reviewdb.client.Project;
 | 
				
			||||||
import com.google.gerrit.server.git.GitRepositoryManager;
 | 
					import com.google.gerrit.server.git.GitRepositoryManager;
 | 
				
			||||||
import com.google.inject.Inject;
 | 
					import com.google.inject.Inject;
 | 
				
			||||||
@@ -32,7 +30,6 @@ import org.eclipse.jgit.treewalk.TreeWalk;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import java.io.IOException;
 | 
					import java.io.IOException;
 | 
				
			||||||
import java.io.OutputStream;
 | 
					import java.io.OutputStream;
 | 
				
			||||||
import java.io.OutputStreamWriter;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class GetContent implements RestReadView<FileResource> {
 | 
					public class GetContent implements RestReadView<FileResource> {
 | 
				
			||||||
  private final GitRepositoryManager repoManager;
 | 
					  private final GitRepositoryManager repoManager;
 | 
				
			||||||
@@ -43,7 +40,7 @@ public class GetContent implements RestReadView<FileResource> {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @Override
 | 
					  @Override
 | 
				
			||||||
  public StreamingResponse apply(FileResource rsrc)
 | 
					  public BinaryResult apply(FileResource rsrc)
 | 
				
			||||||
      throws ResourceNotFoundException, IOException {
 | 
					      throws ResourceNotFoundException, IOException {
 | 
				
			||||||
    Project.NameKey project =
 | 
					    Project.NameKey project =
 | 
				
			||||||
        rsrc.getRevision().getControl().getProject().getNameKey();
 | 
					        rsrc.getRevision().getControl().getProject().getNameKey();
 | 
				
			||||||
@@ -61,21 +58,13 @@ public class GetContent implements RestReadView<FileResource> {
 | 
				
			|||||||
          throw new ResourceNotFoundException();
 | 
					          throw new ResourceNotFoundException();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
          final ObjectLoader loader = repo.open(tw.getObjectId(0));
 | 
					          final ObjectLoader object = repo.open(tw.getObjectId(0));
 | 
				
			||||||
          return new StreamingResponse() {
 | 
					          return new BinaryResult() {
 | 
				
			||||||
            @Override
 | 
					            @Override
 | 
				
			||||||
            public String getContentType() {
 | 
					            public void writeTo(OutputStream os) throws IOException {
 | 
				
			||||||
              return "text/plain;charset=UTF-8";
 | 
					              object.copyTo(os);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					          }.setContentLength(object.getSize()).base64();
 | 
				
			||||||
            @Override
 | 
					 | 
				
			||||||
            public void stream(OutputStream out) throws IOException {
 | 
					 | 
				
			||||||
              OutputStream b64Out = BaseEncoding.base64().encodingStream(
 | 
					 | 
				
			||||||
                  new OutputStreamWriter(out, Charsets.UTF_8));
 | 
					 | 
				
			||||||
              loader.copyTo(b64Out);
 | 
					 | 
				
			||||||
              b64Out.close();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
          };
 | 
					 | 
				
			||||||
        } finally {
 | 
					        } finally {
 | 
				
			||||||
          tw.release();
 | 
					          tw.release();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user