Source file src/pkg/net/http/httputil/dump.go
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.
4
5 package httputil
6
7 import (
8 "bufio"
9 "bytes"
10 "fmt"
11 "io"
12 "io/ioutil"
13 "net"
14 "net/http"
15 "net/url"
16 "strings"
17 "time"
18 )
19
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
23 // debugging only.
24 func drainBody(b io.ReadCloser) (r1, r2 io.ReadCloser, err error) {
25 var buf bytes.Buffer
26 if _, err = buf.ReadFrom(b); err != nil {
27 return nil, nil, err
28 }
29 if err = b.Close(); err != nil {
30 return nil, nil, err
31 }
32 return ioutil.NopCloser(&buf), ioutil.NopCloser(bytes.NewBuffer(buf.Bytes())), nil
33 }
34
35 // dumpConn is a net.Conn which writes to Writer and reads from Reader
36 type dumpConn struct {
37 io.Writer
38 io.Reader
39 }
40
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 }
47
48 // DumpRequestOut is like DumpRequest but includes
49 // headers that the standard http.Transport adds,
50 // such as User-Agent.
51 func DumpRequestOut(req *http.Request, body bool) ([]byte, error) {
52 save := req.Body
53 if !body || req.Body == nil {
54 req.Body = nil
55 } else {
56 var err error
57 save, req.Body, err = drainBody(req.Body)
58 if err != nil {
59 return nil, err
60 }
61 }
62
63 // Since we're using the actual Transport code to write the request,
64 // switch to http so the Transport doesn't try to do an SSL
65 // negotiation with our dumpConn and its bytes.Buffer & pipe.
66 // The wire format for https and http are the same, anyway.
67 reqSend := req
68 if req.URL.Scheme == "https" {
69 reqSend = new(http.Request)
70 *reqSend = *req
71 reqSend.URL = new(url.URL)
72 *reqSend.URL = *req.URL
73 reqSend.URL.Scheme = "http"
74 }
75
76 // Use the actual Transport code to record what we would send
77 // on the wire, but not using TCP. Use a Transport with a
78 // customer dialer that returns a fake net.Conn that waits
79 // for the full input (and recording it), and then responds
80 // with a dummy response.
81 var buf bytes.Buffer // records the output
82 pr, pw := io.Pipe()
83 dr := &delegateReader{c: make(chan io.Reader)}
84 // Wait for the request before replying with a dummy response:
85 go func() {
86 http.ReadRequest(bufio.NewReader(pr))
87 dr.c <- strings.NewReader("HTTP/1.1 204 No Content\r\n\r\n")
88 }()
89
90 t := &http.Transport{
91 Dial: func(net, addr string) (net.Conn, error) {
92 return &dumpConn{io.MultiWriter(pw, &buf), dr}, nil
93 },
94 }
95
96 _, err := t.RoundTrip(reqSend)
97
98 req.Body = save
99 if err != nil {
100 return nil, err
101 }
102 return buf.Bytes(), nil
103 }
104
105 // delegateReader is a reader that delegates to another reader,
106 // once it arrives on a channel.
107 type delegateReader struct {
108 c chan io.Reader
109 r io.Reader // nil until received from c
110 }
111
112 func (r *delegateReader) Read(p []byte) (int, error) {
113 if r.r == nil {
114 r.r = <-r.c
115 }
116 return r.r.Read(p)
117 }
118
119 // Return value if nonempty, def otherwise.
120 func valueOrDefault(value, def string) string {
121 if value != "" {
122 return value
123 }
124 return def
125 }
126
127 var reqWriteExcludeHeaderDump = map[string]bool{
128 "Host": true, // not in Header map anyway
129 "Content-Length": true,
130 "Transfer-Encoding": true,
131 "Trailer": true,
132 }
133
134 // dumpAsReceived writes req to w in the form as it was received, or
135 // at least as accurately as possible from the information retained in
136 // the request.
137 func dumpAsReceived(req *http.Request, w io.Writer) error {
138 return nil
139 }
140
141 // DumpRequest returns the as-received wire representation of req,
142 // optionally including the request body, for debugging.
143 // DumpRequest is semantically a no-op, but in order to
144 // dump the body, it reads the body data into memory and
145 // changes req.Body to refer to the in-memory copy.
146 // The documentation for http.Request.Write details which fields
147 // of req are used.
148 func DumpRequest(req *http.Request, body bool) (dump []byte, err error) {
149 save := req.Body
150 if !body || req.Body == nil {
151 req.Body = nil
152 } else {
153 save, req.Body, err = drainBody(req.Body)
154 if err != nil {
155 return
156 }
157 }
158
159 var b bytes.Buffer
160
161 fmt.Fprintf(&b, "%s %s HTTP/%d.%d\r\n", valueOrDefault(req.Method, "GET"),
162 req.URL.RequestURI(), req.ProtoMajor, req.ProtoMinor)
163
164 host := req.Host
165 if host == "" && req.URL != nil {
166 host = req.URL.Host
167 }
168 if host != "" {
169 fmt.Fprintf(&b, "Host: %s\r\n", host)
170 }
171
172 chunked := len(req.TransferEncoding) > 0 && req.TransferEncoding[0] == "chunked"
173 if len(req.TransferEncoding) > 0 {
174 fmt.Fprintf(&b, "Transfer-Encoding: %s\r\n", strings.Join(req.TransferEncoding, ","))
175 }
176 if req.Close {
177 fmt.Fprintf(&b, "Connection: close\r\n")
178 }
179
180 err = req.Header.WriteSubset(&b, reqWriteExcludeHeaderDump)
181 if err != nil {
182 return
183 }
184
185 io.WriteString(&b, "\r\n")
186
187 if req.Body != nil {
188 var dest io.Writer = &b
189 if chunked {
190 dest = NewChunkedWriter(dest)
191 }
192 _, err = io.Copy(dest, req.Body)
193 if chunked {
194 dest.(io.Closer).Close()
195 io.WriteString(&b, "\r\n")
196 }
197 }
198
199 req.Body = save
200 if err != nil {
201 return
202 }
203 dump = b.Bytes()
204 return
205 }
206
207 // DumpResponse is like DumpRequest but dumps a response.
208 func DumpResponse(resp *http.Response, body bool) (dump []byte, err error) {
209 var b bytes.Buffer
210 save := resp.Body
211 savecl := resp.ContentLength
212 if !body || resp.Body == nil {
213 resp.Body = nil
214 resp.ContentLength = 0
215 } else {
216 save, resp.Body, err = drainBody(resp.Body)
217 if err != nil {
218 return
219 }
220 }
221 err = resp.Write(&b)
222 resp.Body = save
223 resp.ContentLength = savecl
224 if err != nil {
225 return
226 }
227 dump = b.Bytes()
228 return
229 }