dhttp

package module
v0.1.2 Latest Latest
Warning

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

Go to latest
Published: Aug 15, 2025 License: Apache-2.0 Imports: 25 Imported by: 5

README

dhttp - StreamingFast HTTP Library

Go HTTP utilities for microservices with integrated logging, tracing, and error handling used in StreamingFast products.

Key Features

  • Request handling: Extract and validate URL parameters, query strings, and JSON payloads
  • Response writing: JSON, text, HTML, and raw responses with proper headers and error logging
  • Error handling: Structured HTTP error responses with tracing support
  • Handler wrappers: Simplified handler patterns for common use cases
  • Utilities: Real IP detection, response forwarding

Core APIs

Handlers
  • JSONHandler - Wrap functions that return JSON responses
  • RawHandler - Stream raw content from io.ReadCloser
  • DirectHandler - Direct response writer control with error handling
Request Processing
Response Writing
Error Types

See full documentation for complete API reference.

Documentation

Overview

Package dhttp provides HTTP utilities for microservices with integrated logging, tracing, and error handling.

This package is designed for StreamingFast products and provides common HTTP handling patterns used across various microservices. It includes structured error responses, request validation, response writing utilities, and handler wrappers that automatically handle logging and tracing.

Handler Wrappers

The package provides three main handler wrappers that simplify common HTTP patterns:

  • JSONHandler: Wraps functions that return JSON responses
  • RawHandler: Streams raw content from io.ReadCloser
  • DirectHandler: Provides direct response writer control with error handling

Example using JSONHandler:

http.Handle("/api/users", dhttp.JSONHandler(func(r *http.Request) (any, error) {
	// Your logic here
	return users, nil
}))

Request Processing

Extract and validate request data using ExtractRequest for URL/query parameters or ExtractJSONRequest for JSON payloads:

type UserRequest struct {
	ID   int    `schema:"id"`
	Name string `schema:"name"`
}

var req UserRequest
if err := dhttp.ExtractRequest(ctx, r, &req, dhttp.NoValidation); err != nil {
	// Handle validation error
}

Response Writing

Write responses with proper headers, logging, and tracing:

dhttp.WriteJSON(ctx, w, responseData)
dhttp.WriteError(ctx, w, err)
dhttp.WriteText(ctx, w, "Hello World")

Error Handling

Create structured HTTP errors with proper status codes:

return dhttp.BadRequestError(ctx, nil, "invalid_input", "The request is invalid")
return dhttp.NotFoundError(ctx, nil, "user_not_found", "User not found")
return dhttp.InternalServerError(ctx, err, "database_error", "Database connection failed")

The package provides pre-made error functions for all standard HTTP status codes:

return dhttp.UnauthorizedError(ctx, nil, "auth_required", "Authentication required")
return dhttp.ForbiddenError(ctx, nil, "access_denied", "Access denied")
return dhttp.TooManyRequestsError(ctx, nil, "rate_limit", "Rate limit exceeded")

See errors.go for the complete list of available HTTP error functions (4xx and 5xx).

All HTTP errors are wrapped and can be easily used throughout your application. Error responses include trace IDs for debugging and follow a consistent JSON structure.

Utilities

Additional utilities include:

  • RealIP: Extract the real client IP from requests (Google Cloud Load Balancer aware)
  • ForwardResponse: Forward HTTP responses from upstream services
  • EmptyBody: Return empty response bodies from JSON handlers

The package integrates with OpenTelemetry for tracing and uses structured logging throughout all operations.

Index

Constants

This section is empty.

Variables

