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:
Shawn Pearce
2013-05-09 11:54:17 -07:00
parent a523bd8c00
commit 45b0ed1633
3 changed files with 60 additions and 19 deletions

View File

@@ -61,6 +61,7 @@ public abstract class BinaryResult implements Closeable {
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 @@ public abstract class BinaryResult implements Closeable {
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.
*

View File

@@ -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.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_NOT_FOUND;
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.Lists;
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 @@ public class RestApiServlet extends HttpServlet {
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 @@ 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,
OutputStream dst, BinaryResult bin, long len) throws IOException {
if (0 <= len && len < Integer.MAX_VALUE) {
@@ -842,6 +870,18 @@ public class RestApiServlet extends HttpServlet {
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);

View File

@@ -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 org.eclipse.jgit.treewalk.TreeWalk;
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 @@ public class GetContent implements RestReadView<FileResource> {
}
@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 @@ public class GetContent implements RestReadView<FileResource> {
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();
}