customhttp

package
v0.1.6 Latest Latest
Warning

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

Go to latest
Published: Jan 1, 2026 License: MIT Imports: 25 Imported by: 0

Documentation

Overview

h1client.go

Index

Constants

View Source
const (
	// MaxResponseBodyBytes defines a reasonable limit for response bodies.
	MaxResponseBodyBytes = 32 * 1024 * 1024 // 32 MB

	// H2 specific constants (RFC 7540)
	DefaultH2InitialWindowSize = 65535
	DefaultH2MaxFrameSize      = 16384

	// Define target receive window sizes for better throughput.
	// These values are advertised to the server during connection setup.
	TargetH2ConnWindowSize   = 8 * 1024 * 1024 // 8 MB
	TargetH2StreamWindowSize = 4 * 1024 * 1024 // 4 MB
)
View Source
const MaxReplayableBodyBytes = 2 * 1024 * 1024 // 2 MB

Define a reasonable limit for buffering request bodies in memory for replayability. This prevents excessive memory usage when handling large uploads that might need retries/redirects.

Variables

View Source
var ErrCredentialsNotFound = errors.New("credentials not found")

ErrCredentialsNotFound is a sentinel error returned by a CredentialsProvider when it does not have credentials for a given host and realm. This signals to the client that authentication cannot be handled and the original 401/407 response should be returned.

Functions

func SerializeRequest

func SerializeRequest(req *http.Request) ([]byte, error)

SerializeRequest converts an `http.Request` object into its raw HTTP/1.1 wire format as a byte slice. It correctly handles the request line, headers, and body, ensuring the `Content-Length` is set and the `Connection: keep-alive` header is present for persistent connections.

This utility is essential for manual request sending, such as in HTTP pipelining.

Types

type ClientConfig

type ClientConfig struct {
	// DialerConfig holds the low-level configuration for establishing TCP and
	// TLS connections, including proxy settings.
	DialerConfig *network.DialerConfig

	// CookieJar specifies the cookie jar for the client. If nil, cookies are not managed.
	CookieJar http.CookieJar

	// RequestTimeout sets the timeout for a single HTTP request attempt.
	RequestTimeout time.Duration

	// IdleConnTimeout is the maximum duration a connection can remain idle in the
	// pool before it is closed and evicted.
	IdleConnTimeout time.Duration

	// InsecureSkipVerify controls whether to skip TLS certificate verification.
	InsecureSkipVerify bool

	// CheckRedirect provides a function to define a custom redirect policy. If nil,
	// the client's default policy is used.
	CheckRedirect func(req *http.Request, via []*http.Request) error

	// RetryPolicy defines the rules for retrying failed requests.
	RetryPolicy *RetryPolicy

	// CredentialsProvider is an interface for dynamically supplying credentials for
	// HTTP authentication.
	CredentialsProvider CredentialsProvider

	// H2Config holds settings specific to HTTP/2 connections.
	H2Config H2Settings
}

ClientConfig is the primary configuration struct for a CustomClient. It aggregates all configurable aspects of the client, including dialing, cookies, timeouts, redirection, retries, authentication, and HTTP/2 settings.

func NewBrowserClientConfig

func NewBrowserClientConfig() *ClientConfig

NewBrowserClientConfig creates a new ClientConfig with defaults that are optimized to emulate the behavior of a modern web browser. This includes a pre-configured cookie jar, sensible timeouts, a default retry policy, and standard HTTP/2 settings.

func (*ClientConfig) SetProxy

func (c *ClientConfig) SetProxy(proxyURL *url.URL)

SetProxy is a convenience method to configure an HTTP/HTTPS proxy for the client. It sets the `ProxyURL` field in the underlying `DialerConfig`.

type ConnectionPool

type ConnectionPool interface {
	// Close immediately closes all connections in the pool.
	Close() error
	// IsIdle returns true if the pool has been idle for at least the specified duration.
	IsIdle(timeout time.Duration) bool
}

ConnectionPool defines a minimal interface for a connection pool, used by the CustomClient's connection evictor to manage and close idle connections.

type CredentialsProvider

type CredentialsProvider interface {
	// GetCredentials is called when the client receives a 401 (Unauthorized) or
	// 407 (Proxy Authentication Required) response.
	//
	// Parameters:
	//   - host: The host (e.g., "example.com:443") that issued the challenge.
	//   - realm: The authentication realm specified in the WWW-Authenticate header.
	//
	// It should return the username, password, and nil on success. If credentials
	// are not available, it must return ErrCredentialsNotFound. Other errors
	// will halt the request.
	GetCredentials(host string, realm string) (username string, password string, err error)
}

CredentialsProvider defines an interface for dynamically supplying credentials in response to an HTTP authentication challenge (e.g., Basic Auth). This allows the client to support authentication without hardcoding usernames and passwords.

type CustomClient

type CustomClient struct {
	Config *ClientConfig
	Logger *zap.Logger

	// MaxRedirects specifies the maximum number of redirects to follow for a single request.
	MaxRedirects int
	// contains filtered or unexported fields
}

