| package httpclient
|
|
|
| import (
|
| "fmt"
|
| "io"
|
| "net"
|
| "net/http"
|
| "net/url"
|
| "strings"
|
|
|
| "github.com/samber/lo"
|
| )
|
|
|
| func ReadHTTPRequest(rawReq *http.Request) (*Request, error) {
|
| req := &Request{
|
| Method: rawReq.Method,
|
| URL: rawReq.URL.String(),
|
| Path: rawReq.URL.Path,
|
| Query: rawReq.URL.Query(),
|
| Headers: rawReq.Header,
|
| Body: nil,
|
| Auth: &AuthConfig{},
|
| RequestID: "",
|
| ClientIP: getClientIP(rawReq),
|
| RawRequest: rawReq,
|
| }
|
|
|
| body, err := io.ReadAll(rawReq.Body)
|
| if err != nil {
|
| return nil, fmt.Errorf("failed to read request body: %w", err)
|
| }
|
|
|
| req.Body = body
|
|
|
| return req, nil
|
| }
|
|
|
| func getClientIP(req *http.Request) string {
|
| if xff := req.Header.Get("X-Forwarded-For"); xff != "" {
|
| if before, _, ok := strings.Cut(xff, ","); ok {
|
| return strings.TrimSpace(before)
|
| }
|
|
|
| return xff
|
| }
|
|
|
| if xri := req.Header.Get("X-Real-IP"); xri != "" {
|
| return xri
|
| }
|
|
|
| if ip, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
|
| return ip
|
| }
|
|
|
| return req.RemoteAddr
|
| }
|
|
|
|
|
|
|
|
|
| func IsHTTPStatusCodeRetryable(statusCode int) bool {
|
| if statusCode == http.StatusTooManyRequests {
|
| return true
|
| }
|
|
|
| if statusCode >= 400 && statusCode < 500 {
|
| return false
|
| }
|
|
|
| if statusCode >= 500 {
|
| return true
|
| }
|
|
|
| return false
|
| }
|
|
|
|
|
| var libManagedHeaders = map[string]bool{
|
| "Content-Length": true,
|
| "Transfer-Encoding": true,
|
| "Accept-Encoding": true,
|
| "Host": true,
|
| }
|
|
|
| var blockedHeaders = map[string]bool{
|
| "Content-Type": true,
|
| "Connection": true,
|
| "X-Channel-Id": true,
|
| "X-Project-Id": true,
|
| "X-Real-IP": true,
|
| "X-Forwarded-For": true,
|
| "X-Forwarded-Proto": true,
|
| "X-Forwarded-Host": true,
|
| "X-Forwarded-Port": true,
|
|
|
|
|
| "Accept-Language": true,
|
| "Dnt": true,
|
| "Origin": true,
|
| "Referer": true,
|
| "Sec-Fetch-Dest": true,
|
| "Sec-Fetch-Mode": true,
|
| "Sec-Fetch-Site": true,
|
| "Sec-Fetch-User": true,
|
| "Sec-Ch-Ua": true,
|
| "Sec-Ch-Ua-Mobile": true,
|
| "Sec-Ch-Ua-Platform": true,
|
| }
|
|
|
| var sensitiveHeaders = map[string]bool{
|
| "Authorization": true,
|
| "Api-Key": true,
|
| "X-Api-Key": true,
|
| "X-Api-Secret": true,
|
| "X-Api-Token": true,
|
| "X-Goog-Api-Key": true,
|
| "X-Google-Api-Key": true,
|
| "Cookie": true,
|
| "Set-Cookie": true,
|
| "Proxy-Authorization": true,
|
| "WWW-Authenticate": true,
|
| }
|
|
|
| var mergeWithAppendHeaders = map[string]bool{}
|
|
|
|
|
|
|
| func RegisterMergeWithAppendHeaders(headers ...string) {
|
| for _, h := range headers {
|
| mergeWithAppendHeaders[http.CanonicalHeaderKey(h)] = true
|
| }
|
| }
|
|
|
| func MergeInboundRequest(dest, src *Request) *Request {
|
| if src == nil || len(src.Headers) == 0 && len(src.Query) == 0 {
|
| return dest
|
| }
|
|
|
| dest.Headers = MergeHTTPHeaders(dest.Headers, src.Headers)
|
|
|
| if !dest.SkipInboundQueryMerge {
|
| dest.Query = MergeHTTPQuery(dest.Query, src.Query)
|
| }
|
|
|
| return dest
|
| }
|
|
|
|
|
|
|
| func MergeHTTPQuery(dest, src url.Values) url.Values {
|
| if len(src) == 0 {
|
| return dest
|
| }
|
|
|
| if dest == nil {
|
| dest = make(url.Values)
|
| }
|
|
|
| for k, v := range src {
|
| if _, ok := dest[k]; !ok {
|
| dest[k] = v
|
| }
|
| }
|
|
|
| return dest
|
| }
|
|
|
| func MaskSensitiveHeaders(headers http.Header) http.Header {
|
| result := make(http.Header, len(headers))
|
| for key, values := range headers {
|
| var newValues []string
|
| if _, ok := sensitiveHeaders[key]; !ok {
|
| newValues = values
|
| } else {
|
| newValues = append(newValues, "******")
|
| }
|
|
|
| result[key] = newValues
|
| }
|
|
|
| return result
|
| }
|
|
|
|
|
| func FinalizeAuthHeaders(req *Request) (*Request, error) {
|
| if req.Auth == nil {
|
| return req, nil
|
| }
|
|
|
| err := applyAuth(req.Headers, req.Auth)
|
| if err != nil {
|
| return nil, fmt.Errorf("failed to apply authentication: %w", err)
|
| }
|
|
|
| req.Auth = nil
|
|
|
| return req, nil
|
| }
|
|
|
|
|
|
|
|
|
|
|
| func MergeHTTPHeaders(dest, src http.Header) http.Header {
|
| for k, v := range src {
|
| if sensitiveHeaders[k] || libManagedHeaders[k] || blockedHeaders[k] {
|
| continue
|
| }
|
|
|
| if mergeWithAppendHeaders[k] {
|
| if existingValues, ok := dest[k]; ok {
|
| dest[k] = lo.Uniq(append(existingValues, v...))
|
| } else {
|
| dest[k] = v
|
| }
|
| } else {
|
| dest[k] = v
|
| }
|
| }
|
|
|
| return dest
|
| }
|
|
|