httpserver

package module
v1.5.0 Latest Latest
Warning

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

Go to latest
Published: Apr 17, 2024 License: MIT Imports: 16 Imported by: 2

README

Http Server Module

ci go report codecov Deps PkgGoDev

Http server module based on Echo.

Installation

go get github.com/ankorstore/yokai/httpserver

Documentation

Usage

This module provides a HttpServerFactory, allowing to build an echo.Echo instance.

package main

import (
	"github.com/ankorstore/yokai/httpserver"
	"github.com/labstack/echo/v4"
	"github.com/labstack/gommon/log"
)

var server, _ = httpserver.NewDefaultHttpServerFactory().Create()

// equivalent to:
var server, _ = httpserver.NewDefaultHttpServerFactory().Create(
	httpserver.WithDebug(false),                                  // debug disabled by default
	httpserver.WithBanner(false),                                 // banner disabled by default
	httpserver.WithLogger(log.New("default")),                    // echo default logger
	httpserver.WithBinder(&echo.DefaultBinder{}),                 // echo default binder
	httpserver.WithJsonSerializer(&echo.DefaultJSONSerializer{}), // echo default json serializer
	httpserver.WithHttpErrorHandler(nil),                         // echo default error handler
)

server.Start(...)

See Echo documentation for more details.

Add-ons

This module provides several add-ons ready to use to enrich your http server.

Logger

This module provides an EchoLogger, compatible with the log module:

package main

import (
	"github.com/ankorstore/yokai/httpserver"
	"github.com/ankorstore/yokai/log"
)

func main() {
	logger, _ := log.NewDefaultLoggerFactory().Create()

	server, _ := httpserver.NewDefaultHttpServerFactory().Create(
		httpserver.WithLogger(httpserver.NewEchoLogger(logger)),
	)
}
Error handler

This module provides a JsonErrorHandler, with configurable error obfuscation and call stack:

package main

import (
	"github.com/ankorstore/yokai/httpserver"
)

func main() {
	server, _ := httpserver.NewDefaultHttpServerFactory().Create(
		httpserver.WithHttpErrorHandler(httpserver.JsonErrorHandler(
			false, // without error details obfuscation
			false, // without error call stack
		)),
	)
}

You can set the parameters:

  • obfuscate=true to obfuscate the error details from the response message, i.e. will use for example Internal Server Error for a response code 500 (recommended for production)
  • stack=true to add the error call stack to the log and response (not suitable for production)

This will make a call to [GET] https://example.com and forward automatically the authorization, x-request-id and traceparent headers from the handler request.

Http Handlers
Debug handlers

This module provides several debug handlers, compatible with the config module:

  • DebugBuildHandler to dump current build information
  • DebugConfigHandler to dump current config values
  • DebugRoutesHandler to dump current registered routes on the server
  • DebugVersionHandler to dump current version
package main

import (
	"github.com/ankorstore/yokai/config"
	"github.com/ankorstore/yokai/httpserver"
	"github.com/ankorstore/yokai/httpserver/handler"
)

func main() {
	cfg, _ := config.NewDefaultConfigFactory().Create()

	server, _ := httpserver.NewDefaultHttpServerFactory().Create(
		httpserver.WithDebug(true),
	)

	if server.Debug {
		server.GET("/debug/build", handler.DebugBuildHandler())
		server.GET("/debug/config", handler.DebugConfigHandler(cfg))
		server.GET("/debug/routes", handler.DebugRoutesHandler(server))
		server.GET("/debug/version", handler.DebugVersionHandler(cfg))
	}
}

