diff --git a/client.go b/client.go index b7802781..47d61670 100644 --- a/client.go +++ b/client.go @@ -19,6 +19,8 @@ import ( "github.com/andersfylling/disgord/internal/httd" ) +var DefaultHttpClient = &http.Client{} + // New create a Client. But panics on configuration/setup errors. func New(conf Config) *Client { client, err := NewClient(context.Background(), conf) @@ -58,7 +60,7 @@ func createClient(ctx context.Context, conf *Config) (c *Client, err error) { } if conf.HTTPClient == nil { // WARNING: do not set http.Client.Timeout (!) - conf.HTTPClient = &http.Client{} + conf.HTTPClient = DefaultHttpClient } else if conf.HTTPClient.Timeout > 0 { // https://github.com/nhooyr/websocket/issues/67 return nil, errors.New("do not set timeout in the http.Client, use context.Context instead") @@ -70,6 +72,16 @@ func createClient(ctx context.Context, conf *Config) (c *Client, err error) { }, } } + if conf.HttpClient == nil { + if conf.HTTPClient != nil { + conf.HttpClient = conf.HTTPClient + } else { + return nil, errors.New("missing configured HTTP client") + } + } + if conf.WebsocketHttpClient == nil { + conf.WebsocketHttpClient = DefaultHttpClient + } if conf.Intents > 0 { conf.DMIntents |= conf.Intents @@ -111,7 +123,7 @@ func createClient(ctx context.Context, conf *Config) (c *Client, err error) { UserAgentSourceURL: constant.GitHubURL, UserAgentVersion: constant.Version, UserAgentExtra: conf.ProjectName, - HTTPClient: conf.HTTPClient, + HttpClient: conf.HTTPClient, CancelRequestWhenRateLimited: conf.CancelRequestWhenRateLimited, RESTBucketManager: conf.RESTBucketManager, }) @@ -148,17 +160,16 @@ func createClient(ctx context.Context, conf *Config) (c *Client, err error) { // create a disgord Client/instance/session c = &Client{ - shutdownChan: conf.shutdownChan, - config: conf, - httpClient: conf.HTTPClient, - proxy: conf.Proxy, - botToken: conf.BotToken, - dispatcher: dispatch, - req: httdClient, - cache: cache, - log: conf.Logger, - pool: newPools(), - eventChan: evtChan, + shutdownChan: conf.shutdownChan, + config: conf, + WebsocketHttpClient: conf.WebsocketHttpClient, + botToken: conf.BotToken, + dispatcher: dispatch, + req: httdClient, + cache: cache, + log: conf.Logger, + pool: newPools(), + eventChan: evtChan, } c.handlers.c = c // parent reference c.dispatcher.addSessionInstance(c) @@ -182,6 +193,10 @@ func createClient(ctx context.Context, conf *Config) (c *Client, err error) { type ShardConfig = gateway.ShardConfig +type HttpClientDoer interface { + Do(req *http.Request) (*http.Response, error) +} + // Config Configuration for the Disgord Client type Config struct { // ################################################ @@ -192,9 +207,16 @@ type Config struct { // ## what they are doing. // ## // ################################################ - BotToken string + BotToken string + + HttpClient HttpClientDoer + WebsocketHttpClient *http.Client + + // Deprecated: use WebsocketHttpClient and HttpClient HTTPClient *http.Client - Proxy proxy.Dialer + + // Deprecated: use WebsocketHttpClient and HttpClient + Proxy proxy.Dialer // Deprecated: use DMIntents (values here are copied to DMIntents for now) // For direct communication with you bot you must specify intents @@ -291,9 +313,7 @@ type Client struct { // req holds the rate limiting logic and error parsing unique for Discord req *httd.Client - // http Client used for connections - httpClient *http.Client - proxy proxy.Dialer + WebsocketHttpClient *http.Client shardManager gateway.ShardManager eventChan chan *gateway.Event diff --git a/docs/examples/proxy/bot.go b/docs/examples/proxy/bot.go index 5ec13ca6..ff3fc35d 100644 --- a/docs/examples/proxy/bot.go +++ b/docs/examples/proxy/bot.go @@ -1,6 +1,9 @@ package main import ( + "context" + "net" + "net/http" "os" "golang.org/x/net/proxy" @@ -11,17 +14,32 @@ import ( // In the event that the Discord connections need to be routed // through a proxy, you can do so by using this approach. In // this example we will be using SOCKS5, but any custom -// implementation can be used as long as they satisfy the -// proxy.Dialer interface. +// implementation can be used. You just configure your own http client. +// +// For REST methods the only Do method is required. So any configuration, libraries, whatever that +// implements the Do method is good enough. +// +// For websocket connection you must specify the WebsocketHttpClient config option. Currently there is a issue +// when specifying http.Client timeouts for websocket, which is why you have the option to specify both. +// When a WebsocketHttpClient is not specified, a default config is utilised. func main() { p, err := proxy.SOCKS5("tcp", "localhost:8080", nil, proxy.Direct) if err != nil { panic(err) } + + httpClient := &http.Client{ + Transport: &http.Transport{ + DialContext: func(ctx context.Context, network, addr string) (conn net.Conn, e error) { + return p.Dial(network, addr) + }, + }, + } client := disgord.New(disgord.Config{ - BotToken: os.Getenv("DISCORD_TOKEN"), - Proxy: p, // Anything satisfying the proxy.Dialer interface will work + BotToken: os.Getenv("DISCORD_TOKEN"), + HttpClient: httpClient, // REST requests with proxy support + WebsocketHttpClient: httpClient, // Websocket setup with proxy support }) defer client.Gateway().StayConnectedUntilInterrupted() } diff --git a/gateway.go b/gateway.go index 050884a7..6e64b806 100644 --- a/gateway.go +++ b/gateway.go @@ -64,6 +64,7 @@ func (g gatewayQueryBuilder) Connect() (err error) { } shardMngrConf := gateway.ShardManagerConfig{ + HTTPClient: g.client.WebsocketHttpClient, ShardConfig: g.client.config.ShardConfig, Logger: g.client.config.Logger, ShutdownChan: g.client.config.shutdownChan, diff --git a/internal/httd/client.go b/internal/httd/client.go index 928335ff..60dee89c 100644 --- a/internal/httd/client.go +++ b/internal/httd/client.go @@ -81,11 +81,15 @@ func (e *ErrREST) Error() string { return fmt.Sprintf("%s\n%s\n%s => %+v", e.Msg, e.Suggestion, e.HashedEndpoint, e.Bucket) } +type HttpClientDoer interface { + Do(req *http.Request) (*http.Response, error) +} + // Client for handling Discord REST requests type Client struct { url string // base url with API version reqHeader http.Header - httpClient *http.Client + httpClient HttpClientDoer cancelRequestWhenRateLimited bool buckets RESTBucketManager } @@ -131,10 +135,8 @@ func NewClient(conf *Config) (*Client, error) { return nil, errors.New("no Discord Bot Token was provided") } - // if no http client was provided, create a new one - if conf.HTTPClient == nil { - // no need for a timeout, everything uses context.Context now - conf.HTTPClient = &http.Client{} + if conf.HttpClient == nil { + return nil, errors.New("missing http client") } if conf.RESTBucketManager == nil { @@ -160,7 +162,7 @@ func NewClient(conf *Config) (*Client, error) { return &Client{ url: BaseURL + "/v" + strconv.Itoa(conf.APIVersion), reqHeader: header, - httpClient: conf.HTTPClient, + httpClient: conf.HttpClient, buckets: conf.RESTBucketManager, }, nil } @@ -171,7 +173,7 @@ type Config struct { APIVersion int BotToken string - HTTPClient *http.Client + HttpClient HttpClientDoer CancelRequestWhenRateLimited bool