luddite

package module
v0.0.0-...-0ac7008 Latest Latest
Warning

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

Go to latest
Published: Aug 2, 2021 License: MIT Imports: 34 Imported by: 3

README

Luddite Service Framework Package, Version 2

Luddite is a golang package that provides a micro-framework for RESTful web services. It is built around extensible, pluggable middleware layers and includes a flexible resource abstraction that makes it easy to implement services that comply with the Orion REST API Standards.

To run the example service:

$ make all
$ cd example
$ ./example -c config.yaml

Request Handling

The basic request handling built into luddite combines CORS, tracing, logging, metrics, profiling, and recovery actions.

Tracing generates a unique request id and optionally records traces to a file or persistent backend. The framework currently uses v2 of the trace package.

Logging is based on logrus. A service log is established for general use. An access log is maintained separately. Both use structured JSON logging.

Prometheus metrics provide basic request/response stats. By default, the metrics endpoint is served on /metrics.

The standard net/http/pprof profiling handlers may be optionally enabled. These are served on /debug/pprof.

Recovery handles panics that occur in resource handlers and optionally includes stack traces in 500 responses.

Request Middleware

Currently, luddite registers two middleware handlers for each service:

  • Negotiation: Performs JSON (default) and XML content negotiation based on HTTP requests' Accept headers.

  • Version: Performs API version selection and enforces the service's min/max supported version constraints. Makes the selected API version available to resource handlers as part of the request context.

Implementations are free to register their own additional middleware handlers in addition to these two.

Resource Abstraction

Generally, each resource falls into one of two categories.

  • Collection: Supports GET, POST, PUT, and DELETE.
  • Singleton: Supports GET and PUT.

The framework defines several interfaces that establish its resource abstraction. For collection-style resources:

  • CollectionLister returns all elements in response to GET /resource.
  • CollectionCounter returns a count of its elements in response to GET /resource/all/count.
  • CollectionGetter returns a specific element in response to GET /resource/:id.
  • CollectionCreator creates a new element in response to POST /resource.
  • CollectionUpdater updates a specific element in response to PUT /resource/:id.
  • CollectionDeleter deletes a specific element in response to DELETE /resource/:id. It may also optionally delete the entire collection in response to DELETE /resource
  • CollectionActioner executes an action in response to POST /resource/:id/:action.

And for singleton-style resources:

  • SingletonGetter returns a response to GET /resource.
  • SingletonUpdater is updated in response to PUT /resource.
  • SingletonActioner executes an action in response to POST /resource/:action.

Routes are automatically created for resource handler types that implement these interfaces. However, since luddite is a framework, implementations retain substantial flexibility to register their own routes if these are not sufficient.

Resource Versioning

The framework allows implementations to support multiple API versions simultaneously. In addition to API version selection via middleware, the framework also allows for version-specific resource registration.

Typically, implementations define a separate resource handler type for each API version. The routes for each type are registered in a version-specific router. Since route lookup occurs after version negotiation, each router is free to handle requests without further consideration of API version.

Documentation

Index

Constants

