blob: 41d639c7e2feca508cc36c2eb3570ed20c4e9888 [file] [log] [blame]
// Copyright 2011 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 (
"bufio"
"crypto/tls"
"encoding/base64"
"fmt"
"net"
"os"
"strings"
"sync"
)
// DefaultTransport is the default implementation of ClientTransport
// and is used by DefaultClient. It establishes a new network connection for
// each call to Do and uses HTTP proxies as directed by the $HTTP_PROXY and
// $NO_PROXY (or $http_proxy and $no_proxy) environment variables.
var DefaultTransport ClientTransport = &transport{}
// transport implements http.ClientTranport for the default case,
// using TCP connections to either the host or a proxy, serving
// http or https schemes. In the future this may become public
// and support options on keep-alive connection duration, pipelining
// controls, etc. For now this is simply a port of the old Go code
// client code to the http.ClientTransport interface.
type transport struct {
// TODO: keep-alives, pipelining, etc using a map from
// scheme/host to a connection. Something like:
l sync.Mutex
hostConn map[string]*ClientConn
}
func (ct *transport) Do(req *Request) (resp *Response, err os.Error) {
if req.URL.Scheme != "http" && req.URL.Scheme != "https" {
return nil, &badStringError{"unsupported protocol scheme", req.URL.Scheme}
}
addr := req.URL.Host
if !hasPort(addr) {
addr += ":" + req.URL.Scheme
}
var proxyURL *URL
proxyAuth := ""
proxy := ""
if !matchNoProxy(addr) {
proxy = os.Getenv("HTTP_PROXY")
if proxy == "" {
proxy = os.Getenv("http_proxy")
}
}
if proxy != "" {
proxyURL, err = ParseRequestURL(proxy)
if err != nil {
return nil, os.ErrorString("invalid proxy address")
}
if proxyURL.Host == "" {
proxyURL, err = ParseRequestURL("http://" + proxy)
if err != nil {
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.Set("Proxy-Authorization", proxyAuth)
}
} else { // https
if proxyURL != 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
}
h := req.URL.Host
if hasPort(h) {
h = h[:strings.LastIndex(h, ":")]
}
if err = conn.(*tls.Conn).VerifyHostname(h); err != nil {
return nil, err
}
}
err = req.Write(conn)
if err != nil {
conn.Close()
return nil, err
}
reader := bufio.NewReader(conn)
resp, err = ReadResponse(reader, req.Method)
if err != nil {
conn.Close()
return nil, err
}
resp.Body = readClose{resp.Body, conn}
return
}