CustomClient is a sophisticated, low level HTTP client designed for fine grained control over HTTP/1.1 and HTTP/2 connections. It is the core of the browser's networking stack, managing persistent connections on a per-host basis and handling the full request lifecycle, including cookies, redirects, retries, and authentication.

It maintains separate pools of `H1Client` and `H2Client` instances, allowing it to transparently handle different protocol versions. A background goroutine periodically evicts idle connections to conserve resources.

func NewCustomClient

func NewCustomClient(config *ClientConfig, logger *zap.Logger) *CustomClient

NewCustomClient creates and initializes a new CustomClient with the given configuration. It also starts a background goroutine to periodically close idle connections from its pools.

func (*CustomClient) CloseAll

func (c *CustomClient) CloseAll()

CloseAll shuts down the client, closing all active and idle connections and stopping the background connection evictor goroutine. It waits for the evictor to terminate cleanly before returning.

func (*CustomClient) ConnectionCount

func (c *CustomClient) ConnectionCount() int

ConnectionCount returns the total number of active connections (H1 + H2). This method is thread-safe and primarily intended for testing purposes.

func (*CustomClient) Do

func (c *CustomClient) Do(ctx context.Context, req *http.Request) (*http.Response, error)

Do executes a single HTTP request and returns the response. This is the main entry point for the client. It orchestrates the entire request lifecycle, including:

1. Making the request body replayable for retries and redirects. 2. Adding cookies from the client's cookie jar. 3. Executing the request with a configurable retry policy for transient errors. 4. Storing cookies from the response. 5. Handling authentication challenges (e.g., HTTP Basic Auth). 6. Following HTTP redirects up to a configured limit.

Parameters:

  • ctx: The context for the entire request operation, including all retries and redirects.
  • req: The HTTP request to send.

Returns the final HTTP response or an error if the request could not be completed.

type H1Client

type H1Client struct {
	Conn      net.Conn
	Config    *ClientConfig
	TargetURL *url.URL
	Address   string
	Logger    *zap.Logger
	// contains filtered or unexported fields
}

H1Client manages a single, persistent HTTP/1.1 connection to a specific host. It is designed to emulate the behavior of a browser's connection handling for HTTP/1.1, including support for Keep-Alive (reusing a single TCP/TLS connection for multiple sequential requests) and offering primitives for manual HTTP pipelining.

The client establishes its connection lazily (on the first request) and is thread-safe, ensuring that requests over the single connection are properly serialized.

func NewH1Client

func NewH1Client(targetURL *url.URL, config *ClientConfig, logger *zap.Logger) (*H1Client, error)

NewH1Client creates a new, un-connected H1Client for a given target URL and configuration. The actual network connection is established lazily when the first request is made.

func (*H1Client) Close

func (c *H1Client) Close() error

Close terminates the client's network connection. This method is thread-safe.

func (*H1Client) Connect

func (c *H1Client) Connect(ctx context.Context) error

Connect establishes the underlying TCP and TLS connection to the target host if it is not already connected. It is called automatically by `Do` and other methods and is idempotent.

For HTTPS connections, it uses ALPN to explicitly negotiate HTTP/1.1, ensuring that servers that support both H2 and H1.1 will select the correct protocol for this client.

func (*H1Client) Do

func (c *H1Client) Do(ctx context.Context, req *http.Request) (*http.Response, error)

Do sends a single HTTP request over the persistent connection and waits for the response. It serializes access to the underlying connection, ensuring that multiple goroutines calling `Do` will have their requests sent sequentially, respecting HTTP/1.1 Keep-Alive semantics.

This method handles the entire request-response cycle, including serializing the request, writing it to the wire, parsing the response, and handling context cancellation during I/O operations.

func (*H1Client) IsIdle

func (c *H1Client) IsIdle(timeout time.Duration) bool

IsIdle determines if the connection has been idle for a duration longer than the specified timeout. This method is used by the `CustomClient`'s connection evictor to manage the connection pool and implements the `ConnectionPool` interface.

func (*H1Client) ReadPipelinedResponses

func (c *H1Client) ReadPipelinedResponses(ctx context.Context, expectedCount int) ([]*http.Response, error)

ReadPipelinedResponses reads a specified number of HTTP responses from the connection's buffered reader. This method is the counterpart to `SendRaw` and is used to retrieve the results of a pipelined request sequence.

Parameters:

  • ctx: The context for the read operation.
  • expectedCount: The number of responses to parse from the stream.

Returns a slice of the parsed `http.Response` objects or an error if parsing fails or the connection is closed prematurely.

func (*H1Client) SendRaw

func (c *H1Client) SendRaw(ctx context.Context, data []byte) error

SendRaw provides a low level mechanism to send arbitrary raw bytes over the connection. This is primarily intended for implementing manual HTTP/1.1 pipelining, where multiple serialized requests are sent without waiting for individual responses.

The caller is responsible for ensuring the data is a valid sequence of HTTP requests. Access to the connection is serialized. NOTE: SendRaw/ReadPipelinedResponses rely on the deadlines set based on the context/config and do not implement the active context cancellation monitoring used in Do().

type H2Client

