~chipaca/ubuntu-push/gsettings

« back to all changes in this revision

Viewing changes to http13client/httputil/dump.go

  • Committer: Samuele Pedroni (Canonical Services Ltd.)
  • Date: 2014-03-19 20:20:19 UTC
  • mto: This revision was merged to the branch mainline in revision 82.
  • Revision ID: samuele.pedroni@canonical.com-20140319202019-p0w8krshj1098f82
grab go 1.3 dev net/http and massage it so that the test run on 1.2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
        "launchpad.net/ubuntu-push/http13client"
 
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.NewReader(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
type neverEnding byte
 
49
 
 
50
func (b neverEnding) Read(p []byte) (n int, err error) {
 
51
        for i := range p {
 
52
                p[i] = byte(b)
 
53
        }
 
54
        return len(p), nil
 
55
}
 
56
 
 
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) {
 
61
        save := req.Body
 
62
        dummyBody := false
 
63
        if !body || req.Body == nil {
 
64
                req.Body = nil
 
65
                if req.ContentLength != 0 {
 
66
                        req.Body = ioutil.NopCloser(io.LimitReader(neverEnding('x'), req.ContentLength))
 
67
                        dummyBody = true
 
68
                }
 
69
        } else {
 
70
                var err error
 
71
                save, req.Body, err = drainBody(req.Body)
 
72
                if err != nil {
 
73
                        return nil, err
 
74
                }
 
75
        }
 
76
 
 
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.
 
81
        reqSend := req
 
82
        if req.URL.Scheme == "https" {
 
83
                reqSend = new(http.Request)
 
84
                *reqSend = *req
 
85
                reqSend.URL = new(url.URL)
 
86
                *reqSend.URL = *req.URL
 
87
                reqSend.URL.Scheme = "http"
 
88
        }
 
89
 
 
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
 
96
        pr, pw := io.Pipe()
 
97
        dr := &delegateReader{c: make(chan io.Reader)}
 
98
        // Wait for the request before replying with a dummy response:
 
99
        go func() {
 
100
                http.ReadRequest(bufio.NewReader(pr))
 
101
                dr.c <- strings.NewReader("HTTP/1.1 204 No Content\r\n\r\n")
 
102
        }()
 
103
 
 
104
        t := &http.Transport{
 
105
                Dial: func(net, addr string) (net.Conn, error) {
 
106
                        return &dumpConn{io.MultiWriter(&buf, pw), dr}, nil
 
107
                },
 
108
        }
 
109
 
 
110
        _, err := t.RoundTrip(reqSend)
 
111
 
 
112
        req.Body = save
 
113
        if err != nil {
 
114
                return nil, err
 
115
        }
 
116
        dump := buf.Bytes()
 
117
 
 
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.
 
123
        if dummyBody {
 
124
                if i := bytes.Index(dump, []byte("\r\n\r\n")); i >= 0 {
 
125
                        dump = dump[:i+4]
 
126
                }
 
127
        }
 
128
        return dump, nil
 
129
}
 
130
 
 
131
// delegateReader is a reader that delegates to another reader,
 
132
// once it arrives on a channel.
 
133
type delegateReader struct {
 
134
        c chan io.Reader
 
135
        r io.Reader // nil until received from c
 
136
}
 
137
 
 
138
func (r *delegateReader) Read(p []byte) (int, error) {
 
139
        if r.r == nil {
 
140
                r.r = <-r.c
 
141
        }
 
142
        return r.r.Read(p)
 
143
}
 
144
 
 
145
// Return value if nonempty, def otherwise.
 
146
func valueOrDefault(value, def string) string {
 
147
        if value != "" {
 
148
                return value
 
149
        }
 
150
        return def
 
151
}
 
152
 
 
153
var reqWriteExcludeHeaderDump = map[string]bool{
 
154
        "Host":              true, // not in Header map anyway
 
155
        "Content-Length":    true,
 
156
        "Transfer-Encoding": true,
 
157
        "Trailer":           true,
 
158
}
 
159
 
 
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
 
162
// the request.
 
163
func dumpAsReceived(req *http.Request, w io.Writer) error {
 
164
        return nil
 
165
}
 
166
 
 
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
 
173
// of req are used.
 
174
func DumpRequest(req *http.Request, body bool) (dump []byte, err error) {
 
175
        save := req.Body
 
176
        if !body || req.Body == nil {
 
177
                req.Body = nil
 
178
        } else {
 
179
                save, req.Body, err = drainBody(req.Body)
 
180
                if err != nil {
 
181
                        return
 
182
                }
 
183
        }
 
184
 
 
185
        var b bytes.Buffer
 
186
 
 
187
        fmt.Fprintf(&b, "%s %s HTTP/%d.%d\r\n", valueOrDefault(req.Method, "GET"),
 
188
                req.URL.RequestURI(), req.ProtoMajor, req.ProtoMinor)
 
189
 
 
190
        host := req.Host
 
191
        if host == "" && req.URL != nil {
 
192
                host = req.URL.Host
 
193
        }
 
194
        if host != "" {
 
195
                fmt.Fprintf(&b, "Host: %s\r\n", host)
 
196
        }
 
197
 
 
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, ","))
 
201
        }
 
202
        if req.Close {
 
203
                fmt.Fprintf(&b, "Connection: close\r\n")
 
204
        }
 
205
 
 
206
        err = req.Header.WriteSubset(&b, reqWriteExcludeHeaderDump)
 
207
        if err != nil {
 
208
                return
 
209
        }
 
210
 
 
211
        io.WriteString(&b, "\r\n")
 
212
 
 
213
        if req.Body != nil {
 
214
                var dest io.Writer = &b
 
215
                if chunked {
 
216
                        dest = NewChunkedWriter(dest)
 
217
                }
 
218
                _, err = io.Copy(dest, req.Body)
 
219
                if chunked {
 
220
                        dest.(io.Closer).Close()
 
221
                        io.WriteString(&b, "\r\n")
 
222
                }
 
223
        }
 
224
 
 
225
        req.Body = save
 
226
        if err != nil {
 
227
                return
 
228
        }
 
229
        dump = b.Bytes()
 
230
        return
 
231
}
 
232
 
 
233
// DumpResponse is like DumpRequest but dumps a response.
 
234
func DumpResponse(resp *http.Response, body bool) (dump []byte, err error) {
 
235
        var b bytes.Buffer
 
236
        save := resp.Body
 
237
        savecl := resp.ContentLength
 
238
        if !body || resp.Body == nil {
 
239
                resp.Body = nil
 
240
                resp.ContentLength = 0
 
241
        } else {
 
242
                save, resp.Body, err = drainBody(resp.Body)
 
243
                if err != nil {
 
244
                        return
 
245
                }
 
246
        }
 
247
        err = resp.Write(&b)
 
248
        resp.Body = save
 
249
        resp.ContentLength = savecl
 
250
        if err != nil {
 
251
                return
 
252
        }
 
253
        dump = b.Bytes()
 
254
        return
 
255
}