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 pw.Close()
151 dr.err = err
152 close(quitReadCh)
153 return nil, err
154 }
155 dump := buf.Bytes()
156
157
158
159
160
161
162 if dummyBody {
163 if i := bytes.Index(dump, []byte("\r\n\r\n")); i >= 0 {
164 dump = dump[:i+4]
165 }
166 }
167 return dump, nil
168 }
169
170
171
172 type delegateReader struct {
173 c chan io.Reader
174 err error
175 r io.Reader
176 }
177
178 func (r *delegateReader) Read(p []byte) (int, error) {
179 if r.r == nil {
180 var ok bool
181 if r.r, ok = <-r.c; !ok {
182 return 0, r.err
183 }
184 }
185 return r.r.Read(p)
186 }
187
188
189 func valueOrDefault(value, def string) string {
190 if value != "" {
191 return value
192 }
193 return def
194 }
195
196 var reqWriteExcludeHeaderDump = map[string]bool{
197 "Host": true,
198 "Transfer-Encoding": true,
199 "Trailer": true,
200 }
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218 func DumpRequest(req *http.Request, body bool) ([]byte, error) {
219 var err error
220 save := req.Body
221 if !body || req.Body == nil {
222 req.Body = nil
223 } else {
224 save, req.Body, err = drainBody(req.Body)
225 if err != nil {
226 return nil, err
227 }
228 }
229
230 var b bytes.Buffer
231
232
233
234
235
236
237
238 reqURI := req.RequestURI
239 if reqURI == "" {
240 reqURI = req.URL.RequestURI()
241 }
242
243 fmt.Fprintf(&b, "%s %s HTTP/%d.%d\r\n", valueOrDefault(req.Method, "GET"),
244 reqURI, req.ProtoMajor, req.ProtoMinor)
245
246 absRequestURI := strings.HasPrefix(req.RequestURI, "http://") || strings.HasPrefix(req.RequestURI, "https://")
247 if !absRequestURI {
248 host := req.Host
249 if host == "" && req.URL != nil {
250 host = req.URL.Host
251 }
252 if host != "" {
253 fmt.Fprintf(&b, "Host: %s\r\n", host)
254 }
255 }
256
257 chunked := len(req.TransferEncoding) > 0 && req.TransferEncoding[0] == "chunked"
258 if len(req.TransferEncoding) > 0 {
259 fmt.Fprintf(&b, "Transfer-Encoding: %s\r\n", strings.Join(req.TransferEncoding, ","))
260 }
261 if req.Close {
262 fmt.Fprintf(&b, "Connection: close\r\n")
263 }
264
265 err = req.Header.WriteSubset(&b, reqWriteExcludeHeaderDump)
266 if err != nil {
267 return nil, err
268 }
269
270 io.WriteString(&b, "\r\n")
271
272 if req.Body != nil {
273 var dest io.Writer = &b
274 if chunked {
275 dest = NewChunkedWriter(dest)
276 }
277 _, err = io.Copy(dest, req.Body)
278 if chunked {
279 dest.(io.Closer).Close()
280 io.WriteString(&b, "\r\n")
281 }
282 }
283
284 req.Body = save
285 if err != nil {
286 return nil, err
287 }
288 return b.Bytes(), nil
289 }
290
291
292
293 var errNoBody = errors.New("sentinel error value")
294
295
296
297
298
299 type failureToReadBody struct{}
300
301 func (failureToReadBody) Read([]byte) (int, error) { return 0, errNoBody }
302 func (failureToReadBody) Close() error { return nil }
303
304
305 var emptyBody = io.NopCloser(strings.NewReader(""))
306
307
308 func DumpResponse(resp *http.Response, body bool) ([]byte, error) {
309 var b bytes.Buffer
310 var err error
311 save := resp.Body
312 savecl := resp.ContentLength
313
314 if !body {
315
316
317 if resp.ContentLength == 0 {
318 resp.Body = emptyBody
319 } else {
320 resp.Body = failureToReadBody{}
321 }
322 } else if resp.Body == nil {
323 resp.Body = emptyBody
324 } else {
325 save, resp.Body, err = drainBody(resp.Body)
326 if err != nil {
327 return nil, err
328 }
329 }
330 err = resp.Write(&b)
331 if err == errNoBody {
332 err = nil
333 }
334 resp.Body = save
335 resp.ContentLength = savecl
336 if err != nil {
337 return nil, err
338 }
339 return b.Bytes(), nil
340 }
341
View as plain text