View Source
var (
	BadRequestError                   = newErrorClass(http.StatusBadRequest)
	UnauthorizedError                 = newErrorClass(http.StatusUnauthorized)
	PaymentRequiredError              = newErrorClass(http.StatusPaymentRequired)
	ForbiddenError                    = newErrorClass(http.StatusForbidden)
	NotFoundError                     = newErrorClass(http.StatusNotFound)
	MethodNotAllowedError             = newErrorClass(http.StatusMethodNotAllowed)
	NotAcceptableError                = newErrorClass(http.StatusNotAcceptable)
	ProxyAuthRequiredError            = newErrorClass(http.StatusProxyAuthRequired)
	RequestTimeoutError               = newErrorClass(http.StatusRequestTimeout)
	ConflictError                     = newErrorClass(http.StatusConflict)
	GoneError                         = newErrorClass(http.StatusGone)
	LengthRequiredError               = newErrorClass(http.StatusLengthRequired)
	PreconditionFailedError           = newErrorClass(http.StatusPreconditionFailed)
	RequestEntityTooLargeError        = newErrorClass(http.StatusRequestEntityTooLarge)
	RequestURITooLongError            = newErrorClass(http.StatusRequestURITooLong)
	UnsupportedMediaTypeError         = newErrorClass(http.StatusUnsupportedMediaType)
	RequestedRangeNotSatisfiableError = newErrorClass(http.StatusRequestedRangeNotSatisfiable)
	ExpectationFailedError            = newErrorClass(http.StatusExpectationFailed)
	TeapotError                       = newErrorClass(http.StatusTeapot)
	UnprocessableEntityError          = newErrorClass(http.StatusUnprocessableEntity)
	LockedError                       = newErrorClass(http.StatusLocked)
	FailedDependencyError             = newErrorClass(http.StatusFailedDependency)
	UpgradeRequiredError              = newErrorClass(http.StatusUpgradeRequired)
	PreconditionRequiredError         = newErrorClass(http.StatusPreconditionRequired)
	TooManyRequestsError              = newErrorClass(http.StatusTooManyRequests)
	RequestHeaderFieldsTooLargeError  = newErrorClass(http.StatusRequestHeaderFieldsTooLarge)
	UnavailableForLegalReasonsError   = newErrorClass(http.StatusUnavailableForLegalReasons)
)
View Source
var (
	InternalServerError                = newErrorClass(http.StatusInternalServerError)
	NotImplementedError                = newErrorClass(http.StatusNotImplemented)
	BadGatewayError                    = newErrorClass(http.StatusBadGateway)
	ServiceUnavailableError            = newErrorClass(http.StatusServiceUnavailable)
	GatewayTimeoutError                = newErrorClass(http.StatusGatewayTimeout)
	StatusHTTPVersionNotSupportedError = newErrorClass(http.StatusHTTPVersionNotSupported)
	VariantAlsoNegotiatesError         = newErrorClass(http.StatusVariantAlsoNegotiates)
	InsufficientStorageError           = newErrorClass(http.StatusInsufficientStorage)
	LoopDetectedError                  = newErrorClass(http.StatusLoopDetected)
	NotExtendedError                   = newErrorClass(http.StatusNotExtended)
	NetworkAuthenticationRequiredError = newErrorClass(http.StatusNetworkAuthenticationRequired)
)
View Source
var FowardResponse = ForwardResponse

Deprecated: Use ForwardResponse instead.

View Source
var NoValidation = &NoOpValidator{}

Functions

func DirectHandler

func DirectHandler(processor DirectHandlerProcessor) http.Handler

DirectHandler wraps a simpler `func(w http.ResponseWriter, r *http.Request) (err error)` processor.

This handler gives you full control over the response writer but with the added benefinit of having the error handling done for you.

func EmptyBody

func EmptyBody() any

EmptyBody can be returned by a JSONHandlerProcessor to signal the JSON handler that the response body should be empty.

This must be used and not `nil` to avoid the JSON handler to write a `null` value.

func ExtractJSONRequest

func ExtractJSONRequest(ctx context.Context, r *http.Request, request any, validator Validator) error

func ExtractRequest

func ExtractRequest(ctx context.Context, r *http.Request, request any, validator Validator) error

func ForwardResponse

func ForwardResponse(ctx context.Context, w http.ResponseWriter, response *http.Response)

func JSONHandler

func JSONHandler(processor JSONHandlerProcessor) http.Handler

JSONHandler wraps a simpler `func(r *http.Request) (out any, err error)` processor.

If the processor returns something as the `out` value, the `out` is serialized as JSON and return to the user.

If the processor returns an error insteand, the `err` value is written to the user using `dhttp.WriteError` call.

To have nothing returns as the body of the response, you can do:

return dhttp.EmptyBody(), nil

Which will return a 200 OK with an empty body.

func RawHandler

func RawHandler(processor RawHandlerProcessor) http.Handler

RawHandler wraps a simpler `func(r *http.Request) (out io.ReadCloser, err error)` processor.

If the processor returns something as the `out` value, the `out` reader is fully transmitted to the user then close.

If the processor returns an error insteand, the `err` value is written to the user using `dhttp.WriteError` call.

func RealIP

func RealIP(r *http.Request) string

RealIP tries to determine the actual client remote IP that initiated the connection. This method is aware of Google Cloud Load Balancer and if the `X-Forwarded-For` exists and has 2 or more addresses, it will assume it's coming from Google Cloud Load Balancer.

When behind a Google Load Balancer, the only two values that we can be sure about are the `n - 2` and `n - 1` (so the last two values in the array). The very last value (`n - 1`) is the Google IP and the `n - 2` value is the actual remote IP that reached the load balancer.

When there is more than 2 IPs, all other values prior `n - 2` are those coming from the `X-Forwarded-For` HTTP header received by the load balancer directly, so something a client might have added manually. Since they are coming from an HTTP header and not from Google directly, they can be forged and cannot be trusted.

@see https://cloud.google.com/load-balancing/docs/https#x-forwarded-for_header

func WriteError

func WriteError(ctx context.Context, w http.ResponseWriter, err error)

Write a generic error response to HTTP, the error is going to transformed to a ErrorResponse via a call to ToErrorResponse.

A generic `unable to fullfil request` message will be logged, use WriteErrorResponse to control which message will be logged.

func WriteErrorResponse deprecated

func WriteErrorResponse(ctx context.Context, w http.ResponseWriter, message string, err error)