View Source
const (
	ContentTypeCss               = "text/css"
	ContentTypeCsv               = "text/csv"
	ContentTypeGif               = "image/gif"
	ContentTypeHtml              = "text/html"
	ContentTypeJson              = "application/json"
	ContentTypeMsgpack           = "application/msgpack"
	ContentTypeMultipartFormData = "multipart/form-data"
	ContentTypeOctetStream       = "application/octet-stream"
	ContentTypePlain             = "text/plain"
	ContentTypePng               = "image/png"
	ContentTypeProtobuf          = "application/protobuf"
	ContentTypeWwwFormUrlencoded = "application/x-www-form-urlencoded"
	ContentTypeXml               = "application/xml"
)
View Source
const (
	EcodeUnknown               = "UNKNOWN_ERROR"
	EcodeInternal              = "INTERNAL_ERROR"
	EcodeUnsupportedMediaType  = "UNSUPPORTED_MEDIA_TYPE"
	EcodeSerializationFailed   = "SERIALIZATION_FAILED"
	EcodeDeserializationFailed = "DESERIALIZATION_FAILED"
	EcodeResourceIdMismatch    = "RESOURCE_ID_MISMATCH"
	EcodeApiVersionInvalid     = "API_VERSION_INVALID"
	EcodeApiVersionTooOld      = "API_VERSION_TOO_OLD"
	EcodeApiVersionTooNew      = "API_VERSION_TOO_NEW"
	EcodeValidationFailed      = "VALIDATION_FAILED"
	EcodeLocked                = "LOCKED"
	EcodeUpdatePreempted       = "UPDATE_PREEMPTED"
	EcodeInvalidViewName       = "INVALID_VIEW_NAME"
	EcodeMissingViewParameter  = "MISSING_VIEW_PARAMETER"
	EcodeInvalidViewParameter  = "INVALID_VIEW_PARAMETER"
	EcodeInvalidParameterValue = "INVALID_PARAMETER_VALUE"
)
View Source
const (
	HeaderAccept                 = "Accept"
	HeaderAcceptEncoding         = "Accept-Encoding"
	HeaderAuthorization          = "Authorization"
	HeaderCacheControl           = "Cache-Control"
	HeaderContentDisposition     = "Content-Disposition"
	HeaderContentEncoding        = "Content-Encoding"
	HeaderContentLength          = "Content-Length"
	HeaderContentType            = "Content-Type"
	HeaderETag                   = "ETag"
	HeaderExpect                 = "Expect"
	HeaderForwardedFor           = "X-Forwarded-For"
	HeaderForwardedHost          = "X-Forwarded-Host"
	HeaderIfNoneMatch            = "If-None-Match"
	HeaderLocation               = "Location"
	HeaderRequestId              = "X-Request-Id"
	HeaderSessionId              = "X-Session-Id"
	HeaderSpirentApiVersion      = "X-Spirent-Api-Version"
	HeaderSpirentInhibitResponse = "X-Spirent-Inhibit-Response"
	HeaderSpirentNextLink        = "X-Spirent-Next-Link"
	HeaderSpirentPageSize        = "X-Spirent-Page-Size"
	HeaderSpirentResourceNonce   = "X-Spirent-Resource-Nonce"
	HeaderUserAgent              = "User-Agent"
)
View Source
const (
	RouteTagSeg1 = "seg1"
	RouteTagSeg2 = "seg2"

	RouteParamAction = RouteTagSeg2 // e.g. in `POST /resource/id/action`
	RouteParamId     = RouteTagSeg1 // e.g. in `GET /resource/id`
)
View Source
const (
	TraceKindAWS     = "aws"
	TraceKindProcess = "process"
	TraceKindRequest = "request"
	TraceKindWorker  = "worker"
)

Variables

View Source
var (
	// ErrInvalidMinApiVersion occurs when a service's minimum API version is <= 0.
	ErrInvalidMinApiVersion = errors.New("service's minimum API version must be greater than zero")

	// ErrInvalidMaxApiVersion occurs when a service's maximum API version is <= 0.
	ErrInvalidMaxApiVersion = errors.New("service's maximum API version must be greater than zero")

	// ErrMismatchedApiVersions occurs when a service's minimum API version > its maximum API version.
	ErrMismatchedApiVersions = errors.New("service's maximum API version must be greater than or equal to the minimum API version")
)
View Source
var FormDecoder = schema.NewDecoder()

Functions

func AddActionCollectionRoute

func AddActionCollectionRoute(router *httptreemux.ContextMux, basePath string, r CollectionActioner)

AddActionCollectionRoute adds a route for a CollectionActioner.

func AddActionSingletonRoute

func AddActionSingletonRoute(router *httptreemux.ContextMux, basePath string, r SingletonActioner)

AddActionSingletonRoute adds a route for a SingletonActioner.

func AddCountCollectionRoute

