Skip to content

Commit 5c0dae8

Browse files
committed
http2: make Transport send a Content-Length
Same policy and logic (and comments) as the net/http.Transport. Updates golang/go#14003 Change-Id: I5744140fed16c00b0dc9a4bc74631b7df7d8241c Reviewed-on: https://go-review.googlesource.com/18709 Reviewed-by: Andrew Gerrand <[email protected]>
1 parent c92cdcb commit 5c0dae8

File tree

2 files changed

+58
-15
lines changed

2 files changed

+58
-15
lines changed

http2/transport.go

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,28 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
551551
}
552552
hasTrailers := trailers != ""
553553

554+
var body io.Reader = req.Body
555+
contentLen := req.ContentLength
556+
if req.Body != nil && contentLen == 0 {
557+
// Test to see if it's actually zero or just unset.
558+
var buf [1]byte
559+
n, rerr := io.ReadFull(body, buf[:])
560+
if rerr != nil && rerr != io.EOF {
561+
contentLen = -1
562+
body = errorReader{rerr}
563+
} else if n == 1 {
564+
// Oh, guess there is data in this Body Reader after all.
565+
// The ContentLength field just wasn't set.
566+
// Stich the Body back together again, re-attaching our
567+
// consumed byte.
568+
contentLen = -1
569+
body = io.MultiReader(bytes.NewReader(buf[:]), body)
570+
} else {
571+
// Body is actually empty.
572+
body = nil
573+
}
574+
}
575+
554576
cc.mu.Lock()
555577
if cc.closed || !cc.canTakeNewRequestLocked() {
556578
cc.mu.Unlock()
@@ -559,7 +581,7 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
559581

560582
cs := cc.newStream()
561583
cs.req = req
562-
hasBody := req.Body != nil
584+
hasBody := body != nil
563585

564586
// TODO(bradfitz): this is a copy of the logic in net/http. Unify somewhere?
565587
if !cc.t.disableCompression() &&
@@ -584,7 +606,7 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
584606
// we send: HEADERS{1}, CONTINUATION{0,} + DATA{0,} (DATA is
585607
// sent by writeRequestBody below, along with any Trailers,
586608
// again in form HEADERS{1}, CONTINUATION{0,})
587-
hdrs := cc.encodeHeaders(req, cs.requestedGzip, trailers)
609+
hdrs := cc.encodeHeaders(req, cs.requestedGzip, trailers, contentLen)
588610
cc.wmu.Lock()
589611
endStream := !hasBody && !hasTrailers
590612
werr := cc.writeHeaders(cs.ID, endStream, hdrs)
@@ -605,7 +627,7 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
605627
if hasBody {
606628
bodyCopyErrc = make(chan error, 1)
607629
go func() {
608-
bodyCopyErrc <- cs.writeRequestBody(req.Body)
630+
bodyCopyErrc <- cs.writeRequestBody(body, req.Body)
609631
}()
610632
}
611633

@@ -705,7 +727,7 @@ func (cc *ClientConn) writeHeaders(streamID uint32, endStream bool, hdrs []byte)
705727
// It doesn't escape to callers.
706728
var errAbortReqBodyWrite = errors.New("http2: aborting request body write")
707729

708-
func (cs *clientStream) writeRequestBody(body io.ReadCloser) (err error) {
730+
func (cs *clientStream) writeRequestBody(body io.Reader, bodyCloser io.Closer) (err error) {
709731
cc := cs.cc
710732
sentEnd := false // whether we sent the final DATA frame w/ END_STREAM
711733
buf := cc.frameScratchBuffer()
@@ -716,7 +738,7 @@ func (cs *clientStream) writeRequestBody(body io.ReadCloser) (err error) {
716738
// Request.Body is closed by the Transport,
717739
// and in multiple cases: server replies <=299 and >299
718740
// while still writing request body
719-
cerr := body.Close()
741+
cerr := bodyCloser.Close()
720742
if err == nil {
721743
err = cerr
722744
}
@@ -829,7 +851,7 @@ type badStringError struct {
829851
func (e *badStringError) Error() string { return fmt.Sprintf("%s %q", e.what, e.str) }
830852

831853
// requires cc.mu be held.
832-
func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trailers string) []byte {
854+
func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trailers string, contentLength int64) []byte {
833855
cc.hbuf.Reset()
834856

835857
host := req.Host
@@ -855,7 +877,7 @@ func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trail
855877
var didUA bool
856878
for k, vv := range req.Header {
857879
lowKey := strings.ToLower(k)
858-
if lowKey == "host" {
880+
if lowKey == "host" || lowKey == "content-length" {
859881
continue
860882
}
861883
if lowKey == "user-agent" {
@@ -876,6 +898,9 @@ func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trail
876898
cc.writeHeader(lowKey, v)
877899
}
878900
}
901+
if contentLength >= 0 {
902+
cc.writeHeader("content-length", strconv.FormatInt(contentLength, 10))
903+
}
879904
if addGzipHeader {
880905
cc.writeHeader("accept-encoding", "gzip")
881906
}
@@ -1605,3 +1630,7 @@ func (gz *gzipReader) Read(p []byte) (n int, err error) {
16051630
func (gz *gzipReader) Close() error {
16061631
return gz.body.Close()
16071632
}
1633+
1634+
type errorReader struct{ err error }
1635+
1636+
func (r errorReader) Read(p []byte) (int, error) { return 0, r.err }

http2/transport_test.go

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -332,14 +332,19 @@ var bodyTests = []struct {
332332
}
333333

334334
func TestTransportBody(t *testing.T) {
335-
gotc := make(chan interface{}, 1)
335+
type reqInfo struct {
336+
req *http.Request
337+
slurp []byte
338+
err error
339+
}
340+
gotc := make(chan reqInfo, 1)
336341
st := newServerTester(t,
337342
func(w http.ResponseWriter, r *http.Request) {
338343
slurp, err := ioutil.ReadAll(r.Body)
339344
if err != nil {
340-
gotc <- err
345+
gotc <- reqInfo{err: err}
341346
} else {
342-
gotc <- string(slurp)
347+
gotc <- reqInfo{req: r, slurp: slurp}
343348
}
344349
},
345350
optOnlyServer,
@@ -364,13 +369,21 @@ func TestTransportBody(t *testing.T) {
364369
t.Fatalf("#%d: %v", i, err)
365370
}
366371
defer res.Body.Close()
367-
got := <-gotc
368-
if err, ok := got.(error); ok {
369-
t.Fatalf("#%d: %v", i, err)
370-
} else if got.(string) != tt.body {
371-
got := got.(string)
372+
ri := <-gotc
373+
if ri.err != nil {
374+
t.Errorf("%#d: read error: %v", i, ri.err)
375+
continue
376+
}
377+
if got := string(ri.slurp); got != tt.body {
372378
t.Errorf("#%d: Read body mismatch.\n got: %q (len %d)\nwant: %q (len %d)", i, shortString(got), len(got), shortString(tt.body), len(tt.body))
373379
}
380+
wantLen := int64(len(tt.body))
381+
if tt.noContentLen && tt.body != "" {
382+
wantLen = -1
383+
}
384+
if ri.req.ContentLength != wantLen {
385+
t.Errorf("#%d. handler got ContentLength = %v; want %v", i, ri.req.ContentLength, wantLen)
386+
}
374387
}
375388
}
376389

@@ -735,6 +748,7 @@ func TestTransportFullDuplex(t *testing.T) {
735748
if err != nil {
736749
log.Fatal(err)
737750
}
751+
req.ContentLength = -1
738752
res, err := c.Do(req)
739753
if err != nil {
740754
log.Fatal(err)

0 commit comments

Comments
 (0)