httpserver

package
v1.29.0 Latest Latest
Warning

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

Go to latest
Published: Dec 23, 2025 License: MIT Imports: 18 Imported by: 0

Documentation

Overview

Package httpserver provides preconfigured and ready-to-use in production HTTP server.

Example
package main

import (
	"fmt"
	golog "log"
	"net/http"
	"os"
	"strings"

	"github.com/go-chi/chi/v5"

	"github.com/acronis/go-appkit/config"
	"github.com/acronis/go-appkit/httpserver"
	"github.com/acronis/go-appkit/httpserver/middleware"
	"github.com/acronis/go-appkit/log"
	"github.com/acronis/go-appkit/profserver"
	"github.com/acronis/go-appkit/restapi"
	"github.com/acronis/go-appkit/service"
)

/*
Add "// Output:" in the end of Example() function and run:

	$ go test ./httpserver -v -run Example

Application and pprof servers will be ready to handle HTTP requests:

	$ curl localhost:8888/healthz
	{"components":{"component-a":true,"component-b":false}}

	$ curl localhost:8888/metrics
	# Metrics in Prometheus format

	$ curl localhost:8888/api/my-service/v1/hello
	{"message":"Hello"}

	$ curl 'localhost:8888/api/my-service/v2/hi?name=Alice'
	{"message":"Hi Alice"}
*/

func main() {
	if err := runApp(); err != nil {
		golog.Fatal(err)
	}
}

func runApp() error {
	cfg, err := loadAppConfig()
	if err != nil {
		return fmt.Errorf("load config: %w", err)
	}

	logger, loggerClose := log.NewLogger(cfg.Log)
	defer loggerClose()

	var serviceUnits []service.Unit

	// Create HTTP server that provides /healthz, /metrics, and /api/{service-name}/v{number}/* endpoints.
	httpServer, err := makeHTTPServer(cfg.Server, logger)
	if err != nil {
		return err
	}
	serviceUnits = append(serviceUnits, httpServer)

	if cfg.ProfServer.Enabled {
		// Create HTTP server for profiling (pprof is used under the hood).
		serviceUnits = append(serviceUnits, profserver.New(cfg.ProfServer, logger))
	}

	return service.New(logger, service.NewCompositeUnit(serviceUnits...)).Start()
}

func makeHTTPServer(cfg *httpserver.Config, logger log.FieldLogger) (*httpserver.HTTPServer, error) {
	const errorDomain = "MyService" // Error domain is useful for distinguishing errors from different services (e.g. proxies).

	apiRoutes := map[httpserver.APIVersion]httpserver.APIRoute{
		1: func(router chi.Router) {
			router.Get("/hello", v1HelloHandler())
		},
		2: func(router chi.Router) {
			router.Get("/hi", v2HiHandler(errorDomain))
		},
	}

	opts := httpserver.Opts{
		ServiceNameInURL: "my-service",
		ErrorDomain:      errorDomain,
		APIRoutes:        apiRoutes,
		HealthCheck: func() (httpserver.HealthCheckResult, error) {
			// 503 status code will be returned if any of the components is unhealthy.
			return map[httpserver.HealthCheckComponentName]httpserver.HealthCheckStatus{
				"component-a": httpserver.HealthCheckStatusOK,
				"component-b": httpserver.HealthCheckStatusOK,
			}, nil
		},
	}

	httpServer, err := httpserver.New(cfg, logger, opts)
	if err != nil {
		return nil, err
	}

	// Custom routes can be added using chi.Router directly.
	httpServer.HTTPRouter.Get("/custom-route", customRouteHandler)

	return httpServer, nil
}