func AddCountCollectionRoute(router *httptreemux.ContextMux, basePath string, r CollectionCounter)

AddCountCollectionRoute adds a route for a CollectionCounter.

func AddCreateCollectionRoute

func AddCreateCollectionRoute(router *httptreemux.ContextMux, basePath string, r CollectionCreator)

AddCreateCollectionRoute adds a route for a CollectionCreator.

func AddDeleteCollectionRoute

func AddDeleteCollectionRoute(router *httptreemux.ContextMux, basePath string, r CollectionDeleter)

AddDeleteCollectionRoute adds routes for a CollectionDeleter.

func AddGetCollectionRoute

func AddGetCollectionRoute(router *httptreemux.ContextMux, basePath string, r CollectionGetter)

AddGetCollectionRoute adds a route for a CollectionGetter.

func AddGetSingletonRoute

func AddGetSingletonRoute(router *httptreemux.ContextMux, basePath string, r SingletonGetter)

AddGetSingletonRoute adds a route for a SingletonGetter.

func AddListCollectionRoute

func AddListCollectionRoute(router *httptreemux.ContextMux, basePath string, r CollectionLister)

AddListCollectionRoute adds a route for a CollectionLister.

func AddUpdateCollectionRoute

func AddUpdateCollectionRoute(router *httptreemux.ContextMux, basePath string, r CollectionUpdater)

AddUpdateCollectionRoute adds a route for a CollectionUpdater.

func AddUpdateSingletonRoute

func AddUpdateSingletonRoute(router *httptreemux.ContextMux, basePath string, r SingletonUpdater)

AddUpdateSingletonRoute adds a route for a SingletonUpdater.

func ContextApiVersion

func ContextApiVersion(ctx context.Context) (apiVersion int)

ContextApiVersion returns the current HTTP request's API version value from a context.Context, if possible.

func ContextDetail

func ContextDetail(ctx context.Context, key interface{}) (value interface{})

ContextDetail gets a detail from the current HTTP request's context, if possible.

func ContextLogger

func ContextLogger(ctx context.Context) (logger *log.Logger)

ContextLogger returns the Service's logger instance value from a context.Context, if possible.

func ContextRequest

func ContextRequest(ctx context.Context) (request *http.Request)

ContextRequest returns the current HTTP request from a context.Context, if possible.

func ContextRequestId

func ContextRequestId(ctx context.Context) (requestId string)

ContextRequestId returns the current HTTP request's ID value from a context.Context, if possible.

func ContextRequestProgress

func ContextRequestProgress(ctx context.Context) (reqProgress string)

ContextRequestProgress returns the current HTTP request's progress trace from a context.Context, if possible.

func ContextResponseHeaders

func ContextResponseHeaders(ctx context.Context) (header http.Header)

ContextResponseHeaders returns the current HTTP response's header collection from a context.Context, if possible.

func ContextSessionId

func ContextSessionId(ctx context.Context) (sessionId string)

ContextSessionId returns the current HTTP request's session ID value from a context.Context, if possible.

func NewStoppableTCPListener

func NewStoppableTCPListener(addr string, keepalives bool) (net.Listener, error)

func NewStoppableTLSListener

func NewStoppableTLSListener(addr string, keepalives bool, certFile string, keyFile string) (net.Listener, error)

func ReadConfig

func ReadConfig(path string, cfg interface{}) error

ReadConfig reads a YAML config file from path. The file is parsed into the struct pointed to by cfg.

func ReadRequest

func ReadRequest(req *http.Request, v interface{}) error

ReadRequest deserializes a request body according to the Content-Type header.

func RegisterFormat

func RegisterFormat(format string, mimeTypes []string)

RegisterFormat registers a new format and associated MIME types.

func RegisterTraceRecorder

func RegisterTraceRecorder(name string, recorder trace.Recorder)

func RequestBearerToken

func RequestBearerToken(r *http.Request) string

func RequestExternalHost

func RequestExternalHost(r *http.Request) string
func RequestNextLink(r *http.Request, cursor string) *url.URL

