pages-server/server/gitea/client_fasthttp.go

173 lines
4.9 KiB
Go
Raw Normal View History

2022-07-15 16:53:04 +00:00
//go:build fasthttp
package gitea
import (
"fmt"
"net/url"
"strings"
"time"
"github.com/rs/zerolog/log"
2022-07-15 16:53:04 +00:00
"github.com/valyala/fasthttp"
"github.com/valyala/fastjson"
"codeberg.org/codeberg/pages/server/cache"
2022-07-15 16:53:04 +00:00
)
const (
giteaAPIRepos = "/api/v1/repos/"
giteaObjectTypeHeader = "X-Gitea-Object-Type"
)
2022-07-15 16:53:04 +00:00
type Client struct {
2022-07-27 13:39:46 +00:00
giteaRoot string
giteaAPIToken string
infoTimeout time.Duration
contentTimeout time.Duration
fastClient *fasthttp.Client
responseCache cache.SetGetKey
followSymlinks bool
supportLFS bool
2022-07-15 16:53:04 +00:00
}
2022-07-27 13:39:46 +00:00
func NewClient(giteaRoot, giteaAPIToken string, respCache cache.SetGetKey, followSymlinks, supportLFS bool) (*Client, error) {
2022-07-15 16:53:04 +00:00
rootURL, err := url.Parse(giteaRoot)
giteaRoot = strings.Trim(rootURL.String(), "/")
return &Client{
2022-07-27 13:39:46 +00:00
giteaRoot: giteaRoot,
giteaAPIToken: giteaAPIToken,
infoTimeout: 5 * time.Second,
contentTimeout: 10 * time.Second,
fastClient: getFastHTTPClient(),
responseCache: respCache,
followSymlinks: followSymlinks,
supportLFS: supportLFS,
2022-07-15 16:53:04 +00:00
}, err
}
func (client *Client) GiteaRawContent(targetOwner, targetRepo, ref, resource string) ([]byte, error) {
resp, err := client.ServeRawContent(targetOwner, targetRepo, ref, resource)
2022-07-15 16:53:04 +00:00
if err != nil {
return nil, err
}
return resp.Body(), nil
2022-07-15 16:53:04 +00:00
}
func (client *Client) ServeRawContent(targetOwner, targetRepo, ref, resource string) (*fasthttp.Response, error) {
var apiURL string
if client.supportLFS {
apiURL = joinURL(client.giteaRoot, giteaAPIRepos, targetOwner, targetRepo, "media", resource+"?ref="+url.QueryEscape(ref))
} else {
apiURL = joinURL(client.giteaRoot, giteaAPIRepos, targetOwner, targetRepo, "raw", resource+"?ref="+url.QueryEscape(ref))
}
resp, err := client.do(client.contentTimeout, apiURL)
2022-07-15 16:53:04 +00:00
if err != nil {
return nil, err
}
switch resp.StatusCode() {
2022-07-15 16:53:04 +00:00
case fasthttp.StatusOK:
if client.followSymlinks && string(resp.Header.Peek(giteaObjectTypeHeader)) == "symlink" {
linkDest := strings.TrimSpace(string(resp.Body()))
log.Debug().Msgf("follow symlink from '%s' to '%s'", resource, linkDest)
return client.ServeRawContent(targetOwner, targetRepo, ref, linkDest)
}
return resp, nil
2022-07-15 16:53:04 +00:00
case fasthttp.StatusNotFound:
return nil, ErrorNotFound
2022-07-15 16:53:04 +00:00
default:
return nil, fmt.Errorf("unexpected status code '%d'", resp.StatusCode())
2022-07-15 16:53:04 +00:00
}
}
2022-07-27 13:48:48 +00:00
func (client *Client) GiteaGetRepoBranchTimestamp(repoOwner, repoName, branchName string) (*BranchTimestamp, error) {
cacheKey := fmt.Sprintf("%s/%s/%s/%s", branchTimestampCacheKeyPrefix, repoOwner, repoName, branchName)
if stamp, ok := client.responseCache.Get(cacheKey); ok && stamp != nil {
return stamp.(*BranchTimestamp), nil
}
2022-07-15 16:53:04 +00:00
url := joinURL(client.giteaRoot, giteaAPIRepos, repoOwner, repoName, "branches", branchName)
res, err := client.do(client.infoTimeout, url)
if err != nil {
2022-07-27 13:48:48 +00:00
return &BranchTimestamp{}, err
2022-07-15 16:53:04 +00:00
}
if res.StatusCode() != fasthttp.StatusOK {
2022-07-27 13:48:48 +00:00
return &BranchTimestamp{}, fmt.Errorf("unexpected status code '%d'", res.StatusCode())
}
timestamp, err := time.Parse(time.RFC3339, fastjson.GetString(res.Body(), "commit", "timestamp"))
if err != nil {
return &BranchTimestamp{}, err
}
stamp := &BranchTimestamp{
Branch: branchName,
Timestamp: timestamp,
2022-07-15 16:53:04 +00:00
}
2022-07-27 13:48:48 +00:00
client.responseCache.Set(cacheKey, stamp, branchExistenceCacheTimeout)
return stamp, nil
2022-07-15 16:53:04 +00:00
}
func (client *Client) GiteaGetRepoDefaultBranch(repoOwner, repoName string) (string, error) {
2022-07-27 13:48:48 +00:00
cacheKey := fmt.Sprintf("%s/%s/%s", defaultBranchCacheKeyPrefix, repoOwner, repoName)
if branch, ok := client.responseCache.Get(cacheKey); ok && branch != nil {
return branch.(string), nil
}
2022-07-15 16:53:04 +00:00
url := joinURL(client.giteaRoot, giteaAPIRepos, repoOwner, repoName)
res, err := client.do(client.infoTimeout, url)
if err != nil {
return "", err
}
if res.StatusCode() != fasthttp.StatusOK {
return "", fmt.Errorf("unexpected status code '%d'", res.StatusCode())
}
2022-07-27 13:48:48 +00:00
branch := fastjson.GetString(res.Body(), "default_branch")
client.responseCache.Set(cacheKey, branch, defaultBranchCacheTimeout)
return branch, nil
2022-07-15 16:53:04 +00:00
}
func (client *Client) do(timeout time.Duration, url string) (*fasthttp.Response, error) {
req := fasthttp.AcquireRequest()
req.SetRequestURI(url)
req.Header.Set(fasthttp.HeaderAuthorization, "token "+client.giteaAPIToken)
res := fasthttp.AcquireResponse()
err := client.fastClient.DoTimeout(req, res, timeout)
return res, err
}
// TODO: once golang v1.19 is min requirement, we can switch to 'JoinPath()' of 'net/url' package
func joinURL(baseURL string, paths ...string) string {
p := make([]string, 0, len(paths))
for i := range paths {
path := strings.TrimSpace(paths[i])
path = strings.Trim(path, "/")
if len(path) != 0 {
p = append(p, path)
}
}
return baseURL + "/" + strings.Join(p, "/")
}
2022-07-21 20:01:22 +00:00
func getFastHTTPClient() *fasthttp.Client {
return &fasthttp.Client{
MaxConnDuration: 60 * time.Second,
MaxConnWaitTimeout: 1000 * time.Millisecond,
MaxConnsPerHost: 128 * 16, // TODO: adjust bottlenecks for best performance with Gitea!
}
}