func loadAppConfig() (*AppConfig, error) {
	// Environment variables may be used to configure the server as well.
	// Variable name is built from the service name and path to the configuration parameter separated by underscores.
	_ = os.Setenv("MY_SERVICE_SERVER_TIMEOUTS_SHUTDOWN", "10s")
	_ = os.Setenv("MY_SERVICE_LOG_LEVEL", "info")

	// Configuration may be read from a file or io.Reader. YAML and JSON formats are supported.
	cfgReader := strings.NewReader(`
server:
  address: ":8888"
  timeouts:
    write: 1m
    read: 15s
    readHeader: 10s
    idle: 1m
    shutdown: 5s
  limits:
    maxBodySize: 1M
  log:
    requestStart: true
profServer:
  enabled: true
  address: ":8889"
log:
  level: warn
  format: json
  output: stdout
`)

	cfgLoader := config.NewDefaultLoader("my_service")
	cfg := NewAppConfig()
	err := cfgLoader.LoadFromReader(cfgReader, config.DataTypeYAML, cfg)
	return cfg, err
}

func v1HelloHandler() func(rw http.ResponseWriter, r *http.Request) {
	return func(rw http.ResponseWriter, r *http.Request) {
		logger := middleware.GetLoggerFromContext(r.Context())
		restapi.RespondJSON(rw, map[string]string{"message": "Hello"}, logger)
	}
}

func v2HiHandler(errorDomain string) func(rw http.ResponseWriter, r *http.Request) {
	return func(rw http.ResponseWriter, r *http.Request) {
		logger := middleware.GetLoggerFromContext(r.Context())
		name := r.URL.Query().Get("name")
		if len(name) < 3 {
			apiErr := restapi.NewError(errorDomain, "invalidName", "Name must be at least 3 characters long")
			restapi.RespondError(rw, http.StatusBadRequest, apiErr, middleware.GetLoggerFromContext(r.Context()))
			return
		}
		restapi.RespondJSON(rw, map[string]string{"message": fmt.Sprintf("Hi %s", name)}, logger)
	}
}

func customRouteHandler(rw http.ResponseWriter, r *http.Request) {
	logger := middleware.GetLoggerFromContext(r.Context())
	if _, err := rw.Write([]byte("Content from the custom route")); err != nil {
		logger.Error("error while writing response body", log.Error(err))
	}
}

type AppConfig struct {
	Server     *httpserver.Config
	ProfServer *profserver.Config
	Log        *log.Config
}

func NewAppConfig() *AppConfig {
	return &AppConfig{
		Server:     httpserver.NewConfig(),
		ProfServer: profserver.NewConfig(),
		Log:        log.NewConfig(),
	}
}

func (c *AppConfig) SetProviderDefaults(dp config.DataProvider) {
	config.CallSetProviderDefaultsForFields(c, dp)
}

func (c *AppConfig) Set(dp config.DataProvider) error {
	return config.CallSetForFields(c, dp)
}

Index

Examples

Constants

View Source
const StatusClientClosedRequest = 499

StatusClientClosedRequest is a special HTTP status code used by Nginx to show that the client closed the request before the server could send a response

Variables

This section is empty.

Functions

func GetChiRoutePattern

func GetChiRoutePattern(r *http.Request) string

GetChiRoutePattern extracts chi route pattern from request.

func NewRouter

func NewRouter(logger log.FieldLogger, opts RouterOpts) chi.Router

NewRouter creates a new chi.Router and performs its basic configuration.

Types

type APIRoute

type APIRoute = func(router chi.Router)

APIRoute is a type alias for single API route.

type APIVersion

type APIVersion = int

APIVersion is a type alias for API version.

type Config

type Config struct {
	Address        string         `mapstructure:"address" yaml:"address" json:"address"`
	UnixSocketPath string         `mapstructure:"unixSocketPath" yaml:"unixSocketPath" json:"unixSocketPath"`
	Timeouts       TimeoutsConfig `mapstructure:"timeouts" yaml:"timeouts" json:"timeouts"`
	Limits         LimitsConfig   `mapstructure:"limits" yaml:"limits" json:"limits"`
	Log            LogConfig      `mapstructure:"log" yaml:"log" json:"log"`
	TLS            TLSConfig      `mapstructure:"tls" yaml:"tls" json:"tls"`
	// contains filtered or unexported fields
}