func RequestPageSize

func RequestPageSize(r *http.Request) (pageSize int)

func RequestQueryCursor

func RequestQueryCursor(r *http.Request) string

func RequestResourceNonce

func RequestResourceNonce(r *http.Request) string

func SetContextDetail

func SetContextDetail(ctx context.Context, key, value interface{})

SetContextDetail sets a detail in the current HTTP request's context. This may be used by the service's own middleware and avoids allocating a new request with additional context.

func SetContextRequestProgress

func SetContextRequestProgress(ctx context.Context, progress string)

SetContextRequestProgress sets the current HTTP request's progress trace in a context.Context.

func TestDispatch

func TestDispatch(rw http.ResponseWriter, req *http.Request, h http.Handler)

TestDispatch allows external code to test its own handlers, complete with mocked luddite handler details.

func WriteResponse

func WriteResponse(rw http.ResponseWriter, status int, v interface{}) (err error)

WriteResponse serializes a response body according to the negotiated Content-Type.

Types

type CollectionActioner

type CollectionActioner interface {
	// Action returns an HTTP status code and a response body (or error).
	Action(req *http.Request, id string, action string) (int, interface{})
}

CollectionActioner is a collection-style resource that executes an action in response to `POST /resource/id/action`.

type CollectionCounter

type CollectionCounter interface {
	// Count returns an HTTP status code and a count of resources (or error).
	Count(req *http.Request) (int, interface{})
}

CollectionCounter is a collection-style resource that returns a count of its elements in response to `GET /resource/all/count`.

type CollectionCreator

type CollectionCreator interface {
	// New returns a new instance of the resource.
	New() interface{}

	// Id returns a resource's identifier as a string.
	Id(value interface{}) string

	// Create returns an HTTP status code and a new resource (or error).
	Create(req *http.Request, value interface{}) (int, interface{})
}

CollectionCreator is a collection-style resource that creates a new element in response to `POST /resource`.

type CollectionDeleter

type CollectionDeleter interface {
	// Delete returns an HTTP status code and a deleted resource (or error).
	Delete(req *http.Request, id string) (int, interface{})
}

CollectionDeleter is a collection-style resource that deletes a specific element in response to `DELETE /resource/id`. It may also optionally delete the entire collection in response to `DELETE /resource`.

type CollectionGetter

type CollectionGetter interface {
	// Get returns an HTTP status code and a single resource (or error).
	Get(req *http.Request, id string) (int, interface{})
}

CollectionGetter is a collection-style resource that returns a specific element in response to `GET /resource/id`.

type CollectionLister

type CollectionLister interface {
	// List returns an HTTP status code and a slice of resources (or error).
	List(req *http.Request) (int, interface{})
}

CollectionLister is a collection-style resource that returns all its elements in response to `GET /resource`.

type CollectionUpdater

type CollectionUpdater interface {
	// New returns a new instance of the resource.
	New() interface{}

	// Id returns a resource's identifier as a string.
	Id(value interface{}) string

	// Update returns an HTTP status code and an updated resource (or error).
	Update(req *http.Request, id string, value interface{}) (int, interface{})
}

CollectionUpdater is a collection-style resource that updates a specific element in response to `PUT /resource/id`.

type Error

type Error struct {
	XMLName xml.Name `json:"-" xml:"error"`
	Code    string   `json:"code" xml:"code"`
	Message string   `json:"message" xml:"message"`
	Stack   string   `json:"stack,omitempty" xml:"stack,omitempty"`
}

Error is a transfer object that is serialized as the body in 4xx and 5xx responses.

func NewError

func NewError(errorMap map[string]string, code string, args ...interface{}) *Error

NewError allocates and initializes an Error. If a non-nil errorMap map is passed, the error is built using this map. Otherwise a map containing common errors is used as a fallback.

func (*Error) Error

func (e *Error) Error() string

type ListenerStoppedError

type ListenerStoppedError struct{}

