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
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/BinaryResult.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/BinaryResult.java
index 188011c..103874f 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/BinaryResult.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/BinaryResult.java
@@ -61,6 +61,7 @@
private String characterEncoding;
private long contentLength = -1;
private boolean gzip = true;
+ private boolean base64 = false;
/** @return the MIME type of the result, for HTTP clients. */
public String getContentType() {
@@ -110,6 +111,17 @@
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.
*
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
index 1040da3..fa1e902 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
@@ -16,6 +16,7 @@
import static com.google.common.base.Charsets.UTF_8;
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_CONFLICT;
import static javax.servlet.http.HttpServletResponse.SC_CREATED;
@@ -26,6 +27,7 @@
import static javax.servlet.http.HttpServletResponse.SC_OK;
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.Joiner;
import com.google.common.base.Objects;
@@ -38,6 +40,8 @@
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
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.gerrit.audit.AuditService;
import com.google.gerrit.audit.HttpAuditEvent;
@@ -627,10 +631,28 @@
HttpServletResponse res,
BinaryResult bin) throws IOException {
try {
- res.setContentType(bin.getContentType());
OutputStream dst = res.getOutputStream();
try {
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);
if (gzip && 256 <= len && len <= (10 << 20)) {
TemporaryBuffer.Heap buf = compress(bin);
@@ -656,6 +678,12 @@
}
}
+ 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,
OutputStream dst, BinaryResult bin, long len) throws IOException {
if (0 <= len && len < Integer.MAX_VALUE) {
@@ -842,6 +870,18 @@
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)
throws IOException {
TemporaryBuffer.Heap buf = heap(20 << 20);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetContent.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetContent.java
index 569df6f..7c8ec34 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetContent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetContent.java
@@ -14,11 +14,9 @@
package com.google.gerrit.server.change;
-import com.google.common.base.Charsets;
-import com.google.common.io.BaseEncoding;
+import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
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.server.git.GitRepositoryManager;
import com.google.inject.Inject;
@@ -32,7 +30,6 @@
import java.io.IOException;
import java.io.OutputStream;
-import java.io.OutputStreamWriter;
public class GetContent implements RestReadView<FileResource> {
private final GitRepositoryManager repoManager;
@@ -43,7 +40,7 @@
}
@Override
- public StreamingResponse apply(FileResource rsrc)
+ public BinaryResult apply(FileResource rsrc)
throws ResourceNotFoundException, IOException {
Project.NameKey project =
rsrc.getRevision().getControl().getProject().getNameKey();
@@ -61,21 +58,13 @@
throw new ResourceNotFoundException();
}
try {
- final ObjectLoader loader = repo.open(tw.getObjectId(0));
- return new StreamingResponse() {
+ final ObjectLoader object = repo.open(tw.getObjectId(0));
+ return new BinaryResult() {
@Override
- public String getContentType() {
- return "text/plain;charset=UTF-8";
+ public void writeTo(OutputStream os) throws IOException {
+ object.copyTo(os);
}
-
- @Override
- public void stream(OutputStream out) throws IOException {
- OutputStream b64Out = BaseEncoding.base64().encodingStream(
- new OutputStreamWriter(out, Charsets.UTF_8));
- loader.copyTo(b64Out);
- b64Out.close();
- }
- };
+ }.setContentLength(object.getSize()).base64();
} finally {
tw.release();
}