diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoRefsServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoRefsServlet.java
index b766196fdcc5cc5e4a32e4076d4a741a64be61e2..f667ce95a75529dd64f61c1f5714cfc1601aada6 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoRefsServlet.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoRefsServlet.java
@@ -44,9 +44,9 @@
 package org.eclipse.jgit.http.server;
 
 import static org.eclipse.jgit.http.server.ServletUtils.getRepository;
-import static org.eclipse.jgit.http.server.ServletUtils.send;
 
 import java.io.IOException;
+import java.io.OutputStreamWriter;
 import java.util.Map;
 
 import javax.servlet.http.HttpServlet;
@@ -69,20 +69,18 @@ public void doGet(final HttpServletRequest req,
 			final HttpServletResponse rsp) throws IOException {
 		// Assume a dumb client and send back the dumb client
 		// version of the info/refs file.
-		final byte[] raw = dumbHttp(req);
 		rsp.setContentType(HttpSupport.TEXT_PLAIN);
 		rsp.setCharacterEncoding(Constants.CHARACTER_ENCODING);
-		send(raw, req, rsp);
-	}
 
-	private byte[] dumbHttp(final HttpServletRequest req) throws IOException {
 		final Repository db = getRepository(req);
 		final RevWalk walk = new RevWalk(db);
 		final RevFlag ADVERTISED = walk.newFlag("ADVERTISED");
-		final StringBuilder out = new StringBuilder();
+
+		final OutputStreamWriter out = new OutputStreamWriter(
+				new SmartOutputStream(req, rsp), Constants.CHARSET);
 		final RefAdvertiser adv = new RefAdvertiser() {
 			@Override
-			protected void writeOne(final CharSequence line) {
+			protected void writeOne(final CharSequence line) throws IOException {
 				// Whoever decided that info/refs should use a different
 				// delimiter than the native git:// protocol shouldn't
 				// be allowed to design this sort of stuff. :-(
@@ -100,6 +98,6 @@ protected void end() {
 		Map<String, Ref> refs = db.getAllRefs();
 		refs.remove(Constants.HEAD);
 		adv.send(refs);
-		return out.toString().getBytes(Constants.CHARACTER_ENCODING);
+		out.close();
 	}
 }
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java
index 1a426af96d145b3afd255f23246da4041968f57e..ba8b8ab6697ee463d73d518b70b635e5d391c971 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java
@@ -50,9 +50,7 @@
 import static org.eclipse.jgit.http.server.ServletUtils.getInputStream;
 import static org.eclipse.jgit.http.server.ServletUtils.getRepository;
 
-import java.io.ByteArrayOutputStream;
 import java.io.IOException;
-import java.io.OutputStream;
 
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
@@ -104,11 +102,14 @@ public void doPost(final HttpServletRequest req,
 		}
 
 		final Repository db = getRepository(req);
-		final ByteArrayOutputStream out = new ByteArrayOutputStream();
 		try {
 			final ReceivePack rp = receivePackFactory.create(req, db);
 			rp.setBiDirectionalPipe(false);
+			rsp.setContentType(RSP_TYPE);
+
+			final SmartOutputStream out = new SmartOutputStream(req, rsp);
 			rp.receive(getInputStream(req), out, null);
+			out.close();
 
 		} catch (ServiceNotAuthorizedException e) {
 			rsp.sendError(SC_UNAUTHORIZED);
@@ -123,20 +124,5 @@ public void doPost(final HttpServletRequest req,
 			rsp.sendError(SC_INTERNAL_SERVER_ERROR);
 			return;
 		}
-
-		reply(rsp, out.toByteArray());
-	}
-
-	private void reply(final HttpServletResponse rsp, final byte[] result)
-			throws IOException {
-		rsp.setContentType(RSP_TYPE);
-		rsp.setContentLength(result.length);
-		final OutputStream os = rsp.getOutputStream();
-		try {
-			os.write(result);
-			os.flush();
-		} finally {
-			os.close();
-		}
 	}
 }
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ServletUtils.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ServletUtils.java
index d6b039246f47c190931d0738b394d935dbba943c..7514d7c7e73ae540a2b1d5982077aed0f94e07e8 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ServletUtils.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ServletUtils.java
@@ -189,7 +189,7 @@ private static byte[] sendInit(byte[] content,
 		return content;
 	}
 
-	private static boolean acceptsGzipEncoding(final HttpServletRequest req) {
+	static boolean acceptsGzipEncoding(final HttpServletRequest req) {
 		final String accepts = req.getHeader(HDR_ACCEPT_ENCODING);
 		return accepts != null && 0 <= accepts.indexOf(ENCODING_GZIP);
 	}
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartOutputStream.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartOutputStream.java
new file mode 100644
index 0000000000000000000000000000000000000000..00cb67ca9593bc922e96610d2d6ec831eebb6ccb
--- /dev/null
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartOutputStream.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.http.server;
+
+import static org.eclipse.jgit.http.server.ServletUtils.acceptsGzipEncoding;
+import static org.eclipse.jgit.util.HttpSupport.ENCODING_GZIP;
+import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_ENCODING;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.zip.GZIPOutputStream;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jgit.util.TemporaryBuffer;
+
+/**
+ * Buffers a response, trying to gzip it if the user agent supports that.
+ * <p>
+ * If the response overflows the buffer, gzip is skipped and the response is
+ * streamed to the client as its produced, most likely using HTTP/1.1 chunked
+ * encoding. This is useful for servlets that produce mixed-mode content, where
+ * smaller payloads are primarily pure text that compresses well, while much
+ * larger payloads are heavily compressed binary data. {@link UploadPackServlet}
+ * is one such servlet.
+ */
+class SmartOutputStream extends TemporaryBuffer {
+	private static final int LIMIT = 32 * 1024;
+
+	private final HttpServletRequest req;
+
+	private final HttpServletResponse rsp;
+
+	private boolean startedOutput;
+
+	SmartOutputStream(final HttpServletRequest req,
+			final HttpServletResponse rsp) {
+		super(LIMIT);
+		this.req = req;
+		this.rsp = rsp;
+	}
+
+	@Override
+	protected OutputStream overflow() throws IOException {
+		startedOutput = true;
+		return rsp.getOutputStream();
+	}
+
+	public void close() throws IOException {
+		super.close();
+
+		if (!startedOutput) {
+			// If output hasn't started yet, the entire thing fit into our
+			// buffer. Try to use a proper Content-Length header, and also
+			// deflate the response with gzip if it will be smaller.
+			TemporaryBuffer out = this;
+
+			if (256 < out.length() && acceptsGzipEncoding(req)) {
+				TemporaryBuffer gzbuf = new TemporaryBuffer.Heap(LIMIT);
+				try {
+					GZIPOutputStream gzip = new GZIPOutputStream(gzbuf);
+					out.writeTo(gzip, null);
+					gzip.close();
+					if (gzbuf.length() < out.length()) {
+						out = gzbuf;
+						rsp.setHeader(HDR_CONTENT_ENCODING, ENCODING_GZIP);
+					}
+				} catch (IOException err) {
+					// Most likely caused by overflowing the buffer, meaning
+					// its larger if it were compressed. Discard compressed
+					// copy and use the original.
+				}
+			}
+
+			// The Content-Length cannot overflow when cast to an int, our
+			// hardcoded LIMIT constant above assures us we wouldn't store
+			// more than 2 GiB of content in memory.
+			rsp.setContentLength((int) out.length());
+			final OutputStream os = rsp.getOutputStream();
+			try {
+				out.writeTo(os, null);
+				os.flush();
+			} finally {
+				os.close();
+			}
+		}
+	}
+}
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartServiceInfoRefs.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartServiceInfoRefs.java
index 96716d07038db4ecc9cbf3e99f6046a88c7bb133..94e2e7f6f757b7e559756ecf4e776f4b26e71553 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartServiceInfoRefs.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartServiceInfoRefs.java
@@ -46,9 +46,7 @@
 import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
 import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
 import static org.eclipse.jgit.http.server.ServletUtils.getRepository;
-import static org.eclipse.jgit.http.server.ServletUtils.send;
 
-import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 
 import javax.servlet.Filter;
@@ -90,13 +88,14 @@ public void doFilter(ServletRequest request, ServletResponse response,
 			final HttpServletResponse rsp = (HttpServletResponse) response;
 			try {
 				final Repository db = getRepository(req);
-				final ByteArrayOutputStream buf = new ByteArrayOutputStream();
+				rsp.setContentType("application/x-" + svc + "-advertisement");
+
+				final SmartOutputStream buf = new SmartOutputStream(req, rsp);
 				final PacketLineOut out = new PacketLineOut(buf);
 				out.writeString("# service=" + svc + "\n");
 				out.end();
 				advertise(req, db, new PacketLineOutRefAdvertiser(out));
-				rsp.setContentType("application/x-" + svc + "-advertisement");
-				send(buf.toByteArray(), req, rsp);
+				buf.close();
 			} catch (ServiceNotAuthorizedException e) {
 				rsp.sendError(SC_UNAUTHORIZED);
 
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java
index dc874fa5f2aa1f83369039973db804be5ee87736..8de5f0678184ebc5026bc8317d906d5ba5dcd737 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java
@@ -106,7 +106,10 @@ public void doPost(final HttpServletRequest req,
 			final UploadPack up = uploadPackFactory.create(req, db);
 			up.setBiDirectionalPipe(false);
 			rsp.setContentType(RSP_TYPE);
-			up.upload(getInputStream(req), rsp.getOutputStream(), null);
+
+			final SmartOutputStream out = new SmartOutputStream(req, rsp);
+			up.upload(getInputStream(req), out, null);
+			out.close();
 
 		} catch (ServiceNotAuthorizedException e) {
 			rsp.reset();
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java
index fd78bb45152d55c466fe60bc952c871f3ce0c424..f7b3bdb2034174b298ceed76c76406f767a6f6f1 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2009-2010, Google Inc.
+ * Copyright (C) 2010, Google Inc.
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -253,8 +253,6 @@ public void testInitialClone_Small() throws Exception {
 		assertEquals(200, service.getStatus());
 		assertEquals("application/x-git-upload-pack-result", service
 				.getResponseHeader(HDR_CONTENT_TYPE));
-		assertNull("no compression (never compressed)", service
-				.getResponseHeader(HDR_CONTENT_ENCODING));
 	}
 
 	public void testFetchUpdateExisting() throws Exception {