Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(263)

Delta Between Two Patch Sets: src/pkg/http/transport.go

Issue 5284041: code review 5284041: http: RoundTrippers shouldn't mutate Request (Closed)
Left Patch Set: Created 13 years, 5 months ago
Right Patch Set: diff -r b9a8bd8ae691 https://go.googlecode.com/hg/ Created 13 years, 5 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
Right: Side by side diff | Download
« no previous file with change/comment | « src/pkg/http/request.go ('k') | src/pkg/http/transport_test.go » ('j') | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
(no file at all)
1 // Copyright 2011 The Go Authors. All rights reserved. 1 // Copyright 2011 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style 2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file. 3 // license that can be found in the LICENSE file.
4 4
5 package http 5 package http
6 6
7 import ( 7 import (
8 "bufio" 8 "bufio"
9 "compress/gzip" 9 "compress/gzip"
10 "crypto/tls" 10 "crypto/tls"
(...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after
93 } 93 }
94 94
95 // ProxyURL returns a proxy function (for use in a Transport) 95 // ProxyURL returns a proxy function (for use in a Transport)
96 // that always returns the same URL. 96 // that always returns the same URL.
97 func ProxyURL(fixedURL *url.URL) func(*Request) (*url.URL, os.Error) { 97 func ProxyURL(fixedURL *url.URL) func(*Request) (*url.URL, os.Error) {
98 return func(*Request) (*url.URL, os.Error) { 98 return func(*Request) (*url.URL, os.Error) {
99 return fixedURL, nil 99 return fixedURL, nil
100 } 100 }
101 } 101 }
102 102
103 // transportRequest is a wrapper around a *Request that adds
104 // optional extra headers to write.
105 type transportRequest struct {
106 *Request // original request, not to be mutated
107 extra Header // extra headers to write, or nil
108 }
109
110 func (tr *transportRequest) extraHeaders() Header {
111 if tr.extra == nil {
112 tr.extra = make(Header)
113 }
114 return tr.extra
115 }
116
103 // RoundTrip implements the RoundTripper interface. 117 // RoundTrip implements the RoundTripper interface.
104 func (t *Transport) RoundTrip(req *Request) (resp *Response, err os.Error) { 118 func (t *Transport) RoundTrip(req *Request) (resp *Response, err os.Error) {
105 if req.URL == nil { 119 if req.URL == nil {
106 return nil, os.NewError("http: nil Request.URL") 120 return nil, os.NewError("http: nil Request.URL")
121 }
122 if req.Header == nil {
123 return nil, os.NewError("http: nil Request.Header")
107 } 124 }
108 if req.URL.Scheme != "http" && req.URL.Scheme != "https" { 125 if req.URL.Scheme != "http" && req.URL.Scheme != "https" {
109 t.lk.Lock() 126 t.lk.Lock()
110 var rt RoundTripper 127 var rt RoundTripper
111 if t.altProto != nil { 128 if t.altProto != nil {
112 rt = t.altProto[req.URL.Scheme] 129 rt = t.altProto[req.URL.Scheme]
113 } 130 }
114 t.lk.Unlock() 131 t.lk.Unlock()
115 if rt == nil { 132 if rt == nil {
116 return nil, &badStringError{"unsupported protocol scheme ", req.URL.Scheme} 133 return nil, &badStringError{"unsupported protocol scheme ", req.URL.Scheme}
117 } 134 }
118 return rt.RoundTrip(req) 135 return rt.RoundTrip(req)
119 } 136 }
120 137 » treq := &transportRequest{Request: req}
121 » cm, err := t.connectMethodForRequest(req) 138 » cm, err := t.connectMethodForRequest(treq)
122 if err != nil { 139 if err != nil {
123 return nil, err 140 return nil, err
124 } 141 }
125 142
126 // Get the cached or newly-created connection to either the 143 // Get the cached or newly-created connection to either the
127 // host (for http or https), the http proxy, or the http proxy 144 // host (for http or https), the http proxy, or the http proxy
128 // pre-CONNECTed to https server. In any case, we'll be ready 145 // pre-CONNECTed to https server. In any case, we'll be ready
129 // to send it requests. 146 // to send it requests.
130 pconn, err := t.getConn(cm) 147 pconn, err := t.getConn(cm)
131 if err != nil { 148 if err != nil {
132 return nil, err 149 return nil, err
133 } 150 }
134 151
135 » return pconn.roundTrip(req) 152 » return pconn.roundTrip(treq)
136 } 153 }
137 154
138 // RegisterProtocol registers a new protocol with scheme. 155 // RegisterProtocol registers a new protocol with scheme.
139 // The Transport will pass requests using the given scheme to rt. 156 // The Transport will pass requests using the given scheme to rt.
140 // It is rt's responsibility to simulate HTTP request semantics. 157 // It is rt's responsibility to simulate HTTP request semantics.
141 // 158 //
142 // RegisterProtocol can be used by other packages to provide 159 // RegisterProtocol can be used by other packages to provide
143 // implementations of protocol schemes like "ftp" or "file". 160 // implementations of protocol schemes like "ftp" or "file".
144 func (t *Transport) RegisterProtocol(scheme string, rt RoundTripper) { 161 func (t *Transport) RegisterProtocol(scheme string, rt RoundTripper) {
145 if scheme == "http" || scheme == "https" { 162 if scheme == "http" || scheme == "https" {
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
178 // Private implementation past this point. 195 // Private implementation past this point.
179 // 196 //
180 197
181 func getenvEitherCase(k string) string { 198 func getenvEitherCase(k string) string {
182 if v := os.Getenv(strings.ToUpper(k)); v != "" { 199 if v := os.Getenv(strings.ToUpper(k)); v != "" {
183 return v 200 return v
184 } 201 }
185 return os.Getenv(strings.ToLower(k)) 202 return os.Getenv(strings.ToLower(k))
186 } 203 }
187 204
188 func (t *Transport) connectMethodForRequest(req *Request) (*connectMethod, os.Er ror) { 205 func (t *Transport) connectMethodForRequest(treq *transportRequest) (*connectMet hod, os.Error) {
189 cm := &connectMethod{ 206 cm := &connectMethod{
190 » » targetScheme: req.URL.Scheme, 207 » » targetScheme: treq.URL.Scheme,
191 » » targetAddr: canonicalAddr(req.URL), 208 » » targetAddr: canonicalAddr(treq.URL),
192 } 209 }
193 if t.Proxy != nil { 210 if t.Proxy != nil {
194 var err os.Error 211 var err os.Error
195 » » cm.proxyURL, err = t.Proxy(req) 212 » » cm.proxyURL, err = t.Proxy(treq.Request)
196 if err != nil { 213 if err != nil {
197 return nil, err 214 return nil, err
198 } 215 }
199 } 216 }
200 return cm, nil 217 return cm, nil
201 } 218 }
202 219
203 // proxyAuth returns the Proxy-Authorization header to set 220 // proxyAuth returns the Proxy-Authorization header to set
204 // on requests, if applicable. 221 // on requests, if applicable.
205 func (cm *connectMethod) proxyAuth() string { 222 func (cm *connectMethod) proxyAuth() string {
(...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after
288 } 305 }
289 306
290 pa := cm.proxyAuth() 307 pa := cm.proxyAuth()
291 308
292 pconn := &persistConn{ 309 pconn := &persistConn{
293 t: t, 310 t: t,
294 cacheKey: cm.String(), 311 cacheKey: cm.String(),
295 conn: conn, 312 conn: conn,
296 reqch: make(chan requestAndChan, 50), 313 reqch: make(chan requestAndChan, 50),
297 } 314 }
298 newClientConnFunc := NewClientConn
299 315
300 switch { 316 switch {
301 case cm.proxyURL == nil: 317 case cm.proxyURL == nil:
302 // Do nothing. 318 // Do nothing.
303 case cm.targetScheme == "http": 319 case cm.targetScheme == "http":
304 » » newClientConnFunc = NewProxyClientConn 320 » » pconn.isProxy = true
305 if pa != "" { 321 if pa != "" {
306 » » » pconn.mutateRequestFunc = func(req *Request) { 322 » » » pconn.mutateHeaderFunc = func(h Header) {
307 » » » » if req.Header == nil { 323 » » » » h.Set("Proxy-Authorization", pa)
308 » » » » » req.Header = make(Header)
309 » » » » }
310 » » » » req.Header.Set("Proxy-Authorization", pa)
311 } 324 }
312 } 325 }
313 case cm.targetScheme == "https": 326 case cm.targetScheme == "https":
314 connectReq := &Request{ 327 connectReq := &Request{
315 Method: "CONNECT", 328 Method: "CONNECT",
316 URL: &url.URL{RawPath: cm.targetAddr}, 329 URL: &url.URL{RawPath: cm.targetAddr},
317 Host: cm.targetAddr, 330 Host: cm.targetAddr,
318 Header: make(Header), 331 Header: make(Header),
319 } 332 }
320 if pa != "" { 333 if pa != "" {
(...skipping 23 matching lines...) Expand all
344 if err = conn.(*tls.Conn).Handshake(); err != nil { 357 if err = conn.(*tls.Conn).Handshake(); err != nil {
345 return nil, err 358 return nil, err
346 } 359 }
347 if err = conn.(*tls.Conn).VerifyHostname(cm.tlsHost()); err != n il { 360 if err = conn.(*tls.Conn).VerifyHostname(cm.tlsHost()); err != n il {
348 return nil, err 361 return nil, err
349 } 362 }
350 pconn.conn = conn 363 pconn.conn = conn
351 } 364 }
352 365
353 pconn.br = bufio.NewReader(pconn.conn) 366 pconn.br = bufio.NewReader(pconn.conn)
354 » pconn.cc = newClientConnFunc(conn, pconn.br) 367 » pconn.cc = NewClientConn(conn, pconn.br)
355 go pconn.readLoop() 368 go pconn.readLoop()
356 return pconn, nil 369 return pconn, nil
357 } 370 }
358 371
359 // useProxy returns true if requests to addr should use a proxy, 372 // useProxy returns true if requests to addr should use a proxy,
360 // according to the NO_PROXY or no_proxy environment variable. 373 // according to the NO_PROXY or no_proxy environment variable.
361 // addr is always a canonicalAddr with a host and port. 374 // addr is always a canonicalAddr with a host and port.
362 func useProxy(addr string) bool { 375 func useProxy(addr string) bool {
363 if len(addr) == 0 { 376 if len(addr) == 0 {
364 return true 377 return true
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after
440 // tlsHost returns the host name to match against the peer's 453 // tlsHost returns the host name to match against the peer's
441 // TLS certificate. 454 // TLS certificate.
442 func (cm *connectMethod) tlsHost() string { 455 func (cm *connectMethod) tlsHost() string {
443 h := cm.targetAddr 456 h := cm.targetAddr
444 if hasPort(h) { 457 if hasPort(h) {
445 h = h[:strings.LastIndex(h, ":")] 458 h = h[:strings.LastIndex(h, ":")]
446 } 459 }
447 return h 460 return h
448 } 461 }
449 462
450 type readResult struct {
451 res *Response // either res or err will be set
452 err os.Error
453 }
454
455 type writeRequest struct {
456 // Set by client (in pc.roundTrip)
457 req *Request
458 resch chan *readResult
459
460 // Set by writeLoop if an error writing headers.
461 writeErr os.Error
462 }
463
464 // persistConn wraps a connection, usually a persistent one 463 // persistConn wraps a connection, usually a persistent one
465 // (but may be used for non-keep-alive requests as well) 464 // (but may be used for non-keep-alive requests as well)
466 type persistConn struct { 465 type persistConn struct {
467 » t *Transport 466 » t *Transport
468 » cacheKey string // its connectMethod.String() 467 » cacheKey string // its connectMethod.String()
469 » conn net.Conn 468 » conn net.Conn
470 » cc *ClientConn 469 » cc *ClientConn
471 » br *bufio.Reader 470 » br *bufio.Reader
472 » reqch chan requestAndChan // written by roundTrip(); read by readLoop() 471 » reqch chan requestAndChan // written by roundTrip(); read by readLoop ()
473 » mutateRequestFunc func(*Request) // nil or func to modify each outb ound request 472 » isProxy bool
473
474 » // mutateHeaderFunc is an optional func to modify extra
475 » // headers on each outbound request before it's written. (the
476 » // original Request given to RoundTrip is not modified)
477 » mutateHeaderFunc func(Header)
474 478
475 lk sync.Mutex // guards numExpectedResponses and broke n 479 lk sync.Mutex // guards numExpectedResponses and broke n
476 numExpectedResponses int 480 numExpectedResponses int
477 broken bool // an error has happened on this connection; m arked broken so it's not reused. 481 broken bool // an error has happened on this connection; m arked broken so it's not reused.
478 } 482 }
479 483
480 func (pc *persistConn) isBroken() bool { 484 func (pc *persistConn) isBroken() bool {
481 pc.lk.Lock() 485 pc.lk.Lock()
482 defer pc.lk.Unlock() 486 defer pc.lk.Unlock()
483 return pc.broken 487 return pc.broken
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
518 string(pb), err) 522 string(pb), err)
519 pc.close() 523 pc.close()
520 return 524 return
521 } 525 }
522 526
523 rc := <-pc.reqch 527 rc := <-pc.reqch
524 resp, err := pc.cc.readUsing(rc.req, func(buf *bufio.Reader, for Req *Request) (*Response, os.Error) { 528 resp, err := pc.cc.readUsing(rc.req, func(buf *bufio.Reader, for Req *Request) (*Response, os.Error) {
525 resp, err := ReadResponse(buf, forReq) 529 resp, err := ReadResponse(buf, forReq)
526 if err != nil || resp.ContentLength == 0 { 530 if err != nil || resp.ContentLength == 0 {
527 return resp, err 531 return resp, err
528 }
529 if rc.addedGzip {
530 forReq.Header.Del("Accept-Encoding")
531 } 532 }
532 if rc.addedGzip && resp.Header.Get("Content-Encoding") = = "gzip" { 533 if rc.addedGzip && resp.Header.Get("Content-Encoding") = = "gzip" {
533 resp.Header.Del("Content-Encoding") 534 resp.Header.Del("Content-Encoding")
534 resp.Header.Del("Content-Length") 535 resp.Header.Del("Content-Length")
535 resp.ContentLength = -1 536 resp.ContentLength = -1
536 gzReader, err := gzip.NewReader(resp.Body) 537 gzReader, err := gzip.NewReader(resp.Body)
537 if err != nil { 538 if err != nil {
538 pc.close() 539 pc.close()
539 return nil, err 540 return nil, err
540 } 541 }
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after
597 type requestAndChan struct { 598 type requestAndChan struct {
598 req *Request 599 req *Request
599 ch chan responseAndError 600 ch chan responseAndError
600 601
601 // did the Transport (as opposed to the client code) add an 602 // did the Transport (as opposed to the client code) add an
602 // Accept-Encoding gzip header? only if it we set it do 603 // Accept-Encoding gzip header? only if it we set it do
603 // we transparently decode the gzip. 604 // we transparently decode the gzip.
604 addedGzip bool 605 addedGzip bool
605 } 606 }
606 607
607 func (pc *persistConn) roundTrip(req *Request) (resp *Response, err os.Error) { 608 func (pc *persistConn) roundTrip(req *transportRequest) (resp *Response, err os. Error) {
608 » if pc.mutateRequestFunc != nil { 609 » if pc.mutateHeaderFunc != nil {
609 » » pc.mutateRequestFunc(req) 610 » » pc.mutateHeaderFunc(req.extraHeaders())
610 } 611 }
611 612
612 // Ask for a compressed version if the caller didn't set their 613 // Ask for a compressed version if the caller didn't set their
613 // own value for Accept-Encoding. We only attempted to 614 // own value for Accept-Encoding. We only attempted to
614 // uncompress the gzip stream if we were the layer that 615 // uncompress the gzip stream if we were the layer that
615 // requested it. 616 // requested it.
616 requestedGzip := false 617 requestedGzip := false
617 if !pc.t.DisableCompression && req.Header.Get("Accept-Encoding") == "" { 618 if !pc.t.DisableCompression && req.Header.Get("Accept-Encoding") == "" {
618 // Request gzip only, not deflate. Deflate is ambiguous and· 619 // Request gzip only, not deflate. Deflate is ambiguous and·
619 » » // as universally supported anyway. 620 » » // not as universally supported anyway.
620 // See: http://www.gzip.org/zlib/zlib_faq.html#faq38 621 // See: http://www.gzip.org/zlib/zlib_faq.html#faq38
621 requestedGzip = true 622 requestedGzip = true
622 » » req.Header.Set("Accept-Encoding", "gzip") 623 » » req.extraHeaders().Set("Accept-Encoding", "gzip")
623 } 624 }
624 625
625 pc.lk.Lock() 626 pc.lk.Lock()
626 pc.numExpectedResponses++ 627 pc.numExpectedResponses++
627 pc.lk.Unlock() 628 pc.lk.Unlock()
628 629
629 » err = pc.cc.Write(req) 630 » pc.cc.writeReq = func(r *Request, w io.Writer) os.Error {
631 » » return r.write(w, pc.isProxy, req.extra)
632 » }
633
634 » err = pc.cc.Write(req.Request)
630 if err != nil { 635 if err != nil {
631 pc.close() 636 pc.close()
632 return 637 return
633 } 638 }
634 639
635 ch := make(chan responseAndError, 1) 640 ch := make(chan responseAndError, 1)
636 » pc.reqch <- requestAndChan{req, ch, requestedGzip} 641 » pc.reqch <- requestAndChan{req.Request, ch, requestedGzip}
637 re := <-ch 642 re := <-ch
638 pc.lk.Lock() 643 pc.lk.Lock()
639 pc.numExpectedResponses-- 644 pc.numExpectedResponses--
640 pc.lk.Unlock() 645 pc.lk.Unlock()
641 646
642 return re.res, re.err 647 return re.res, re.err
643 } 648 }
644 649
645 func (pc *persistConn) close() { 650 func (pc *persistConn) close() {
646 pc.lk.Lock() 651 pc.lk.Lock()
647 defer pc.lk.Unlock() 652 defer pc.lk.Unlock()
648 pc.broken = true 653 pc.broken = true
649 pc.cc.Close() 654 pc.cc.Close()
650 pc.conn.Close() 655 pc.conn.Close()
651 » pc.mutateRequestFunc = nil 656 » pc.mutateHeaderFunc = nil
652 } 657 }
653 658
654 var portMap = map[string]string{ 659 var portMap = map[string]string{
655 "http": "80", 660 "http": "80",
656 "https": "443", 661 "https": "443",
657 } 662 }
658 663
659 // canonicalAddr returns url.Host but always with a ":port" suffix 664 // canonicalAddr returns url.Host but always with a ":port" suffix
660 func canonicalAddr(url *url.URL) string { 665 func canonicalAddr(url *url.URL) string {
661 addr := url.Host 666 addr := url.Host
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after
722 727
723 // discardOnCloseReadCloser consumes all its input on Close. 728 // discardOnCloseReadCloser consumes all its input on Close.
724 type discardOnCloseReadCloser struct { 729 type discardOnCloseReadCloser struct {
725 io.ReadCloser 730 io.ReadCloser
726 } 731 }
727 732
728 func (d *discardOnCloseReadCloser) Close() os.Error { 733 func (d *discardOnCloseReadCloser) Close() os.Error {
729 io.Copy(ioutil.Discard, d.ReadCloser) // ignore errors; likely invalid o r already closed 734 io.Copy(ioutil.Discard, d.ReadCloser) // ignore errors; likely invalid o r already closed
730 return d.ReadCloser.Close() 735 return d.ReadCloser.Close()
731 } 736 }
LEFTRIGHT

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b