feat(14-01): fetchguard client covers PUT, multipart, bearer and a trusted mode

- TrustedMode (declared after PublicOnlyMode) lifts the scheme, host and dial checks for Client only
- PutJSON, PostMultipart with FormField/FormFile, Bearer
- tests for modes, redirects, multipart order, body cap and the scheme guard
- README, root modules row and outbound HTTP docs describe the client and its test seam
This commit is contained in:
Jakub Zych
2026-10-03 19:42:37 +02:00
parent 93b7142059
commit e6a67134d1
10 changed files with 543 additions and 38 deletions

View File

@@ -59,7 +59,7 @@ func Fetch(ctx context.Context, rawURL string, policy Policy, cfg *compass.Confi
return nil, err
}
client := newHTTPClient(newTransport(policy, timeout, false), timeout)
client := newHTTPClient(newTransport(policy, timeout, false, true), timeout)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, parsed.String(), nil)
if err != nil {
@@ -86,18 +86,19 @@ func Fetch(ctx context.Context, rawURL string, policy Policy, cfg *compass.Confi
}, nil
}
// newTransport builds the guarded transport for policy. keepAlives is off for
// one-shot Fetch calls and on for a reusable Client; the dial Control runs on
// every new connection either way.
func newTransport(policy Policy, timeout time.Duration, keepAlives bool) *http.Transport {
// newTransport builds the transport for policy. keepAlives is off for
// one-shot Fetch calls and on for a reusable Client. When guard is set the
// dial Control runs on every new connection.
func newTransport(policy Policy, timeout time.Duration, keepAlives, guard bool) *http.Transport {
dialer := &net.Dialer{Timeout: timeout}
if guard {
dialer.Control = dialControl(policy)
}
return &http.Transport{
// User-supplied URLs must not be forwarded through HTTP_PROXY:
// the dial-time IP check would then see the proxy, not the target.
Proxy: nil,
DialContext: (&net.Dialer{
Timeout: timeout,
Control: dialControl(policy),
}).DialContext,
Proxy: nil,
DialContext: dialer.DialContext,
TLSClientConfig: policy.tlsConfig,
DisableKeepAlives: !keepAlives,
ForceAttemptHTTP2: true,