1
// Copyright 2009 The Go Authors. All rights reserved.
2
// Use of this source code is governed by a BSD-style
3
// license that can be found in the LICENSE file.
14
"launchpad.net/ubuntu-push/http13client"
20
// One of the copies, say from b to r2, could be avoided by using a more
21
// elaborate trick where the other copy is made during Request/Response.Write.
22
// This would complicate things too much, given that these functions are for
24
func drainBody(b io.ReadCloser) (r1, r2 io.ReadCloser, err error) {
26
if _, err = buf.ReadFrom(b); err != nil {
29
if err = b.Close(); err != nil {
32
return ioutil.NopCloser(&buf), ioutil.NopCloser(bytes.NewReader(buf.Bytes())), nil
35
// dumpConn is a net.Conn which writes to Writer and reads from Reader
36
type dumpConn struct {
41
func (c *dumpConn) Close() error { return nil }
42
func (c *dumpConn) LocalAddr() net.Addr { return nil }
43
func (c *dumpConn) RemoteAddr() net.Addr { return nil }
44
func (c *dumpConn) SetDeadline(t time.Time) error { return nil }
45
func (c *dumpConn) SetReadDeadline(t time.Time) error { return nil }
46
func (c *dumpConn) SetWriteDeadline(t time.Time) error { return nil }
50
func (b neverEnding) Read(p []byte) (n int, err error) {
57
// DumpRequestOut is like DumpRequest but includes
58
// headers that the standard http.Transport adds,
59
// such as User-Agent.
60
func DumpRequestOut(req *http.Request, body bool) ([]byte, error) {
63
if !body || req.Body == nil {
65
if req.ContentLength != 0 {
66
req.Body = ioutil.NopCloser(io.LimitReader(neverEnding('x'), req.ContentLength))
71
save, req.Body, err = drainBody(req.Body)
77
// Since we're using the actual Transport code to write the request,
78
// switch to http so the Transport doesn't try to do an SSL
79
// negotiation with our dumpConn and its bytes.Buffer & pipe.
80
// The wire format for https and http are the same, anyway.
82
if req.URL.Scheme == "https" {
83
reqSend = new(http.Request)
85
reqSend.URL = new(url.URL)
86
*reqSend.URL = *req.URL
87
reqSend.URL.Scheme = "http"
90
// Use the actual Transport code to record what we would send
91
// on the wire, but not using TCP. Use a Transport with a
92
// custom dialer that returns a fake net.Conn that waits
93
// for the full input (and recording it), and then responds
94
// with a dummy response.
95
var buf bytes.Buffer // records the output
97
dr := &delegateReader{c: make(chan io.Reader)}
98
// Wait for the request before replying with a dummy response:
100
http.ReadRequest(bufio.NewReader(pr))
101
dr.c <- strings.NewReader("HTTP/1.1 204 No Content\r\n\r\n")
104
t := &http.Transport{
105
Dial: func(net, addr string) (net.Conn, error) {
106
return &dumpConn{io.MultiWriter(&buf, pw), dr}, nil
110
_, err := t.RoundTrip(reqSend)
118
// If we used a dummy body above, remove it now.
119
// TODO: if the req.ContentLength is large, we allocate memory
120
// unnecessarily just to slice it off here. But this is just
121
// a debug function, so this is acceptable for now. We could
122
// discard the body earlier if this matters.
124
if i := bytes.Index(dump, []byte("\r\n\r\n")); i >= 0 {
131
// delegateReader is a reader that delegates to another reader,
132
// once it arrives on a channel.
133
type delegateReader struct {
135
r io.Reader // nil until received from c
138
func (r *delegateReader) Read(p []byte) (int, error) {
145
// Return value if nonempty, def otherwise.
146
func valueOrDefault(value, def string) string {
153
var reqWriteExcludeHeaderDump = map[string]bool{
154
"Host": true, // not in Header map anyway
155
"Content-Length": true,
156
"Transfer-Encoding": true,
160
// dumpAsReceived writes req to w in the form as it was received, or
161
// at least as accurately as possible from the information retained in
163
func dumpAsReceived(req *http.Request, w io.Writer) error {
167
// DumpRequest returns the as-received wire representation of req,
168
// optionally including the request body, for debugging.
169
// DumpRequest is semantically a no-op, but in order to
170
// dump the body, it reads the body data into memory and
171
// changes req.Body to refer to the in-memory copy.
172
// The documentation for http.Request.Write details which fields
174
func DumpRequest(req *http.Request, body bool) (dump []byte, err error) {
176
if !body || req.Body == nil {
179
save, req.Body, err = drainBody(req.Body)
187
fmt.Fprintf(&b, "%s %s HTTP/%d.%d\r\n", valueOrDefault(req.Method, "GET"),
188
req.URL.RequestURI(), req.ProtoMajor, req.ProtoMinor)
191
if host == "" && req.URL != nil {
195
fmt.Fprintf(&b, "Host: %s\r\n", host)
198
chunked := len(req.TransferEncoding) > 0 && req.TransferEncoding[0] == "chunked"
199
if len(req.TransferEncoding) > 0 {
200
fmt.Fprintf(&b, "Transfer-Encoding: %s\r\n", strings.Join(req.TransferEncoding, ","))
203
fmt.Fprintf(&b, "Connection: close\r\n")
206
err = req.Header.WriteSubset(&b, reqWriteExcludeHeaderDump)
211
io.WriteString(&b, "\r\n")
214
var dest io.Writer = &b
216
dest = NewChunkedWriter(dest)
218
_, err = io.Copy(dest, req.Body)
220
dest.(io.Closer).Close()
221
io.WriteString(&b, "\r\n")
233
// DumpResponse is like DumpRequest but dumps a response.
234
func DumpResponse(resp *http.Response, body bool) (dump []byte, err error) {
237
savecl := resp.ContentLength
238
if !body || resp.Body == nil {
240
resp.ContentLength = 0
242
save, resp.Body, err = drainBody(resp.Body)
249
resp.ContentLength = savecl