mirror of
https://github.com/golang/net.git
synced 2026-03-31 10:27:08 +09:00
http2: add automatic gzip compression for the Transport
Tests are in net/http (clientserver_test.go, TestH12_AutoGzip) from https://golang.org/cl/17241 Fixes golang/go#13298 Change-Id: I3f0b237ffdf6d547d57f29383e1a78c4f272fc44 Reviewed-on: https://go-review.googlesource.com/17242 Reviewed-by: Andrew Gerrand <adg@golang.org>
This commit is contained in:
@@ -9,6 +9,7 @@ package http2
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -61,10 +62,29 @@ type Transport struct {
|
||||
// If nil, the default is used.
|
||||
ConnPool ClientConnPool
|
||||
|
||||
// DisableCompression, if true, prevents the Transport from
|
||||
// requesting compression with an "Accept-Encoding: gzip"
|
||||
// request header when the Request contains no existing
|
||||
// Accept-Encoding value. If the Transport requests gzip on
|
||||
// its own and gets a gzipped response, it's transparently
|
||||
// decoded in the Response.Body. However, if the user
|
||||
// explicitly requested gzip it is not automatically
|
||||
// uncompressed.
|
||||
DisableCompression bool
|
||||
|
||||
connPoolOnce sync.Once
|
||||
connPoolOrDef ClientConnPool // non-nil version of ConnPool
|
||||
}
|
||||
|
||||
func (t *Transport) disableCompression() bool {
|
||||
if t.DisableCompression {
|
||||
return true
|
||||
}
|
||||
// TODO: also disable if this transport is somehow linked to an http1 Transport
|
||||
// and it's configured there?
|
||||
return false
|
||||
}
|
||||
|
||||
var errTransportVersion = errors.New("http2: ConfigureTransport is only supported starting at Go 1.6")
|
||||
|
||||
// ConfigureTransport configures a net/http HTTP/1 Transport to use HTTP/2.
|
||||
@@ -124,11 +144,12 @@ type ClientConn struct {
|
||||
// clientStream is the state for a single HTTP/2 stream. One of these
|
||||
// is created for each Transport.RoundTrip call.
|
||||
type clientStream struct {
|
||||
cc *ClientConn
|
||||
req *http.Request
|
||||
ID uint32
|
||||
resc chan resAndError
|
||||
bufPipe pipe // buffered pipe with the flow-controlled response payload
|
||||
cc *ClientConn
|
||||
req *http.Request
|
||||
ID uint32
|
||||
resc chan resAndError
|
||||
bufPipe pipe // buffered pipe with the flow-controlled response payload
|
||||
requestedGzip bool
|
||||
|
||||
flow flow // guarded by cc.mu
|
||||
inflow flow // guarded by cc.mu
|
||||
@@ -441,8 +462,28 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
cs.req = req
|
||||
hasBody := req.Body != nil
|
||||
|
||||
// TODO(bradfitz): this is a copy of the logic in net/http. Unify somewhere?
|
||||
if !cc.t.disableCompression() &&
|
||||
req.Header.Get("Accept-Encoding") == "" &&
|
||||
req.Header.Get("Range") == "" &&
|
||||
req.Method != "HEAD" {
|
||||
// Request gzip only, not deflate. Deflate is ambiguous and
|
||||
// not as universally supported anyway.
|
||||
// See: http://www.gzip.org/zlib/zlib_faq.html#faq38
|
||||
//
|
||||
// Note that we don't request this for HEAD requests,
|
||||
// due to a bug in nginx:
|
||||
// http://trac.nginx.org/nginx/ticket/358
|
||||
// https://golang.org/issue/5522
|
||||
//
|
||||
// We don't request gzip if the request is for a range, since
|
||||
// auto-decoding a portion of a gzipped document will just fail
|
||||
// anyway. See https://golang.org/issue/8923
|
||||
cs.requestedGzip = true
|
||||
}
|
||||
|
||||
// we send: HEADERS{1}, CONTINUATION{0,} + DATA{0,}
|
||||
hdrs := cc.encodeHeaders(req)
|
||||
hdrs := cc.encodeHeaders(req, cs.requestedGzip)
|
||||
first := true // first frame written (HEADERS is first, then CONTINUATION)
|
||||
|
||||
cc.wmu.Lock()
|
||||
@@ -598,7 +639,7 @@ func (cs *clientStream) awaitFlowControl(maxBytes int32) (taken int32, err error
|
||||
}
|
||||
|
||||
// requires cc.mu be held.
|
||||
func (cc *ClientConn) encodeHeaders(req *http.Request) []byte {
|
||||
func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool) []byte {
|
||||
cc.hbuf.Reset()
|
||||
|
||||
// TODO(bradfitz): figure out :authority-vs-Host stuff between http2 and Go
|
||||
@@ -626,6 +667,9 @@ func (cc *ClientConn) encodeHeaders(req *http.Request) []byte {
|
||||
cc.writeHeader(lowKey, v)
|
||||
}
|
||||
}
|
||||
if addGzipHeader {
|
||||
cc.writeHeader("accept-encoding", "gzip")
|
||||
}
|
||||
return cc.hbuf.Bytes()
|
||||
}
|
||||
|
||||
@@ -853,6 +897,13 @@ func (rl *clientConnReadLoop) processHeaderBlockFragment(frag []byte, streamID u
|
||||
cs.bufPipe = pipe{b: buf}
|
||||
cs.bytesRemain = res.ContentLength
|
||||
res.Body = transportResponseBody{cs}
|
||||
|
||||
if cs.requestedGzip && res.Header.Get("Content-Encoding") == "gzip" {
|
||||
res.Header.Del("Content-Encoding")
|
||||
res.Header.Del("Content-Length")
|
||||
res.ContentLength = -1
|
||||
res.Body = &gzipReader{body: res.Body}
|
||||
}
|
||||
}
|
||||
|
||||
rl.activeRes[cs.ID] = cs
|
||||
@@ -1146,3 +1197,24 @@ func strSliceContains(ss []string, s string) bool {
|
||||
type erringRoundTripper struct{ err error }
|
||||
|
||||
func (rt erringRoundTripper) RoundTrip(*http.Request) (*http.Response, error) { return nil, rt.err }
|
||||
|
||||
// gzipReader wraps a response body so it can lazily
|
||||
// call gzip.NewReader on the first call to Read
|
||||
type gzipReader struct {
|
||||
body io.ReadCloser // underlying Response.Body
|
||||
zr io.Reader // lazily-initialized gzip reader
|
||||
}
|
||||
|
||||
func (gz *gzipReader) Read(p []byte) (n int, err error) {
|
||||
if gz.zr == nil {
|
||||
gz.zr, err = gzip.NewReader(gz.body)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return gz.zr.Read(p)
|
||||
}
|
||||
|
||||
func (gz *gzipReader) Close() error {
|
||||
return gz.body.Close()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user