httpreq

package
v0.61.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 10, 2026 License: Apache-2.0 Imports: 25 Imported by: 0

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

View Source
const (
	DefaultTimeout          = 30 * time.Second
	DefaultMaxResponseBytes = int64(256 * 1024)
	MaxRequestTimeout       = 2 * time.Minute
)

Variables

View Source
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

func NewAllowlist(patterns []string) (Allowlist, error)
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

func (Allowlist) Allows

func (a Allowlist) Allows(target *url.URL) bool

Allows reports whether target's host and effective port match a pattern.

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)

func (*Client) Do

func (c *Client) Do(ctx context.Context, request *Request) (*Response, error)

Do applies the frozen client policy. If body reading or closure fails, the returned Response retains the received status, headers, and admitted body prefix as evidence alongside the error. The error does not establish whether a request with side effects committed at the server.

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

func (Method) JSONSchemaEnum() []string

func (Method) Normalize

func (m Method) Normalize() (Method, error)

Normalize applies the wire default and canonical HTTP casing, and rejects methods outside the tool contract.

func (Method) Validate

func (m Method) Validate() error

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

func (*Request) Validate

func (r *Request) Validate() error

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.

type Tool

type Tool struct {
	// contains filtered or unexported fields
}

Tool requires a Client so every invocation enforces its frozen network policy.

func NewTool

func NewTool(client *Client) (*Tool, error)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL