summaryrefslogtreecommitdiff
path: root/src/pkg/http/transport.go
blob: 78d316a558b92db03ded18af8fa3b03d3e9f5b11 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
// 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 Transport 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 Transport = &transport{}

// transport implements Tranport 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
// Transport 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")
		}
	}

	var write = (*Request).Write

	if proxy != "" {
		write = (*Request).WriteProxy
		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 = write(req, 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
}