Config represents a set of configuration parameters for HTTPServer. Configuration can be loaded in different formats (YAML, JSON) using config.Loader, viper, or with json.Unmarshal/yaml.Unmarshal functions directly.

func NewConfig

func NewConfig(options ...ConfigOption) *Config

NewConfig creates a new instance of the Config.

func NewConfigWithKeyPrefix

func NewConfigWithKeyPrefix(keyPrefix string) *Config

NewConfigWithKeyPrefix creates a new instance of the Config with a key prefix. This prefix will be used by config.Loader. Deprecated: use NewConfig with WithKeyPrefix instead.

func NewDefaultConfig added in v1.11.0

func NewDefaultConfig(options ...ConfigOption) *Config

NewDefaultConfig creates a new instance of the Config with default values.

func (*Config) KeyPrefix

func (c *Config) KeyPrefix() string

KeyPrefix returns a key prefix with which all configuration parameters should be presented. Implements config.KeyPrefixProvider interface.

func (*Config) Set

func (c *Config) Set(dp config.DataProvider) error

Set sets HTTPServer configuration values from config.DataProvider.

func (*Config) SetProviderDefaults

func (c *Config) SetProviderDefaults(dp config.DataProvider)

SetProviderDefaults sets default configuration values for HTTPServer in config.DataProvider. Implements config.Config interface.

type ConfigOption added in v1.11.0

type ConfigOption func(*configOptions)

ConfigOption is a type for functional options for the Config.

func WithKeyPrefix added in v1.11.0

func WithKeyPrefix(keyPrefix string) ConfigOption

WithKeyPrefix returns a ConfigOption that sets a key prefix for parsing configuration parameters. This prefix will be used by config.Loader.

type HTTPRequestMetricsOpts

type HTTPRequestMetricsOpts struct {
	// Metrics opts.
	Namespace       string
	DurationBuckets []float64
	ConstLabels     prometheus.Labels

	// Middleware opts.
	// Deprecated: GetUserAgentType be removed in the next major version. Please use CustomLabels with Context instead.
	GetUserAgentType middleware.UserAgentTypeGetterFunc
	GetRoutePattern  middleware.RoutePatternGetterFunc
}

HTTPRequestMetricsOpts represents options for HTTPRequestMetricsOpts middleware that used in HTTPServer.

type HTTPServer

type HTTPServer struct {
	// TODO: URL does not contain port when port is dynamically chosen
	URL             string
	HTTPServer      *http.Server
	UnixSocketPath  string
	TLS             TLSConfig
	HTTPRouter      chi.Router
	Logger          log.FieldLogger
	ShutdownTimeout time.Duration
	// contains filtered or unexported fields
}

HTTPServer represents a wrapper around http.Server with additional fields and methods. chi.Router is used as a handler for the server by default. It also implements service.Unit and service.MetricsRegisterer interfaces.

func New

func New(cfg *Config, logger log.FieldLogger, opts Opts) (*HTTPServer, error)

New creates a new HTTPServer with predefined logging, metrics collecting, recovering after panics and health-checking functionality.

func NewWithHandler

func NewWithHandler(cfg *Config, logger log.FieldLogger, handler http.Handler) *HTTPServer

NewWithHandler creates a new HTTPServer receiving already created http.Handler. Unlike the New constructor, it doesn't add any middlewares. Typical use case: create a chi.Router using NewRouter and pass it into NewWithHandler. Deprecated: Will be removed in the next major version. Please use New with Handler options instead.

func (*HTTPServer) GetPort

func (s *HTTPServer) GetPort() int

func (*HTTPServer) MustRegisterMetrics

func (s *HTTPServer) MustRegisterMetrics()

