netramesh

Форк
0
313 строк · 8.7 Кб
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
	"errors"
11
	"fmt"
12
	"io"
13
	"io/ioutil"
14
	"net"
15
	"net/http"
16
	"net/url"
17
	"strings"
18
	"time"
19
)
20

21
// drainBody reads all of b to memory and then returns two equivalent
22
// ReadClosers yielding the same bytes.
23
//
24
// It returns an error if the initial slurp of all bytes fails. It does not attempt
25
// to make the returned ReadClosers have identical error-matching behavior.
26
func drainBody(b io.ReadCloser) (r1, r2 io.ReadCloser, err error) {
27
	if b == http.NoBody {
28
		// No copying needed. Preserve the magic sentinel meaning of NoBody.
29
		return http.NoBody, http.NoBody, nil
30
	}
31
	var buf bytes.Buffer
32
	if _, err = buf.ReadFrom(b); err != nil {
33
		return nil, b, err
34
	}
35
	if err = b.Close(); err != nil {
36
		return nil, b, err
37
	}
38
	return ioutil.NopCloser(&buf), ioutil.NopCloser(bytes.NewReader(buf.Bytes())), nil
39
}
40

41
// dumpConn is a net.Conn which writes to Writer and reads from Reader
42
type dumpConn struct {
43
	io.Writer
44
	io.Reader
45
}
46

47
func (c *dumpConn) Close() error                       { return nil }
48
func (c *dumpConn) LocalAddr() net.Addr                { return nil }
49
func (c *dumpConn) RemoteAddr() net.Addr               { return nil }
50
func (c *dumpConn) SetDeadline(t time.Time) error      { return nil }
51
func (c *dumpConn) SetReadDeadline(t time.Time) error  { return nil }
52
func (c *dumpConn) SetWriteDeadline(t time.Time) error { return nil }
53

54
type neverEnding byte
55

56
func (b neverEnding) Read(p []byte) (n int, err error) {
57
	for i := range p {
58
		p[i] = byte(b)
59
	}
60
	return len(p), nil
61
}
62

63
// DumpRequestOut is like DumpRequest but for outgoing client requests. It
64
// includes any headers that the standard http.Transport adds, such as
65
// User-Agent.
66
func DumpRequestOut(req *http.Request, body bool) ([]byte, error) {
67
	save := req.Body
68
	dummyBody := false
69
	if !body || req.Body == nil {
70
		req.Body = nil
71
		if req.ContentLength != 0 {
72
			req.Body = ioutil.NopCloser(io.LimitReader(neverEnding('x'), req.ContentLength))
73
			dummyBody = true
74
		}
75
	} else {
76
		var err error
77
		save, req.Body, err = drainBody(req.Body)
78
		if err != nil {
79
			return nil, err
80
		}
81
	}
82

83
	// Since we're using the actual Transport code to write the request,
84
	// switch to http so the Transport doesn't try to do an SSL
85
	// negotiation with our dumpConn and its bytes.Buffer & pipe.
86
	// The wire format for https and http are the same, anyway.
87
	reqSend := req
88
	if req.URL.Scheme == "https" {
89
		reqSend = new(http.Request)
90
		*reqSend = *req
91
		reqSend.URL = new(url.URL)
92
		*reqSend.URL = *req.URL
93
		reqSend.URL.Scheme = "http"
94
	}
95

96
	// Use the actual Transport code to record what we would send
97
	// on the wire, but not using TCP.  Use a Transport with a
98
	// custom dialer that returns a fake net.Conn that waits
99
	// for the full input (and recording it), and then responds
100
	// with a dummy response.
101
	var buf bytes.Buffer // records the output
102
	pr, pw := io.Pipe()
103
	defer pr.Close()
104
	defer pw.Close()
105
	dr := &delegateReader{c: make(chan io.Reader)}
106

107
	t := &http.Transport{
108
		Dial: func(net, addr string) (net.Conn, error) {
109
			return &dumpConn{io.MultiWriter(&buf, pw), dr}, nil
110
		},
111
	}
112
	defer t.CloseIdleConnections()
113

114
	// Wait for the request before replying with a dummy response:
115
	go func() {
116
		req, err := http.ReadRequest(bufio.NewReader(pr))
117
		if err == nil {
118
			// Ensure all the body is read; otherwise
119
			// we'll get a partial dump.
120
			io.Copy(ioutil.Discard, req.Body)
121
			req.Body.Close()
122
		}
123
		dr.c <- strings.NewReader("HTTP/1.1 204 No Content\r\nConnection: close\r\n\r\n")
124
	}()
125

126
	_, err := t.RoundTrip(reqSend)
127

128
	req.Body = save
129
	if err != nil {
130
		return nil, err
131
	}
132
	dump := buf.Bytes()
133

134
	// If we used a dummy body above, remove it now.
135
	// TODO: if the req.ContentLength is large, we allocate memory
136
	// unnecessarily just to slice it off here. But this is just
137
	// a debug function, so this is acceptable for now. We could
138
	// discard the body earlier if this matters.
139
	if dummyBody {
140
		if i := bytes.Index(dump, []byte("\r\n\r\n")); i >= 0 {
141
			dump = dump[:i+4]
142
		}
143
	}
144
	return dump, nil
145
}
146

147
// delegateReader is a reader that delegates to another reader,
148
// once it arrives on a channel.
149
type delegateReader struct {
150
	c chan io.Reader
151
	r io.Reader // nil until received from c
152
}
153

154
func (r *delegateReader) Read(p []byte) (int, error) {
155
	if r.r == nil {
156
		r.r = <-r.c
157
	}
158
	return r.r.Read(p)
159
}
160

