Documentation
¶
Overview ¶
Package restserver provides an HTTP/HTTPS REST server that hosts a set of Service implementations behind a single httprouter-based mux.
The server assembles a fixed middleware chain around the router (outermost first): trusted proxy policy, correlation ID, request metrics, identity mapping, a nested metrics handler that reports the caller role to the outer one, request logging, optional CORS (which answers preflights before authorization), optional path/role authorization (restserver/authz), readiness gating (restserver/ready), and finally the router. Custom chains can be supplied via WithMuxFactory; StartHTTP still applies the trusted proxy policy around them, and they must keep CORS outside authz themselves.
By default the socket peer supplies the client IP and scheme. WithTrustedProxies accepts forwarding headers from the proxies in a policy built with identity.ParseTrustedProxies; the client IP is resolved once per request by identity.NewTrustedProxyHandler.
Typical usage:
cfg := myConfig{bindAddr: ":8080", name: "WebAPI"} // implements restserver.Config
srv, err := restserver.New("v1.0.0", "", cfg, nil /* tlsConfig */)
if err != nil {
return err
}
trust, err := identity.ParseTrustedProxies([]string{"10.2.0.0/16"}) // optional
if err != nil {
return err
}
srv.WithCORS(&restserver.CORSOptions{AllowedOrigins: []string{"*"}}).
WithTrustedProxies(trust).
WithAuthz(authzProvider) // optional
srv.AddService(mySvc) // implements restserver.Service
if err := srv.StartHTTP(); err != nil { // non-blocking, serves in a goroutine
return err
}
defer srv.StopHTTP() // drains requests, then closes services
A Service registers its routes on the Router passed to Register:
func (s *mySvc) Register(r restserver.Router) {
r.GET("/v1/items/:id", func(w http.ResponseWriter, req *http.Request, p restserver.Params) {
marshal.WriteJSON(w, req, s.get(p.ByName("id")))
})
}
Handlers are expected to write responses with xhttp/marshal and report failures as xhttp/httperror values.
Index ¶
- Constants
- func GetHostName(bindAddr string) string
- func GetPort(bindAddr string) string
- func GetServerBaseURL(s Server) *url.URL
- func GetServerURL(s Server, r *http.Request, relativeEndpoint string) *url.URL
- type CORSOptions
- type Config
- type HTTPServer
- func (server *HTTPServer) AddService(s Service)
- func (server *HTTPServer) Config() Config
- func (server *HTTPServer) HTTPConfig() Config
- func (server *HTTPServer) HostName() string
- func (server *HTTPServer) IsReady() bool
- func (server *HTTPServer) LocalIP() string
- func (server *HTTPServer) Name() string
- func (server *HTTPServer) NewMux() http.Handler
- func (server *HTTPServer) OnEvent(evt ServerEvent, handler ServerEventFunc)
- func (server *HTTPServer) Port() string
- func (server *HTTPServer) Protocol() string
- func (server *HTTPServer) PublicURL() string
- func (server *HTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request)
- func (server *HTTPServer) Service(name string) Service
- func (server *HTTPServer) StartHTTP() error
- func (server *HTTPServer) StartedAt() time.Time
- func (server *HTTPServer) StopHTTP()
- func (server *HTTPServer) TLSConfig() *tls.Config
- func (server *HTTPServer) Uptime() time.Duration
- func (server *HTTPServer) Version() string
- func (server *HTTPServer) WithAuthz(authz authz.HTTPAuthz) *HTTPServer
- func (server *HTTPServer) WithCORS(cors *CORSOptions) *HTTPServer
- func (server *HTTPServer) WithIdentityProvider(provider identity.ProviderFromRequest) *HTTPServer
- func (server *HTTPServer) WithMaxRequestBody(maxBytes int64) *HTTPServer
- func (server *HTTPServer) WithMuxFactory(muxFactory MuxFactory)
- func (server *HTTPServer) WithShutdownTimeout(timeout time.Duration) *HTTPServer
- func (server *HTTPServer) WithTimeouts(timeouts limits.Timeouts) *HTTPServer
- func (server *HTTPServer) WithTrustedProxies(trust *identity.TrustedProxies) *HTTPServer
- type Handle
- type MuxFactory
- type Params
- type Router
- type Server
- type ServerEvent
- type ServerEventFunc
- type Service
- type TLSInfoConfig
Examples ¶
Constants ¶
const ( // EvtSourceStatus is the event source name for service status events. EvtSourceStatus = "status" // EvtServiceStarted is the event message for a started service. EvtServiceStarted = "service started" // EvtServiceStopped is the event message for a stopped service. EvtServiceStopped = "service stopped" )
Event source and message names that services may use when reporting lifecycle status (for example to an audit log).
const MaxRequestSize = limits.DefaultMaxRequestBody
MaxRequestSize is the default HTTP request body limit in bytes (10 MiB). WithMaxRequestBody overrides it for a server.
Variables ¶
This section is empty.
Functions ¶
func GetHostName ¶
GetHostName returns the host part of an HTTP bind address, or the OS hostname when the address has no host (for example ":8080"). IPv6 literals are returned without brackets; use net.JoinHostPort to rebuild host:port.
func GetPort ¶
GetPort returns the port from an HTTP bind address ("host:port" or ":port"), or "443" when the address has no port, including a bare IPv6 literal.
func GetServerBaseURL ¶
GetServerBaseURL returns scheme://host:port for the server's own bind address, without consulting any request headers. IPv6 hosts are bracketed.
func GetServerURL ¶
GetServerURL returns the absolute URL for relativeEndpoint as seen by the client of request r. The scheme uses X-Forwarded-Proto only when the peer is trusted and the value is http or https; otherwise it uses s.Protocol(). The host from r.URL.Host, then r.Host, then the server's host:port.
Types ¶
type CORSOptions ¶
type CORSOptions struct {
// AllowedOrigins is a list of origins a cross-domain request can be executed from.
// If the special "*" value is present in the list, all origins will be allowed.
// An origin may contain a wildcard (*) to replace 0 or more characters
// (i.e.: http://*.domain.com). Usage of wildcards implies a small performance penalty.
// Only one wildcard can be used per origin.
// Default value is ["*"]
AllowedOrigins []string
// AllowOriginFunc is a custom function to validate the origin. It take the origin
// as argument and returns true if allowed or false otherwise. If this option is
// set, the content of AllowedOrigins is ignored.
AllowOriginFunc func(origin string) bool
// AllowOriginRequestFunc is a custom function to validate the origin. It takes the HTTP Request object and the
// origin as argument and returns true if allowed or false otherwise. If this option is set, the content of
// `AllowedOrigins` and `AllowOriginFunc` is ignored.
AllowOriginRequestFunc func(r *http.Request, origin string) bool
// AllowedMethods is a list of methods the client is allowed to use with
// cross-domain requests. Default value is simple methods (HEAD, GET and POST).
AllowedMethods []string
// AllowedHeaders is list of non simple headers the client is allowed to use with
// cross-domain requests.
// If the special "*" value is present in the list, all headers will be allowed.
// Default value is [] but "Origin" is always appended to the list.
AllowedHeaders []string
// ExposedHeaders indicates which headers are safe to expose to the API of a CORS
// API specification
ExposedHeaders []string
// MaxAge indicates how long (in seconds) the results of a preflight request
// can be cached
MaxAge int
// AllowCredentials indicates whether the request can include user credentials like
// cookies, HTTP authentication or client side SSL certificates.
AllowCredentials bool
// OptionsPassthrough instructs preflight to let other potential next handlers to
// process the OPTIONS method. Turn this on if your application handles OPTIONS.
OptionsPassthrough bool
// Debugging flag adds additional output to debug server side CORS issues
Debug bool
}
CORSOptions is a configuration container to setup the CORS middleware.
type Config ¶
type Config interface {
// GetServerName provides name of the server: WebAPI|Admin etc
GetServerName() string
// GetBindAddr provides the address that the HTTPS server should be listening on
GetBindAddr() string
// GetPublicURL is the FQ name of the VIP to the cluster that clients use to connect
GetPublicURL() string
// GetServices returns the names of services to enable for this HTTP server
GetServices() []string
}
Config is the server configuration contract passed to New. Consumers typically satisfy it with a struct loaded from YAML/JSON.
type HTTPServer ¶
type HTTPServer struct {
// contains filtered or unexported fields
}
HTTPServer exposes a collection of Service implementations as a single HTTP or HTTPS server. Configure it with the With* methods and AddService before calling StartHTTP; those setters are not synchronised against a running server.
func New ¶
func New( version string, ipaddr string, httpConfig Config, tlsConfig *tls.Config, ) (*HTTPServer, error)
New creates a server for the given configuration. version is reported by Version(); ipaddr is the address reported by LocalIP() and is auto-detected (falling back to 127.0.0.1) when empty; a nil tlsConfig serves plain HTTP. The default shutdown timeout is 5 seconds. New never returns an error today.
func (*HTTPServer) AddService ¶
func (server *HTTPServer) AddService(s Service)
AddService registers a service by its Name. It panics (via the logger) if a service with the same name is already registered. Call it before StartHTTP.
func (*HTTPServer) Config ¶ added in v1.0.438
func (server *HTTPServer) Config() Config
Config returns the server configuration passed to New.
func (*HTTPServer) HTTPConfig ¶
func (server *HTTPServer) HTTPConfig() Config
HTTPConfig returns the Config passed to New.
func (*HTTPServer) HostName ¶
func (server *HTTPServer) HostName() string
HostName returns the host name of the server
func (*HTTPServer) IsReady ¶
func (server *HTTPServer) IsReady() bool
IsReady reports whether the listener has been started and every registered service reports IsReady. It is used by the ready middleware to answer 503 until then.
func (*HTTPServer) LocalIP ¶
func (server *HTTPServer) LocalIP() string
LocalIP returns the IP address passed to New, or the auto-detected local IP.
func (*HTTPServer) Name ¶
func (server *HTTPServer) Name() string
Name returns the configured server name (Config.GetServerName).
func (*HTTPServer) NewMux ¶
func (server *HTTPServer) NewMux() http.Handler
NewMux builds the default handler chain: a Router on which every registered service has called Register, wrapped (innermost to outermost) by the ready verifier, the authz handler when set, the CORS middleware when configured, the request logger, a nested request metrics handler that only reports the caller role, the identity context handler, the request metrics handler that records every response (including identity rejections), the body limiter, correlation ID handler and, outermost, identity.NewTrustedProxyHandler with the WithTrustedProxies policy, which resolves the client IP once for all of them. CORS sits outside authz so that preflights are answered before authorization (they carry no credentials) and denied responses carry CORS headers; with OptionsPassthrough, OPTIONS requests are authorized like any other. It is called by StartHTTP through the MuxFactory; call it directly only in tests. It panics via the logger if the authz handler cannot be created.
func (*HTTPServer) OnEvent ¶
func (server *HTTPServer) OnEvent(evt ServerEvent, handler ServerEventFunc)
OnEvent registers a callback for the given lifecycle event. Handlers are invoked synchronously in registration order.
func (*HTTPServer) Port ¶
func (server *HTTPServer) Port() string
Port returns the port part of the bind address (see GetPort).
func (*HTTPServer) Protocol ¶
func (server *HTTPServer) Protocol() string
Protocol returns "https" when a TLS config was supplied, otherwise "http".
func (*HTTPServer) PublicURL ¶
func (server *HTTPServer) PublicURL() string
PublicURL returns the configured public URL (Config.GetPublicURL).
func (*HTTPServer) ServeHTTP ¶
func (server *HTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP should write reply headers and data to the ResponseWriter and then return. Returning signals that the request is finished; it is not valid to use the ResponseWriter or read from the Request.Body after or concurrently with the completion of the ServeHTTP call.
func (*HTTPServer) Service ¶
func (server *HTTPServer) Service(name string) Service
Service returns the registered service with the given name, or nil.
func (*HTTPServer) StartHTTP ¶
func (server *HTTPServer) StartHTTP() error
StartHTTP builds the handler via the MuxFactory and starts serving in a background goroutine. The listener is bound synchronously for both HTTP and HTTPS, so bind errors are returned. ServerStartedEvent is broadcast from the serving goroutine.
func (*HTTPServer) StartedAt ¶
func (server *HTTPServer) StartedAt() time.Time
StartedAt returns the UTC time at which the server instance was created.
func (*HTTPServer) StopHTTP ¶
func (server *HTTPServer) StopHTTP()
StopHTTP marks the server unready, broadcasts ServerStoppingEvent, and waits for active requests to drain before closing services. The wait is bounded by WithShutdownTimeout; errors are logged. Calls before StartHTTP do nothing; repeated calls wait for the first shutdown. The instance cannot be restarted.
func (*HTTPServer) TLSConfig ¶
func (server *HTTPServer) TLSConfig() *tls.Config
TLSConfig returns the TLS configuration passed to New, or nil for plain HTTP.
func (*HTTPServer) Uptime ¶
func (server *HTTPServer) Uptime() time.Duration
Uptime returns the time elapsed since StartedAt.
func (*HTTPServer) Version ¶
func (server *HTTPServer) Version() string
Version returns the version string passed to New.
func (*HTTPServer) WithAuthz ¶
func (server *HTTPServer) WithAuthz(authz authz.HTTPAuthz) *HTTPServer
WithAuthz enables path/role authorization; the handler is created from authz by NewMux, so it must be set before StartHTTP.
func (*HTTPServer) WithCORS ¶
func (server *HTTPServer) WithCORS(cors *CORSOptions) *HTTPServer
WithCORS enables the CORS middleware with the given options; nil options disable CORS. NewMux places it outside the authz handler, so preflights are answered without authorization unless OptionsPassthrough is set.
func (*HTTPServer) WithIdentityProvider ¶
func (server *HTTPServer) WithIdentityProvider(provider identity.ProviderFromRequest) *HTTPServer
WithIdentityProvider sets the mapper that derives the caller identity for each request. When unset identity.GuestIdentityMapper is used.
func (*HTTPServer) WithMaxRequestBody ¶ added in v1.0.438
func (server *HTTPServer) WithMaxRequestBody(maxBytes int64) *HTTPServer
WithMaxRequestBody sets the body limit in bytes before StartHTTP. Zero uses MaxRequestSize; a negative value disables it. Custom muxes are also limited.
func (*HTTPServer) WithMuxFactory ¶
func (server *HTTPServer) WithMuxFactory(muxFactory MuxFactory)
WithMuxFactory replaces the factory used by StartHTTP to build the root handler, allowing a custom middleware chain instead of NewMux.
func (*HTTPServer) WithShutdownTimeout ¶
func (server *HTTPServer) WithShutdownTimeout(timeout time.Duration) *HTTPServer
WithShutdownTimeout sets how long StopHTTP waits for in-flight requests to drain before closing services (default 5s).
func (*HTTPServer) WithTimeouts ¶ added in v1.0.438
func (server *HTTPServer) WithTimeouts(timeouts limits.Timeouts) *HTTPServer
WithTimeouts configures HTTP read deadlines before StartHTTP. Zero fields select limits defaults; negative fields disable their deadlines. TLS uses net/http's smaller positive Header or Read deadline for its handshake.
func (*HTTPServer) WithTrustedProxies ¶ added in v1.0.438
func (server *HTTPServer) WithTrustedProxies(trust *identity.TrustedProxies) *HTTPServer
WithTrustedProxies permits forwarding headers only from socket peers in trust, built with identity.ParseTrustedProxies. Configure it before StartHTTP; nil or an empty policy trusts no proxy, which is the default.
type Handle ¶
type Handle func(http.ResponseWriter, *http.Request, Params)
Handle is a function that can be registered to a route to handle HTTP requests. Like http.HandlerFunc, but has a third parameter for the values of wildcards (variables).
type MuxFactory ¶
MuxFactory creates the root http.Handler used by StartHTTP. HTTPServer is its own default MuxFactory (see HTTPServer.NewMux); use WithMuxFactory to substitute a custom middleware chain.
type Params ¶
type Params httprouter.Params
Params is a Param-slice, as returned by the router. The slice is ordered, the first URL parameter is also the first slice value. It is therefore safe to read values by the index.
type Router ¶
type Router interface {
// Handler returns the http.Handler serving the registered routes,
// wrapped with CORS when the router was created with NewRouterWithCORS.
Handler() http.Handler
// GET registers handle for GET requests to path.
GET(path string, handle Handle)
// HEAD registers handle for HEAD requests to path.
HEAD(path string, handle Handle)
// OPTIONS registers handle for OPTIONS requests to path.
OPTIONS(path string, handle Handle)
// POST registers handle for POST requests to path.
POST(path string, handle Handle)
// PUT registers handle for PUT requests to path.
PUT(path string, handle Handle)
// PATCH registers handle for PATCH requests to path.
PATCH(path string, handle Handle)
// DELETE registers handle for DELETE requests to path.
DELETE(path string, handle Handle)
// CONNECT registers handle for CONNECT requests to path.
CONNECT(path string, handle Handle)
}
Router is the route registry handed to Service.Register. Paths use httprouter syntax (":name" and "*catchall" segments); registering the same method and path twice panics, as does registering after Handler has been served (httprouter is not safe for concurrent mutation). Before calling a handle, the Router records its registered path with telemetry.SetRoute, so request metrics are labelled by route template, not by URL path.
func NewRouter ¶
func NewRouter(notfoundhandler http.HandlerFunc) Router
NewRouter returns a Router backed by httprouter with the given handler serving unmatched paths (restserver uses a JSON 404 not_found error).
func NewRouterWithCORS ¶
func NewRouterWithCORS(notfoundhandler http.HandlerFunc, opt *CORSOptions) Router
NewRouterWithCORS returns a Router whose Handler is wrapped by the rs/cors middleware configured from opt; a nil opt uses cors.Default() (all origins, simple methods, no credentials). Custom mux factories that also use restserver/authz must place the authz handler inside this Router's Handler, or wrap the authz handler with newCORS-equivalent middleware, so that preflights never bypass authorization; NewMux does the latter.
type Server ¶
type Server interface {
http.Handler
// Name returns the configured server name (Config.GetServerName).
Name() string
// Version returns the version string passed to New.
Version() string
// HostName returns the host part of the bind address, or the OS hostname.
HostName() string
// LocalIP returns the IP address passed to New, or the auto-detected local IP.
LocalIP() string
// Port returns the port part of the bind address.
Port() string
// Protocol returns "https" when a TLS config is set, "http" otherwise.
Protocol() string
// PublicURL returns Config.GetPublicURL.
PublicURL() string
// StartedAt returns the UTC time the server instance was created.
StartedAt() time.Time
// Service returns a registered service by name, or nil.
Service(name string) Service
// Config returns the server configuration.
Config() Config
// TLSConfig returns the TLS configuration, or nil for plain HTTP.
TLSConfig() *tls.Config
// IsReady indicates that the server is serving and all services are ready to serve
IsReady() bool
// AddService registers a service; it must be called before StartHTTP.
AddService(s Service)
// StartHTTP starts serving in a background goroutine.
StartHTTP() error
// StopHTTP drains requests, then closes services and the listener.
StopHTTP()
// OnEvent registers a handler for a lifecycle event.
OnEvent(evt ServerEvent, handler ServerEventFunc)
}
Server is the interface exposed to services and middleware for querying server identity, configuration and lifecycle. *HTTPServer is the implementation; services receive it via their constructors.
Example ¶
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
"time"
rest "github.com/effective-security/porto/restserver"
"github.com/effective-security/xlog"
)
var logger = xlog.NewPackageLogger("github.com/effective-security/porto", "rest_test")
func main() {
sigs := make(chan os.Signal, 2)
tlsCfg := &tlsConfig{
CertFile: "testdata/test-server.pem",
KeyFile: "testdata/test-server-key.pem",
TrustedCAFile: "testdata/test-server-rootca.pem",
WithClientAuth: false,
}
tlsInfo, tlsloader, err := createServerTLSInfo(tlsCfg)
if err != nil {
panic("unable to create TLS config")
}
defer tlsloader.Close()
cfg := &serverConfig{
// any free loopback port, so the example cannot collide with other
// tests; a deployment sets its own address, such as ":8181"
BindAddr: "127.0.0.1:0",
}
server, err := rest.New("v1.0.123", "", cfg, tlsInfo)
if err != nil {
panic("unable to create the server")
}
svc := NewService(server)
server.AddService(svc)
fmt.Println("starting server")
err = server.StartHTTP()
if err != nil {
logger.Panicf("unable to start the server: [%+v]", err)
}
go func() {
// Send STOP signal after few seconds,
// in production the service should listen to
// os.Interrupt, os.Kill, syscall.SIGTERM, syscall.SIGUSR2, syscall.SIGABRT events
time.Sleep(3 * time.Second)
fmt.Println("sending syscall.SIGTERM signal")
sigs <- syscall.SIGTERM
}()
// register for signals, and wait to be shutdown
signal.Notify(sigs, os.Interrupt, os.Kill, syscall.SIGTERM, syscall.SIGUSR2, syscall.SIGABRT)
// Block until a signal is received.
sig := <-sigs
server.StopHTTP()
fmt.Println("stopped server")
// SIGUSR2 is triggered by the upstart pre-stop script, we don't want
// to actually exit the process in that case until upstart sends SIGTERM
if sig == syscall.SIGUSR2 {
select {
case <-time.After(time.Second * 5):
logger.KV(xlog.INFO, "status", "service shutdown from SIGUSR2 complete, waiting for SIGTERM to exit")
case sig = <-sigs:
logger.KV(xlog.INFO, "status", "exiting", "reason", "received_signal", "sig", sig)
}
}
}
Output: starting server sending syscall.SIGTERM signal stopped server
type ServerEvent ¶
type ServerEvent int
ServerEvent identifies a server lifecycle event delivered to OnEvent handlers.
const ( // ServerStartedEvent is fired on server start ServerStartedEvent ServerEvent = iota // ServerStoppedEvent is fired after server stopped ServerStoppedEvent // ServerStoppingEvent is fired before server stopped ServerStoppingEvent )
type ServerEventFunc ¶
type ServerEventFunc func(evt ServerEvent)
ServerEventFunc is a callback invoked synchronously when a ServerEvent is broadcast. ServerStartedEvent is delivered on the serving goroutine; ServerStoppingEvent and ServerStoppedEvent on the StopHTTP caller. Event handlers must not call StopHTTP synchronously: it waits for lifecycle callbacks and shutdown to finish.
type Service ¶
type Service interface {
// Name returns the unique service name used for registration and lookup.
Name() string
// Register adds the service's routes to the router.
Register(Router)
// Close releases the service's resources after StopHTTP drains requests.
// It is called once, even when the shutdown deadline expires.
Close()
// IsReady indicates that service is ready to serve its end-points;
// while any service returns false the server answers 503 to all requests.
IsReady() bool
}
Service is a component that contributes routes to the HTTPServer. It is registered with HTTPServer.AddService and its lifecycle is driven by the server: Register during StartHTTP (mux creation), Close during StopHTTP.
type TLSInfoConfig ¶
type TLSInfoConfig interface {
// GetCertFile returns location of the cert
GetCertFile() string
// GetKeyFile returns location of the key
GetKeyFile() string
// GetTrustedCAFile specifies location of the Trusted CA file
GetTrustedCAFile() string
// GetClientCAFile specifies location of the client CA bundle file
GetClientCAFile() string
// GetClientCertAuth controls client auth
GetClientCertAuth() *bool
}
TLSInfoConfig describes where a server's TLS material lives. It is the contract consumers' config structs satisfy to build a *tls.Config for New.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package authz provides HTTP and gRPC authorization where URI paths (and their children) are allowed access by a set of roles.
|
Package authz provides HTTP and gRPC authorization where URI paths (and their children) are allowed access by a set of roles. |
|
Package ready provides an http.Handler wrapper that gates requests on a readiness check.
|
Package ready provides an http.Handler wrapper that gates requests on a readiness check. |
|
Package telemetry provides http.Handler middleware for request logging and request metrics, plus the ResponseCapture writer they rely on to observe the status code and body size of a response.
|
Package telemetry provides http.Handler middleware for request logging and request metrics, plus the ResponseCapture writer they rely on to observe the status code and body size of a response. |