webserver

package module
v0.0.11 Latest Latest
Warning

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

Go to latest
Published: Aug 8, 2022 License: MIT Imports: 18 Imported by: 0

README

webserver

webserver provides a web server:

  • Gracefully handles shutdown
  • Applies best practices such as setting up timeouts
  • Routing powered by Gorilla Mux
  • Logging powered Sypl
  • HTTP server powered by Go built-in HTTP server
  • Observability is first-class:
    • Telemetry powered by Open Telemetry
    • Metrics powered by ExpVar
    • Built-in useful handlers such as liveness, and readiness

Install

$ go get github.com/saucelabs/webserver

Specific version

Example: $ go get github.com/saucelabs/webserver@v1.2.3

Usage

See example_test.go, and webserver_test.go file.

Documentation

Run $ make doc or check out online.

Development

Check out CONTRIBUTION.

Release
  1. Update CHANGELOG accordingly.
  2. Once changes from MR are merged.
  3. Tag and release.

Roadmap

Check out CHANGELOG.

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

Examples

Constants

View Source
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

View Source
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

func New(name, address string, opts ...Option) (IServer, error)

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

func NewDefault(name, address string) (IServer, error)

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

func WithHandlers(handlers ...handler.Handler) Option

WithHandlers sets the list of pre-loaded handlers.

NOTE: Use `handler.New` to bring your own handler.

func WithLogging added in v0.0.5

func WithLogging(console, request, filepath string) Option

WithLogging sets logging configuration.

NOTE: Set filepath to "" to disabled that.

func WithMetrics

func WithMetrics(metrics ...metric.Metric) Option

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

func WithRouter(router *mux.Router) Option

WithRouter sets the base router.

func WithTelemetry

func WithTelemetry(t *telemetry.Telemetry) Option

WithTelemetry sets telemetry.

NOTE: Use `telemetry.New` to bring your own telemetry.

SEE: https://opentelemetry.io/vendors

func WithTimeout

func WithTimeout(read, request, inflight, tasks, write time.Duration) Option

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) GetLogger

func (s *Server) GetLogger() sypl.ISypl

GetLogger returns the server logger.

func (*Server) GetRouter

func (s *Server) GetRouter() *mux.Router

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.

func (*Server) Start

func (s *Server) Start() error

Start the server.

func (*Server) Stop added in v0.0.7

func (s *Server) Stop(sig os.Signal) error

Stop the server.

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.

Jump to

Keyboard shortcuts

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