http

package
v0.16.1 Latest Latest
Warning

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

Go to latest
Published: May 24, 2026 License: MIT Imports: 14 Imported by: 0

README

pkg/http

HTTP client with zstd compression, TLS enforcement, retry logic, and typed errors.

Files

File Role
client.go Client struct, DoJSON method, compression, retries, error handling

Key API

client := http.NewClient(cfg, timeout)
err := client.Post("/api/v1/sync/chunk", reqBody, &respBody)
  • NewClient(cfg, timeout) — Creates client with zstd encoder, TLS config, and timeout.
  • DoJSON(method, path, reqBody, respBody) — Core method: marshals JSON, optionally compresses, sends request, handles retries/errors, unmarshals response.
  • Get / Post / Patch — Convenience wrappers around DoJSON.
  • GetRawToWriter(path, w) — Streaming GET that writes the raw response body to w. Used by confab session download for large transcript files. Body is streamed through io.LimitReader(maxResponseSize); on write error mid-stream the destination may be left partially populated, so callers should treat the output as incomplete on error.
  • SetUserAgent(ua) — Package-level function, must be called once at startup (from main.go).
  • BuildUserAgent(version) — Constructs the canonical user-agent string from a version.

Sentinel Errors

Error HTTP Status Meaning
ErrUnauthorized 401, 403 Invalid or expired API key
ErrSessionNotFound 404 Session doesn't exist on backend
ErrConflict 409 Duplicate resource

Note: 429 (rate limited) errors use an internal sentinel (errRateLimited) since no callers currently need to distinguish rate limiting from other failures.

Callers use errors.Is(err, http.ErrUnauthorized) to handle specific cases.

Design Decisions

Zstd over gzip. Better compression ratio for JSON payloads, which matters for large transcript chunks. The 1KB compression threshold (compressionThreshold) avoids compressing tiny payloads where overhead exceeds savings.

Retry only on 429. Rate limiting is transient and retryable. Other errors (400, 500) are not retried — they indicate bugs or server issues that won't resolve by waiting. Retries use exponential backoff (1s initial, 2x multiplier, 60s max) and respect Retry-After headers (capped at maxRetryAfterSeconds = 3600s).

Bounded response reading. Response bodies are read with io.LimitReader capped at maxResponseSize (32MB) to prevent memory exhaustion from malicious or malformed responses. Error messages include response body truncated to 256 bytes via truncateBody() to avoid log flooding.

Localhost TLS exemption. Non-localhost URLs enforce TLS 1.2+. Localhost is exempt for local development. This is checked by hostname, not scheme.

Never log payloads. DoJSON logs payload byte counts but never the content. Payloads contain transcript data which may include sensitive information even after redaction.

How to Extend

Adding a new error type: Define a sentinel error (var ErrFoo = errors.New("...")), add a case in mapStatusToError, and document it above.

Changing retry behavior: Modify maxRetries, initialBackoff, maxBackoff, or backoffMultiplier constants. Consider that retry changes affect all API calls.

Invariants

  • SetUserAgent() must be called once at startup before any HTTP requests.
  • TLS 1.2+ is enforced for all non-localhost connections — do not weaken this.
  • Payloads must never be logged (privacy).
  • Retry logic must only apply to 429 responses.
  • Response bodies must always be read with a size limit (maxResponseSize) — never use unbounded io.ReadAll on HTTP responses.
  • Retry-After values must be capped (maxRetryAfterSeconds) — a malicious server must not be able to make the client sleep indefinitely.

Testing

go test ./pkg/http/...

Tests use httptest.NewServer to verify compression thresholds, error handling, and retry behavior.

Dependencies

Uses: github.com/klauspost/compress/zstd, pkg/config (UploadConfig for backend URL/API key), pkg/logger

Used by: pkg/sync/ (via Client), cmd/ (login, status validation)

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrConflict = errors.New("conflict")

ErrConflict is returned when the server returns 409. This typically means the resource already exists (e.g., duplicate link).

View Source
var ErrSessionNotFound = errors.New("session not found")

ErrSessionNotFound is returned when the server returns 404. This typically means the session was deleted from the backend.

View Source
var ErrUnauthorized = errors.New("unauthorized")

ErrUnauthorized is returned when the server returns 401 or 403. This typically means the API key is invalid or expired.

Functions

func BuildUserAgent

func BuildUserAgent(version string) string

BuildUserAgent constructs a User-Agent string in the format: confab/version (os; arch)

func SetMaxResponseSizeForTest added in v0.16.0

func SetMaxResponseSizeForTest(n int64) (restore func())

SetMaxResponseSizeForTest temporarily lowers the response-size cap so tests can exercise the limit without allocating 32MB of memory. Returns a restore function that callers should defer. Intended for test code only — do not call from production.

func SetUserAgent

func SetUserAgent(ua string)

SetUserAgent sets the User-Agent header for all HTTP requests. Should be called once at startup before any requests are made.

Types

type Client

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

Client is a configured HTTP client for making authenticated requests to the backend

func NewClient

func NewClient(cfg *config.UploadConfig, timeout time.Duration) (*Client, error)

NewClient creates a new authenticated HTTP client

func (*Client) DoJSON

func (c *Client) DoJSON(method, path string, reqBody, respBody interface{}) error

DoJSON performs an HTTP request with JSON body and parses JSON response Automatically sets Content-Type, Authorization, and handles error responses. Payloads larger than 1KB are compressed with zstd. Retries with exponential backoff on 429 (rate limited) responses.

func (*Client) Get

func (c *Client) Get(path string, respBody interface{}) error

Get performs a GET request with JSON response parsing

func (*Client) GetRawToWriter added in v0.15.0

func (c *Client) GetRawToWriter(path string, w io.Writer) error

GetRawToWriter performs a GET request and streams the response body to w. Unlike Get/DoJSON, this does not attempt JSON unmarshaling — it copies the raw response bytes directly. Handles auth and error status mapping, but does not retry on 429 (retrying a stream would produce corrupt output since partial data from the first attempt is already written to w).

func (*Client) Patch

func (c *Client) Patch(path string, reqBody, respBody interface{}) error

Patch performs a PATCH request with JSON body and response

func (*Client) Post

func (c *Client) Post(path string, reqBody, respBody interface{}) error

Post performs a POST request with JSON body and response

Jump to

Keyboard shortcuts

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