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 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.
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user