Files
request/github/github.go
Matheus Sampaio Queiroga 59c8a863d6
All checks were successful
Golang test / go-test (push) Successful in 14s
Improve GitHub rate limit handling
Check the `retry-after` header first for rate limit waits. Fall back
to `x-ratelimit-remaining` and `x-ratelimit-reset` if necessary,
correctly interpreting the reset value as a Unix timestamp.

Also simplifies the `ReRequest` helper to use `http.DefaultClient` and
refines panic recovery in Gitea asset uploads.
2025-04-22 21:13:50 -03:00

126 lines
3.6 KiB
Go

// Client to Github APIs
package github
import (
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
"time"
"sirherobrine23.com.br/go-bds/request/v2"
)
var (
GithubAPI = &url.URL{Scheme: "https", Host: "api.github.com", Path: "/"} // Defaut github api
ErrToken error = errors.New("require Token to request")
err500 = request.RequestStatusFunction(func(res *http.Response) (*http.Response, error) {
buffLog, _ := io.ReadAll(res.Body)
if res.Body != nil {
res.Body.Close()
}
if len(buffLog) == 0 {
buffLog = []byte("not body response")
}
return nil, fmt.Errorf("cannot process request (%s), github api is down: %s", res.Request.URL.String(), buffLog)
})
processCodes = request.MapCode{
500: err500,
501: err500,
401: func(res *http.Response) (*http.Response, error) {
if res.Body != nil {
res.Body.Close()
}
return nil, ErrToken
},
403: func(res *http.Response) (*http.Response, error) {
if res.Body != nil {
res.Body.Close()
}
if retryAfter := res.Header.Get("retry-after"); retryAfter != "" && retryAfter != "0" {
if dateSec, err := strconv.ParseInt(retryAfter, 10, 64); err == nil {
<-time.After(time.Unix(dateSec, dateSec*int64(time.Second)).Sub(time.Now()))
return request.ReRequest(res)
}
} else if remining := res.Header.Get("x-ratelimit-remaining"); remining == "0" {
if reset := res.Header.Get("x-ratelimit-reset"); reset != "" {
if dateSec, err := strconv.ParseInt(reset, 10, 64); err == nil {
<-time.After(time.Unix(dateSec, dateSec*int64(time.Second)).Sub(time.Now()))
return request.ReRequest(res)
}
}
}
return nil, errors.New("cannot process request, authorization or not have permission")
},
429: func(res *http.Response) (*http.Response, error) {
if res.Body != nil {
res.Body.Close()
}
if retryAfter := res.Header.Get("retry-after"); retryAfter != "" && retryAfter != "0" {
if dateSec, err := strconv.ParseInt(retryAfter, 10, 64); err == nil {
<-time.After(time.Unix(dateSec, dateSec*int64(time.Second)).Sub(time.Now()))
return request.ReRequest(res)
}
} else if remining := res.Header.Get("x-ratelimit-remaining"); remining == "0" {
if reset := res.Header.Get("x-ratelimit-reset"); reset != "" {
if dateSec, err := strconv.ParseInt(reset, 10, 64); err == nil {
<-time.After(time.Unix(dateSec, dateSec*int64(time.Second)).Sub(time.Now()))
return request.ReRequest(res)
}
}
}
return nil, errors.New("many requests to github api")
},
}
)
// Create new Github Client with Github.com API host
func NewClient(Username, Repository, Token string) *Github {
return &Github{
Host: GithubAPI,
Repository: Repository,
Username: Username,
Token: Token,
}
}
// Create new Github Client
func NewClientHost(apiHost, Username, Repository, Token string) (*Github, error) {
hostUrl, err := url.Parse(apiHost)
if err != nil {
return nil, err
}
return &Github{
Host: hostUrl,
Repository: Repository,
Username: Username,
Token: Token,
}, nil
}
// Basic client to Github APIs
type Github struct {
Host *url.URL // Github API host
Token string // Github Token
// User and Repository name
Username, Repository string
}
// Return escapated Owner+/+Repository
func (client Github) repoPath() string {
return fmt.Sprintf("%s/%s", url.PathEscape(client.Username), url.PathEscape(client.Repository))
}
// Set token to header
func (client Github) authHeader(header *request.Header) {
if client.Token != "" {
(*header)["Authorization"] = fmt.Sprintf("Bearer %s", client.Token)
}
}