| | |
| | |
| | |
| |
|
| | package httputil |
| |
|
| | import ( |
| | "bufio" |
| | "bytes" |
| | "errors" |
| | "fmt" |
| | "io" |
| | "net" |
| | "net/http" |
| | "net/url" |
| | "strings" |
| | "time" |
| | ) |
| |
|
| | |
| | |
| | |
| | |
| | |
| | func drainBody(b io.ReadCloser) (r1, r2 io.ReadCloser, err error) { |
| | if b == nil || b == http.NoBody { |
| | |
| | return http.NoBody, http.NoBody, nil |
| | } |
| | var buf bytes.Buffer |
| | if _, err = buf.ReadFrom(b); err != nil { |
| | return nil, b, err |
| | } |
| | if err = b.Close(); err != nil { |
| | return nil, b, err |
| | } |
| | return io.NopCloser(&buf), io.NopCloser(bytes.NewReader(buf.Bytes())), nil |
| | } |
| |
|
| | |
| | type dumpConn struct { |
| | io.Writer |
| | io.Reader |
| | } |
| |
|
| | func (c *dumpConn) Close() error { return nil } |
| | func (c *dumpConn) LocalAddr() net.Addr { return nil } |
| | func (c *dumpConn) RemoteAddr() net.Addr { return nil } |
| | func (c *dumpConn) SetDeadline(t time.Time) error { return nil } |
| | func (c *dumpConn) SetReadDeadline(t time.Time) error { return nil } |
| | func (c *dumpConn) SetWriteDeadline(t time.Time) error { return nil } |
| |
|
| | type neverEnding byte |
| |
|
| | func (b neverEnding) Read(p []byte) (n int, err error) { |
| | for i := range p { |
| | p[i] = byte(b) |
| | } |
| | return len(p), nil |
| | } |
| |
|
| | |
| | |
| | func outgoingLength(req *http.Request) int64 { |
| | if req.Body == nil || req.Body == http.NoBody { |
| | return 0 |
| | } |
| | if req.ContentLength != 0 { |
| | return req.ContentLength |
| | } |
| | return -1 |
| | } |
| |
|
| | |
| | |
| | |
| | func DumpRequestOut(req *http.Request, body bool) ([]byte, error) { |
| | save := req.Body |
| | dummyBody := false |
| | if !body { |
| | contentLength := outgoingLength(req) |
| | if contentLength != 0 { |
| | req.Body = io.NopCloser(io.LimitReader(neverEnding('x'), contentLength)) |
| | dummyBody = true |
| | } |
| | } else { |
| | var err error |
| | save, req.Body, err = drainBody(req.Body) |
| | if err != nil { |
| | return nil, err |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | reqSend := req |
| | if req.URL.Scheme == "https" { |
| | reqSend = new(http.Request) |
| | *reqSend = *req |
| | reqSend.URL = new(url.URL) |
| | *reqSend.URL = *req.URL |
| | reqSend.URL.Scheme = "http" |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | var buf bytes.Buffer |
| | pr, pw := io.Pipe() |
| | defer pr.Close() |
| | defer pw.Close() |
| | dr := &delegateReader{c: make(chan io.Reader)} |
| |
|
| | t := &http.Transport{ |
| | Dial: func(net, addr string) (net.Conn, error) { |
| | return &dumpConn{io.MultiWriter(&buf, pw), dr}, nil |
| | }, |
| | } |
| | defer t.CloseIdleConnections() |
| |
|
| | |
| | |
| | |
| | quitReadCh := make(chan struct{}) |
| | |
| | go func() { |
| | req, err := http.ReadRequest(bufio.NewReader(pr)) |
| | if err == nil { |
| | |
| | |
| | io.Copy(io.Discard, req.Body) |
| | req.Body.Close() |
| | } |
| | select { |
| | case dr.c <- strings.NewReader("HTTP/1.1 204 No Content\r\nConnection: close\r\n\r\n"): |
| | case <-quitReadCh: |
| | |
| | close(dr.c) |
| | } |
| | }() |
| |
|
| | _, err := t.RoundTrip(reqSend) |
| |
|
| | req.Body = save |
| | if err != nil { |
| | dr.err = err |
| | close(quitReadCh) |
| | return nil, err |
| | } |
| | dump := buf.Bytes() |
| |
|
| | |
| | |
| | |
| | |
| | |
| | if dummyBody { |
| | if i := bytes.Index(dump, []byte("\r\n\r\n")); i >= 0 { |
| | dump = dump[:i+4] |
| | } |
| | } |
| | return dump, nil |
| | } |
| |
|
| | |
| | |
| | type delegateReader struct { |
| | c chan io.Reader |
| | err error |
| | r io.Reader |
| | } |
| |
|
| | func (r *delegateReader) Read(p []byte) (int, error) { |
| | if r.r == nil { |
| | var ok bool |
| | if r.r, ok = <-r.c; !ok { |
| | return 0, r.err |
| | } |
| | } |
| | return r.r.Read(p) |
| | } |
| |
|
| | |
| | func valueOrDefault(value, def string) string { |
| | if value != "" { |
| | return value |
| | } |
| | return def |
| | } |
| |
|
| | var reqWriteExcludeHeaderDump = map[string]bool{ |
| | "Host": true, |
| | "Transfer-Encoding": true, |
| | "Trailer": true, |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | func DumpRequest(req *http.Request, body bool) ([]byte, error) { |
| | var err error |
| | save := req.Body |
| | if !body || req.Body == nil { |
| | req.Body = nil |
| | } else { |
| | save, req.Body, err = drainBody(req.Body) |
| | if err != nil { |
| | return nil, err |
| | } |
| | } |
| |
|
| | var b bytes.Buffer |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | reqURI := req.RequestURI |
| | if reqURI == "" { |
| | reqURI = req.URL.RequestURI() |
| | } |
| |
|
| | fmt.Fprintf(&b, "%s %s HTTP/%d.%d\r\n", valueOrDefault(req.Method, "GET"), |
| | reqURI, req.ProtoMajor, req.ProtoMinor) |
| |
|
| | absRequestURI := strings.HasPrefix(req.RequestURI, "http://") || strings.HasPrefix(req.RequestURI, "https://") |
| | if !absRequestURI { |
| | host := req.Host |
| | if host == "" && req.URL != nil { |
| | host = req.URL.Host |
| | } |
| | if host != "" { |
| | fmt.Fprintf(&b, "Host: %s\r\n", host) |
| | } |
| | } |
| |
|
| | chunked := len(req.TransferEncoding) > 0 && req.TransferEncoding[0] == "chunked" |
| | if len(req.TransferEncoding) > 0 { |
| | fmt.Fprintf(&b, "Transfer-Encoding: %s\r\n", strings.Join(req.TransferEncoding, ",")) |
| | } |
| |
|
| | err = req.Header.WriteSubset(&b, reqWriteExcludeHeaderDump) |
| | if err != nil { |
| | return nil, err |
| | } |
| |
|
| | io.WriteString(&b, "\r\n") |
| |
|
| | if req.Body != nil { |
| | var dest io.Writer = &b |
| | if chunked { |
| | dest = NewChunkedWriter(dest) |
| | } |
| | _, err = io.Copy(dest, req.Body) |
| | if chunked { |
| | dest.(io.Closer).Close() |
| | io.WriteString(&b, "\r\n") |
| | } |
| | } |
| |
|
| | req.Body = save |
| | if err != nil { |
| | return nil, err |
| | } |
| | return b.Bytes(), nil |
| | } |
| |
|
| | |
| | |
| | var errNoBody = errors.New("sentinel error value") |
| |
|
| | |
| | |
| | |
| | |
| | type failureToReadBody struct{} |
| |
|
| | func (failureToReadBody) Read([]byte) (int, error) { return 0, errNoBody } |
| | func (failureToReadBody) Close() error { return nil } |
| |
|
| | |
| | var emptyBody = io.NopCloser(strings.NewReader("")) |
| |
|
| | |
| | func DumpResponse(resp *http.Response, body bool) ([]byte, error) { |
| | var b bytes.Buffer |
| | var err error |
| | save := resp.Body |
| | savecl := resp.ContentLength |
| |
|
| | if !body { |
| | |
| | |
| | if resp.ContentLength == 0 { |
| | resp.Body = emptyBody |
| | } else { |
| | resp.Body = failureToReadBody{} |
| | } |
| | } else if resp.Body == nil { |
| | resp.Body = emptyBody |
| | } else { |
| | save, resp.Body, err = drainBody(resp.Body) |
| | if err != nil { |
| | return nil, err |
| | } |
| | } |
| | err = resp.Write(&b) |
| | if err == errNoBody { |
| | err = nil |
| | } |
| | resp.Body = save |
| | resp.ContentLength = savecl |
| | if err != nil { |
| | return nil, err |
| | } |
| | return b.Bytes(), nil |
| | } |
| |
|