161
// Return value if nonempty, def otherwise.
162
func valueOrDefault(value, def string) string {
163
	if value != "" {
164
		return value
165
	}
166
	return def
167
}
168

169
var reqWriteExcludeHeaderDump = map[string]bool{
170
	"Host":              true, // not in Header map anyway
171
	"Transfer-Encoding": true,
172
	"Trailer":           true,
173
}
174

175
// DumpRequest returns the given request in its HTTP/1.x wire
176
// representation. It should only be used by servers to debug client
177
// requests. The returned representation is an approximation only;
178
// some details of the initial request are lost while parsing it into
179
// an http.Request. In particular, the order and case of header field
180
// names are lost. The order of values in multi-valued headers is kept
181
// intact. HTTP/2 requests are dumped in HTTP/1.x form, not in their
182
// original binary representations.
183
//
184
// If body is true, DumpRequest also returns the body. To do so, it
185
// consumes req.Body and then replaces it with a new io.ReadCloser
186
// that yields the same bytes. If DumpRequest returns an error,
187
// the state of req is undefined.
188
//
189
// The documentation for http.Request.Write details which fields
190
// of req are included in the dump.
191
func DumpRequest(req *http.Request, body bool) ([]byte, error) {
192
	var err error
193
	save := req.Body
194
	if !body || req.Body == nil {
195
		req.Body = nil
196
	} else {
197
		save, req.Body, err = drainBody(req.Body)
198
		if err != nil {
199
			return nil, err
200
		}
201
	}
202

203
	var b bytes.Buffer
204

205
	// By default, print out the unmodified req.RequestURI, which
206
	// is always set for incoming server requests. But because we
207
	// previously used req.URL.RequestURI and the docs weren't
208
	// always so clear about when to use DumpRequest vs
209
	// DumpRequestOut, fall back to the old way if the caller
210
	// provides a non-server Request.
211
	reqURI := req.RequestURI
212
	if reqURI == "" {
213
		reqURI = req.URL.RequestURI()
214
	}
215

216
	fmt.Fprintf(&b, "%s %s HTTP/%d.%d\r\n", valueOrDefault(req.Method, "GET"),
217
		reqURI, req.ProtoMajor, req.ProtoMinor)
218

219
	absRequestURI := strings.HasPrefix(req.RequestURI, "http://") || strings.HasPrefix(req.RequestURI, "https://")
220
	if !absRequestURI {
221
		host := req.Host
222
		if host == "" && req.URL != nil {
223
			host = req.URL.Host
224
		}
225
		if host != "" {
226
			fmt.Fprintf(&b, "Host: %s\r\n", host)
227
		}
228
	}
229

230
	chunked := len(req.TransferEncoding) > 0 && req.TransferEncoding[0] == "chunked"
231
	if len(req.TransferEncoding) > 0 {
232
		fmt.Fprintf(&b, "Transfer-Encoding: %s\r\n", strings.Join(req.TransferEncoding, ","))
233
	}
234
	if req.Close {
235
		fmt.Fprintf(&b, "Connection: close\r\n")
236
	}
237

238
	err = req.Header.WriteSubset(&b, reqWriteExcludeHeaderDump)
239
	if err != nil {
240
		return nil, err
241
	}
242

243
	io.WriteString(&b, "\r\n")
244

245
	if req.Body != nil {
246
		var dest io.Writer = &b
247
		if chunked {
248
			dest = NewChunkedWriter(dest)
249
		}
250
		_, err = io.Copy(dest, req.Body)
251
		if chunked {
252
			dest.(io.Closer).Close()
253
			io.WriteString(&b, "\r\n")
254
		}
255
	}
256

257
	req.Body = save
258
	if err != nil {
259
		return nil, err
260
	}
261
	return b.Bytes(), nil
262
}
263

264
// errNoBody is a sentinel error value used by failureToReadBody so we
265
// can detect that the lack of body was intentional.
266
var errNoBody = errors.New("sentinel error value")
267

268
// failureToReadBody is a io.ReadCloser that just returns errNoBody on
269
// Read. It's swapped in when we don't actually want to consume
270
// the body, but need a non-nil one, and want to distinguish the
271
// error from reading the dummy body.
272
type failureToReadBody struct{}
273

274
func (failureToReadBody) Read([]byte) (int, error) { return 0, errNoBody }
275
func (failureToReadBody) Close() error             { return nil }
276

277
// emptyBody is an instance of empty reader.
278
var emptyBody = ioutil.NopCloser(strings.NewReader(""))
279

280
// DumpResponse is like DumpRequest but dumps a response.
281
func DumpResponse(resp *http.Response, body bool) ([]byte, error) {
282
	var b bytes.Buffer
283
	var err error
284
	save := resp.Body
285
	savecl := resp.ContentLength
286

287
	if !body {
288
		// For content length of zero. Make sure the body is an empty
289
		// reader, instead of returning error through failureToReadBody{}.
290
		if resp.ContentLength == 0 {
291
			resp.Body = emptyBody
292
		} else {
293
			resp.Body = failureToReadBody{}
294
		}
295
	} else if resp.Body == nil {
296
		resp.Body = emptyBody
297
	} else {
298
		save, resp.Body, err = drainBody(resp.Body)
299
		if err != nil {
300
			return nil, err
301
		}
302
	}
303
	err = resp.Write(&b)
304
	if err == errNoBody {
305
		err = nil
306
	}
307
	resp.Body = save
308
	resp.ContentLength = savecl
309
	if err != nil {
310
		return nil, err
311
	}
312
	return b.Bytes(), nil
313
}
314

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.