Documentation
¶
Overview ¶
Package httpreq exposes a single model-callable HTTP-request tool. It uses net/http and enforces host, method, redirect, timeout, and response-size policy at one client boundary.
The model chooses every URL, so the policy assumes a hostile caller. Host patterns bind a port as well as a name. Names are judged before sending, but DNS answers only at dial time: the client's own transport refuses private, loopback, link-local and special-purpose addresses that are not explicitly allowed, including those a permitted name or redirect resolves to. It connects directly, because a proxy would choose the address instead. A host-supplied transport owns where its connections go.
The host redirect policy can refuse a redirect but cannot extend the allowlist. Cross-origin redirects discard carried headers and refuse body replay. The host allowlist is mandatory; there is no "allow all" mode. A request the client refuses before sending is a definite core/tool.Failure: rejected for host or method policy, failed for invalid input. A redirect the policy refuses stays an ordinary error, because the first request was sent. An interrupted response remains an error. Received status, headers, and body bytes are preserved as core/tool.CallError evidence, not a complete tool result. Body and header values use content.Content; UTF-8 text is readable and binary data, partial characters, and HTTP obs-text remain lossless.
Index ¶
Examples ¶
Constants ¶
Variables ¶
var ( ErrNilClient = errors.New("httpreq: client must not be nil") ErrNilRequest = errors.New("httpreq: request must not be nil") ErrMissingAllowedHosts = errors.New("httpreq: allowed hosts must not be empty; configure an explicit network allowlist") ErrInvalidClientConfig = errors.New("httpreq: client configuration is invalid") ErrInvalidHostPattern = errors.New("httpreq: host pattern is invalid") ErrEmptyURL = errors.New("httpreq: url must not be empty") ErrInvalidURL = errors.New("httpreq: url must be an absolute http(s) URL") ErrInvalidMethod = errors.New("httpreq: method must be GET, HEAD, POST, PUT, PATCH, or DELETE") ErrInvalidRequestTimeout = fmt.Errorf("httpreq: timeout_ms must be between 1 and %d when set", MaxRequestTimeout.Milliseconds()) ErrHostNotAllowed = errors.New("httpreq: host is not allowed by client policy") ErrAddressNotAllowed = errors.New("httpreq: connection address is not allowed by client policy") ErrHostHeaderOverride = errors.New("httpreq: model-supplied Host header overrides are prohibited") ErrBodyNotAllowed = errors.New("httpreq: method does not carry a request body") ErrDuplicateQueryParameter = errors.New("httpreq: query parameter is named by both the URL and Query") ErrDuplicateHeader = errors.New("httpreq: header names one field more than once") ErrInvalidHeaderField = errors.New("httpreq: header name or value is not valid HTTP syntax") ErrMethodNotAllowed = errors.New("httpreq: method is not allowed by client policy") ErrRedirectBody = errors.New("httpreq: a redirect cannot replay a request body to another origin") ErrRedirectLimitReached = errors.New("httpreq: redirect limit reached") )
Functions ¶
This section is empty.
Types ¶
type Allowlist ¶
type Allowlist struct {
// contains filtered or unexported fields
}
Allowlist is a compiled authority policy. A pattern without a port admits only the request scheme's default port, so allowing a host never opens every service it runs. Exact and leading-wildcard patterns share the same case, trailing-dot, IP, and IDNA normalization as request hosts. The zero value allows nothing.
func NewAllowlist ¶
Example ¶
package main
import (
"fmt"
"net/url"
"github.com/Tangerg/scope/tools/httpreq"
)
func main() {
allowlist, err := httpreq.NewAllowlist([]string{"api.example.com", "*.services.example.com", "localhost:8080"})
if err != nil {
panic(err)
}
for _, target := range []string{
"https://API.EXAMPLE.COM/v1",
"https://search.services.example.com/",
"https://api.example.com:8443/",
"http://localhost:8080/health",
"https://example.net/",
} {
parsed, err := url.Parse(target)
if err != nil {
panic(err)
}
fmt.Println(target, allowlist.Allows(parsed))
}
}
Output: https://API.EXAMPLE.COM/v1 true https://search.services.example.com/ true https://api.example.com:8443/ false http://localhost:8080/health true https://example.net/ false
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client executes requests through an immutable network and resource policy.
func NewClient ¶
func NewClient(config ClientConfig) (*Client, error)
type ClientConfig ¶
type ClientConfig struct {
// AllowedHosts accepts exact hosts and one leading wildcard, each with an
// optional port, such as "api.example.com", "*.example.com" or
// "localhost:8080". A wildcard does not match its root. A pattern without a
// port admits only the request scheme's default port.
AllowedHosts []string
// AllowedNetworks admits connections to addresses outside public unicast
// space, such as 127.0.0.0/8 for a loopback service. Without a matching
// network the client refuses private, loopback, link-local and other
// special-purpose addresses, including those a permitted host resolves to.
// It governs only the client's own transport.
AllowedNetworks []netip.Prefix
// AllowedMethods defaults to GET and HEAD. Comparison is case-insensitive.
AllowedMethods []Method
// DefaultHeaders are added unless [Request.Headers] overrides them.
DefaultHeaders map[string]string
// MaxResponseBytes selects [DefaultMaxResponseBytes] at zero.
MaxResponseBytes int64
// DefaultTimeout selects [DefaultTimeout] at zero.
DefaultTimeout time.Duration
// HTTPClient supplies caller-owned redirect policy, cookie jar, and
// timeout. NewClient clones the value before installing its own redirect
// policy. A nil Transport selects the client's own, which connects directly
// through the address policy. A supplied Transport decides where its
// connections go, so it cannot be combined with AllowedNetworks.
HTTPClient *http.Client
}
ClientConfig requires AllowedHosts; there is no default network access.
func (ClientConfig) Validate ¶
func (c ClientConfig) Validate() error
type Method ¶
type Method string
const ( MethodGET Method = http.MethodGet MethodHEAD Method = http.MethodHead MethodPOST Method = http.MethodPost MethodPUT Method = http.MethodPut MethodPATCH Method = http.MethodPatch MethodDELETE Method = http.MethodDelete )
func (Method) JSONSchemaEnum ¶ added in v0.54.0
type Request ¶
type Request struct {
URL string `json:"url" jsonschema:"minLength=1" jsonschema_description:"Absolute http(s) URL. Host must match the configured allowlist."`
Method Method `` /* 155-byte string literal not displayed */
Headers map[string]string `` /* 197-byte string literal not displayed */
Query map[string]string `` /* 146-byte string literal not displayed */
Body string `` /* 197-byte string literal not displayed */
TimeoutMS int `` /* 157-byte string literal not displayed */
}
func (Request) JSONSchemaConstraints ¶ added in v0.54.0
func (Request) JSONSchemaConstraints() jsonschema.Constraints
type Response ¶
type Response struct {
Status int `json:"status"`
Headers map[string][]content.Content `json:"headers,omitempty"`
Body content.Content `json:"body"`
Truncated bool `json:"truncated,omitzero"`
// Duration includes transport execution, body reading, and body closure.
Duration string `json:"duration"`
}
Response is the model-facing result. Body and header values preserve bytes; UTF-8 remains readable text and other bytes use explicit base64 on the wire. Alongside an error from Client.Do it contains incomplete observations only; Truncated reports the configured size cap, not successful body completion.