mirror of
https://github.com/Buriburizaem0n/nezha_domains.git
synced 2026-09-19 17:50:12 +00:00
159 lines
4.2 KiB
Go
159 lines
4.2 KiB
Go
package client
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/cookiejar"
|
|
"net/url"
|
|
"strings"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/gorilla/websocket"
|
|
)
|
|
|
|
const (
|
|
defaultRequestTimeout = 10 * time.Second
|
|
defaultTransferTimeout = 5 * time.Minute
|
|
defaultMaxResponseBytes = int64(8 << 20)
|
|
defaultMaxTransferBytes = int64(100 << 20)
|
|
)
|
|
|
|
var ErrInvalidConfig = errors.New("client: invalid configuration")
|
|
|
|
type Config struct {
|
|
BaseURL string
|
|
HTTPClient *http.Client
|
|
WebSocketDialer *websocket.Dialer
|
|
BearerToken string
|
|
Origin string
|
|
RequestTimeout time.Duration
|
|
TransferTimeout time.Duration
|
|
MaxResponseBytes int64
|
|
MaxTransferBytes int64
|
|
}
|
|
|
|
type Client struct {
|
|
baseURL *url.URL
|
|
httpClient *http.Client
|
|
webSocketDialer *websocket.Dialer
|
|
bearerToken string
|
|
origin string
|
|
requestTimeout time.Duration
|
|
transferTimeout time.Duration
|
|
maxResponseBytes int64
|
|
maxTransferBytes int64
|
|
nextRequestID atomic.Uint64
|
|
requestCount atomic.Uint64
|
|
}
|
|
|
|
func (client *Client) RequestCount() uint64 {
|
|
return client.requestCount.Load()
|
|
}
|
|
|
|
func New(config Config) (*Client, error) {
|
|
baseURL, err := url.Parse(config.BaseURL)
|
|
if err != nil || baseURL.Host == "" || (baseURL.Scheme != "http" && baseURL.Scheme != "https") {
|
|
return nil, fmt.Errorf("base URL: %w", ErrInvalidConfig)
|
|
}
|
|
|
|
requestTimeout := config.RequestTimeout
|
|
if requestTimeout == 0 {
|
|
requestTimeout = defaultRequestTimeout
|
|
}
|
|
transferTimeout := config.TransferTimeout
|
|
if transferTimeout == 0 {
|
|
transferTimeout = defaultTransferTimeout
|
|
}
|
|
maxResponseBytes := config.MaxResponseBytes
|
|
if maxResponseBytes == 0 {
|
|
maxResponseBytes = defaultMaxResponseBytes
|
|
}
|
|
maxTransferBytes := config.MaxTransferBytes
|
|
if maxTransferBytes == 0 {
|
|
maxTransferBytes = defaultMaxTransferBytes
|
|
}
|
|
if requestTimeout < 0 || transferTimeout < 0 || maxResponseBytes < 1 || maxTransferBytes < 1 {
|
|
return nil, fmt.Errorf("request limits: %w", ErrInvalidConfig)
|
|
}
|
|
|
|
httpClient := &http.Client{}
|
|
if config.HTTPClient != nil {
|
|
clone := *config.HTTPClient
|
|
httpClient = &clone
|
|
}
|
|
if httpClient.Jar == nil {
|
|
jar, jarErr := cookiejar.New(nil)
|
|
if jarErr != nil {
|
|
return nil, fmt.Errorf("cookie jar: %w", jarErr)
|
|
}
|
|
httpClient.Jar = jar
|
|
}
|
|
httpClient.CheckRedirect = rejectRedirect
|
|
|
|
dialer := websocket.DefaultDialer
|
|
if config.WebSocketDialer != nil {
|
|
dialer = config.WebSocketDialer
|
|
}
|
|
dialerClone := *dialer
|
|
if dialerClone.HandshakeTimeout == 0 || dialerClone.HandshakeTimeout > requestTimeout {
|
|
dialerClone.HandshakeTimeout = requestTimeout
|
|
}
|
|
|
|
origin := strings.TrimSpace(config.Origin)
|
|
if origin == "" {
|
|
origin = baseURL.Scheme + "://" + baseURL.Host
|
|
}
|
|
return &Client{
|
|
baseURL: baseURL,
|
|
httpClient: httpClient,
|
|
webSocketDialer: &dialerClone,
|
|
bearerToken: strings.TrimSpace(config.BearerToken),
|
|
origin: origin,
|
|
requestTimeout: requestTimeout,
|
|
transferTimeout: transferTimeout,
|
|
maxResponseBytes: maxResponseBytes,
|
|
maxTransferBytes: maxTransferBytes,
|
|
}, nil
|
|
}
|
|
|
|
func rejectRedirect(*http.Request, []*http.Request) error {
|
|
return ErrRedirect
|
|
}
|
|
|
|
func (client *Client) requestContext(parent context.Context) (context.Context, context.CancelFunc) {
|
|
return context.WithTimeout(parent, client.requestTimeout)
|
|
}
|
|
|
|
func (client *Client) transferContext(parent context.Context) (context.Context, context.CancelFunc) {
|
|
return context.WithTimeout(parent, client.transferTimeout)
|
|
}
|
|
|
|
func (client *Client) resolvePath(path string) (*url.URL, error) {
|
|
reference, err := url.Parse(path)
|
|
if err != nil || reference.IsAbs() || reference.Host != "" {
|
|
return nil, fmt.Errorf("request path: %w", ErrInvalidConfig)
|
|
}
|
|
return client.baseURL.ResolveReference(reference), nil
|
|
}
|
|
|
|
func (client *Client) applyAuthenticatedHeaders(request *http.Request, includeCSRF bool) {
|
|
if client.bearerToken != "" {
|
|
request.Header.Set("Authorization", "Bearer "+client.bearerToken)
|
|
}
|
|
if client.origin != "" {
|
|
request.Header.Set("Origin", client.origin)
|
|
}
|
|
if !includeCSRF || client.httpClient.Jar == nil {
|
|
return
|
|
}
|
|
for _, cookie := range client.httpClient.Jar.Cookies(client.baseURL) {
|
|
if cookie.Name == "nz-csrf" {
|
|
request.Header.Set("X-CSRF-Token", cookie.Value)
|
|
return
|
|
}
|
|
}
|
|
}
|