MustRegisterMetrics registers metrics in Prometheus client and panics if any error occurs.

func (*HTTPServer) NetworkAndAddr

func (s *HTTPServer) NetworkAndAddr() (network string, addr string)

NetworkAndAddr returns network type ("tcp" or "unix") and address (path to unix socket in case of "unix" network).

func (*HTTPServer) Start

func (s *HTTPServer) Start(fatalError chan<- error)

Start starts application HTTP server in a blocking way. It's supposed that this method will be called in a separate goroutine. If a fatal error occurs, it will be sent to the fatalError channel.

func (*HTTPServer) Stop

func (s *HTTPServer) Stop(gracefully bool) error

Stop stops application HTTP server (gracefully or not).

func (*HTTPServer) UnregisterMetrics

func (s *HTTPServer) UnregisterMetrics()

UnregisterMetrics unregisters metrics in Prometheus client.

type HealthCheck

type HealthCheck = func() (HealthCheckResult, error)

HealthCheck is a type alias for health-check operation. It's used for better readability.

type HealthCheckComponentName

type HealthCheckComponentName = string

HealthCheckComponentName is a type alias for component names. It's used for better readability.

type HealthCheckContext

type HealthCheckContext = func(ctx context.Context) (HealthCheckResult, error)

HealthCheckContext is a type alias for health-check operation that has access to the request Context

type HealthCheckHandler

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

HealthCheckHandler implements http.Handler and does health-check of a service.

func NewHealthCheckHandler

func NewHealthCheckHandler(fn HealthCheck) *HealthCheckHandler

NewHealthCheckHandler creates a new http.Handler for doing health-check. Passing function will be called inside handler and should return statuses of service's components.

func NewHealthCheckHandlerContext

func NewHealthCheckHandlerContext(fn HealthCheckContext) *HealthCheckHandler

NewHealthCheckHandlerContext creates a new http.Handler for doing health-check. It is able to access the request Context. Passing function will be called inside handler and should return statuses of service's components.

func (*HealthCheckHandler) ServeHTTP

func (h *HealthCheckHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request)

ServeHTTP serves heath-check HTTP request.

type HealthCheckResult

type HealthCheckResult = map[HealthCheckComponentName]HealthCheckStatus

HealthCheckResult is a type alias for result of health-check operation. It's used for better readability.

type HealthCheckStatus

type HealthCheckStatus int

HealthCheckStatus is a resulting status of the health-check.

const (
	HealthCheckStatusOK HealthCheckStatus = iota
	HealthCheckStatusFail
)

Health-check statuses.

type LimitsConfig

type LimitsConfig struct {
	// MaxRequests is the maximum number of requests that can be processed concurrently.
	MaxRequests int `mapstructure:"maxRequests" yaml:"maxRequests" json:"maxRequests"`

	// MaxBodySizeBytes is the maximum size of the request body in bytes.
	MaxBodySizeBytes config.ByteSize `mapstructure:"maxBodySize" yaml:"maxBodySize" json:"maxBodySize"`
}

LimitsConfig represents a set of configuration parameters for HTTPServer relating to limits.

func (*LimitsConfig) Set

Set sets limit server configuration values from config.DataProvider.

type LogConfig

type LogConfig struct {
	RequestStart           bool                `mapstructure:"requestStart" yaml:"requestStart" json:"requestStart"`
	RequestHeaders         []string            `mapstructure:"requestHeaders" yaml:"requestHeaders" json:"requestHeaders"`
	ExcludedEndpoints      []string            `mapstructure:"excludedEndpoints" yaml:"excludedEndpoints" json:"excludedEndpoints"`
	SecretQueryParams      []string            `mapstructure:"secretQueryParams" yaml:"secretQueryParams"`
	AddRequestInfoToLogger bool                `mapstructure:"addRequestInfo" yaml:"addRequestInfo" json:"addRequestInfo"`
	SlowRequestThreshold   config.TimeDuration `mapstructure:"slowRequestThreshold" yaml:"slowRequestThreshold" json:"slowRequestThreshold"`
	TimeSlotsThreshold     config.TimeDuration `mapstructure:"timeSlotsThreshold" yaml:"timeSlotsThreshold" json:"timeSlotsThreshold"`
}

