Permalink
Browse files

Added HTTPS support

The "hc_" hack was generalized to also work for https connections.  In
addition, there now exists a TLSClientConfig object within the
HttpClient object that can be used to control the TLS settings for HTTPS
connections.  One caviat is that the object cannot be moved anywhere in
memory, so it can only be changed inplace.
  • Loading branch information...
1 parent ac0aeb8 commit 94e2e45d3513968f2cde78b3683125d2ee8d1671 @mynameisfiber mynameisfiber committed Oct 4, 2012
Showing with 64 additions and 6 deletions.
  1. +5 −0 README.md
  2. +32 −6 httpclient.go
  3. +27 −0 httpclient_test.go
View
5 README.md
@@ -14,6 +14,7 @@ type HttpClient struct {
ReadWriteTimeout time.Duration
MaxConnsPerHost int
RedirectPolicy func(*http.Request, []*http.Request) error
+ TLSClientConfig *tls.Config
}
func New() *HttpClient
@@ -67,6 +68,10 @@ func main() {
httpClient.ConnectTimeout = time.Second
httpClient.ReadWriteTimeout = time.Second
+ // Allow insecure HTTPS connections. Note: the TLSClientConfig pointer can't change
+ // places, so you can only modify the existing tls.Config object
+ httpClient.TLSClientConfig.InsecureSkipVerify = true
+
// Make a custom redirect policy to keep track of the number of redirects we've followed
var numRedirects int
httpClient.RedirectPolicy = func(r *http.Request, v []*http.Request) error {
View
38 httpclient.go
@@ -3,6 +3,7 @@ package httpclient
import (
"bufio"
"container/list"
+ "crypto/tls"
"errors"
"io"
"net"
@@ -37,6 +38,7 @@ type HttpClient struct {
ReadWriteTimeout time.Duration
MaxConnsPerHost int
RedirectPolicy func(*http.Request, []*http.Request) error
+ TLSClientConfig *tls.Config
}
// create a new HttpClient
@@ -51,14 +53,18 @@ func New() *HttpClient {
ReadWriteTimeout: 5 * time.Second,
MaxConnsPerHost: 5,
RedirectPolicy: DefaultRedirectPolicy,
+ TLSClientConfig: &tls.Config{},
}
redirFunc := func(r *http.Request, v []*http.Request) error {
return h.RedirectPolicy(r, v)
}
- transport := &http.Transport{}
+ transport := &http.Transport{
+ TLSClientConfig: h.TLSClientConfig,
+ }
transport.RegisterProtocol("hc_http", h)
+ transport.RegisterProtocol("hc_https", h)
client.CheckRedirect = redirFunc
client.Transport = transport
@@ -79,7 +85,7 @@ func (h *HttpClient) RoundTrip(req *http.Request) (*http.Response, error) {
var c net.Conn
var err error
- addr := canonicalAddr(req.URL.Host)
+ addr := canonicalAddr(req.URL.Host, req.URL.Scheme)
c, err = h.checkConnCache(addr)
if err != nil {
return nil, err
@@ -90,6 +96,20 @@ func (h *HttpClient) RoundTrip(req *http.Request) (*http.Response, error) {
if err != nil {
return nil, err
}
+
+ if req.URL.Scheme == "hc_https" {
+ // Initiate TLS and check remote host name against certificate.
+ c = tls.Client(c, h.TLSClientConfig)
+ if err = c.(*tls.Conn).Handshake(); err != nil {
+ return nil, err
+ }
+ if h.TLSClientConfig == nil || !h.TLSClientConfig.InsecureSkipVerify {
+ hostname, _, _ := net.SplitHostPort(req.URL.Host) // Remove port from host
+ if err = c.(*tls.Conn).VerifyHostname(hostname); err != nil {
+ return nil, err
+ }
+ }
+ }
}
h.Lock()
@@ -176,7 +196,9 @@ func (h *HttpClient) GetConn(req *http.Request) (net.Conn, error) {
// perform the specified request
func (h *HttpClient) Do(req *http.Request) (*http.Response, error) {
// h@x0r Go's http client to use our RoundTripper
- req.URL.Scheme = "hc_http"
+ if !strings.HasPrefix(req.URL.Scheme, "hc_") {
+ req.URL.Scheme = "hc_" + req.URL.Scheme
+ }
resp, err := h.client.Do(req)
if err != nil || resp.Close || req.Close {
@@ -228,12 +250,16 @@ func (h *HttpClient) FinishRequest(req *http.Request) error {
delete(h.connMap, req)
h.Unlock()
- return h.cacheConn(canonicalAddr(req.URL.Host), conn)
+ return h.cacheConn(canonicalAddr(req.URL.Host, req.URL.Scheme), conn)
}
-func canonicalAddr(s string) string {
+func canonicalAddr(s string, scheme string) string {
if !hasPort(s) {
- s = s + ":80"
+ if scheme == "hc_http" {
+ s = s + ":80"
+ } else if scheme == "hc_https" {
+ s = s + ":443"
+ }
}
return s
}
View
27 httpclient_test.go
@@ -47,6 +47,33 @@ func setupMockServer(t *testing.T) {
addr = ln.Addr()
}
+func TestHttpsConnection(t *testing.T) {
+ httpClient := New()
+ httpClient.TLSClientConfig.InsecureSkipVerify = true
+
+ req, _ := http.NewRequest("GET", "https://httpbin.org/ip", nil)
+
+ resp, err := httpClient.Do(req)
+ if err != nil {
+ t.Fatalf("1st request failed - %s", err.Error())
+ }
+ defer resp.Body.Close()
+ _, err = ioutil.ReadAll(resp.Body)
+ if err != nil {
+ t.Fatalf("1st failed to read body - %s", err.Error())
+ }
+ httpClient.FinishRequest(req)
+
+ httpClient.ReadWriteTimeout = 20 * time.Millisecond
+ req2, _ := http.NewRequest("GET", "https://httpbin.org/delay/5", nil)
+
+ _, err = httpClient.Do(req)
+ if err == nil {
+ t.Fatalf("HTTPS request should have timed out")
+ }
+ httpClient.FinishRequest(req2)
+}
+
func TestCustomRedirectPolicy(t *testing.T) {
starter.Do(func() { setupMockServer(t) })

0 comments on commit 94e2e45

Please sign in to comment.