Skip to content

Commit

Permalink
feat: optimize rest client
Browse files Browse the repository at this point in the history
  • Loading branch information
amatsagu committed Mar 2, 2024
1 parent 77daf14 commit 2a70b09
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 39 deletions.
6 changes: 3 additions & 3 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (

type ClientOptions struct {
PublicKey string // Hash like key used to verify incoming payloads from Discord. (default: <nil>)
Rest Rest
Rest *RestClient
HTTPServer HTTPServer
HTTPServeMux HTTPServeMux
PreCommandHook func(cmd *Command, itx *CommandInteraction) bool // Function that runs before each command. Return type signals whether to continue command execution (return with false to stop early).
Expand All @@ -23,7 +23,7 @@ type ClientOptions struct {
}

type Client struct {
Rest Rest
Rest *RestClient
ApplicationID Snowflake
PublicKey ed25519.PublicKey
httpServer HTTPServer
Expand Down Expand Up @@ -241,7 +241,7 @@ func NewClient(options ClientOptions) *Client {
panic("failed to decode discord's public key (check if it's correct key): " + err.Error())
}

botUserID, err := extractUserIDFromToken(options.Rest.Token())
botUserID, err := extractUserIDFromToken(options.Rest.Token)
if err != nil {
panic("failed to extract bot user ID from bot token: " + err.Error())
}
Expand Down
63 changes: 29 additions & 34 deletions rest.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,12 @@ import (
"time"
)

var _ Rest = (*iRest)(nil)

type Rest interface {
Request(method string, route string, jsonPayload interface{}) ([]byte, error)
Token() string
}

type HTTPClient interface {
Do(req *http.Request) (*http.Response, error)
}

type iRest struct {
mu sync.RWMutex
token string
httpClient HTTPClient
lockedTo time.Time
maxReconnectAttempts uint8
type RestClient struct {
HTTPClient *http.Client
Token string
MaxRetries uint8
mu sync.RWMutex
lockedTo time.Time
}

type rateLimitError struct {
Expand All @@ -36,7 +25,7 @@ type rateLimitError struct {
RetryAfter float32 `json:"retry_after"`
}

func (rest *iRest) Request(method string, route string, jsonPayload interface{}) ([]byte, error) {
func (rest *RestClient) Request(method string, route string, jsonPayload interface{}) ([]byte, error) {
if !rest.lockedTo.IsZero() {
timeLeft := time.Until(rest.lockedTo)
if timeLeft > 0 {
Expand All @@ -45,25 +34,19 @@ func (rest *iRest) Request(method string, route string, jsonPayload interface{})
}

var i uint8 = 0
for i < rest.maxReconnectAttempts {
for i < rest.MaxRetries {
i++
rest.mu.RLock()
raw, err, finished := rest.handleRequest(method, route, jsonPayload)
if finished {
return raw, err
}
rest.mu.RUnlock()
time.Sleep(time.Microsecond * time.Duration(250*i))
}

return nil, errors.New("failed to make http request 3 times to " + method + " :: " + route + " (check internet connection and/or app credentials)")
}

func (rest *iRest) Token() string {
return strings.TrimPrefix(rest.token, "Bot ")
}

func (rest *iRest) handleRequest(method string, route string, jsonPayload interface{}) ([]byte, error, bool) {
func (rest *RestClient) handleRequest(method string, route string, jsonPayload interface{}) ([]byte, error, bool) {
var req *http.Request
if jsonPayload == nil {
request, err := http.NewRequest(method, DISCORD_API_URL+route, nil)
Expand Down Expand Up @@ -95,9 +78,9 @@ func (rest *iRest) handleRequest(method string, route string, jsonPayload interf

req.Header.Add("Content-Type", "application/json")
req.Header.Add("User-Agent", USER_AGENT)
req.Header.Add("Authorization", rest.token)
req.Header.Add("Authorization", rest.Token)

res, err := rest.httpClient.Do(req)
res, err := rest.HTTPClient.Do(req)
if err != nil {
return nil, errors.New("failed to process request: " + err.Error()), false
}
Expand All @@ -115,8 +98,9 @@ func (rest *iRest) handleRequest(method string, route string, jsonPayload interf
rateErr := rateLimitError{}
json.Unmarshal(body, &rateErr)

rest.mu.Lock()
timeLeft := time.Now().Add(time.Second * time.Duration(rateErr.RetryAfter+5))

rest.mu.Lock()
rest.lockedTo = timeLeft
rest.mu.Unlock()

Expand All @@ -133,10 +117,21 @@ func (rest *iRest) handleRequest(method string, route string, jsonPayload interf
return body, nil, true
}

func NewRest(token string) Rest {
return &iRest{
token: "Bot " + token,
httpClient: &http.Client{Timeout: time.Second * 3},
maxReconnectAttempts: 3,
func NewRestClient(token string) *RestClient {
t := token
if !strings.HasPrefix(t, "Bot ") {
t = "Bot " + t
}

return &RestClient{
HTTPClient: &http.Client{
Transport: &http.Transport{
TLSHandshakeTimeout: time.Second * 3,
},
Timeout: time.Second * 3,
},
Token: t,
MaxRetries: 3,
lockedTo: time.Time{},
}
}
4 changes: 2 additions & 2 deletions rest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ func TestRest(t *testing.T) {
t.Skip("can't test rest due to no provided token")
}

rest := NewRest(token)
rest := NewRestClient(token)
go requestGateway(rest, t)
go requestGateway(rest, t)
go requestGateway(rest, t)
Expand All @@ -22,7 +22,7 @@ func TestRest(t *testing.T) {
requestGateway(rest, t)
}

func requestGateway(rest Rest, t *testing.T) {
func requestGateway(rest *RestClient, t *testing.T) {
_, err := rest.Request(http.MethodGet, "/gateway/bot", nil)
if err != nil {
t.Error(err)
Expand Down

0 comments on commit 2a70b09

Please sign in to comment.