1
2
3
4
5 package httputil
6
7 import (
8 "bufio"
9 "bytes"
10 "errors"
11 "fmt"
12 "io"
13 "net"
14 "net/http"
15 "net/url"
16 "strings"
17 "time"
18 )
19
20
21
22
23
24
25 func drainBody(b io.ReadCloser) (r1, r2 io.ReadCloser, err error) {
26 if b == nil || b == http.NoBody {
27
28 return http.NoBody, http.NoBody, nil
29 }
30 var buf bytes.Buffer
31 if _, err = buf.ReadFrom(b); err != nil {
32 return nil, b, err
33 }
34 if err = b.Close(); err != nil {
35 return nil, b, err
36 }
37 return io.NopCloser(&buf), io.NopCloser(bytes.NewReader(buf.Bytes())), nil
38 }
39
40
41 type dumpConn struct {
42 io.Writer
43 io.Reader
44 }
45
46 func (c *dumpConn) Close() error { return nil }
47 func (c *dumpConn) LocalAddr() net.Addr { return nil }
48 func (c *dumpConn) RemoteAddr() net.Addr { return nil }
49 func (c *dumpConn) SetDeadline(t time.Time) error { return nil }
50 func (c *dumpConn) SetReadDeadline(t time.Time) error { return nil }
51 func (c *dumpConn) SetWriteDeadline(t time.Time) error { return nil }
52
53 type neverEnding byte
54
55 func (b neverEnding) Read(p []byte) (n int, err error) {
56 for i := range p {
57 p[i] = byte(b)
58 }
59 return len(p), nil
60 }
61
62
63
64 func outgoingLength(req *http.Request) int64 {
65 if req.Body == nil || req.Body == http.NoBody {
66 return 0
67 }
68 if req.ContentLength != 0 {
69 return req.ContentLength
70 }
71 return -1
72 }
73
74
75
76
77 func DumpRequestOut(req *http.Request, body bool) ([]byte, error) {
78 save := req.Body
79 dummyBody := false
80 if !body {
81 contentLength := outgoingLength(req)
82 if contentLength != 0 {
83 req.Body = io.NopCloser(io.LimitReader(neverEnding('x'), contentLength))
84 dummyBody = true
85 }
86 } else {
87 var err error
88 save, req.Body, err = drainBody(req.Body)
89 if err != nil {
90 return nil, err
91 }
92 }
93
94
95
96
97
98 reqSend := req
99 if req.URL.Scheme == "https" {
100 reqSend = new(http.Request)
101 *reqSend = *req
102 reqSend.URL = new(url.URL)
103 *reqSend.URL = *req.URL
104 reqSend.URL.Scheme = "http"
105 }
106
107
108
109
110
111
112 var buf bytes.Buffer
113 pr, pw := io.Pipe()
114 defer pr.Close()
115 defer pw.Close()
116 dr := &delegateReader{c: make(chan io.Reader)}
117
118 t := &http.Transport{
119 Dial: func(net, addr string) (net.Conn, error) {
120 return &dumpConn{io.MultiWriter(&buf, pw), dr}, nil
121 },
122 }
123 defer t.CloseIdleConnections()
124
125
126
127
128 quitReadCh := make(chan struct{})
129
130 go func() {
131 req, err := http.ReadRequest(bufio.NewReader(pr))
132 if err == nil {
133
134
135 io.Copy(io.Discard, req.Body)
136 req.Body.Close()
137 }
138 select {
139 case dr.c <- strings.NewReader("HTTP/1.1 204 No Content\r\nConnection: close\r\n\r\n"):
140 case <-quitReadCh:
141
142 close(dr.c)
143 }
144 }()
145
146 _, err := t.RoundTrip(reqSend)
147
148 req.Body = save
149 if err != nil {
150 dr.err = err
151 close(quitReadCh)
152 return nil, err
153 }
154 dump := buf.Bytes()
155
156
157
158
159
160
161 if dummyBody {
162 if i := bytes.Index(dump, []byte("\r\n\r\n")); i >= 0 {
163 dump = dump[:i+4]
164 }
165 }
166 return dump, nil
167 }
168
169
170
171 type delegateReader struct {
172 c chan io.Reader
173 err error
174 r io.Reader
175 }
176
177 func (r *delegateReader) Read(p []byte) (int, error) {
178 if r.r == nil {
179 var ok bool
180 if r.r, ok = <-r.c; !ok {
181 return 0, r.err
182 }
183 }
184 return r.r.Read(p)
185 }
186
187
188 func valueOrDefault(value, def string) string {
189 if value != "" {
190 return value
191 }
192 return def
193 }
194
195 var reqWriteExcludeHeaderDump = map[string]bool{
196 "Host": true,
197 "Transfer-Encoding": true,
198 "Trailer": true,
199 }
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217 func DumpRequest(req *http.Request, body bool) ([]byte, error) {
218 var err error
219 save := req.Body
220 if !body || req.Body == nil {
221 req.Body = nil
222 } else {
223 save, req.Body, err = drainBody(req.Body)
224 if err != nil {
225 return nil, err
226 }
227 }
228
229 var b bytes.Buffer
230
231
232
233
234
235
236
237 reqURI := req.RequestURI
238 if reqURI == "" {
239 reqURI = req.URL.RequestURI()
240 }
241
242 fmt.Fprintf(&b, "%s %s HTTP/%d.%d\r\n", valueOrDefault(req.Method, "GET"),
243 reqURI, req.ProtoMajor, req.ProtoMinor)
244
245 absRequestURI := strings.HasPrefix(req.RequestURI, "http://") || strings.HasPrefix(req.RequestURI, "https://")
246 if !absRequestURI {
247 host := req.Host
248 if host == "" && req.URL != nil {
249 host = req.URL.Host
250 }
251 if host != "" {
252 fmt.Fprintf(&b, "Host: %s\r\n", host)
253 }
254 }
255
256 chunked := len(req.TransferEncoding) > 0 && req.TransferEncoding[0] == "chunked"
257 if len(req.TransferEncoding) > 0 {
258 fmt.Fprintf(&b, "Transfer-Encoding: %s\r\n", strings.Join(req.TransferEncoding, ","))
259 }
260
261 err = req.Header.WriteSubset(&b, reqWriteExcludeHeaderDump)
262 if err != nil {
263 return nil, err
264 }
265
266 io.WriteString(&b, "\r\n")
267
268 if req.Body != nil {
269 var dest io.Writer = &b
270 if chunked {
271 dest = NewChunkedWriter(dest)
272 }
273 _, err = io.Copy(dest, req.Body)
274 if chunked {
275 dest.(io.Closer).Close()
276 io.WriteString(&b, "\r\n")
277 }
278 }
279
280 req.Body = save
281 if err != nil {
282 return nil, err
283 }
284 return b.Bytes(), nil
285 }
286
287
288
289 var errNoBody = errors.New("sentinel error value")
290
291
292
293
294
295 type failureToReadBody struct{}
296
297 func (failureToReadBody) Read([]byte) (int, error) { return 0, errNoBody }
298 func (failureToReadBody) Close() error { return nil }
299
300
301 var emptyBody = io.NopCloser(strings.NewReader(""))
302
303
304 func DumpResponse(resp *http.Response, body bool) ([]byte, error) {
305 var b bytes.Buffer
306 var err error
307 save := resp.Body
308 savecl := resp.ContentLength
309
310 if !body {
311
312
313 if resp.ContentLength == 0 {
314 resp.Body = emptyBody
315 } else {
316 resp.Body = failureToReadBody{}
317 }
318 } else if resp.Body == nil {
319 resp.Body = emptyBody
320 } else {
321 save, resp.Body, err = drainBody(resp.Body)
322 if err != nil {
323 return nil, err
324 }
325 }
326 err = resp.Write(&b)
327 if err == errNoBody {
328 err = nil
329 }
330 resp.Body = save
331 resp.ContentLength = savecl
332 if err != nil {
333 return nil, err
334 }
335 return b.Bytes(), nil
336 }
337
View as plain text