Documentation
¶
Overview ¶
Package webserver provides a HTTP server: - Gracefully handles shutdown - Applies best practices such as setting up timeouts - Routing powered by Gorilla Mux - HTTP server powered by Go built-in HTTP server - Observability is first-class:
- Logging powered Sypl
- Telemetry powered by Open Telemetry
- Metrics powered by ExpVar
- Built-in useful handlers such as liveness, and readiness.
Index ¶
- Constants
- Variables
- type IServer
- type Logging
- type Option
- func WithHandlers(handlers ...handler.Handler) Option
- func WithLogging(console, request, filepath string) Option
- func WithMetrics(metrics ...metric.Metric) Option
- func WithReadiness(readinessDeterminers ...*handler.ReadinessDeterminer) Option
- func WithRouter(router *mux.Router) Option
- func WithTelemetry(t *telemetry.Telemetry) Option
- func WithTimeout(read, request, inflight, tasks, write time.Duration) Option
- type Server
- type Timeout
Examples ¶
Constants ¶
const ( MIMEHTML = "text/html" MIMEJSON = "application/json" MIMEMSGPACK = "application/x-msgpack" MIMEMSGPACK2 = "application/msgpack" MIMEMultipartPOSTForm = "multipart/form-data" MIMEPlain = "text/plain" MIMEPOSTForm = "application/x-www-form-urlencoded" MIMEPROTOBUF = "application/x-protobuf" MIMEXML = "application/xml" MIMEXML2 = "text/xml" MIMEYAML = "application/x-yaml" )
Variables ¶
var ErrRequesTimeout = customerror.NewFailedToError( "finish request, timed out", customerror.WithStatusCode(http.StatusRequestTimeout), )
ErrRequesTimeout indicates a request failed to finish, it timed out.
Functions ¶
This section is empty.
Types ¶
type IServer ¶
type IServer interface {
// GetLogger returns the server logger.
GetLogger() sypl.ISypl
// GetRouter returns the server router.
GetRouter() *mux.Router
GetTelemetry() telemetry.ITelemetry
// Start the server.
Start() error
// Stop the server.
Stop(sig os.Signal) error
}
IServer defines what a server does.
func New ¶
New returns a basic web server without: - logging - telemetry - metrics - pre-loaded handlers (Liveness, OK, and Stop).
Example ¶
Example showing how to use the Web Server.
package main
import (
"errors"
"fmt"
"io"
"net/http"
"os"
"strings"
"sync"
"time"
"github.com/saucelabs/randomness"
"github.com/saucelabs/webserver"
"github.com/saucelabs/webserver/handler"
)
const serverName = "test-server"
// Logs, and exit.
//
//nolint:forbidigo
func logAndExit(msg string) {
fmt.Println(msg)
os.Exit(1)
}
func callAndExpect(port int, url string, sc int, expectedBodyContains string) (int, string) {
c := http.Client{Timeout: time.Duration(10) * time.Second}
resp, err := c.Get(fmt.Sprintf("http://0.0.0.0:%d/%s", port, url))
if err != nil {
logAndExit(err.Error())
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
logAndExit(err.Error())
}
if sc != 0 {
if resp.StatusCode != sc {
logAndExit(fmt.Sprintf("Expect %v got %v\n", sc, resp.StatusCode))
}
}
bodyS := string(body)
if expectedBodyContains != "" {
if !strings.Contains(bodyS, expectedBodyContains) {
logAndExit(fmt.Sprintf("Expect %v got %v\n", expectedBodyContains, bodyS))
}
}
return resp.StatusCode, bodyS
}
func main() {
// Probe results accumulator. Golang's example requires some output to be
// tested against. This accumulator will serve it.
probeResults := []string{}
probeResultsLocker := sync.Mutex{}
// Part of the readiness simulation.
readinessFlag := handler.NewReadinessDeterminer("tunnel")
// Golang's example are like tests, it's a bad practice to have a hardcoded
// port because of the possibility of collision. Generate a random port.
r, err := randomness.New(3000, 7000, 10, true)
if err != nil {
logAndExit(err.Error())
}
port := r.MustGenerate()
// Setup server settings some options.
testServer, err := webserver.New(serverName, fmt.Sprintf("0.0.0.0:%d", port),
// Sets server readiness.
webserver.WithReadiness(readinessFlag),
)
if err != nil {
logAndExit(err.Error())
}
// Start server, non-blocking way.
go func() {
if err := testServer.Start(); err != nil {
if errors.Is(err, http.ErrServerClosed) {
fmt.Println("server stopped")
} else {
logAndExit(err.Error())
}
}
}()
// Ensures enough time for the server to be up, and ready - just for testing.
time.Sleep(3 * time.Second)
// Simulates a Readiness probe, for example, Kubernetes.
go func() {
for {
_, body := callAndExpect(int(port), "/readiness", 0, "")
probeResultsLocker.Lock()
probeResults = append(probeResults, body)
probeResultsLocker.Unlock()
// Probe wait time.
time.Sleep(500 * time.Millisecond)
}
}()
// Simulates some action which indicates server is ready, example data was
// loaded from DB, or got updated data from another service.
go func() {
time.Sleep(2 * time.Second)
readinessFlag.SetReadiness(true)
}()
// Hold the server online for testing.
time.Sleep(5 * time.Second)
// Satisfies Golang example output need.
probeResultsLocker.Lock()
fmt.Println(strings.Contains(strings.Join(probeResults, ","), "OK"))
probeResultsLocker.Unlock()
}
Output: true
func NewDefault ¶ added in v0.0.9
NewDefault returns a web server with observability: - Metrics: `cmdline`, `memstats`, and `server` - Telemetry: `stdout` provider - Logging: `error`, no file - Pre-loaded handlers (Liveness, OK, and Stop) - Versioned router: `/api/v1`.
Example ¶
package main
import (
"errors"
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
"github.com/saucelabs/randomness"
"github.com/saucelabs/webserver"
)
const serverName = "test-server"
// Logs, and exit.
//
//nolint:forbidigo
func logAndExit(msg string) {
fmt.Println(msg)
os.Exit(1)
}
func callAndExpect(port int, url string, sc int, expectedBodyContains string) (int, string) {
c := http.Client{Timeout: time.Duration(10) * time.Second}
resp, err := c.Get(fmt.Sprintf("http://0.0.0.0:%d/%s", port, url))
if err != nil {
logAndExit(err.Error())
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
logAndExit(err.Error())
}
if sc != 0 {
if resp.StatusCode != sc {
logAndExit(fmt.Sprintf("Expect %v got %v\n", sc, resp.StatusCode))
}
}
bodyS := string(body)
if expectedBodyContains != "" {
if !strings.Contains(bodyS, expectedBodyContains) {
logAndExit(fmt.Sprintf("Expect %v got %v\n", expectedBodyContains, bodyS))
}
}
return resp.StatusCode, bodyS
}
func main() {
// Golang's example are like tests, it's a bad practice to have a hardcoded
// port because of the possibility of collision. Generate a random port.
r, err := randomness.New(3000, 7000, 10, true)
if err != nil {
logAndExit(err.Error())
}
port := r.MustGenerate()
// Setup server settings some options.
testServer, err := webserver.NewDefault(serverName, fmt.Sprintf("0.0.0.0:%d", port))
if err != nil {
logAndExit(err.Error())
}
// Start server, non-blocking way.
go func() {
if err := testServer.Start(); err != nil {
if errors.Is(err, http.ErrServerClosed) {
fmt.Println("server stopped")
} else {
logAndExit(err.Error())
}
}
}()
// Ensures enough time for the server to be up, and ready - just for testing.
time.Sleep(1 * time.Second)
responseCode, body := callAndExpect(int(port), "/api/v1/", 200, "OK")
fmt.Println(responseCode)
fmt.Println(body)
}
Output: 200 OK
type Logging ¶
type Logging struct {
// ConsoleLevel defines the level for the `Console` output, default: "none".
ConsoleLevel string `json:"console_level" validate:"required,gte=3,oneof=none fatal error info warn debug trace"`
// RequestLevel defines the level for logging requests, default: "none".
RequestLevel string `json:"request_level" validate:"required,gte=3,oneof=none fatal error info warn debug trace"`
// Filepath is the file path to optionally write logs, default: ""
Filepath string `json:"filepath" validate:"omitempty,gte=3"`
}
Logging settings.
type Option ¶
type Option func(s *Server)
Option allows to define options for the Server.
func WithHandlers ¶ added in v0.0.5
WithHandlers sets the list of pre-loaded handlers.
NOTE: Use `handler.New` to bring your own handler.
func WithLogging ¶ added in v0.0.5
WithLogging sets logging configuration.
NOTE: Set filepath to "" to disabled that.
func WithMetrics ¶
WithMetrics sets the list of pre-loaded metrics.
NOTE: Use `metric.New` to bring your own metric.
func WithReadiness ¶
func WithReadiness(readinessDeterminers ...*handler.ReadinessDeterminer) Option
WithReadiness sets server readiness. Multiple readinesses determiners can be passed. In this case, only if ALL are ready, the server will be considered ready.
NOTE: Use `handler.NewReadinessDeterminer` to bring your own determiner.
func WithRouter ¶ added in v0.0.5
WithRouter sets the base router.
func WithTelemetry ¶
WithTelemetry sets telemetry.
NOTE: Use `telemetry.New` to bring your own telemetry.
func WithTimeout ¶
WithTimeout sets the maximum duration for each individual timeouts.
type Server ¶
type Server struct {
// Address is a TCP address to listen on.
Address string `json:"address" validate:"required,hostname_port"`
// EnableMetrics controls whether metrics are enable, or not, default: false.
EnableMetrics bool `json:"enable_metrics"`
// EnableTelemetry controls whether telemetry are enable, or not,
// default: false.
EnableTelemetry bool `json:"enable_telemetry"`
// Name of the server.
Name string `json:"name" validate:"required,gte=3"`
// Logging fine-control.
*Logging `json:"logging" validate:"required"`
// Timeouts fine-control.
*Timeout `json:"timeout" validate:"required"`
// contains filtered or unexported fields
}
Server definition.
func (*Server) GetRouter ¶
GetRouter returns the server base router. Use it do add your own handlers.
func (*Server) GetTelemetry ¶
func (s *Server) GetTelemetry() telemetry.ITelemetry
GetTelemetry returns telemetry.
type Timeout ¶
type Timeout struct {
// ReadTimeout max duration for READING the entire request, including the
// body, default: 3s.
ReadTimeout time.Duration `json:"read_timeout"`
// RequestTimeout max duration to WAIT BEFORE CANCELING A REQUEST,
// default: 1s.
//
// NOTE: It's automatically validated against other timeouts, and needs to
// be smaller.
RequestTimeout time.Duration `json:"request_timeout" validate:"ltfield=ReadTimeout"`
// ShutdownInFlightTimeout max duration to WAIT IN-FLIGHT REQUESTS,
// default: 3s.
ShutdownInFlightTimeout time.Duration `json:"shutdown_in_flight_timeout"`
// ShutdownTaskTimeout max duration TO WAIT for tasks such as flush cache,
// files, and telemetry, default: 10s.
ShutdownTaskTimeout time.Duration `json:"shutdown_task_timeout"`
// ShutdownTimeout max duration for WRITING the response, default: 3s.
WriteTimeout time.Duration `json:"write_timeout"`
}
Timeout definition.
Directories
¶
| Path | Synopsis |
|---|---|
|
package handler provides a collection of useful/built-in handlers.
|
package handler provides a collection of useful/built-in handlers. |
|
internal
|
|
|
logger
Package logger provides server's logging powered by Sypl.
|
Package logger provides server's logging powered by Sypl. |
|
middleware
Package middleware offers common, and usual middlewares.
|
Package middleware offers common, and usual middlewares. |
|
validation
Package validation provides data validation.
|
Package validation provides data validation. |
|
package metric provides a collection of useful/built-in metrics.
|
package metric provides a collection of useful/built-in metrics. |
|
Package telemetry provides server's telemetry powered by Open Telemetry.
|
Package telemetry provides server's telemetry powered by Open Telemetry. |