mirror of
https://github.com/golang/go
synced 2024-11-25 00:57:59 -07:00
http: introduce start of Client and ClientTransport
Much yet to come, but this is a safe first step, introducing an in-the-future configurable Client object (where policy for cookies, auth, redirects will live) as well as introducing a ClientTransport interface for sending requests. The CL intentionally ignores everything around the creation and configuration of Clients and merely ports/wraps the old interfaces to/around Client/ClientTransport. R=rsc, dsymonds, nigeltao, bradfitzwork CC=golang-dev https://golang.org/cl/4182086
This commit is contained in:
parent
690291a2c0
commit
e0a2c5d4b5
@ -18,6 +18,7 @@ GOFILES=\
|
|||||||
server.go\
|
server.go\
|
||||||
status.go\
|
status.go\
|
||||||
transfer.go\
|
transfer.go\
|
||||||
|
transport.go\
|
||||||
url.go\
|
url.go\
|
||||||
|
|
||||||
include ../../Make.pkg
|
include ../../Make.pkg
|
||||||
|
@ -7,18 +7,37 @@
|
|||||||
package http
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/tls"
|
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// A Client is an HTTP client.
|
||||||
|
// It is not yet possible to create custom Clients; use DefaultClient.
|
||||||
|
type Client struct {
|
||||||
|
transport ClientTransport // if nil, DefaultTransport is used
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultClient is the default Client and is used by Get, Head, and Post.
|
||||||
|
var DefaultClient = &Client{}
|
||||||
|
|
||||||
|
// ClientTransport is an interface representing the ability to execute a
|
||||||
|
// single HTTP transaction, obtaining the Response for a given Request.
|
||||||
|
type ClientTransport interface {
|
||||||
|
// Do executes a single HTTP transaction, returning the Response for the
|
||||||
|
// request req. Do should not attempt to interpret the response.
|
||||||
|
// In particular, Do must return err == nil if it obtained a response,
|
||||||
|
// regardless of the response's HTTP status code. A non-nil err should
|
||||||
|
// be reserved for failure to obtain a response. Similarly, Do should
|
||||||
|
// not attempt to handle higher-level protocol details such as redirects,
|
||||||
|
// authentication, or cookies.
|
||||||
|
Do(req *Request) (resp *Response, err os.Error)
|
||||||
|
}
|
||||||
|
|
||||||
// Given a string of the form "host", "host:port", or "[ipv6::address]:port",
|
// Given a string of the form "host", "host:port", or "[ipv6::address]:port",
|
||||||
// return true if the string includes a port.
|
// return true if the string includes a port.
|
||||||
func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") }
|
func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") }
|
||||||
@ -65,19 +84,25 @@ func matchNoProxy(addr string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send issues an HTTP request. Caller should close resp.Body when done reading it.
|
// Do sends an HTTP request and returns an HTTP response, following
|
||||||
|
// policy (e.g. redirects, cookies, auth) as configured on the client.
|
||||||
|
//
|
||||||
|
// Callers should close resp.Body when done reading it.
|
||||||
|
//
|
||||||
|
// Generally Get, Post, or PostForm will be used instead of Do.
|
||||||
|
func (c *Client) Do(req *Request) (resp *Response, err os.Error) {
|
||||||
|
return send(req, c.transport)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 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).
|
||||||
// send() method is nonpublic because, when we refactor the code for persistent
|
// send() method is nonpublic because, when we refactor the code for persistent
|
||||||
// connections, it may no longer make sense to have a method with this signature.
|
// connections, it may no longer make sense to have a method with this signature.
|
||||||
func send(req *Request) (resp *Response, err os.Error) {
|
func send(req *Request, t ClientTransport) (resp *Response, err os.Error) {
|
||||||
if req.URL.Scheme != "http" && req.URL.Scheme != "https" {
|
if t == nil {
|
||||||
return nil, &badStringError{"unsupported protocol scheme", req.URL.Scheme}
|
t = DefaultTransport
|
||||||
}
|
|
||||||
|
|
||||||
addr := req.URL.Host
|
|
||||||
if !hasPort(addr) {
|
|
||||||
addr += ":" + req.URL.Scheme
|
|
||||||
}
|
}
|
||||||
info := req.URL.RawUserinfo
|
info := req.URL.RawUserinfo
|
||||||
if len(info) > 0 {
|
if len(info) > 0 {
|
||||||
@ -89,108 +114,7 @@ func send(req *Request) (resp *Response, err os.Error) {
|
|||||||
}
|
}
|
||||||
req.Header.Set("Authorization", "Basic "+string(encoded))
|
req.Header.Set("Authorization", "Basic "+string(encoded))
|
||||||
}
|
}
|
||||||
|
return t.Do(req)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// True if the specified HTTP status code is one for which the Get utility should
|
// True if the specified HTTP status code is one for which the Get utility should
|
||||||
@ -215,11 +139,31 @@ func shouldRedirect(statusCode int) bool {
|
|||||||
// input URL unless redirects were followed.
|
// input URL unless redirects were followed.
|
||||||
//
|
//
|
||||||
// Caller should close r.Body when done reading it.
|
// Caller should close r.Body when done reading it.
|
||||||
|
//
|
||||||
|
// Get is a convenience wrapper around DefaultClient.Get.
|
||||||
func Get(url string) (r *Response, finalURL string, err os.Error) {
|
func Get(url string) (r *Response, finalURL string, err os.Error) {
|
||||||
|
return DefaultClient.Get(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get issues a GET to the specified URL. If the response is one of the following
|
||||||
|
// redirect codes, it follows the redirect, up to a maximum of 10 redirects:
|
||||||
|
//
|
||||||
|
// 301 (Moved Permanently)
|
||||||
|
// 302 (Found)
|
||||||
|
// 303 (See Other)
|
||||||
|
// 307 (Temporary Redirect)
|
||||||
|
//
|
||||||
|
// finalURL is the URL from which the response was fetched -- identical to the
|
||||||
|
// input URL unless redirects were followed.
|
||||||
|
//
|
||||||
|
// Caller should close r.Body when done reading it.
|
||||||
|
func (c *Client) Get(url string) (r *Response, finalURL string, err os.Error) {
|
||||||
// TODO: if/when we add cookie support, the redirected request shouldn't
|
// TODO: if/when we add cookie support, the redirected request shouldn't
|
||||||
// necessarily supply the same cookies as the original.
|
// necessarily supply the same cookies as the original.
|
||||||
// TODO: set referrer header on redirects.
|
// TODO: set referrer header on redirects.
|
||||||
var base *URL
|
var base *URL
|
||||||
|
// TODO: remove this hard-coded 10 and use the Client's policy
|
||||||
|
// (ClientConfig) instead.
|
||||||
for redirect := 0; ; redirect++ {
|
for redirect := 0; ; redirect++ {
|
||||||
if redirect >= 10 {
|
if redirect >= 10 {
|
||||||
err = os.ErrorString("stopped after 10 redirects")
|
err = os.ErrorString("stopped after 10 redirects")
|
||||||
@ -236,7 +180,7 @@ func Get(url string) (r *Response, finalURL string, err os.Error) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
url = req.URL.String()
|
url = req.URL.String()
|
||||||
if r, err = send(&req); err != nil {
|
if r, err = send(&req, c.transport); err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if shouldRedirect(r.StatusCode) {
|
if shouldRedirect(r.StatusCode) {
|
||||||
@ -259,7 +203,16 @@ func Get(url string) (r *Response, finalURL string, err os.Error) {
|
|||||||
// Post issues a POST to the specified URL.
|
// Post issues a POST to the specified URL.
|
||||||
//
|
//
|
||||||
// Caller should close r.Body when done reading it.
|
// Caller should close r.Body when done reading it.
|
||||||
|
//
|
||||||
|
// Post is a wrapper around DefaultClient.Post
|
||||||
func Post(url string, bodyType string, body io.Reader) (r *Response, err os.Error) {
|
func Post(url string, bodyType string, body io.Reader) (r *Response, err os.Error) {
|
||||||
|
return DefaultClient.Post(url, bodyType, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post issues a POST to the specified URL.
|
||||||
|
//
|
||||||
|
// Caller should close r.Body when done reading it.
|
||||||
|
func (c *Client) Post(url string, bodyType string, body io.Reader) (r *Response, err os.Error) {
|
||||||
var req Request
|
var req Request
|
||||||
req.Method = "POST"
|
req.Method = "POST"
|
||||||
req.ProtoMajor = 1
|
req.ProtoMajor = 1
|
||||||
@ -276,14 +229,24 @@ func Post(url string, bodyType string, body io.Reader) (r *Response, err os.Erro
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return send(&req)
|
return send(&req, c.transport)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PostForm issues a POST to the specified URL,
|
// PostForm issues a POST to the specified URL,
|
||||||
// with data's keys and values urlencoded as the request body.
|
// with data's keys and values urlencoded as the request body.
|
||||||
//
|
//
|
||||||
// Caller should close r.Body when done reading it.
|
// Caller should close r.Body when done reading it.
|
||||||
|
//
|
||||||
|
// PostForm is a wrapper around DefaultClient.PostForm
|
||||||
func PostForm(url string, data map[string]string) (r *Response, err os.Error) {
|
func PostForm(url string, data map[string]string) (r *Response, err os.Error) {
|
||||||
|
return DefaultClient.PostForm(url, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostForm issues a POST to the specified URL,
|
||||||
|
// with data's keys and values urlencoded as the request body.
|
||||||
|
//
|
||||||
|
// Caller should close r.Body when done reading it.
|
||||||
|
func (c *Client) PostForm(url string, data map[string]string) (r *Response, err os.Error) {
|
||||||
var req Request
|
var req Request
|
||||||
req.Method = "POST"
|
req.Method = "POST"
|
||||||
req.ProtoMajor = 1
|
req.ProtoMajor = 1
|
||||||
@ -302,7 +265,7 @@ func PostForm(url string, data map[string]string) (r *Response, err os.Error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return send(&req)
|
return send(&req, c.transport)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: remove this function when PostForm takes a multimap.
|
// TODO: remove this function when PostForm takes a multimap.
|
||||||
@ -315,13 +278,20 @@ func urlencode(data map[string]string) (b *bytes.Buffer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Head issues a HEAD to the specified URL.
|
// Head issues a HEAD to the specified URL.
|
||||||
|
//
|
||||||
|
// Head is a wrapper around DefaultClient.Head
|
||||||
func Head(url string) (r *Response, err os.Error) {
|
func Head(url string) (r *Response, err os.Error) {
|
||||||
|
return DefaultClient.Head(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Head issues a HEAD to the specified URL.
|
||||||
|
func (c *Client) Head(url string) (r *Response, err os.Error) {
|
||||||
var req Request
|
var req Request
|
||||||
req.Method = "HEAD"
|
req.Method = "HEAD"
|
||||||
if req.URL, err = ParseURL(url); err != nil {
|
if req.URL, err = ParseURL(url); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return send(&req)
|
return send(&req, c.transport)
|
||||||
}
|
}
|
||||||
|
|
||||||
type nopCloser struct {
|
type nopCloser struct {
|
||||||
|
@ -149,7 +149,7 @@ func TestServeFile(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getBody(t *testing.T, req Request) (*Response, []byte) {
|
func getBody(t *testing.T, req Request) (*Response, []byte) {
|
||||||
r, err := send(&req)
|
r, err := send(&req, DefaultTransport)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(req.URL.String(), "send:", err)
|
t.Fatal(req.URL.String(), "send:", err)
|
||||||
}
|
}
|
||||||
|
150
src/pkg/http/transport.go
Normal file
150
src/pkg/http/transport.go
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
// 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"
|
||||||
|
"log"
|
||||||
|
"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
|
||||||
|
log.Printf("Temporary necessary log statement to work around http://code.google.com/p/go/issues/detail?id=1547")
|
||||||
|
conn, err := net.Dial("tcp", "", addr)
|
||||||
|
log.Printf("Temporary necessary log statement to work around http://code.google.com/p/go/issues/detail?id=1547")
|
||||||
|
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
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user