func (*ListenerStoppedError) Error

func (e *ListenerStoppedError) Error() string

type ResponseWriter

type ResponseWriter interface {
	http.ResponseWriter
	http.Flusher
	http.Hijacker

	// Written returns true once the ResponseWriter has been written.
	Written() bool

	// Status returns the status code of the response or 0 if the
	// response has not been written.
	Status() int

	// Size returns the size of the response body or 0 if the response has
	// not been written.
	Size() int64
}

ResponseWriter is a wrapper around http.ResponseWriter that provides extra information about the response.

func ContextResponseWriter

func ContextResponseWriter(ctx context.Context) (rw ResponseWriter)

ContextResponseWriter returns the current HTTP request's ResponseWriter from a context.Context, if possible.

type Service

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

Service implements a standalone RESTful web service.

func ContextService

func ContextService(ctx context.Context) (s *Service)

ContextService returns the Service instance value from a context.Context, if possible.

func NewService

func NewService(config *ServiceConfig) (*Service, error)

NewService creates a new Service instance based on the given config. Middleware handlers and resources should be added before the service is run. The service may be run one time.

func (*Service) AddHandler

func (s *Service) AddHandler(h http.Handler)

AddHandler adds a middleware handler to the service's middleware stack. All handlers must be added before Run is called.

func (*Service) AddResource

func (s *Service) AddResource(version int, basePath string, r interface{}) error

AddResource is a convenience method that performs runtime type assertions on a resource handler and adds routes as appropriate based on what interfaces are implemented. The same effect can be achieved by calling the various "Add*CollectionResource" and "Add*SingletonResource" functions with the appropriate router instance.

func (*Service) Config

func (s *Service) Config() *ServiceConfig

Config returns the service's ServiceConfig instance.

func (*Service) Logger

func (s *Service) Logger() *log.Logger

Logger returns the service's log.Logger instance.

func (*Service) Router

func (s *Service) Router(version int) (*httptreemux.ContextMux, error)

Router returns the service's router instance for the given API version.

func (*Service) Run

func (s *Service) Run() (err error)

Run starts the service's HTTP server and runs it forever or until SIGINT is received. This method should be invoked once per service.

func (*Service) ServeHTTP

func (s *Service) ServeHTTP(rw http.ResponseWriter, req *http.Request)

func (*Service) SetRecoveryHandler

func (s *Service) SetRecoveryHandler(handler func(h func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request))

func (*Service) SetSchemas

func (s *Service) SetSchemas(schemas http.FileSystem)

SetSchemas allows a service to provide its own HTTP filesystem to be used for schema assets. This overrides the use of the local filesystem and paths given in the service config.

type ServiceConfig

