Cleanup handling of BinaryResult

Simplify the logic to stack BinaryResult instances on top of each
other when applying the base64 and/or gzip transforms before writing
to the HttpServletResponse.

Change-Id: I1a477d5b9888cac981021149905a9aaa8f2f89ad
This commit is contained in:
Shawn Pearce
2013-05-20 08:15:49 -07:00
parent b6df03968d
commit 2012c46e26
3 changed files with 81 additions and 71 deletions

View File

@@ -535,7 +535,7 @@ public class RestApiServlet extends HttpServlet {
Multimap<String, String> config, Multimap<String, String> config,
Object result) Object result)
throws IOException { throws IOException {
final TemporaryBuffer.Heap buf = heap(Integer.MAX_VALUE); TemporaryBuffer.Heap buf = heap(Integer.MAX_VALUE);
buf.write(JSON_MAGIC); buf.write(JSON_MAGIC);
Writer w = new BufferedWriter(new OutputStreamWriter(buf, UTF_8)); Writer w = new BufferedWriter(new OutputStreamWriter(buf, UTF_8));
Gson gson = newGson(config, req); Gson gson = newGson(config, req);
@@ -546,18 +546,9 @@ public class RestApiServlet extends HttpServlet {
} }
w.write('\n'); w.write('\n');
w.flush(); w.flush();
replyBinaryResult(req, res, asBinaryResult(buf)
replyBinaryResult(req, res, new BinaryResult() { .setContentType(JSON_TYPE)
@Override .setCharacterEncoding(UTF_8.name()));
public long getContentLength() {
return buf.length();
}
@Override
public void writeTo(OutputStream os) throws IOException {
buf.writeTo(os, null);
}
}.setContentType(JSON_TYPE).setCharacterEncoding(UTF_8.name()));
} }
private static Gson newGson(Multimap<String, String> config, private static Gson newGson(Multimap<String, String> config,
@@ -627,68 +618,78 @@ public class RestApiServlet extends HttpServlet {
@Nullable HttpServletRequest req, @Nullable HttpServletRequest req,
HttpServletResponse res, HttpServletResponse res,
BinaryResult bin) throws IOException { BinaryResult bin) throws IOException {
final BinaryResult appResult = bin;
try { try {
if (bin.isBase64()) {
bin = stackBase64(res, bin);
}
if (bin.canGzip() && acceptsGzip(req)) {
bin = stackGzip(res, bin);
}
res.setContentType(bin.getContentType());
long len = bin.getContentLength();
if (0 <= len && len < Integer.MAX_VALUE) {
res.setContentLength((int) len);
} else if (0 <= len) {
res.setHeader("Content-Length", Long.toString(len));
}
OutputStream dst = res.getOutputStream(); OutputStream dst = res.getOutputStream();
try { try {
long len = bin.getContentLength(); bin.writeTo(dst);
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);
if (buf.length() < len) {
res.setContentLength((int) buf.length());
res.setHeader("Content-Encoding", "gzip");
buf.writeTo(dst, null);
} else {
replyUncompressed(res, dst, bin, len);
}
} else if (gzip) {
res.setHeader("Content-Encoding", "gzip");
dst = new GZIPOutputStream(dst);
bin.writeTo(dst);
} else {
replyUncompressed(res, dst, bin, len);
}
} finally { } finally {
dst.close(); dst.close();
} }
} finally { } finally {
bin.close(); appResult.close();
} }
} }
private static void base64(HttpServletResponse res, BinaryResult bin) { private static BinaryResult stackBase64(HttpServletResponse res,
res.setContentType("text/plain; charset=ISO-8859-1"); final BinaryResult src) throws IOException {
BinaryResult b64;
long len = src.getContentLength();
if (0 <= len && len <= (7 << 20)) {
b64 = base64(src);
} else {
b64 = new BinaryResult() {
@Override
public void writeTo(OutputStream out) throws IOException {
OutputStream e = BaseEncoding.base64().encodingStream(
new OutputStreamWriter(out, Charsets.ISO_8859_1));
src.writeTo(e);
e.flush();
}
};
}
res.setHeader("X-FYI-Content-Encoding", "base64"); res.setHeader("X-FYI-Content-Encoding", "base64");
res.setHeader("X-FYI-Content-Type", bin.getContentType()); res.setHeader("X-FYI-Content-Type", src.getContentType());
return b64.setContentType("text/plain").setCharacterEncoding("ISO-8859-1");
} }
private static void replyUncompressed(HttpServletResponse res, private static BinaryResult stackGzip(HttpServletResponse res,
OutputStream dst, BinaryResult bin, long len) throws IOException { final BinaryResult src) throws IOException {
if (0 <= len && len < Integer.MAX_VALUE) { BinaryResult gz;
res.setContentLength((int) len); long len = src.getContentLength();
} else if (0 <= len) { if (256 <= len && len <= (10 << 20)) {
res.setHeader("Content-Length", Long.toString(len)); gz = compress(src);
if (len <= gz.getContentLength()) {
return src;
}
} else {
gz = new BinaryResult() {
@Override
public void writeTo(OutputStream out) throws IOException {
GZIPOutputStream gz = new GZIPOutputStream(out);
src.writeTo(gz);
gz.finish();
gz.flush();
}
};
} }
bin.writeTo(dst); res.setHeader("Content-Encoding", "gzip");
return gz.setContentType(src.getContentType());
} }
private RestView<RestResource> view( private RestView<RestResource> view(
@@ -867,26 +868,33 @@ public class RestApiServlet extends HttpServlet {
return false; return false;
} }
private static TemporaryBuffer.Heap base64(BinaryResult bin) private static BinaryResult base64(BinaryResult bin)
throws IOException { throws IOException {
int len = (int) bin.getContentLength(); int max = 4 * IntMath.divide((int) bin.getContentLength(), 3, CEILING);
int max = 4 * IntMath.divide(len, 3, CEILING);
TemporaryBuffer.Heap buf = heap(max); TemporaryBuffer.Heap buf = heap(max);
OutputStream encoded = BaseEncoding.base64().encodingStream( OutputStream encoded = BaseEncoding.base64().encodingStream(
new OutputStreamWriter(buf, Charsets.ISO_8859_1)); new OutputStreamWriter(buf, Charsets.ISO_8859_1));
bin.writeTo(encoded); bin.writeTo(encoded);
encoded.close(); encoded.close();
return buf; return asBinaryResult(buf);
} }
private static TemporaryBuffer.Heap compress(BinaryResult bin) private static BinaryResult compress(BinaryResult bin)
throws IOException { throws IOException {
TemporaryBuffer.Heap buf = heap(20 << 20); TemporaryBuffer.Heap buf = heap(20 << 20);
GZIPOutputStream gz = new GZIPOutputStream(buf); GZIPOutputStream gz = new GZIPOutputStream(buf);
bin.writeTo(gz); bin.writeTo(gz);
gz.finish(); gz.close();
gz.flush(); return asBinaryResult(buf).setContentType(bin.getContentType());
return buf; }
private static BinaryResult asBinaryResult(final TemporaryBuffer.Heap buf) {
return new BinaryResult() {
@Override
public void writeTo(OutputStream os) throws IOException {
buf.writeTo(os, null);
}
}.setContentLength(buf.length());
} }
private static Heap heap(int max) { private static Heap heap(int max) {

View File

@@ -64,7 +64,8 @@ public class GetContent implements RestReadView<FileResource> {
public void writeTo(OutputStream os) throws IOException { public void writeTo(OutputStream os) throws IOException {
object.copyTo(os); object.copyTo(os);
} }
}.setContentLength(object.getSize()).base64(); }.setContentLength(object.getSize())
.base64();
} finally { } finally {
tw.release(); tw.release();
} }

View File

@@ -83,7 +83,8 @@ public class GarbageCollect implements RestModifyView<ProjectResource, Input> {
writer.flush(); writer.flush();
} }
} }
}.setContentType("text/plain; charset=UTF-8") }.setContentType("text/plain")
.setCharacterEncoding(Charsets.UTF_8.name())
.disableGzip(); .disableGzip();
} }
} }