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 ¶
- Constants
- func GetChiRoutePattern(r *http.Request) string
- func NewRouter(logger log.FieldLogger, opts RouterOpts) chi.Router
- type APIRoute
- type APIVersion
- type Config
- type ConfigOption
- type HTTPRequestMetricsOpts
- type HTTPServer
- type HealthCheck
- type HealthCheckComponentName
- type HealthCheckContext
- type HealthCheckHandler
- type HealthCheckResult
- type HealthCheckStatus
- type LimitsConfig
- type LogConfig
- type Opts
- type RouterOpts
- type TLSConfig
- type TimeoutsConfig
Examples ¶
Constants ¶
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 ¶
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 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 ¶
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 ¶
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 ¶
func (l *LimitsConfig) Set(dp config.DataProvider) error
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.
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
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 ¶
func (t *TimeoutsConfig) Set(dp config.DataProvider) error
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. |