type ServiceConfig struct {
	// Addr is the address:port pair that the HTTP server listens on.
	Addr string

	// Prefix is a prefix to add to every path
	Prefix string

	CORS struct {
		// Enabled, when true, enables CORS.
		Enabled bool
		// AllowedOrigins contains the list of origins a cross-domain request can be executed from. Defaults to "*" on an empty list.
		AllowedOrigins []string `yaml:"allowed_origins"`
		// AllowedMethods contains the list of methods the client is allowed to use with cross-domain requests. Defaults to "GET", "POST", "PUT" and "DELETE" on an empty list.
		AllowedMethods []string `yaml:"allowed_methods"`
		// AllowedHeaders contains the list of non-simple headers clients are allowed to use in cross-origin requests.  An empty list is interpreted literally however "Origin" is always appended.
		AllowedHeaders []string `yaml:"allowed_headers"`
		// ExposedHeaders contains the list of non-simple headers clients are allowed to access in cross-origin responses.  An empty list is interpreted literally.
		ExposedHeaders []string `yaml:"exposed_headers"`
		// AllowCredentials indicates whether the request can include user credentials like cookies or HTTP auth.
		AllowCredentials bool `yaml:"allow_credentials"`
	}

	// Credentials is a generic map of strings that may be used to store tokens, AWS keys, etc.
	Credentials map[string]string

	Debug struct {
		// Stacks, when true, causes stack traces to appear in 500 error responses.
		Stacks bool
		// StackSize sets an upper limit on the length of stack traces that appear in 500 error responses.
		StackSize int `yaml:"stack_size"`
	}

	Log struct {
		// ServiceLogPath sets the file path for the service log (written as JSON). If unset, defaults to stdout (written as text).
		ServiceLogPath string `yaml:"service_log_path"`
		// ServiceLogLevel sets the minimum log level for the service log, If unset, defaults to INFO.
		ServiceLogLevel string `yaml:"service_log_level"`
		// AccessLogPath sets the file path for the access log (written as JSON). If unset, defaults to stdout (written as text).
		AccessLogPath string `yaml:"access_log_path"`
	}

	Metrics struct {
		// Enabled, when true, enables the service's prometheus client.
		Enabled bool
		// UriPath sets the metrics path. Defaults to "/metrics".
		URIPath string `yaml:"uri_path"`
	}

	Profiler struct {
		// Enabled, when true, enables the service's profiling endpoints.
		Enabled bool
		// UriPath sets the profiler path. Defaults to "/debug/pprof".
		URIPath string `yaml:"uri_path"`
	}

	Schema struct {
		// Enabled, when true, self-serve the service's own schema.
		Enabled bool
		// URIPath sets the URI path for the schema.
		URIPath string `yaml:"uri_path"`
		// FilePath sets the base file path for the schema.
		FilePath string `yaml:"file_path"`
		// FileName sets the schema file name.
		FileName string `yaml:"file_name"`
		// RootRedirect, when true, redirects the service's root to the default schema.
		RootRedirect bool `yaml:"root_redirect"`
	}

	Trace struct {
		// Enabled, when true, enables trace recording.
		Enabled bool
		// Buffer sets the trace package's buffer size.
		Buffer int
		// Recorder selects the trace recorder implementation: json | other.
		Recorder string
		// Params is a map of trace recorder parameters.
		Params map[string]string
	}

	Transport struct {
		// Tls, when true, causes the service to listen using HTTPS.
		TLS bool `yaml:"tls"`
		// CertFilePath sets the path to the server's certificate file.
		CertFilePath string `yaml:"cert_file_path"`
		// KeyFilePath sets the path to the server's key file.
		KeyFilePath string `yaml:"key_file_path"`
	}

	Version struct {
		// Min sets the minimum API version that the service supports.
		Min int
		// Max sets the maximum API version that the service supports.
		Max int
	}
}

ServiceConfig holds a service's config values.

func (*ServiceConfig) Normalize

func (config *ServiceConfig) Normalize()

Normalize applies sensible defaults to service config values when they are otherwise unspecified or invalid.

func (*ServiceConfig) Validate

func (config *ServiceConfig) Validate() error

Validate sanity-checks service config values.

type SingletonActioner

type SingletonActioner interface {
	// Action returns an HTTP status code and a response body (or error).
	Action(req *http.Request, action string) (int, interface{})
}

SingletonActioner is a singleton-style resource that executes an action in response to `POST /resource/action`.

type SingletonGetter

type SingletonGetter interface {
	// Get returns an HTTP status code and a single resource (or error).
	Get(req *http.Request) (int, interface{})
}

SingletonGetter is a singleton-style resource that returns a response to `GET /resource`.

type SingletonUpdater

type SingletonUpdater interface {
	// New returns a new instance of the resource.
	New() interface{}

	// Update returns an HTTP status code and an updated resource (or error).
	Update(req *http.Request, value interface{}) (int, interface{})
}

SingletonUpdater is a singleton-style resource that is updated in response to `PUT /resource`.

type StoppableTCPListener

type StoppableTCPListener struct {
	*net.TCPListener
	// contains filtered or unexported fields
}

func (*StoppableTCPListener) Accept

func (sl *StoppableTCPListener) Accept() (net.Conn, error)

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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