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 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
UnixSocketPath string
Timeouts TimeoutsConfig
Limits LimitsConfig
Log LogConfig
TLS TLSConfig
// contains filtered or unexported fields
}
Config represents a set of configuration parameters for HTTPServer.
func NewConfigWithKeyPrefix ¶
NewConfigWithKeyPrefix creates a new instance of the Config. Allows specifying key prefix which will be used for parsing configuration parameters.
func (*Config) KeyPrefix ¶
KeyPrefix returns a key prefix with which all configuration parameters should be presented.
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.
type HTTPRequestMetricsOpts ¶
type HTTPRequestMetricsOpts struct {
// Metrics opts.
Namespace string
DurationBuckets []float64
ConstLabels prometheus.Labels
// Middleware opts.
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.
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 ¶
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
RequestHeaders []string
ExcludedEndpoints []string
SecretQueryParams []string
AddRequestInfoToLogger bool
SlowRequestThreshold time.Duration
}
LogConfig represents a set of configuration parameters for HTTPServer relating to logging.
type Opts ¶
type Opts struct {
ServiceNameInURL string
APIRoutes map[APIVersion]APIRoute
RootMiddlewares []func(http.Handler) http.Handler
ErrorDomain string
HealthCheck HealthCheck
HealthCheckContext HealthCheckContext
MetricsHandler http.Handler
HTTPRequestMetrics HTTPRequestMetricsOpts
}
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 ¶
TLSConfig contains configuration parameters needed to initialize(or not) secure server
type TimeoutsConfig ¶
type TimeoutsConfig struct {
Write time.Duration
Read time.Duration
ReadHeader time.Duration
Idle time.Duration
Shutdown time.Duration
}
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.
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. |