diff --git a/clientutil/backend.go b/clientutil/backend.go
index cdde33b45ffe954a37fcf9d53dfd1601e8430876..3f4d31379bb8a70d01eb4680a6572c517a40e9e6 100644
--- a/clientutil/backend.go
+++ b/clientutil/backend.go
@@ -2,6 +2,7 @@ package clientutil
 
 import (
 	"context"
+	"net/http"
 )
 
 // BackendConfig specifies the configuration of a service backend.
@@ -39,6 +40,13 @@ type Backend interface {
 	// *without* a shard ID on a sharded service is an error.
 	Call(context.Context, string, string, interface{}, interface{}) error
 
+	// Make a simple HTTP GET request to the remote backend,
+	// without parsing the response as JSON.
+	//
+	// Useful for streaming large responses, where the JSON
+	// encoding overhead is undesirable.
+	Get(context.Context, string, string) (*http.Response, error)
+
 	// Close all resources associated with the backend.
 	Close()
 }
diff --git a/clientutil/balancer.go b/clientutil/balancer.go
index 5c37d6bfcb7e97c0c249d21c236d90df7dcc1637..b70602c8bea87bfb2d53c048163f0711f9ee4154 100644
--- a/clientutil/balancer.go
+++ b/clientutil/balancer.go
@@ -168,6 +168,44 @@ func (b *balancedBackend) Call(ctx context.Context, shard, path string, req, res
 	}, backoff.WithContext(newExponentialBackOff(), ctx))
 }
 
+// Makes a generic HTTP GET request to the backend uri.
+func (b *balancedBackend) Get(ctx context.Context, shard, path string) (*http.Response, error) {
+	// Create the target sequence for this call. If there are multiple
+	// targets, reduce the timeout on each individual call accordingly to
+	// accomodate eventual failover.
+	seq, err := b.makeSequence(shard)
+	if err != nil {
+		return nil, err
+	}
+	innerTimeout := 1 * time.Hour
+	if deadline, ok := ctx.Deadline(); ok {
+		innerTimeout = time.Until(deadline) / time.Duration(seq.Len())
+	}
+	if b.requestMaxTimeout > 0 && innerTimeout > b.requestMaxTimeout {
+		innerTimeout = b.requestMaxTimeout
+	}
+
+	req, err := http.NewRequest("GET", b.getURIForRequest(shard, path), nil)
+	if err != nil {
+		return nil, err
+	}
+
+	// Call the backends in the sequence until one succeeds, with an
+	// exponential backoff policy controlled by the outer Context.
+	var resp *http.Response
+	err = backoff.Retry(func() error {
+		innerCtx, cancel := context.WithTimeout(ctx, innerTimeout)
+		defer cancel()
+
+		// When do() returns successfully, we already know that the
+		// response had an HTTP status of 200.
+		var rerr error
+		resp, rerr = b.do(innerCtx, seq, req)
+		return rerr
+	}, backoff.WithContext(newExponentialBackOff(), ctx))
+	return resp, err
+}
+
 // Initialize a new target sequence.
 func (b *balancedBackend) makeSequence(shard string) (*sequence, error) {
 	var tg targetGenerator = b.backendTracker