Skip to content

Commit 9509716

Browse files
Merge pull request #21 from rcchopra/hyp-17516
proxy support
2 parents 523c18d + 902019a commit 9509716

File tree

3 files changed

+199
-0
lines changed

3 files changed

+199
-0
lines changed

scm/driver/github/github.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,25 @@ import (
99
"bytes"
1010
"context"
1111
"encoding/json"
12+
"net/http"
1213
"net/url"
1314
"strconv"
1415
"strings"
1516

1617
"github.com/drone/go-scm/scm"
18+
"github.com/drone/go-scm/scm/transport/proxy"
1719
)
1820

1921
// New returns a new GitHub API client.
22+
// This function maintains backward compatibility and creates a client without proxy.
2023
func New(uri string) (*scm.Client, error) {
24+
return NewWithProxy(uri, "")
25+
}
26+
27+
// NewWithProxy returns a new GitHub API client with optional proxy support.
28+
// If proxyURL is empty or nil, no proxy will be used.
29+
// If proxyURL is provided, all HTTP requests will be routed through the specified proxy.
30+
func NewWithProxy(uri, proxyURL string) (*scm.Client, error) {
2131
base, err := url.Parse(uri)
2232
if err != nil {
2333
return nil, err
@@ -47,6 +57,15 @@ func New(uri string) (*scm.Client, error) {
4757
client.Reviews = &reviewService{client}
4858
client.Users = &userService{client}
4959
client.Webhooks = &webhookService{client}
60+
61+
if proxyURL != "" {
62+
transport, err := proxy.NewTransport(http.DefaultTransport, proxyURL)
63+
if err != nil {
64+
return nil, err
65+
}
66+
client.Client.Client = &http.Client{Transport: transport}
67+
}
68+
5069
return client.Client, nil
5170
}
5271

@@ -57,6 +76,15 @@ func NewDefault() *scm.Client {
5776
return client
5877
}
5978

79+
// NewDefaultWithProxy returns a new GitHub API client using the
80+
// default api.github.com address with optional proxy support.
81+
// If proxyURL is empty or nil, no proxy will be used.
82+
// If proxyURL is provided, all HTTP requests will be routed through the specified proxy.
83+
func NewDefaultWithProxy(proxyURL string) *scm.Client {
84+
client, _ := NewWithProxy("https://api.github.com", proxyURL)
85+
return client
86+
}
87+
6088
// wraper wraps the Client to provide high level helper functions
6189
// for making http requests and unmarshaling the response.
6290
type wrapper struct {

scm/transport/proxy/proxy.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright 2018 Drone.IO Inc. 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.
4+
5+
package proxy
6+
7+
import (
8+
"net/http"
9+
"net/url"
10+
)
11+
12+
// Transport is an http.RoundTripper that makes HTTP
13+
// requests through a proxy, wrapping a base RoundTripper
14+
type Transport struct {
15+
Base http.RoundTripper
16+
ProxyURL *url.URL
17+
}
18+
19+
// RoundTrip makes the request through the configured proxy.
20+
func (t *Transport) RoundTrip(r *http.Request) (*http.Response, error) {
21+
// If no proxy is configured, use the base transport
22+
if t.ProxyURL == nil {
23+
return t.base().RoundTrip(r)
24+
}
25+
26+
// Create a new transport with the proxy configuration
27+
proxyTransport := &http.Transport{
28+
Proxy: func(_ *http.Request) (*url.URL, error) {
29+
return t.ProxyURL, nil
30+
},
31+
}
32+
33+
// If we have a base transport, copy its configuration
34+
if t.Base != nil {
35+
if baseTransport, ok := t.Base.(*http.Transport); ok {
36+
proxyTransport.TLSClientConfig = baseTransport.TLSClientConfig
37+
proxyTransport.DialContext = baseTransport.DialContext
38+
proxyTransport.MaxIdleConns = baseTransport.MaxIdleConns
39+
proxyTransport.MaxIdleConnsPerHost = baseTransport.MaxIdleConnsPerHost
40+
proxyTransport.IdleConnTimeout = baseTransport.IdleConnTimeout
41+
proxyTransport.TLSHandshakeTimeout = baseTransport.TLSHandshakeTimeout
42+
proxyTransport.ExpectContinueTimeout = baseTransport.ExpectContinueTimeout
43+
}
44+
}
45+
46+
return proxyTransport.RoundTrip(r)
47+
}
48+
49+
// base returns the base transport. If no base transport
50+
// is configured, the default transport is returned.
51+
func (t *Transport) base() http.RoundTripper {
52+
if t.Base != nil {
53+
return t.Base
54+
}
55+
return http.DefaultTransport
56+
}
57+
58+
// NewTransport creates a new proxy transport with the given proxy URL.
59+
// If proxyURL is empty or nil, it returns the base transport unchanged.
60+
func NewTransport(base http.RoundTripper, proxyURL string) (http.RoundTripper, error) {
61+
if proxyURL == "" {
62+
return base, nil
63+
}
64+
65+
parsedURL, err := url.Parse(proxyURL)
66+
if err != nil {
67+
return nil, err
68+
}
69+
70+
return &Transport{
71+
Base: base,
72+
ProxyURL: parsedURL,
73+
}, nil
74+
}

scm/transport/proxy/proxy_test.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Copyright 2018 Drone.IO Inc. 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.
4+
5+
package proxy
6+
7+
import (
8+
"net/http"
9+
"net/http/httptest"
10+
"net/url"
11+
"testing"
12+
)
13+
14+
func TestNewTransport_EmptyProxyURL(t *testing.T) {
15+
base := http.DefaultTransport
16+
transport, err := NewTransport(base, "")
17+
if err != nil {
18+
t.Fatalf("Expected no error for empty proxy URL, got: %v", err)
19+
}
20+
if transport != base {
21+
t.Error("Expected transport to be the same as base for empty proxy URL")
22+
}
23+
}
24+
25+
func TestNewTransport_InvalidProxyURL(t *testing.T) {
26+
base := http.DefaultTransport
27+
_, err := NewTransport(base, "://invalid")
28+
if err == nil {
29+
t.Error("Expected error for invalid proxy URL")
30+
}
31+
}
32+
33+
func TestNewTransport_ValidProxyURL(t *testing.T) {
34+
base := http.DefaultTransport
35+
proxyURL := "http://proxy.example.com:8080"
36+
transport, err := NewTransport(base, proxyURL)
37+
if err != nil {
38+
t.Fatalf("Expected no error for valid proxy URL, got: %v", err)
39+
}
40+
41+
proxyTransport, ok := transport.(*Transport)
42+
if !ok {
43+
t.Fatal("Expected transport to be of type *Transport")
44+
}
45+
46+
expectedURL, _ := url.Parse(proxyURL)
47+
if proxyTransport.ProxyURL.String() != expectedURL.String() {
48+
t.Errorf("Expected proxy URL %s, got %s", expectedURL.String(), proxyTransport.ProxyURL.String())
49+
}
50+
}
51+
52+
func TestTransport_RoundTrip_NoProxy(t *testing.T) {
53+
// Create a test server
54+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
55+
w.WriteHeader(http.StatusOK)
56+
w.Write([]byte("OK"))
57+
}))
58+
defer server.Close()
59+
60+
transport := &Transport{
61+
Base: http.DefaultTransport,
62+
ProxyURL: nil,
63+
}
64+
65+
client := &http.Client{Transport: transport}
66+
resp, err := client.Get(server.URL)
67+
if err != nil {
68+
t.Fatalf("Expected no error, got: %v", err)
69+
}
70+
defer resp.Body.Close()
71+
72+
if resp.StatusCode != http.StatusOK {
73+
t.Errorf("Expected status OK, got: %d", resp.StatusCode)
74+
}
75+
}
76+
77+
func TestTransport_RoundTrip_WithProxy(t *testing.T) {
78+
// Create a test server
79+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
80+
w.WriteHeader(http.StatusOK)
81+
w.Write([]byte("OK"))
82+
}))
83+
defer server.Close()
84+
85+
proxyURL, _ := url.Parse("http://proxy.example.com:8080")
86+
transport := &Transport{
87+
Base: http.DefaultTransport,
88+
ProxyURL: proxyURL,
89+
}
90+
91+
// This test verifies that the transport is configured with the proxy
92+
// The actual proxy behavior would require a real proxy server for testing
93+
// We're just ensuring the transport is properly configured
94+
if transport.ProxyURL.String() != proxyURL.String() {
95+
t.Errorf("Expected proxy URL %s, got %s", proxyURL.String(), transport.ProxyURL.String())
96+
}
97+
}

0 commit comments

Comments
 (0)