Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for http proxy #21

Merged
merged 6 commits into from
Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
go: [1.20.x, 1.19.x] # when updating versions, update it below too.
go: [1.21.x, 1.20.x] # when updating versions, update it below too.
runs-on: ${{ matrix.os }}
name: Test
steps:
Expand All @@ -35,11 +35,11 @@ jobs:

- name: Test
# TODO(henvic): Skip generating code coverage when not sending it to Coveralls to speed up testing.
continue-on-error: ${{ matrix.os != 'ubuntu-latest' || matrix.go != '1.20.x' }}
continue-on-error: ${{ matrix.os != 'ubuntu-latest' || matrix.go != '1.21.x' }}
run: go test -race -covermode atomic -coverprofile=profile.cov ./...

- name: Code coverage
if: ${{ matrix.os == 'ubuntu-latest' && matrix.go == '1.20.x' }}
if: ${{ matrix.os == 'ubuntu-latest' && matrix.go == '1.21.x' }}
uses: shogo82148/actions-goveralls@v1
with:
path-to-profile: profile.cov
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
- name: Set up Go 1.x
uses: actions/setup-go@v2
with:
go-version: "1.20.x"
go-version: "1.21.x"

- name: Check out code
uses: actions/checkout@v2
Expand Down
78 changes: 65 additions & 13 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ import (
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"mime"
"mime/multipart"
"net"
"net/http"
"net/http/httptest"
"net/http/httputil"
"net/url"
"os"
"regexp"
Expand Down Expand Up @@ -320,7 +320,7 @@ func TestOutgoingFilter(t *testing.T) {
ResponseHeader: true,
ResponseBody: true,
}
logger.SetOutput(ioutil.Discard)
logger.SetOutput(io.Discard)
logger.SetFilter(filteredURIs)
client := &http.Client{
Transport: logger.RoundTripper(newTransport()),
Expand Down Expand Up @@ -362,7 +362,7 @@ func TestOutgoingFilterPanicked(t *testing.T) {
ResponseHeader: true,
ResponseBody: true,
}
logger.SetOutput(ioutil.Discard)
logger.SetOutput(io.Discard)
logger.SetFilter(func(req *http.Request) (bool, error) {
panic("evil panic")
})
Expand Down Expand Up @@ -1202,7 +1202,7 @@ func (h multipartHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
t.Errorf("got size %d, wanted %d", header.Size, len(petition))
}

b, err := ioutil.ReadAll(file)
b, err := io.ReadAll(file)
if err != nil {
t.Errorf("server cannot read file sent over multipart: %v", err)
}
Expand Down Expand Up @@ -1250,6 +1250,57 @@ func TestOutgoingMultipartForm(t *testing.T) {
}
}

func TestOutgoingProxy(t *testing.T) {
t.Parallel()
ts := httptest.NewServer(&helloHandler{})
defer ts.Close()

proxyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
u, err := url.Parse(ts.URL)
if err != nil {
t.Fatal(err)
}
w.Header()["Date"] = nil
httputil.NewSingleHostReverseProxy(u).ServeHTTP(w, r)
}))
defer proxyServer.Close()

logger := &Logger{
RequestHeader: true,
RequestBody: true,
ResponseHeader: true,
ResponseBody: true,
}
var buf bytes.Buffer
logger.SetOutput(&buf)
client := ts.Client()
transport := client.Transport.(*http.Transport)
proxyURL, err := url.Parse(proxyServer.URL)
if err != nil {
t.Errorf("cannot parse proxy URL: %v", err)

}
transport.Proxy = http.ProxyURL(proxyURL)
client.Transport = logger.RoundTripper(transport)

req, err := http.NewRequest(http.MethodGet, ts.URL, nil)
if err != nil {
t.Errorf("cannot create request: %v", err)
}
req.Host = "example.com" // overriding the Host header to send
req.Header.Add("User-Agent", "Robot/0.1 crawler@example.com")
resp, err := client.Do(req)
if err != nil {
t.Errorf("cannot connect to the server: %v", err)
}
defer resp.Body.Close()
want := fmt.Sprintf(golden(t.Name()), ts.URL, proxyServer.URL)
if got := buf.String(); !regexp.MustCompile(want).MatchString(got) {
t.Errorf("logged HTTP request %s; want %s", got, want)
}
testBody(t, resp.Body, []byte("Hello, world!"))
}

