}.run(t)
}
+func TestH12_RequestContentLength_Known_NonZero(t *testing.T) {
+ h12requestContentLength(t, func() io.Reader { return strings.NewReader("FOUR") }, 4)
+}
+
+func TestH12_RequestContentLength_Known_Zero(t *testing.T) {
+ h12requestContentLength(t, func() io.Reader { return strings.NewReader("") }, 0)
+}
+
+func TestH12_RequestContentLength_Unknown(t *testing.T) {
+ h12requestContentLength(t, func() io.Reader { return struct{ io.Reader }{strings.NewReader("Stuff")} }, -1)
+}
+
+func h12requestContentLength(t *testing.T, bodyfn func() io.Reader, wantLen int64) {
+ h12Compare{
+ Handler: func(w ResponseWriter, r *Request) {
+ w.Header().Set("Got-Length", fmt.Sprint(r.ContentLength))
+ fmt.Fprintf(w, "Req.ContentLength=%v", r.ContentLength)
+ },
+ ReqFunc: func(c *Client, url string) (*Response, error) {
+ return c.Post(url, "text/plain", bodyfn())
+ },
+ CheckResponse: func(proto string, res *Response) {
+ if got, want := res.Header.Get("Got-Length"), fmt.Sprint(wantLen); got != want {
+ t.Errorf("Proto %q got length %q; want %q", proto, got, want)
+ }
+ },
+ }.run(t)
+}
+
// Tests that closing the Request.Cancel channel also while still
// reading the response body. Issue 13159.
func TestCancelRequestMidBody_h1(t *testing.T) { testCancelRequestMidBody(t, h1Mode) }
}
hasTrailers := trailers != ""
+ var body io.Reader = req.Body
+ contentLen := req.ContentLength
+ if req.Body != nil && contentLen == 0 {
+ // Test to see if it's actually zero or just unset.
+ var buf [1]byte
+ n, rerr := io.ReadFull(body, buf[:])
+ if rerr != nil && rerr != io.EOF {
+ contentLen = -1
+ body = http2errorReader{rerr}
+ } else if n == 1 {
+
+ contentLen = -1
+ body = io.MultiReader(bytes.NewReader(buf[:]), body)
+ } else {
+
+ body = nil
+ }
+ }
+
cc.mu.Lock()
if cc.closed || !cc.canTakeNewRequestLocked() {
cc.mu.Unlock()
cs := cc.newStream()
cs.req = req
- hasBody := req.Body != nil
+ hasBody := body != nil
if !cc.t.disableCompression() &&
req.Header.Get("Accept-Encoding") == "" &&
cs.requestedGzip = true
}
- hdrs := cc.encodeHeaders(req, cs.requestedGzip, trailers)
+ hdrs := cc.encodeHeaders(req, cs.requestedGzip, trailers, contentLen)
cc.wmu.Lock()
endStream := !hasBody && !hasTrailers
werr := cc.writeHeaders(cs.ID, endStream, hdrs)
if hasBody {
bodyCopyErrc = make(chan error, 1)
go func() {
- bodyCopyErrc <- cs.writeRequestBody(req.Body)
+ bodyCopyErrc <- cs.writeRequestBody(body, req.Body)
}()
}
// It doesn't escape to callers.
var http2errAbortReqBodyWrite = errors.New("http2: aborting request body write")
-func (cs *http2clientStream) writeRequestBody(body io.ReadCloser) (err error) {
+func (cs *http2clientStream) writeRequestBody(body io.Reader, bodyCloser io.Closer) (err error) {
cc := cs.cc
sentEnd := false
buf := cc.frameScratchBuffer()
defer func() {
- cerr := body.Close()
+ cerr := bodyCloser.Close()
if err == nil {
err = cerr
}
func (e *http2badStringError) Error() string { return fmt.Sprintf("%s %q", e.what, e.str) }
// requires cc.mu be held.
-func (cc *http2ClientConn) encodeHeaders(req *Request, addGzipHeader bool, trailers string) []byte {
+func (cc *http2ClientConn) encodeHeaders(req *Request, addGzipHeader bool, trailers string, contentLength int64) []byte {
cc.hbuf.Reset()
host := req.Host
var didUA bool
for k, vv := range req.Header {
lowKey := strings.ToLower(k)
- if lowKey == "host" {
+ if lowKey == "host" || lowKey == "content-length" {
continue
}
if lowKey == "user-agent" {
cc.writeHeader(lowKey, v)
}
}
+ if contentLength >= 0 {
+ cc.writeHeader("content-length", strconv.FormatInt(contentLength, 10))
+ }
if addGzipHeader {
cc.writeHeader("accept-encoding", "gzip")
}
return gz.body.Close()
}
+type http2errorReader struct{ err error }
+
+func (r http2errorReader) Read(p []byte) (int, error) { return 0, r.err }
+
// writeFramer is implemented by any type that is used to write frames.
type http2writeFramer interface {
writeFrame(http2writeContext) error