Skip to content

Commit fa7f5b7

Browse files
authored
feat: refactor HTTP client to use new proxy package with timeout options (#215)
- Add timeout configuration option to `NewAnthropic` function - Remove unused imports and replace `golang.org/x/net/proxy` with `github.com/appleboy/CodeGPT/proxy` in `anthropic.go` - Refactor HTTP client creation to use new proxy package in `anthropic.go` - Add `WithTimeout` option function in `options.go` - Add `timeout` field to `config` struct in `options.go` - Remove unused imports and replace `golang.org/x/net/proxy` with `github.com/appleboy/CodeGPT/proxy` in `openai.go` - Refactor HTTP client creation to use new proxy package in `openai.go` - Create new `proxy` package with options and proxy configuration functions - Add `defaultHeaderTransport` for setting default headers in HTTP client in `proxy.go` Signed-off-by: appleboy <[email protected]>
1 parent 9c9399d commit fa7f5b7

File tree

6 files changed

+209
-57
lines changed

6 files changed

+209
-57
lines changed

cmd/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ func NewAnthropic(ctx context.Context) (*anthropic.Client, error) {
6565
anthropic.WithProxyURL(viper.GetString("openai.proxy")),
6666
anthropic.WithSocksURL(viper.GetString("openai.socks")),
6767
anthropic.WithSkipVerify(viper.GetBool("openai.skip_verify")),
68+
anthropic.WithTimeout(viper.GetDuration("openai.timeout")),
6869
)
6970
}
7071

provider/anthropic/anthropic.go

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,13 @@ package anthropic
22