WriteError writes the receiver error to HTTP and log it into a Zap logger at the same time with the right level based on the actual status code. The `WriteError` handles various type for the `err` parameter.

Deprecated: HTTP error handling has moved to dhttp(https://github.com/streamingfast/dhttp) package, which provides a more comprehensive and flexible approach to error handling in HTTP contexts.

func WriteFromBytes

func WriteFromBytes(ctx context.Context, w http.ResponseWriter, bytes []byte)

func WriteFromReader

func WriteFromReader(ctx context.Context, w http.ResponseWriter, reader io.Reader)

func WriteHTML

func WriteHTML(ctx context.Context, w http.ResponseWriter, htmlTpl *template.Template, data any)

func WriteJSON

func WriteJSON(ctx context.Context, w http.ResponseWriter, v any)

func WriteJSONString

func WriteJSONString(ctx context.Context, w http.ResponseWriter, json string)

func WriteText

func WriteText(ctx context.Context, w http.ResponseWriter, content string)

func WriteTextf

func WriteTextf(ctx context.Context, w http.ResponseWriter, format string, arguments ...any)

Types

type DirectHandlerProcessor

type DirectHandlerProcessor = func(w http.ResponseWriter, r *http.Request) (err error)

type ErrorResponse

type ErrorResponse struct {
	Code    string         `json:"code"`
	TraceID string         `json:"trace_id"`
	Status  int            `json:"-"`
	Message string         `json:"message"`
	Details map[string]any `json:"details,omitempty"`
	Causer  error          `json:"-"`
}

func ErrorFromStatus

func ErrorFromStatus(status int, ctx context.Context, cause error, errorCode string, message any, keyvals ...any) *ErrorResponse

ErrorFromStatus can be used to programmatically route the right status to one of the HTTP error class above

func InvalidJSONError

func InvalidJSONError(ctx context.Context, err error) *ErrorResponse

func MissingBodyError

func MissingBodyError(ctx context.Context) *ErrorResponse

func RequestValidationError

func RequestValidationError(ctx context.Context, errors url.Values) *ErrorResponse

func ServiceNotAvailableError

func ServiceNotAvailableError(ctx context.Context, cause error, serviceName string) *ErrorResponse

ServiceNotAvailableError represents a failure at the transport layer to reach a given micro-service. Note that while `serviceName` is required, it's not directly available to final response for now, will probably encrypt it into an opaque string if you ever make usage of it

func ToErrorResponse

func ToErrorResponse(ctx context.Context, err error) *ErrorResponse

ToErrorResponse turns a plain `error` interface into a proper `ErrorResponse` object. It does so with the following rules:

- If `err` is already an `ErrorResponse`, turns it into such and returns it. - If `err` was wrapped, find the most cause which is an `ErrorResponse` and returns it. - If `err` is a status.Status (or one that was wrapped), convert it to an ErrorResponse - Otherwise, return an `UnexpectedError` with the cause sets to `err` received.

func UnexpectedError

func UnexpectedError(ctx context.Context, cause error) *ErrorResponse

func (*ErrorResponse) Cause

func (e *ErrorResponse) Cause() error

func (*ErrorResponse) Error

func (e *ErrorResponse) Error() string

func (*ErrorResponse) ResponseStatus

func (e *ErrorResponse) ResponseStatus() int

func (*ErrorResponse) Unwrap

func (e *ErrorResponse) Unwrap() error

type JSONHandlerProcessor

type JSONHandlerProcessor = func(r *http.Request) (out any, err error)

type LoggingRoundTripper

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

func NewLoggingRoundTripper

func NewLoggingRoundTripper(logger *zap.Logger, tracer logging.Tracer, next http.RoundTripper) *LoggingRoundTripper

NewLoggingRoundTripper create a wrapping `http.RoundTripper` aware object that intercepts the request as well as the response and logs them to the specified logger according to some rules if the debug and tracing level are enabled or not.

If the received `next` argument is set as `nil`, the `http.DefaultTransport` value will be used as the actual transport handler.

If debug is enabled, a one-line Request log containing HTTP method, URL and Headers is logged, and if tracing is enabled, the full request is dumped to the logger, in a multi-line log.

If debug is enabled, a one-line Response log containing HTTP status and body length is logged, and if tracing is enabled, the full request is dumped to the logger, in a multi-line log.

func (*LoggingRoundTripper) RoundTrip

func (t *LoggingRoundTripper) RoundTrip(request *http.Request) (*http.Response, error)

type NoOpValidator

type NoOpValidator struct{}

type RawHandlerProcessor

type RawHandlerProcessor = func(r *http.Request) (out io.ReadCloser, err error)

type RequestValidator

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

func NewJSONRequestValidator

func NewJSONRequestValidator(rules validator.Rules, options ...validator.Option) *RequestValidator

func NewRequestValidator

func NewRequestValidator(rules validator.Rules, options ...validator.Option) *RequestValidator

type Validator

type Validator interface {
	// contains filtered or unexported methods
}

Directories

Path Synopsis
example
json_server command

Jump to

Keyboard shortcuts

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