func TestOutgoingTLS(t *testing.T) {
t.Parallel()
ts := httptest.NewTLSServer(&helloHandler{})
Expand Down Expand Up @@ -1325,7 +1376,7 @@ func TestOutgoingTLSInsecureSkipVerify(t *testing.T) {
func TestOutgoingTLSInvalidCertificate(t *testing.T) {
t.Parallel()
ts := httptest.NewTLSServer(&helloHandler{})
ts.Config.ErrorLog = log.New(ioutil.Discard, "", 0)
ts.Config.ErrorLog = log.New(io.Discard, "", 0)
defer ts.Close()

logger := &Logger{
Expand Down Expand Up @@ -1374,7 +1425,7 @@ func TestOutgoingTLSBadClientCertificate(t *testing.T) {
}
var buf bytes.Buffer
logger.SetOutput(&buf)
ts.Config.ErrorLog = log.New(ioutil.Discard, "", 0)
ts.Config.ErrorLog = log.New(io.Discard, "", 0)
client := ts.Client()
cert, err := tls.LoadX509KeyPair("testdata/cert-client.pem", "testdata/key-client.pem")
if err != nil {
Expand All @@ -1396,22 +1447,23 @@ func TestOutgoingTLSBadClientCertificate(t *testing.T) {
}
req.Host = "example.com" // overriding the Host header to send
req.Header.Add("User-Agent", "Robot/0.1 crawler@example.com")
if _, err = client.Do(req); err == nil || !strings.Contains(err.Error(), "bad certificate") {
var ue = &url.Error{}
if _, err = client.Do(req); err == nil || !errors.As(err, &ue) {
t.Errorf("got: %v, expected bad certificate error message", err)
}
want := fmt.Sprintf(golden(t.Name()), ts.URL)
if got := buf.String(); got != want {
want := fmt.Sprintf(golden(t.Name()), ts.URL, strings.SplitAfter(err.Error(), "remote error: tls: ")[1])
if got := buf.String(); !strings.Contains(got, want) {
t.Errorf("logged HTTP request %s; want %s", got, want)
}
}

func TestOutgoingHTTP2MutualTLS(t *testing.T) {
t.Parallel()
caCert, err := ioutil.ReadFile("testdata/cert.pem")
caCert, err := os.ReadFile("testdata/cert.pem")
if err != nil {
panic(err)
}
clientCert, err := ioutil.ReadFile("testdata/cert-client.pem")
clientCert, err := os.ReadFile("testdata/cert-client.pem")
if err != nil {
panic(err)
}
Expand Down Expand Up @@ -1505,11 +1557,11 @@ func TestOutgoingHTTP2MutualTLS(t *testing.T) {

func TestOutgoingHTTP2MutualTLSNoSafetyLogging(t *testing.T) {
t.Parallel()
caCert, err := ioutil.ReadFile("testdata/cert.pem")
caCert, err := os.ReadFile("testdata/cert.pem")
if err != nil {
panic(err)
}
clientCert, err := ioutil.ReadFile("testdata/cert-client.pem")
clientCert, err := os.ReadFile("testdata/cert-client.pem")
if err != nil {
panic(err)
}
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module github.com/henvic/httpretty

go 1.16
go 1.20

require golang.org/x/tools v0.1.10
require golang.org/x/tools v0.14.0
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
11 changes: 10 additions & 1 deletion httpretty.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,16 @@ func (r roundTripper) RoundTrip(req *http.Request) (resp *http.Response, err err
if !l.SkipRequestInfo {
p.printRequestInfo(req)
}
if transport, ok := tripper.(*http.Transport); ok && transport.TLSClientConfig != nil {
// Try to get some information from transport
transport, ok := tripper.(*http.Transport)
// If proxy is used, then print information about proxy server
if ok && transport.Proxy != nil {
proxyUrl, err := transport.Proxy(req)
if proxyUrl != nil && err == nil {
p.printf("* Using proxy: %s\n", p.format(color.FgBlue, proxyUrl.String()))
}
}
if ok && transport.TLSClientConfig != nil {
tlsClientConfig = transport.TLSClientConfig
if tlsClientConfig.InsecureSkipVerify {
p.printf("* Skipping TLS verification: %s\n",
Expand Down
3 changes: 1 addition & 2 deletions httpretty_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"bytes"
_ "embed"
"io"
"io/ioutil"
"net"
"net/http"
"net/url"
Expand Down Expand Up @@ -219,7 +218,7 @@ func TestPrintResponseNil(t *testing.T) {

func testBody(t *testing.T, r io.Reader, want []byte) {
t.Helper()
got, err := ioutil.ReadAll(r)
got, err := io.ReadAll(r)
if err != nil {
t.Errorf("expected no error reading response body, got %v instead", err)
}
Expand Down
7 changes: 3 additions & 4 deletions printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"crypto/x509"
"fmt"
"io"
"io/ioutil"
"mime"
"net"
"net/http"
Expand Down Expand Up @@ -196,7 +195,7 @@ func (p *printer) printResponseBodyOut(resp *http.Response) {
tee := io.TeeReader(resp.Body, &buf)
defer resp.Body.Close()
defer func() {
resp.Body = ioutil.NopCloser(&buf)
resp.Body = io.NopCloser(&buf)
}()
p.printBodyReader(contentType, tee)
}
Expand Down Expand Up @@ -431,7 +430,7 @@ func (p *printer) printResponseHeader(proto, status string, h http.Header) {

func (p *printer) printBodyReader(contentType string, r io.Reader) {
mediatype, _, _ := mime.ParseMediaType(contentType)
body, err := ioutil.ReadAll(r)
body, err := io.ReadAll(r)
if err != nil {
p.printf("* cannot read body: %v\n", p.format(color.FgRed, err.Error()))
return
Expand Down Expand Up @@ -571,7 +570,7 @@ func (p *printer) printRequestBody(req *http.Request) {
tee := io.TeeReader(req.Body, &buf)
defer req.Body.Close()
defer func() {
req.Body = ioutil.NopCloser(&buf)
req.Body = io.NopCloser(&buf)
}()
p.printBodyReader(contentType, tee)
return
Expand Down
10 changes: 5 additions & 5 deletions server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ import (
"crypto/x509"
"errors"
"fmt"
"io/ioutil"
"mime"
"mime/multipart"
"net"
"net/http"
"net/http/httptest"
"net/url"
"os"
"regexp"
"strings"
"sync"
Expand Down Expand Up @@ -1074,11 +1074,11 @@ func TestIncomingTLS(t *testing.T) {

func TestIncomingMutualTLS(t *testing.T) {
t.Parallel()
caCert, err := ioutil.ReadFile("testdata/cert.pem")
caCert, err := os.ReadFile("testdata/cert.pem")
if err != nil {
panic(err)
}
clientCert, err := ioutil.ReadFile("testdata/cert-client.pem")
clientCert, err := os.ReadFile("testdata/cert-client.pem")
if err != nil {
panic(err)
}
Expand Down Expand Up @@ -1174,11 +1174,11 @@ func TestIncomingMutualTLS(t *testing.T) {

func TestIncomingMutualTLSNoSafetyLogging(t *testing.T) {
t.Parallel()
caCert, err := ioutil.ReadFile("testdata/cert.pem")
caCert, err := os.ReadFile("testdata/cert.pem")
if err != nil {
panic(err)
}
clientCert, err := ioutil.ReadFile("testdata/cert-client.pem")
clientCert, err := os.ReadFile("testdata/cert-client.pem")
if err != nil {
panic(err)
}
Expand Down
14 changes: 13 additions & 1 deletion testdata/log.txtar
Original file line number Diff line number Diff line change
Expand Up @@ -644,7 +644,7 @@ Hello, world!
> Host: example.com
> User-Agent: Robot/0.1 crawler@example.com

* remote error: tls: bad certificate
* remote error: tls: %s
-- TestOutgoingTLSInsecureSkipVerify --
^\* Request to %s
\* Skipping TLS verification: connection is susceptible to man-in-the-middle attacks\.
Expand Down Expand Up @@ -681,3 +681,15 @@ Hello, world!
< Content-Type: text/plain; charset=utf-8

* body is too long (9846 bytes) to print, skipping (longer than 5000 bytes)
-- TestOutgoingProxy --
\* Request to %s
\* Using proxy: %s
> GET / HTTP/1.1
> Host: example.com
> User-Agent: Robot/0.1 crawler@example.com

< HTTP/1.1 200 OK
< Content-Length: 13
< Content-Type: text/plain; charset=utf-8

Hello, world!
Loading