33
import (
44
"context"
5-
"crypto/tls"
65
"encoding/json"
76
"errors"
87
"fmt"
98
"net/http"
10-
"net/url"
119

1210
"github.com/appleboy/CodeGPT/core"
13-
"golang.org/x/net/proxy"
11+
"github.com/appleboy/CodeGPT/proxy"
1412

1513
"github.com/appleboy/com/convert"
1614
"github.com/liushuangls/go-anthropic/v2"
@@ -137,26 +135,15 @@ func New(opts ...Option) (c *Client, err error) {
137135
if err := cfg.valid(); err != nil {
138136
return nil, err
139137
}
140-
// Create a new HTTP transport with optional TLS configuration.
141-
tr := &http.Transport{
142-
TLSClientConfig: &tls.Config{InsecureSkipVerify: cfg.skipVerify}, //nolint:gosec
143-
}
144-
145-
// Create a new HTTP client with the specified timeout and proxy, if any.
146-
httpClient := &http.Client{Transport: tr}
147138

148-
if cfg.proxyURL != "" {
149-
proxyURL, err := url.Parse(cfg.proxyURL)
150-
if err != nil {
151-
return nil, fmt.Errorf("can't parse the proxy URL: %w", err)
152-
}
153-
tr.Proxy = http.ProxyURL(proxyURL)
154-
} else if cfg.socksURL != "" {
155-
dialer, err := proxy.SOCKS5("tcp", cfg.socksURL, nil, proxy.Direct)
156-
if err != nil {
157-
return nil, fmt.Errorf("can't connect to the proxy: %w", err)
158-
}
159-
tr.DialContext = dialer.(proxy.ContextDialer).DialContext
139+
httpClient, err := proxy.New(
140+
proxy.WithProxyURL(cfg.proxyURL),
141+
proxy.WithSocksURL(cfg.socksURL),
142+
proxy.WithSkipVerify(cfg.skipVerify),
143+
proxy.WithTimeout(cfg.timeout),
144+
)
145+
if err != nil {
146+
return nil, fmt.Errorf("can't create a new HTTP client: %w", err)
160147
}
161148

162149
// Create a new client instance with the necessary fields.

provider/anthropic/options.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package anthropic
22

33
import (
44
"errors"
5+
"time"
56

67
"github.com/liushuangls/go-anthropic/v2"
78
)
@@ -102,6 +103,15 @@ func WithSkipVerify(val bool) Option {
102103
})
103104
}
104105

106+
// WithTimeout returns a new Option that sets the timeout for the client configuration.
107+
// It takes a time.Duration value representing the timeout duration.
108+
// It returns an optionFunc that sets the timeout field of the configuration to the provided value.
109+
func WithTimeout(val time.Duration) Option {
110+
return optionFunc(func(c *config) {
111+
c.timeout = val
112+
})
113+
}
114+
105115
// config is a struct that stores configuration options for the instrumentation.
106116
type config struct {
107117
apiKey string
@@ -112,6 +122,7 @@ type config struct {
112122
proxyURL string
113123
socksURL string
114124
skipVerify bool
125+
timeout time.Duration
115126
}
116127

117128
// valid checks whether a config object is valid, returning an error if it is not.

provider/openai/openai.go

Lines changed: 10 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,12 @@ package openai
22

33
import (
44
"context"
5-
"crypto/tls"
6-
"fmt"
7-
"net/http"
8-
"net/url"
95
"regexp"
106

117
"github.com/appleboy/CodeGPT/core"
8+
"github.com/appleboy/CodeGPT/proxy"
129

1310
openai "github.com/sashabaranov/go-openai"
14-
"golang.org/x/net/proxy"
1511
)
1612

1713
// DefaultModel is the default OpenAI model to use if one is not provided.
@@ -221,36 +217,15 @@ func New(opts ...Option) (*Client, error) {
221217
c.BaseURL = cfg.baseURL
222218
}
223219

224-
// Create a new HTTP transport with optional TLS configuration.
225-
tr := &http.Transport{
226-
TLSClientConfig: &tls.Config{InsecureSkipVerify: cfg.skipVerify}, //nolint:gosec
227-
}
228-
229-
// Create a new HTTP client with the specified timeout.
230-
httpClient := &http.Client{
231-
Timeout: cfg.timeout,
232-
Transport: tr,
233-
}
234-
235-
// Configure proxy settings if provided.
236-
if cfg.proxyURL != "" {
237-
proxyURL, err := url.Parse(cfg.proxyURL)
238-
if err != nil {
239-
return nil, fmt.Errorf("invalid proxy URL: %s", err)
240-
}
241-
tr.Proxy = http.ProxyURL(proxyURL)
242-
} else if cfg.socksURL != "" {
243-
dialer, err := proxy.SOCKS5("tcp", cfg.socksURL, nil, proxy.Direct)
244-
if err != nil {
245-
return nil, fmt.Errorf("can't connect to the SOCKS5 proxy: %s", err)
246-
}
247-
tr.DialContext = dialer.(proxy.ContextDialer).DialContext
248-
}
249-
250-
// Set the HTTP client to use the default header transport with the specified headers.
251-
httpClient.Transport = &DefaultHeaderTransport{
252-
Origin: tr,
253-
Header: NewHeaders(cfg.headers),
220+
httpClient, err := proxy.New(
221+
proxy.WithProxyURL(cfg.proxyURL),
222+
proxy.WithSocksURL(cfg.socksURL),
223+
proxy.WithSkipVerify(cfg.skipVerify),
224+
proxy.WithTimeout(cfg.timeout),
225+
proxy.WithHeaders(cfg.headers),
226+
)
227+
if err != nil {
228+
return nil, err
254229
}
255230

256231
// Set the OpenAI client to use the default configuration with Azure-specific options, if the provider is Azure.

proxy/options.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package proxy
2+
3+
import (
4+
"time"
5+
)
6+
7+
// Option is an interface that specifies instrumentation configuration options.
8+
type Option interface {
9+
apply(*config)
10+
}
11+
12+
// optionFunc is a type of function that can be used to implement the Option interface.
13+
// It takes a pointer to a config struct and modifies it.
14+
type optionFunc func(*config)
15+
16+
// Ensure that optionFunc satisfies the Option interface.
17+
var _ Option = (*optionFunc)(nil)
18+
19+
// The apply method of optionFunc type is implemented here to modify the config struct based on the function passed.
20+
func (o optionFunc) apply(c *config) {
21+
o(c)
22+
}
23+
24+
// WithProxyURL is a function that returns an Option, which sets the proxyURL field of the config struct.
25+
func WithProxyURL(val string) Option {
26+
return optionFunc(func(c *config) {
27+
c.proxyURL = val
28+
})
29+
}
30+
31+
// WithSocksURL is a function that returns an Option, which sets the socksURL field of the config struct.
32+
func WithSocksURL(val string) Option {
33+
return optionFunc(func(c *config) {
34+
c.socksURL = val
35+
})
36+
}
37+
38+
// WithTimeout returns a new Option that sets the timeout for the client configuration.
39+
// It takes a time.Duration value representing the timeout duration.
40+
// It returns an optionFunc that sets the timeout field of the configuration to the provided value.
41+
func WithTimeout(val time.Duration) Option {
42+
return optionFunc(func(c *config) {
43+
c.timeout = val
44+
})
45+
}
46+
47+
// WithHeaders returns a new Option that sets the headers for the http client configuration.
48+
func WithHeaders(headers []string) Option {
49+
return optionFunc(func(c *config) {
50+
c.headers = headers
51+
})
52+
}
53+
54+
// WithSkipVerify returns a new Option that sets the insecure flag for the http client configuration.
55+
func WithSkipVerify(insecure bool) Option {
56+
return optionFunc(func(c *config) {
57+
c.insecure = insecure
58+
})
59+
}
60+
61+
// config is a struct that stores configuration options for the instrumentation.
62+
type config struct {
63+
proxyURL string
64+
socksURL string
65+
timeout time.Duration
66+
insecure bool
67+
headers []string
68+
}
69+
70+
// newConfig creates a new config object with default values, and applies the given options.
71+
func newConfig(opts ...Option) *config {
72+
// Create a new config object with default values.
73+
c := &config{
74+
timeout: 30 * time.Second,
75+
insecure: false,
76+
}
77+
78+
// Apply each of the given options to the config object.
79+
for _, opt := range opts {
80+
opt.apply(c)
81+
}
82+
83+
// Return the resulting config object.
84+
return c
85+
}

proxy/proxy.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package proxy
2+
3+
import (
4+
"crypto/tls"
5+
"fmt"
6+
"net/http"
7+
"net/url"
8+
"strings"
9+
10+
"golang.org/x/net/proxy"
11+
)
12+
13+
// convertHeaders creates a new http.Header from the given slice of headers.
14+
func convertHeaders(headers []string) http.Header {
15+
h := make(http.Header)
16+
for _, header := range headers {
17+
// split header into key and value with = as delimiter
18+
vals := strings.Split(header, "=")
19+
if len(vals) != 2 {
20+
continue
21+
}
22+
h.Add(vals[0], vals[1])
23+
}
24+
return h
25+
}
26+
27+
// DefaultHeaderTransport is an http.RoundTripper that adds the given headers to
28+
type defaultHeaderTransport struct {
29+
origin http.RoundTripper
30+
header http.Header
31+
}
32+
33+
// RoundTrip implements the http.RoundTripper interface.
34+
func (t *defaultHeaderTransport) RoundTrip(req *http.Request) (*http.Response, error) {
35+
for key, values := range t.header {
36+
for _, value := range values {
37+
req.Header.Add(key, value)
38+
}
39+
}
40+
return t.origin.RoundTrip(req)
41+
}
42+
43+
// New creates a new HTTP client with the provided options.
44+
// It configures the client with optional TLS settings, proxy settings, and custom headers.
45+
//
46+
// Parameters:
47+
//
48+
// opts - A variadic list of Option functions to configure the client.
49+
//
50+
// Returns:
51+
//
52+
// *http.Client - A pointer to the configured HTTP client.
53+
// error - An error if the proxy URL is invalid or if there is an issue connecting to the SOCKS5 proxy.
54+
func New(opts ...Option) (*http.Client, error) {
55+
cfg := newConfig(opts...)
56+
if cfg == nil {
57+
return nil, fmt.Errorf("configuration is nil")
58+
}
59+
60+
// Create a new HTTP transport with optional TLS configuration.
61+
tr := &http.Transport{
62+
TLSClientConfig: &tls.Config{InsecureSkipVerify: cfg.insecure}, //nolint:gosec
63+
}
64+
65+
// Create a new HTTP client with the specified timeout.
66+
httpClient := &http.Client{
67+
Timeout: cfg.timeout,
68+
Transport: tr,
69+
}
70+
71+
// Configure proxy settings if provided.
72+
if cfg.proxyURL != "" {
73+
proxyURL, err := url.Parse(cfg.proxyURL)
74+
if err != nil {
75+
return nil, fmt.Errorf("invalid proxy URL: %s", err)
76+
}
77+
tr.Proxy = http.ProxyURL(proxyURL)
78+
} else if cfg.socksURL != "" {
79+
dialer, err := proxy.SOCKS5("tcp", cfg.socksURL, nil, proxy.Direct)
80+
if err != nil {
81+
return nil, fmt.Errorf("can't connect to the SOCKS5 proxy: %s", err)
82+
}
83+
tr.DialContext = dialer.(proxy.ContextDialer).DialContext
84+
}
85+
86+
// Set the HTTP client to use the default header transport with the specified headers.
87+
httpClient.Transport = &defaultHeaderTransport{
88+
origin: tr,
89+
header: convertHeaders(cfg.headers),
90+
}
91+
92+
return httpClient, nil
93+
}

0 commit comments

Comments
 (0)