http: add proxy support

Fixes #53.

R=agl1, jacek.masiulaniec, adg, rsc, agl
CC=golang-dev
https://golang.org/cl/3794041
This commit is contained in:
Yasuhiro Matsumoto 2011-02-16 14:06:50 -05:00 committed by Adam Langley
parent be560e0401
commit f04b5a3bac
2 changed files with 146 additions and 8 deletions

View File

@ -31,6 +31,40 @@ type readClose struct {
io.Closer io.Closer
} }
// matchNoProxy returns true if requests to addr should not use a proxy,
// according to the NO_PROXY or no_proxy environment variable.
func matchNoProxy(addr string) bool {
if len(addr) == 0 {
return false
}
no_proxy := os.Getenv("NO_PROXY")
if len(no_proxy) == 0 {
no_proxy = os.Getenv("no_proxy")
}
if no_proxy == "*" {
return true
}
addr = strings.ToLower(strings.TrimSpace(addr))
if hasPort(addr) {
addr = addr[:strings.LastIndex(addr, ":")]
}
for _, p := range strings.Split(no_proxy, ",", -1) {
p = strings.ToLower(strings.TrimSpace(p))
if len(p) == 0 {
continue
}
if hasPort(p) {
p = p[:strings.LastIndex(p, ":")]
}
if addr == p || (p[0] == '.' && (strings.HasSuffix(addr, p) || addr == p[1:])) {
return true
}
}
return false
}
// Send issues an HTTP request. Caller should close resp.Body when done reading it. // Send issues an HTTP request. Caller should close resp.Body when done reading it.
// //
// TODO: support persistent connections (multiple requests on a single connection). // TODO: support persistent connections (multiple requests on a single connection).
@ -56,22 +90,81 @@ func send(req *Request) (resp *Response, err os.Error) {
req.Header["Authorization"] = "Basic " + string(encoded) req.Header["Authorization"] = "Basic " + string(encoded)
} }
var conn io.ReadWriteCloser var proxyURL *URL
if req.URL.Scheme == "http" { proxyAuth := ""
conn, err = net.Dial("tcp", "", addr) proxy := os.Getenv("HTTP_PROXY")
if proxy == "" {
proxy = os.Getenv("http_proxy")
}
if matchNoProxy(addr) {
proxy = ""
}
if proxy != "" {
proxyURL, err = ParseURL(proxy)
if err != nil { if err != nil {
return nil, err return nil, os.ErrorString("invalid proxy address")
}
addr = proxyURL.Host
proxyInfo := proxyURL.RawUserinfo
if proxyInfo != "" {
enc := base64.URLEncoding
encoded := make([]byte, enc.EncodedLen(len(proxyInfo)))
enc.Encode(encoded, []byte(proxyInfo))
proxyAuth = "Basic " + string(encoded)
}
}
// Connect to server or proxy.
conn, err := net.Dial("tcp", "", addr)
if err != nil {
return nil, err
}
if req.URL.Scheme == "http" {
// Include proxy http header if needed.
if proxyAuth != "" {
req.Header["Proxy-Authorization"] = proxyAuth
} }
} else { // https } else { // https
conn, err = tls.Dial("tcp", "", addr, nil) if proxyURL != nil {
if err != nil { // Ask proxy for direct connection to server.
// addr defaults above to ":https" but we need to use numbers
addr = req.URL.Host
if !hasPort(addr) {
addr += ":443"
}
fmt.Fprintf(conn, "CONNECT %s HTTP/1.1\r\n", addr)
fmt.Fprintf(conn, "Host: %s\r\n", addr)
if proxyAuth != "" {
fmt.Fprintf(conn, "Proxy-Authorization: %s\r\n", proxyAuth)
}
fmt.Fprintf(conn, "\r\n")
// Read response.
// Okay to use and discard buffered reader here, because
// TLS server will not speak until spoken to.
br := bufio.NewReader(conn)
resp, err := ReadResponse(br, "CONNECT")
if err != nil {
return nil, err
}
if resp.StatusCode != 200 {
f := strings.Split(resp.Status, " ", 2)
return nil, os.ErrorString(f[1])
}
}
// Initiate TLS and check remote host name against certificate.
conn = tls.Client(conn, nil)
if err = conn.(*tls.Conn).Handshake(); err != nil {
return nil, err return nil, err
} }
h := req.URL.Host h := req.URL.Host
if hasPort(h) { if hasPort(h) {
h = h[0:strings.LastIndex(h, ":")] h = h[:strings.LastIndex(h, ":")]
} }
if err := conn.(*tls.Conn).VerifyHostname(h); err != nil { if err = conn.(*tls.Conn).VerifyHostname(h); err != nil {
return nil, err return nil, err
} }
} }

View File

@ -0,0 +1,45 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package http
import (
"os"
"testing"
)
// TODO(mattn):
// test ProxyAuth
var MatchNoProxyTests = []struct {
host string
match bool
}{
{"localhost", true}, // match completely
{"barbaz.net", true}, // match as .barbaz.net
{"foobar.com:443", true}, // have a port but match
{"foofoobar.com", false}, // not match as a part of foobar.com
{"baz.com", false}, // not match as a part of barbaz.com
{"localhost.net", false}, // not match as suffix of address
{"local.localhost", false}, // not match as prefix as address
{"barbarbaz.net", false}, // not match because NO_PROXY have a '.'
{"www.foobar.com", false}, // not match because NO_PROXY is not .foobar.com
}
func TestMatchNoProxy(t *testing.T) {
oldenv := os.Getenv("NO_PROXY")
no_proxy := "foobar.com, .barbaz.net , localhost"
os.Setenv("NO_PROXY", no_proxy)
defer os.Setenv("NO_PROXY", oldenv)
for _, test := range MatchNoProxyTests {
if matchNoProxy(test.host) != test.match {
if test.match {
t.Errorf("matchNoProxy(%v) = %v, want %v", test.host, !test.match, test.match)
} else {
t.Errorf("not expected: '%s' shouldn't match as '%s'", test.host, no_proxy)
}
}
}
}