LogConfig represents a set of configuration parameters for HTTPServer relating to logging.

func (*LogConfig) Set

func (l *LogConfig) Set(dp config.DataProvider) error

Set sets log server configuration values from config.DataProvider.

type Opts

type Opts struct {
	// ServiceNameInURL is a prefix for API routes (e.g., "/api/service_name/v1").
	ServiceNameInURL string
	// APIRoutes is a map of API versions to their route configuration functions.
	APIRoutes map[APIVersion]APIRoute
	// RootMiddlewares is a list of middlewares to be applied to the root router.
	RootMiddlewares []func(http.Handler) http.Handler
	// ErrorDomain is used for error response formatting.
	ErrorDomain string
	// HealthCheck is a function that performs health check logic.
	HealthCheck HealthCheck
	// HealthCheckContext is a function that performs context-aware health check logic.
	HealthCheckContext HealthCheckContext
	// MetricsHandler is a custom handler for the /metrics endpoint (e.g., Prometheus handler).
	MetricsHandler http.Handler
	// HTTPRequestMetrics contains options for configuring HTTP request metrics middleware.
	HTTPRequestMetrics HTTPRequestMetricsOpts
	// Handler is a custom HTTP handler to use instead of the default router with middlewares.
	// When provided, default middlewares are not applied.
	Handler http.Handler
	// Listener is a pre-configured network listener to use instead of creating a new one.
	// Useful for custom listener configurations or testing with mock listeners.
	Listener net.Listener
}

Opts represents options for creating HTTPServer.

type RouterOpts

type RouterOpts struct {
	ServiceNameInURL   string
	APIRoutes          map[APIVersion]APIRoute
	RootMiddlewares    []func(http.Handler) http.Handler
	ErrorDomain        string
	HealthCheck        HealthCheck
	HealthCheckContext HealthCheckContext
	MetricsHandler     http.Handler
}

RouterOpts represents options for creating chi.Router.

type TLSConfig

type TLSConfig struct {
	Enabled     bool   `mapstructure:"enabled" yaml:"enabled" json:"enabled"`
	Certificate string `mapstructure:"cert" yaml:"cert" json:"cert"`
	Key         string `mapstructure:"key" yaml:"key" json:"key"`
}

TLSConfig contains configuration parameters needed to initialize(or not) secure server

func (*TLSConfig) Set

func (s *TLSConfig) Set(dp config.DataProvider) error

Set sets security server configuration values from config.DataProvider.

type TimeoutsConfig

type TimeoutsConfig struct {
	Write      config.TimeDuration `mapstructure:"write" yaml:"write" json:"write"`
	Read       config.TimeDuration `mapstructure:"read" yaml:"read" json:"read"`
	ReadHeader config.TimeDuration `mapstructure:"readHeader" yaml:"readHeader" json:"readHeader"`
	Idle       config.TimeDuration `mapstructure:"idle" yaml:"idle" json:"idle"`
	Shutdown   config.TimeDuration `mapstructure:"shutdown" yaml:"shutdown" json:"shutdown"`
}

TimeoutsConfig represents a set of configuration parameters for HTTPServer relating to timeouts.

func (*TimeoutsConfig) Set

Set sets timeout server configuration values from config.DataProvider. Implements config.Config interface.

Directories

Path Synopsis
Package middleware contains set of middlewares for HTTP server.
Package middleware contains set of middlewares for HTTP server.
throttle
Package throttle provides configurable middleware for throttling HTTP requests on the server side.
Package throttle provides configurable middleware for throttling HTTP requests on the server side.

Jump to

Keyboard shortcuts

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