mirror of
https://github.com/hanzoai/platform-services.git
synced 2026-10-30 18:23:56 +00:00
90 lines
2.9 KiB
Go
90 lines
2.9 KiB
Go
package server
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"time"
|
|
)
|
|
|
|
// httpClient is the single shared client for all upstream proxy calls. One
|
|
// client = pooled connections across categories. Per-call context can shorten
|
|
// the timeout below.
|
|
var httpClient = &http.Client{Timeout: 30 * time.Second}
|
|
|
|
// proxyResult is the normalized outcome of an upstream HTTP call: the upstream
|
|
// status mapped into our wire Status* space, plus the raw JSON body (passed
|
|
// through verbatim — the console client decodes the upstream shape).
|
|
type proxyResult struct {
|
|
Status uint32
|
|
Body []byte
|
|
}
|
|
|
|
// doJSON performs one upstream HTTP request and normalizes the result. method,
|
|
// url, and an optional JSON body + bearer token are supplied by the category
|
|
// handler. The body is passed through untouched: this service is a typed
|
|
// transport in front of the same HTTP APIs the routers called, so the upstream
|
|
// JSON IS the contract. A transport error becomes StatusBadGateway; a non-2xx
|
|
// upstream status maps through mapHTTPStatus.
|
|
//
|
|
// Auth: bearer is sent as `Authorization: Bearer <token>` when non-empty. The
|
|
// token is never logged.
|
|
func doJSON(ctx context.Context, method, url string, body []byte, bearer string, headers map[string]string) (proxyResult, error) {
|
|
var rdr io.Reader
|
|
if len(body) > 0 {
|
|
rdr = bytes.NewReader(body)
|
|
}
|
|
req, err := http.NewRequestWithContext(ctx, method, url, rdr)
|
|
if err != nil {
|
|
return proxyResult{}, fmt.Errorf("build request: %w", err)
|
|
}
|
|
req.Header.Set("Accept", "application/json")
|
|
if len(body) > 0 {
|
|
req.Header.Set("Content-Type", "application/json")
|
|
}
|
|
if bearer != "" {
|
|
req.Header.Set("Authorization", "Bearer "+bearer)
|
|
}
|
|
for k, v := range headers {
|
|
req.Header.Set(k, v)
|
|
}
|
|
|
|
resp, err := httpClient.Do(req)
|
|
if err != nil {
|
|
return proxyResult{Status: StatusBadGateway}, fmt.Errorf("upstream %s %s: %w", method, url, err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
respBody, err := io.ReadAll(io.LimitReader(resp.Body, 8<<20)) // 8 MiB cap
|
|
if err != nil {
|
|
return proxyResult{Status: StatusBadGateway}, fmt.Errorf("read upstream body: %w", err)
|
|
}
|
|
|
|
return proxyResult{Status: mapHTTPStatus(resp.StatusCode), Body: respBody}, nil
|
|
}
|
|
|
|
// mapHTTPStatus folds an upstream HTTP status code into this service's Status*
|
|
// space. 2xx → StatusOK; recognized 4xx pass through; everything else → 502
|
|
// (the upstream is reachable but unhappy, which from the caller's view is a
|
|
// gateway-level problem).
|
|
func mapHTTPStatus(code int) uint32 {
|
|
switch {
|
|
case code >= 200 && code < 300:
|
|
return StatusOK
|
|
case code == http.StatusBadRequest:
|
|
return StatusBadRequest
|
|
case code == http.StatusUnauthorized:
|
|
return StatusUnauthorized
|
|
case code == http.StatusForbidden:
|
|
return StatusForbidden
|
|
case code == http.StatusNotFound:
|
|
return StatusNotFound
|
|
case code == http.StatusPreconditionFailed:
|
|
return StatusPrecondition
|
|
default:
|
|
return StatusBadGateway
|
|
}
|
|
}
|