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.
5 // Primitive HTTP client. See RFC 2616.
22 // Given a string of the form "host", "host:port", or "[ipv6::address]:port",
23 // return true if the string includes a port.
24 func hasPort(s
string) bool { return strings
.LastIndex(s
, ":") > strings
.LastIndex(s
, "]") }
26 // Used in Send to implement io.ReadCloser by bundling together the
27 // bufio.Reader through which we read the response, and the underlying
28 // network connection.
29 type readClose
struct {
34 // Send issues an HTTP request. Caller should close resp.Body when done reading it.
36 // TODO: support persistent connections (multiple requests on a single connection).
37 // send() method is nonpublic because, when we refactor the code for persistent
38 // connections, it may no longer make sense to have a method with this signature.
39 func send(req
*Request
) (resp
*Response
, err os
.Error
) {
40 if req
.URL
.Scheme
!= "http" && req
.URL
.Scheme
!= "https" {
41 return nil, &badStringError
{"unsupported protocol scheme", req
.URL
.Scheme
}
46 addr
+= ":" + req
.URL
.Scheme
48 info
:= req
.URL
.RawUserinfo
50 enc
:= base64
.URLEncoding
51 encoded
:= make([]byte, enc
.EncodedLen(len(info
)))
52 enc
.Encode(encoded
, []byte(info
))
53 if req
.Header
== nil {
54 req
.Header
= make(map[string]string)
56 req
.Header
["Authorization"] = "Basic " + string(encoded
)
59 var conn io
.ReadWriteCloser
60 if req
.URL
.Scheme
== "http" {
61 conn
, err
= net
.Dial("tcp", "", addr
)
66 conn
, err
= tls
.Dial("tcp", "", addr
)
72 h
= h
[0:strings
.LastIndex(h
, ":")]
74 if err
:= conn
.(*tls
.Conn
).VerifyHostname(h
); err
!= nil {
85 reader
:= bufio
.NewReader(conn
)
86 resp
, err
= ReadResponse(reader
, req
.Method
)
92 resp
.Body
= readClose
{resp
.Body
, conn
}
97 // True if the specified HTTP status code is one for which the Get utility should
98 // automatically redirect.
99 func shouldRedirect(statusCode
int) bool {
101 case StatusMovedPermanently
, StatusFound
, StatusSeeOther
, StatusTemporaryRedirect
:
107 // Get issues a GET to the specified URL. If the response is one of the following
108 // redirect codes, it follows the redirect, up to a maximum of 10 redirects:
110 // 301 (Moved Permanently)
113 // 307 (Temporary Redirect)
115 // finalURL is the URL from which the response was fetched -- identical to the
116 // input URL unless redirects were followed.
118 // Caller should close r.Body when done reading it.
119 func Get(url
string) (r
*Response
, finalURL
string, err os
.Error
) {
120 // TODO: if/when we add cookie support, the redirected request shouldn't
121 // necessarily supply the same cookies as the original.
122 // TODO: set referrer header on redirects.
123 for redirect
:= 0; ; redirect
++ {
125 err
= os
.ErrorString("stopped after 10 redirects")
130 if req
.URL
, err
= ParseURL(url
); err
!= nil {
133 url
= req
.URL
.String()
134 if r
, err
= send(&req
); err
!= nil {
137 if shouldRedirect(r
.StatusCode
) {
139 if url
= r
.GetHeader("Location"); url
== "" {
140 err
= os
.ErrorString(fmt
.Sprintf("%d response missing Location header", r
.StatusCode
))
149 err
= &URLError
{"Get", url
, err
}
153 // Post issues a POST to the specified URL.
155 // Caller should close r.Body when done reading it.
156 func Post(url
string, bodyType
string, body io
.Reader
) (r
*Response
, err os
.Error
) {
162 req
.Body
= nopCloser
{body
}
163 req
.Header
= map[string]string{
164 "Content-Type": bodyType
,
166 req
.TransferEncoding
= []string{"chunked"}
168 req
.URL
, err
= ParseURL(url
)
176 // PostForm issues a POST to the specified URL,
177 // with data's keys and values urlencoded as the request body.
179 // Caller should close r.Body when done reading it.
180 func PostForm(url
string, data
map[string]string) (r
*Response
, err os
.Error
) {
186 body
:= urlencode(data
)
187 req
.Body
= nopCloser
{body
}
188 req
.Header
= map[string]string{
189 "Content-Type": "application/x-www-form-urlencoded",
190 "Content-Length": strconv
.Itoa(body
.Len()),
192 req
.ContentLength
= int64(body
.Len())
194 req
.URL
, err
= ParseURL(url
)
202 func urlencode(data
map[string]string) (b
*bytes
.Buffer
) {
203 b
= new(bytes
.Buffer
)
205 for k
, v
:= range data
{
211 b
.WriteString(URLEscape(k
))
213 b
.WriteString(URLEscape(v
))
218 // Head issues a HEAD to the specified URL.
219 func Head(url
string) (r
*Response
, err os
.Error
) {
222 if req
.URL
, err
= ParseURL(url
); err
!= nil {
225 url
= req
.URL
.String()
226 if r
, err
= send(&req
); err
!= nil {
232 type nopCloser
struct {
236 func (nopCloser
) Close() os
.Error
{ return nil }