Skip to content

Commit

Permalink
Support for HTTP(S) Proxy (#655)
Browse files Browse the repository at this point in the history
Works like with standard Go client
  • Loading branch information
mattrobenolt authored and buger committed Feb 23, 2019
1 parent e3f3348 commit f6b0d5d
Showing 1 changed file with 87 additions and 5 deletions.
92 changes: 87 additions & 5 deletions http_client.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package main

import (
"bufio"
"bytes"
"crypto/tls"
"encoding/base64"
"io"
"log"
"net"
"net/http"
"net/url"
"runtime/debug"
"strconv"
Expand Down Expand Up @@ -47,6 +49,8 @@ type HTTPClient struct {
host string
auth string
conn net.Conn
proxy *url.URL
proxyAuth string
respBuf []byte
config *HTTPClientConfig
redirectsCount int
Expand Down Expand Up @@ -80,26 +84,82 @@ func NewHTTPClient(baseURL string, config *HTTPClientConfig) *HTTPClient {
client.auth = "Basic " + base64.StdEncoding.EncodeToString([]byte(u.User.String()))
}

client.proxy, _ = http.ProxyFromEnvironment(&http.Request{URL: u})

if client.isProxy() && client.proxy.User != nil {
client.proxyAuth = "Basic " + base64.StdEncoding.EncodeToString([]byte(client.proxy.User.String()))
}

return client
}

func (c *HTTPClient) Connect() (err error) {
c.Disconnect()

var toDial string
if !strings.Contains(c.host, ":") {
c.conn, err = net.DialTimeout("tcp", c.host+":"+defaultPorts[c.scheme], c.config.ConnectionTimeout)
toDial = c.host + ":" + defaultPorts[c.scheme]
} else {
toDial = c.host
}

if c.isProxy() {
if c.proxy.Scheme != "http" {
panic("Unsupported HTTP Proxy method")
}
Debug("[HTTPClient] Connecting to proxy", c.proxy.String(), "<>", toDial)
c.conn, err = net.DialTimeout("tcp", c.proxy.Host, c.config.ConnectionTimeout)
if err != nil {
return
}
if c.scheme == "https" {
c.conn.Write([]byte("CONNECT " + toDial + " HTTP/1.1\r\n"))
if c.proxyAuth != "" {
c.conn.Write([]byte("Proxy-Authorization: " + c.proxyAuth + "\r\n"))
}
c.conn.Write([]byte("\r\n"))
br := bufio.NewReader(c.conn)
l, _, err := br.ReadLine()
if err != nil {
return err
}
if len(l) < 12 {
panic("HTTP proxy did not respond correctly")
}
status := l[9:12]
if !bytes.Equal(status, []byte("200")) {
panic("HTTP proxy did not respond correctly")
}
for {
// Read until we find the empty line
l, _, err := br.ReadLine()
if err != nil {
return err
}
if len(l) == 0 {
break
}
}
}
Debug("[HTTPClient] Proxy successfully connected")
} else {
c.conn, err = net.DialTimeout("tcp", c.host, c.config.ConnectionTimeout)
c.conn, err = net.DialTimeout("tcp", toDial, c.config.ConnectionTimeout)
if err != nil {
return
}
}

if c.scheme == "https" {
// Wrap our socket in TLS
Debug("[HTTPClient] Wrapping socket in TLS", c.host)
tlsConn := tls.Client(c.conn, &tls.Config{InsecureSkipVerify: true, ServerName: c.host})

if err = tlsConn.Handshake(); err != nil {
return
}

c.conn = tlsConn
Debug("[HTTPClient] Successfully wrapped in TLS")
}

return
Expand Down Expand Up @@ -135,8 +195,6 @@ func (c *HTTPClient) isAlive(readBytes *int) bool {
}

func (c *HTTPClient) Send(data []byte) (response []byte, err error) {
var payload []byte

// Don't exit on panic
defer func() {
if r := recover(); r != nil {
Expand All @@ -150,7 +208,7 @@ func (c *HTTPClient) Send(data []byte) (response []byte, err error) {
}
}()

var readBytes, n int
var readBytes int
if c.conn == nil || !c.isAlive(&readBytes) {
Debug("[HTTPClient] Connecting:", c.baseURL)
if err = c.Connect(); err != nil {
Expand All @@ -168,6 +226,16 @@ func (c *HTTPClient) Send(data []byte) (response []byte, err error) {
data = proto.SetHost(data, []byte(c.baseURL), []byte(c.host))
}

if c.isProxy() && c.scheme == "http" {
path := proto.Path(data)
if len(path) > 0 && path[0] == '/' {
data = proto.SetPath(data, c.proxyPath(path))
if c.proxyAuth != "" {
data = proto.SetHeader(data, []byte("Proxy-Authorization"), []byte(c.proxyAuth))
}
}
}

if c.auth != "" {
data = proto.SetHeader(data, []byte("Authorization"), []byte(c.auth))
}
Expand All @@ -176,6 +244,12 @@ func (c *HTTPClient) Send(data []byte) (response []byte, err error) {
Debug("[HTTPClient] Sending:", string(data))
}

return c.send(data, readBytes, timeout)
}

func (c *HTTPClient) send(data []byte, readBytes int, timeout time.Time) (response []byte, err error) {
var payload []byte
var n int
if _, err = c.conn.Write(data); err != nil {
Debug("[HTTPClient] Write error:", err, c.baseURL)
response = errorPayload(HTTP_TIMEOUT)
Expand Down Expand Up @@ -376,6 +450,14 @@ func (c *HTTPClient) Post(path string, body []byte) (response []byte, err error)
return c.Send([]byte(payload))
}

func (c *HTTPClient) proxyPath(path []byte) []byte {
return append([]byte(c.scheme+"://"+c.host), path...)
}

func (c *HTTPClient) isProxy() bool {
return c.proxy != nil
}

const (
// https://support.cloudflare.com/hc/en-us/articles/200171936-Error-520-Web-server-is-returning-an-unknown-error
HTTP_UNKNOWN_ERROR = "520"
Expand Down

0 comments on commit f6b0d5d

Please sign in to comment.