import (
"io"
+ "net/http/httptrace"
"net/textproto"
"sort"
"strings"
// Write writes a header in wire format.
func (h Header) Write(w io.Writer) error {
- return h.WriteSubset(w, nil)
+ return h.write(w, nil)
+}
+
+func (h Header) write(w io.Writer, trace *httptrace.ClientTrace) error {
+ return h.writeSubset(w, nil, trace)
}
func (h Header) clone() Header {
// WriteSubset writes a header in wire format.
// If exclude is not nil, keys where exclude[key] == true are not written.
func (h Header) WriteSubset(w io.Writer, exclude map[string]bool) error {
+ return h.writeSubset(w, exclude, nil)
+}
+
+func (h Header) writeSubset(w io.Writer, exclude map[string]bool, trace *httptrace.ClientTrace) error {
ws, ok := w.(writeStringer)
if !ok {
ws = stringWriter{w}
}
kvs, sorter := h.sortedKeyValues(exclude)
+ var formattedVals []string
for _, kv := range kvs {
for _, v := range kv.values {
v = headerNewlineToSpace.Replace(v)
return err
}
}
+ if trace != nil && trace.WroteHeaderField != nil {
+ formattedVals = append(formattedVals, v)
+ }
+ }
+ if trace != nil && trace.WroteHeaderField != nil {
+ trace.WroteHeaderField(kv.key, formattedVals)
+ formattedVals = nil
}
}
headerSorterPool.Put(sorter)
// failure.
TLSHandshakeDone func(tls.ConnectionState, error)
+ // WroteHeaderField is called after the Transport has written
+ // each request header.
+ WroteHeaderField func(key string, value []string)
+
// WroteHeaders is called after the Transport has written
- // the request headers.
+ // all request headers.
WroteHeaders func()
// Wait100Continue is called if the Request specified
if err != nil {
return err
}
+ if trace != nil && trace.WroteHeaderField != nil {
+ trace.WroteHeaderField("Host", []string{host})
+ }
// Use the defaultUserAgent unless the Header contains one, which
// may be blank to not send the header.
if err != nil {
return err
}
+ if trace != nil && trace.WroteHeaderField != nil {
+ trace.WroteHeaderField("User-Agent", []string{userAgent})
+ }
}
// Process Body,ContentLength,Close,Trailer
if err != nil {
return err
}
- err = tw.WriteHeader(w)
+ err = tw.writeHeader(w, trace)
if err != nil {
return err
}
- err = r.Header.WriteSubset(w, reqWriteExcludeHeader)
+ err = r.Header.writeSubset(w, reqWriteExcludeHeader, trace)
if err != nil {
return err
}
if extraHeaders != nil {
- err = extraHeaders.Write(w)
+ err = extraHeaders.write(w, trace)
if err != nil {
return err
}
}
// Write body and trailer
- err = tw.WriteBody(w)
+ err = tw.writeBody(w)
if err != nil {
if tw.bodyReadError == err {
err = requestBodyReadError{err}
if err != nil {
return err
}
- err = tw.WriteHeader(w)
+ err = tw.writeHeader(w, nil)
if err != nil {
return err
}
}
// Write body and trailer
- err = tw.WriteBody(w)
+ err = tw.writeBody(w)
if err != nil {
return err
}
res *response
// header is either nil or a deep clone of res.handlerHeader
- // at the time of res.WriteHeader, if res.WriteHeader is
+ // at the time of res.writeHeader, if res.writeHeader is
// called and extra buffering is being done to calculate
// Content-Type and/or Content-Length.
header Header
"fmt"
"io"
"io/ioutil"
+ "net/http/httptrace"
"net/http/internal"
"net/textproto"
"reflect"
return false
}
-func (t *transferWriter) WriteHeader(w io.Writer) error {
+func (t *transferWriter) writeHeader(w io.Writer, trace *httptrace.ClientTrace) error {
if t.Close && !hasToken(t.Header.get("Connection"), "close") {
if _, err := io.WriteString(w, "Connection: close\r\n"); err != nil {
return err
}
+ if trace != nil && trace.WroteHeaderField != nil {
+ trace.WroteHeaderField("Connection", []string{"close"})
+ }
}
// Write Content-Length and/or Transfer-Encoding whose values are a
if _, err := io.WriteString(w, strconv.FormatInt(t.ContentLength, 10)+"\r\n"); err != nil {
return err
}
+ if trace != nil && trace.WroteHeaderField != nil {
+ trace.WroteHeaderField("Content-Length", []string{strconv.FormatInt(t.ContentLength, 10)})
+ }
} else if chunked(t.TransferEncoding) {
if _, err := io.WriteString(w, "Transfer-Encoding: chunked\r\n"); err != nil {
return err
}
+ if trace != nil && trace.WroteHeaderField != nil {
+ trace.WroteHeaderField("Transfer-Encoding", []string{"chunked"})
+ }
}
// Write Trailer header
if _, err := io.WriteString(w, "Trailer: "+strings.Join(keys, ",")+"\r\n"); err != nil {
return err
}
+ if trace != nil && trace.WroteHeaderField != nil {
+ trace.WroteHeaderField("Trailer", keys)
+ }
}
}
return nil
}
-func (t *transferWriter) WriteBody(w io.Writer) error {
+func (t *transferWriter) writeBody(w io.Writer) error {
var err error
var ncopy int64
return []net.IPAddr{{IP: net.ParseIP(ip)}}, nil
})
- req, _ := NewRequest("POST", cst.scheme()+"://dns-is-faked.golang:"+port, strings.NewReader("some body"))
+ body := "some body"
+ req, _ := NewRequest("POST", cst.scheme()+"://dns-is-faked.golang:"+port, strings.NewReader(body))
+ req.Header["X-Foo-Multiple-Vals"] = []string{"bar", "baz"}
trace := &httptrace.ClientTrace{
GetConn: func(hostPort string) { logf("Getting conn for %v ...", hostPort) },
GotConn: func(ci httptrace.GotConnInfo) { logf("got conn: %+v", ci) },
}
logf("ConnectDone: connected to %s %s = %v", network, addr, err)
},
+ WroteHeaderField: func(key string, value []string) {
+ logf("WroteHeaderField: %s: %v", key, value)
+ },
+ WroteHeaders: func() {
+ logf("WroteHeaders")
+ },
Wait100Continue: func() { logf("Wait100Continue") },
Got100Continue: func() { logf("Got100Continue") },
WroteRequest: func(e httptrace.WroteRequestInfo) {
wantOnce("tls handshake done")
} else {
wantOnce("PutIdleConn = <nil>")
- }
+ wantOnce("WroteHeaderField: User-Agent: [Go-http-client/1.1]")
+ // TODO(meirf): issue 19761. Make these agnostic to h1/h2. (These are not h1 specific, but the
+ // WroteHeaderField hook is not yet implemented in h2.)
+ wantOnce(fmt.Sprintf("WroteHeaderField: Host: [dns-is-faked.golang:%s]", port))
+ wantOnce(fmt.Sprintf("WroteHeaderField: Content-Length: [%d]", len(body)))
+ wantOnce("WroteHeaderField: X-Foo-Multiple-Vals: [bar baz]")
+ wantOnce("WroteHeaderField: Accept-Encoding: [gzip]")
+ }
+ wantOnce("WroteHeaders")
wantOnce("Wait100Continue")
wantOnce("Got100Continue")
wantOnce("WroteRequest: {Err:<nil>}")