This will expose [GET] /debug/* endpoints (not suitable for production).

Pprof handlers

This module provides pprof handlers, compatible with the net/http/pprof package:

  • PprofIndexHandler to offer pprof index dashboard
  • PprofAllocsHandler to provide a sampling of all past memory allocations
  • PprofBlockHandler to provide stack traces that led to blocking on synchronization primitives
  • PprofCmdlineHandler to provide the command line invocation of the current program
  • PprofGoroutineHandler to provide the stack traces of all current goroutines
  • PprofHeapHandler to provide a sampling of memory allocations of live objects
  • PprofMutexHandler to provide stack traces of holders of contended mutexes
  • PprofProfileHandler to provide CPU profile
  • PprofSymbolHandler to look up the program counters listed in the request
  • PprofThreadCreateHandler to provide stack traces that led to the creation of new OS threads
  • PprofTraceHandler to provide a trace of execution of the current program
package main

import (
	"github.com/ankorstore/yokai/config"
	"github.com/ankorstore/yokai/httpserver"
	"github.com/ankorstore/yokai/httpserver/handler"
)

func main() {
	server, _ := httpserver.NewDefaultHttpServerFactory().Create()

	// index dashboard
	server.GET("/debug/pprof/", handler.PprofIndexHandler())

	// linked from index dashboard
	server.GET("/debug/pprof/allocs", handler.PprofAllocsHandler())
	server.GET("/debug/pprof/block", handler.PprofBlockHandler())
	server.GET("/debug/pprof/cmdline", handler.PprofCmdlineHandler())
	server.GET("/debug/pprof/goroutine", handler.PprofGoroutineHandler())
	server.GET("/debug/pprof/heap", handler.PprofHeapHandler())
	server.GET("/debug/pprof/mutex", handler.PprofMutexHandler())
	server.GET("/debug/pprof/profile", handler.PprofProfileHandler())
	server.GET("/debug/pprof/symbol", handler.PprofSymbolHandler())
	server.POST("/debug/pprof/symbol", handler.PprofSymbolHandler())
	server.GET("/debug/pprof/threadcreate", handler.PprofThreadCreateHandler())
	server.GET("/debug/pprof/trace", handler.PprofTraceHandler())
}

This will expose pprof index dashboard on [GET] /debug/pprof/, from where you'll be able to retrieve all pprof profiles types.

Healthcheck handlers

This module provides a HealthCheckHandler, compatible with the healthcheck module:

package main

import (
	"probes"

	"github.com/ankorstore/yokai/healthcheck"
	"github.com/ankorstore/yokai/httpserver"
	"github.com/ankorstore/yokai/httpserver/handler"
)

func main() {
	checker, _ := healthcheck.NewDefaultCheckerFactory().Create(
		healthcheck.WithProbe(probes.SomeProbe()),                            // register for startup, liveness and readiness checks           
		healthcheck.WithProbe(probes.SomeOtherProbe(), healthcheck.Liveness), // register liveness checks only
	)

	server, _ := httpserver.NewDefaultHttpServerFactory().Create()

	server.GET("/healthz", handler.HealthCheckHandler(checker, healthcheck.Startup))
	server.GET("/livez", handler.HealthCheckHandler(checker, healthcheck.Liveness))
	server.GET("/readyz", handler.HealthCheckHandler(checker, healthcheck.Readiness))
}

This will expose endpoints for k8s startup, readiness and liveness probes:

  • [GET] /healthz: startup probes checks
  • [GET] /livez: liveness probes checks
  • [GET] /readyz: readiness probes checks
Middlewares
Request id middleware

This module provides a RequestIdMiddleware, ensuring the request and response will always have a request id (coming by default from the X-Request-Id header or generated if missing) for correlation needs.

package main

import (
	"github.com/ankorstore/yokai/httpserver"
	"github.com/ankorstore/yokai/httpserver/middleware"
)

func main() {
	server, _ := httpserver.NewDefaultHttpServerFactory().Create()

	server.Use(middleware.RequestIdMiddleware())
}

If you need, you can configure the request header name it fetches the id from, or the generator used for missing id generation:

import (
	"github.com/ankorstore/yokai/generate/generatetest/uuid"
)

server.Use(middleware.RequestIdMiddlewareWithConfig(middleware.RequestIdMiddlewareConfig{
	RequestIdHeader: "custom-header",
	Generator: uuid.NewTestUuidGenerator("some-value"),
}))
Request logger middleware

This module provides a RequestLoggerMiddleware:

  • compatible with the log module
  • ensuring all log entries will contain the requests x-request-id header value by default, in the field requestID, for correlation
  • ensuring a recap log entry will be emitted at request completion

You can then use the CtxLogger method to access the correlated logger from with your handlers:

package main

import (
	"github.com/ankorstore/yokai/httpserver"
	"github.com/ankorstore/yokai/httpserver/middleware"
	"github.com/ankorstore/yokai/log"
	"github.com/labstack/echo/v4"
)

func main() {
	logger, _ := log.NewDefaultLoggerFactory().Create()

	server, _ := httpserver.NewDefaultHttpServerFactory().Create(
		httpserver.WithLogger(httpserver.NewEchoLogger(logger)),
	)

	server.Use(middleware.RequestLoggerMiddleware())

	// handler
	server.GET("/test", func(c echo.Context) error {
		// emit correlated log
		httpserver.CtxLogger(c).Info().Msg("info")

		// equivalent to
		log.CtxLogger(c.Request().Context()).Info().Msg("info")
	})
}

By default, the middleware logs all requests with info level, even if failed. If needed, you can configure it to log with a level matching the response (or http error) code:

  • code < 400: log level info
  • 400 <= code < 500: log level warn
  • code >= 500 or non http error: log level error
server.Use(middleware.RequestLoggerMiddlewareWithConfig(middleware.RequestLoggerMiddlewareConfig{
	LogLevelFromResponseOrErrorCode: true,
}))

You can configure additional request headers to log:

  • the key is the header name to fetch
  • the value is the log field name to fill
server.Use(middleware.RequestLoggerMiddlewareWithConfig(middleware.RequestLoggerMiddlewareConfig{
	RequestHeadersToLog: map[string]string{
		"x-header-foo": "foo",
		"x-header-bar": "bar",
	},
}))

You can also configure the request URI prefixes to exclude from logging:

server.Use(middleware.RequestLoggerMiddlewareWithConfig(middleware.RequestLoggerMiddlewareConfig{
	RequestUriPrefixesToExclude: []string{
		"/foo",
		"/bar",
	},
}))

Note: if a request to an excluded URI fails (error or http code >= 500), the middleware will still log for observability purposes.

Request tracer middleware

This module provides a RequestTracerMiddleware:

  • using the global tracer by default
  • compatible with the trace module
  • ensuring a recap trace span will be emitted at request completion

You can then use, from within your handlers the CtxTracer method to access the correlated tracer:

package main

import (
	"github.com/ankorstore/yokai/httpserver"
	"github.com/ankorstore/yokai/httpserver/middleware"
	"github.com/ankorstore/yokai/trace"
	"github.com/labstack/echo/v4"
)

func main() {
	server, _ := httpserver.NewDefaultHttpServerFactory().Create()

	server.Use(middleware.RequestTracerMiddleware("my-service"))

	// handler
	server.GET("/test", func(c echo.Context) error {
		// emit correlated span
		_, span := httpserver.CtxTracer(c).Start(c.Request().Context(), "my-span")
		defer span.End()

		// equivalent to
		_, span = trace.CtxTracerProvider(c.Request().Context()).Tracer("my-tracer").Start(c.Request().Context(), "my-span")
		defer span.End()
	})
}

If you need, you can configure the tracer provider and propagators:

import (
	"github.com/ankorstore/yokai/trace"
	"go.opentelemetry.io/otel/propagation"
)

tracerProvider, _ := trace.NewDefaultTracerProviderFactory().Create()

server.Use(middleware.RequestTracerMiddlewareWithConfig("my-service", middleware.RequestTracerMiddlewareConfig{
	TracerProvider: tracerProvider,
	TextMapPropagator: propagation.TraceContext{},
}))

And you can also configure the request URI prefixes to exclude from tracing:

server.Use(middleware.RequestTracerMiddlewareWithConfig("my-service", middleware.RequestTracerMiddlewareConfig{
	RequestUriPrefixesToExclude: []string{"/test"},
}))
Request metrics middleware

This module provides a RequestMetricsMiddleware:

  • ensuring requests processing count and duration are collected
  • using the global promauto metrics registry by default
package main

import (
	"github.com/ankorstore/yokai/httpserver"
	"github.com/ankorstore/yokai/httpserver/middleware"
	"github.com/labstack/echo/v4"
)

func main() {
	server, _ := httpserver.NewDefaultHttpServerFactory().Create()

	server.Use(middleware.RequestMetricsMiddleware())

	// handler
	server.GET("/test", func(c echo.Context) error {
		// ...
	})
}

If you need, you can configure the metrics registry, namespace, subsystem, buckets and request path / response code normalization:

import (
	"github.com/prometheus/client_golang/prometheus"
)

registry := prometheus.NewPedanticRegistry()

server.Use(middleware.RequestMetricsMiddlewareWithConfig(middleware.RequestMetricsMiddlewareConfig{
	Registry:            registry,
	Namespace:           "foo",
	Subsystem:           "bar",
	Buckets:             []float64{0.01, 1, 10},
	NormalizeRequestPath: true,
	NormalizeResponseStatus: true,
}))

Regarding metrics normalization, if you create a handler like:

server.GET("/foo/bar/:id", func(c echo.Context) error {
	// returns a 200 response
	return c.String(http.StatusOK, c.Param("id"))
})

And receive requests on /foo/bar/baz?page=1:

  • if NormalizeRequestPath=true, the metrics path label will be /foo/bar/:id, otherwise it'll be /foo/bar/baz?page=1
  • if NormalizeResponseStatus=true, the metrics status label will be 2xx, otherwise it'll be 200
HTML Templates

This module provides a HtmlTemplateRenderer for rendering HTML templates.

Considering the following template:

<!-- path/to/templates/welcome.html -->
<html>
	<body>
		<h1>Welcome {{index . "name"}}!</h1>
	</body>
</html>

To render it:

package main

import (
	"net/http"

	"github.com/ankorstore/yokai/httpserver"
	"github.com/labstack/echo/v4"
)

func main() {
	server, _ := httpserver.NewDefaultHttpServerFactory().Create(
		httpserver.WithRenderer(httpserver.NewHtmlTemplateRenderer("path/to/templates/*.html")), // templates lookup pattern
	)

	// handler
	server.GET("/welcome", func(c echo.Context) error {
		return c.Render(http.StatusOK, "welcome.html", map[string]interface{}{
			"name": "some name",
		})
	})
}

See Echo templates documentation for more details.

Documentation

Index

Constants

View Source
const TraceSpanAttributeHttpRequestId = "guid:x-request-id"

TraceSpanAttributeHttpRequestId is a span attribute representing the request id.

View Source
const TracerName = "httpserver"

TracerName is the httpserver tracer name.

Variables

This section is empty.

Functions

func AnnotateTracerProvider

func AnnotateTracerProvider(base oteltrace.TracerProvider) oteltrace.TracerProvider

AnnotateTracerProvider extend adds the TracerProviderRequestIdAnnotator span processor to the provided oteltrace.TracerProvider.

func CtxLogger

func CtxLogger(c echo.Context) *log.Logger

CtxRequestId returns the contextual log.Logger.

func CtxRequestId

func CtxRequestId(c echo.Context) string

CtxRequestId returns the contextual request id.

func CtxTracer

func CtxTracer(c echo.Context) oteltrace.Tracer

CtxTracer returns the contextual Tracer.

func JsonErrorHandler

func JsonErrorHandler(obfuscate bool, stack bool) echo.HTTPErrorHandler

JsonErrorHandler is an echo.HTTPErrorHandler that outputs errors in JSON format. It can also be configured to obfuscate error message (to avoid to leak sensitive details), and to add the error stack to the response.

func MatchPrefix

func MatchPrefix(prefixes []string, str string) bool

MatchPrefix returns true if a given prefix matches an item of a given prefixes list.

Types

type CtxRequestIdKey

type CtxRequestIdKey struct{}

CtxRequestIdKey is a contextual struct key.

type DefaultHttpServerFactory

type DefaultHttpServerFactory struct{}

DefaultHttpServerFactory is the default HttpServerFactory implementation.

func (*DefaultHttpServerFactory) Create

func (f *DefaultHttpServerFactory) Create(options ...HttpServerOption) (*echo.Echo, error)

Create returns a new echo.Echo, and accepts a list of HttpServerOption. For example:

var server, _ = httpserver.NewDefaultHttpServerFactory().Create()

is equivalent to:

var server, _ = httpserver.NewDefaultHttpServerFactory().Create(
	httpserver.WithDebug(false),                                  // debug disabled by default
	httpserver.WithBanner(false),                                 // banner disabled by default
	httpserver.WithLogger(log.New("default")),                    // echo default logger
	httpserver.WithBinder(&echo.DefaultBinder{}),                 // echo default binder
	httpserver.WithJsonSerializer(&echo.DefaultJSONSerializer{}), // echo default json serializer
	httpserver.WithHttpErrorHandler(nil),                         // echo default error handler
)

type EchoLogger

type EchoLogger struct {
	// contains filtered or unexported fields
}

EchoLogger is a log.Logger wrapper for echo.Logger compatibility.

func NewEchoLogger

func NewEchoLogger(logger *log.Logger) *EchoLogger

NewEchoLogger returns a new log.Logger.

func (*EchoLogger) Debug

func (e *EchoLogger) Debug(i ...interface{})

Debug produces a log with debug level.

func (*EchoLogger) Debugf

func (e *EchoLogger) Debugf(format string, args ...interface{})

Debugf produces a formatted log with debug level.

func (*EchoLogger) Debugj

func (e *EchoLogger) Debugj(j echologger.JSON)

Debugj produces a json log with debug level.

func (*EchoLogger) Error

func (e *EchoLogger) Error(i ...interface{})

Error produces a log with error level.

func (*EchoLogger) Errorf

func (e *EchoLogger) Errorf(format string, args ...interface{})

Errorf produces a formatted log with error level.

func (*EchoLogger) Errorj

func (e *EchoLogger) Errorj(j echologger.JSON)

Errorj produces a json log with error level.

func (*EchoLogger) Fatal

func (e *EchoLogger) Fatal(i ...interface{})

Fatal produces a log with fatal level.

func (*EchoLogger) Fatalf

func (e *EchoLogger) Fatalf(format string, args ...interface{})

Fatalf produces a formatted log with fatal level.

func (*EchoLogger) Fatalj

func (e *EchoLogger) Fatalj(j echologger.JSON)

Fatalj produces a json log with fatal level.

func (*EchoLogger) Info

func (e *EchoLogger) Info(i ...interface{})

Info produces a log with info level.

func (*EchoLogger) Infof

func (e *EchoLogger) Infof(format string, args ...interface{})

Infof produces a formatted log with info level.

func (*EchoLogger) Infoj

func (e *EchoLogger) Infoj(j echologger.JSON)

Infoj produces a json log with info level.

func (*EchoLogger) Level

func (e *EchoLogger) Level() echologger.Lvl

Level returns the log level.

func (*EchoLogger) Output

func (e *EchoLogger) Output() io.Writer

Output returns the output writer.

func (*EchoLogger) Panic

func (e *EchoLogger) Panic(i ...interface{})

Panic produces a log with panic level.

func (*EchoLogger) Panicf

func (e *EchoLogger) Panicf(format string, args ...interface{})

Panicf produces a formatted log with panic level.

func (*EchoLogger) Panicj

func (e *EchoLogger) Panicj(j echologger.JSON)

Panicj produces a json log with panic level.

func (*EchoLogger) Prefix

func (e *EchoLogger) Prefix() string

Prefix returns the log prefix.

func (*EchoLogger) Print

func (e *EchoLogger) Print(i ...interface{})

Print produces a log with no level.

func (*EchoLogger) Printf

func (e *EchoLogger) Printf(format string, i ...interface{})

Printf produces a formatted log with no level.

func (*EchoLogger) Printj

func (e *EchoLogger) Printj(j echologger.JSON)

Printj produces a json log with no level.

func (*EchoLogger) SetHeader

func (e *EchoLogger) SetHeader(h string)

SetHeader sets the log header field.

func (*EchoLogger) SetLevel

func (e *EchoLogger) SetLevel(v echologger.Lvl)

SetLevel sets the log level.

func (*EchoLogger) SetOutput

func (e *EchoLogger) SetOutput(w io.Writer)

SetOutput sets the output writer.

func (*EchoLogger) SetPrefix

func (e *EchoLogger) SetPrefix(p string)

SetPrefix sets the log prefix.

func (*EchoLogger) ToZerolog

func (e *EchoLogger) ToZerolog() *zerolog.Logger

ToZerolog converts new log.Logger to a zerolog.Logger.

func (*EchoLogger) Warn

func (e *EchoLogger) Warn(i ...interface{})

Warn produces a log with warn level.

func (*EchoLogger) Warnf

func (e *EchoLogger) Warnf(format string, args ...interface{})

Warnf produces a formatted log with warn level.

func (*EchoLogger) Warnj

func (e *EchoLogger) Warnj(j echologger.JSON)

Warnj produces a json log with warn level.

type HtmlTemplateRenderer

type HtmlTemplateRenderer struct {
	// contains filtered or unexported fields
}

HtmlTemplateRenderer allows to render HTML templates, based on html/template.

func NewHtmlTemplateRenderer

func NewHtmlTemplateRenderer(pattern string) *HtmlTemplateRenderer

NewHtmlTemplateRenderer returns a HtmlTemplateRenderer, for a file pattern.

func (*HtmlTemplateRenderer) Render

func (r *HtmlTemplateRenderer) Render(w io.Writer, name string, data interface{}, c echo.Context) error

Render executes a named template, with provided data, and write the result to the provided io.Writer.

type HttpServerFactory

type HttpServerFactory interface {
	Create(options ...HttpServerOption) (*echo.Echo, error)
}

HttpServerFactory is the interface for echo.Echo factories.

func NewDefaultHttpServerFactory

func NewDefaultHttpServerFactory() HttpServerFactory

NewDefaultHttpServerFactory returns a DefaultHttpServerFactory, implementing HttpServerFactory.

type HttpServerOption

type HttpServerOption func(o *Options)

HttpServerOption are functional options for the HttpServerFactory implementations.

func WithBanner

func WithBanner(b bool) HttpServerOption

WithBanner is used to activate the server banner.

func WithBinder

func WithBinder(b echo.Binder) HttpServerOption

WithBinder is used to specify a echo.Binder to be used by the server.

func WithDebug

func WithDebug(d bool) HttpServerOption

WithDebug is used to activate the server debug mode.

func WithHttpErrorHandler

func WithHttpErrorHandler(h echo.HTTPErrorHandler) HttpServerOption

WithHttpErrorHandler is used to specify a echo.HTTPErrorHandler to be used by the server.

func WithJsonSerializer

func WithJsonSerializer(s echo.JSONSerializer) HttpServerOption

WithJsonSerializer is used to specify a echo.JSONSerializer to be used by the server.

func WithLogger

func WithLogger(l echo.Logger) HttpServerOption

WithLogger is used to specify a echo.Logger to be used by the server.

func WithRenderer

func WithRenderer(r echo.Renderer) HttpServerOption

WithRenderer is used to specify a echo.Renderer to be used by the server.

type Options

type Options struct {
	Debug            bool
	Banner           bool
	Logger           echo.Logger
	Binder           echo.Binder
	JsonSerializer   echo.JSONSerializer
	HttpErrorHandler echo.HTTPErrorHandler
	Renderer         echo.Renderer
}

Options are options for the HttpServerFactory implementations.

func DefaultHttpServerOptions

func DefaultHttpServerOptions() Options

DefaultHttpServerOptions are the default options used in the DefaultHttpServerFactory.

type TracerProviderRequestIdAnnotator

type TracerProviderRequestIdAnnotator struct{}

TracerProviderRequestIdAnnotator is a span processor to add the contextual request id as span attribute.

func NewTracerProviderRequestIdAnnotator

func NewTracerProviderRequestIdAnnotator() *TracerProviderRequestIdAnnotator

NewTracerProviderRequestIdAnnotator returns a new TracerProviderRequestIdAnnotator.

func (*TracerProviderRequestIdAnnotator) ForceFlush

ForceFlush performs no operations.

func (*TracerProviderRequestIdAnnotator) OnEnd

OnEnd performs no operations.

func (*TracerProviderRequestIdAnnotator) OnStart

OnStart adds the contextual request id as span attribute.

func (*TracerProviderRequestIdAnnotator) Shutdown

Shutdown performs no operations.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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