type H2Client struct {
	Conn      net.Conn
	Config    *ClientConfig
	TargetURL *url.URL
	Address   string
	Logger    *zap.Logger

	Framer    *http2.Framer
	HPEncoder *hpack.Encoder
	HPDecoder *hpack.Decoder
	// contains filtered or unexported fields
}

H2Client manages a single, persistent HTTP/2 connection to a specific host. It is a low level client that provides full control over the HTTP/2 framing layer, including concurrent request multiplexing, stream management, and manual flow control.

A background goroutine reads and processes incoming frames, dispatching them to the appropriate streams. Another optional goroutine handles keep-alive PINGs to maintain connection liveness. The client is thread-safe and designed for high concurrency scenarios.

func NewH2Client

func NewH2Client(targetURL *url.URL, config *ClientConfig, logger *zap.Logger) (*H2Client, error)

NewH2Client creates a new, un-connected H2Client for a given target URL and configuration. The actual network connection and H2 preface exchange happen lazily on the first request.

func (*H2Client) Close

func (c *H2Client) Close() error

Close gracefully shuts down the H2 connection. It sends a GOAWAY frame with no error code, signals the background loops to terminate, closes the underlying network connection, and waits for the loops to exit. It implements the `ConnectionPool` interface.

func (*H2Client) Connect

func (c *H2Client) Connect(ctx context.Context) error

Connect establishes the full H2 connection if it is not already active. This is the primary setup method and is called lazily by `Do`. It is idempotent.

The connection process involves:

  1. Establishing a TCP connection and performing a TLS handshake with ALPN to negotiate "h2".
  2. Sending the client preface string.
  3. Sending initial SETTINGS and WINDOW_UPDATE frames to configure the connection.
  4. Starting background goroutines to read incoming frames (`readLoop`) and handle keep-alive PINGs (`pingLoop`).

func (*H2Client) Do

func (c *H2Client) Do(ctx context.Context, req *http.Request) (*http.Response, error)

Do executes a single HTTP request over the multiplexed H2 connection. It is the primary method for interacting with the client and is safe for concurrent use.

The process involves:

  1. Lazily establishing the H2 connection if needed.
  2. Initializing a new stream for the request.
  3. Starting a new goroutine to serialize and send the request headers and body, respecting H2 flow control.
  4. Waiting for the response to be received by the background `readLoop`, or for the request context to be cancelled.

This concurrent design allows multiple `Do` calls to be in flight simultaneously over the single TCP connection.

func (*H2Client) IsIdle

func (c *H2Client) IsIdle(timeout time.Duration) bool

IsIdle determines if the connection has been idle for a duration longer than the specified timeout. A connection is considered idle only if it is active and has no streams in progress. This method is used by the `CustomClient`'s connection evictor and implements the `ConnectionPool` interface.

type H2Settings

type H2Settings struct {
	// PingInterval specifies the time between sending HTTP/2 PING frames to the
	// server to check for connection liveness.
	PingInterval time.Duration
	// PingTimeout is the maximum time to wait for a PING acknowledgment before
	// considering the connection dead and closing it.
	PingTimeout time.Duration
}

H2Settings defines configuration parameters specific to HTTP/2 connections.

func DefaultH2Settings

func DefaultH2Settings() H2Settings

DefaultH2Settings returns a default H2Settings configuration with a 30-second ping interval and a 5-second ping timeout.

type H2StreamResetError

type H2StreamResetError struct {
	ErrCode http2.ErrCode
}

H2StreamResetError is returned when an H2 stream is reset by the server. This allows higher-level clients (like CustomClient) to inspect the error code and determine if the request is retryable (e.g., REFUSED_STREAM).

func (H2StreamResetError) Error

func (e H2StreamResetError) Error() string

type RetryPolicy

type RetryPolicy struct {
	// MaxRetries is the maximum number of retry attempts after the initial request fails.
	MaxRetries int
	// InitialBackoff is the base duration to wait before the first retry.
	InitialBackoff time.Duration
	// MaxBackoff is the upper limit for the backoff duration, preventing excessively long waits.
	MaxBackoff time.Duration
	// BackoffFactor is the multiplier for the exponential backoff calculation (e.g., 2.0).
	BackoffFactor float64
	// Jitter, if true, adds a random factor to the backoff duration to prevent
	// multiple clients from retrying in synchronized waves (thundering herd problem).
	Jitter bool
	// RetryableStatusCodes is a set of HTTP status codes that should trigger a
	// retry for idempotent requests (e.g., GET, PUT, DELETE).
	RetryableStatusCodes map[int]bool
}

RetryPolicy encapsulates the rules for retrying failed or transient HTTP requests. It supports exponential backoff with jitter to prevent overwhelming a server that is temporarily unavailable.

func NewDefaultRetryPolicy

func NewDefaultRetryPolicy() *RetryPolicy

NewDefaultRetryPolicy creates and returns a RetryPolicy with sensible defaults, such as 3 max retries, exponential backoff starting at 500ms, and retries for common transient server errors like 502, 503, and 504.

Jump to

Keyboard shortcuts

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