luddite

package module
v0.0.0-...-14cce6c Latest Latest
Warning

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

Go to latest
Published: Dec 1, 2022 License: MIT Imports: 33 Imported by: 0

README

Luddite service framework package

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 standard Resource interface that makes it easy to implement services that comply with the Orion REST API Standards.

To run the example service:

$ cd example
$ go build .
$ ./example -c config.yaml

Resources

Two types of resources are provided:

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

Resources may also implement POST actions and be made read-only. Since luddite is a micro-framework, implementations retain substantial flexibility.

Middleware

Currently, the framework registers several middleware handlers for each service:

  • Bottom: Combines CORS, tracing, logging, metrics, and recovery actions. Tracing generates a unique request id and optionally records traces to a persistent backend. Logging logs requests/responses in a structured JSON format. Metrics increments basic request/response stats. Recovery handles panics that occur in HTTP method handlers and optionally includes stack traces in 500 responses. Also makes the Service instance, request id, and response headers available to resource handlers as part of the request [context][context].
  • 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][context]. [context]: http://blog.golang.org/context

TODO

Need to document these:

  • Metrics
  • Schema file serving
  • TLS requirements
  • Min/max versioning

Documentation

Index

Constants

View Source
const (
	ContentTypeCss               = "text/css"
	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 (
	CredentialAwsAccessKeyId     = "aws_access_key_id"
	CredentialAwsSecretAccessKey = "aws_secret_access_key"
	CredentialToken              = "token"
)
View Source
const (
	EcodeUnknown               = "UNKNOWN_ERROR"
	EcodeInternal              = "INTERNAL_ERROR"
	EcodeUnsupportedMediaType  = "UNSUPPORTED_MEDIA_TYPE"
	EcodeSerializationFailed   = "SERIALIZATION_FAILED"
	EcodeDeserializationFailed = "DESERIALIZATION_FAILED"
	EcodeResourceIdMismatch    = "RESOURCE_ID_MISMATCH"
	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"
	HeaderSpirentNextLink      = "X-Spirent-Next-Link"
	HeaderSpirentPageSize      = "X-Spirent-Page-Size"
	HeaderSpirentResourceNonce = "X-Spirent-Resource-Nonce"
	HeaderUserAgent            = "User-Agent"
)
View Source
const (
	DefaultMetricsURIPath  = "/metrics"
	DefaultProfilerURIPath = "/debug/pprof"
)
View Source
const (
	TraceKindAWS     = "aws"
	TraceKindProcess = "process"
	TraceKindRequest = "request"
	TraceKindWorker  = "worker"
)
View Source
const MAX_STACK_SIZE = 8 * 1024

Variables

This section is empty.

Functions

func AddActionRoute

func AddActionRoute(router *httprouter.Router, basePath string, withId bool, r Resource)

func AddCountRoute

func AddCountRoute(router *httprouter.Router, basePath string, r Resource)

func AddCreateRoute

func AddCreateRoute(router *httprouter.Router, basePath string, r Resource)

func AddDeleteRoute

func AddDeleteRoute(router *httprouter.Router, basePath string, withId bool, r Resource)

func AddGetRoute

func AddGetRoute(router *httprouter.Router, basePath string, withId bool, r Resource)

func AddListRoute

func AddListRoute(router *httprouter.Router, basePath string, r Resource)

func AddUpdateRoute

func AddUpdateRoute(router *httprouter.Router, basePath string, withId bool, r Resource)

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 ContextCloseNotify

func ContextCloseNotify(ctx context.Context) (closeNotify <-chan bool)

ContextCloseNotify returns a channel that receives at most a single value (true) when the client connection has gone away, 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) (respHeaders 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 ConvertTime

func ConvertTime(value string) reflect.Value

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

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 RequestApiVersion

func RequestApiVersion(r *http.Request, defaultVersion int) (version int)

func RequestBearerToken

func RequestBearerToken(r *http.Request) (token string)

func RequestExternalHost

func RequestExternalHost(r *http.Request) string

func RequestId

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

func RequestPageSize

func RequestPageSize(r *http.Request) int

func RequestQueryCursor

func RequestQueryCursor(r *http.Request) string

func RequestResourceNonce

func RequestResourceNonce(r *http.Request) string

func SetContextRequestProgress

func SetContextRequestProgress(ctx context.Context, pkgName, funcName, stage string)

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

func WriteResponse

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

Types

type Bottom

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

Bottom is the bottom-most middleware layer that combines CORS, tracing, logging, metrics and recovery actions. Tracing generates a unique request id and optionally records traces to a persistent backend. Logging logs requests/responses in a structured JSON format. Metrics increments basic request/response stats. Recovery handles panics that occur in HTTP method handlers and optionally includes stack traces in 500 responses.

func NewBottom

func NewBottom(s Service, defaultLogger, accessLogger *log.Logger) *Bottom

NewBottom returns a new Bottom instance.

func (*Bottom) HandleHTTP

func (b *Bottom) HandleHTTP(rw http.ResponseWriter, req *http.Request, next http.HandlerFunc)

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 FormatNegotiator

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

FormatNegotiator is middleware that handles Content-Type negotiation.

func NewNegotiator

func NewNegotiator(acceptedFormats []string) *FormatNegotiator

NewNegotiator returns a new FormatNegotiator instance.

func (*FormatNegotiator) HandleHTTP

func (n *FormatNegotiator) HandleHTTP(rw http.ResponseWriter, req *http.Request, next http.HandlerFunc)

type Handler

type Handler interface {
	HandleHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc)
}

Handler is an interface that objects can implement to be registered to serve as middleware in a service's middleware stack. HandleHTTP should yield to the next middleware in the chain by invoking the next http.HandlerFunc passed in.

If the Handler writes to the ResponseWriter, the next http.HandlerFunc should not be invoked.

func WrapHttpHandler

func WrapHttpHandler(h http.Handler) Handler

WrapHttpHandler converts an http.Handler into a Handler.

type HandlerFunc

type HandlerFunc func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc)

HandlerFunc is an adapter to allow the use of ordinary functions as middleware handlers. If f is a function with the appropriate signature, HandlerFunc(f) is a Handler object that calls f.

func (HandlerFunc) HandleHTTP

func (h HandlerFunc) HandleHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc)

type ListenerStoppedError

type ListenerStoppedError struct {
}

func (*ListenerStoppedError) Error

func (e *ListenerStoppedError) Error() string

type MethodNotAllowedResource

type MethodNotAllowedResource struct {
}

MethodNotAllowedResource returns HTTP 405 MethodNotAllowed for all HTTP methods.

func (*MethodNotAllowedResource) Action

func (r *MethodNotAllowedResource) Action(req *http.Request, id, action string) (int, interface{})

func (*MethodNotAllowedResource) Count

func (r *MethodNotAllowedResource) Count(req *http.Request) (int, interface{})

func (*MethodNotAllowedResource) Create

func (r *MethodNotAllowedResource) Create(req *http.Request, value interface{}) (int, interface{})

func (*MethodNotAllowedResource) Delete

func (r *MethodNotAllowedResource) Delete(req *http.Request, id string) (int, interface{})

func (*MethodNotAllowedResource) Get

func (r *MethodNotAllowedResource) Get(req *http.Request, id string) (int, interface{})

func (*MethodNotAllowedResource) Id

func (r *MethodNotAllowedResource) Id(value interface{}) string

func (*MethodNotAllowedResource) List

func (r *MethodNotAllowedResource) List(req *http.Request) (int, interface{})

func (*MethodNotAllowedResource) New

func (r *MethodNotAllowedResource) New() interface{}

func (*MethodNotAllowedResource) Update

func (r *MethodNotAllowedResource) Update(req *http.Request, id string, value interface{}) (int, interface{})

type NotImplementedResource

type NotImplementedResource struct {
}

NotImplementedResource returns HTTP 501 NotImplemented for all HTTP methods.

func (*NotImplementedResource) Action

func (r *NotImplementedResource) Action(req *http.Request, id, action string) (int, interface{})

func (*NotImplementedResource) Count

func (r *NotImplementedResource) Count(req *http.Request) (int, interface{})

func (*NotImplementedResource) Create

func (r *NotImplementedResource) Create(req *http.Request, value interface{}) (int, interface{})

func (*NotImplementedResource) Delete

func (r *NotImplementedResource) Delete(req *http.Request, id string) (int, interface{})

func (*NotImplementedResource) Get

func (r *NotImplementedResource) Get(req *http.Request, id string) (int, interface{})

func (*NotImplementedResource) Id

func (r *NotImplementedResource) Id(value interface{}) string

func (*NotImplementedResource) List

func (r *NotImplementedResource) List(req *http.Request) (int, interface{})

func (*NotImplementedResource) New

func (r *NotImplementedResource) New() interface{}

func (*NotImplementedResource) Update

func (r *NotImplementedResource) Update(req *http.Request, id string, value interface{}) (int, interface{})

type Resource

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

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

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

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

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

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

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

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

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

Resource is a set of REST-oriented HTTP method handlers.

All resources must implement New.

Singleton-style resources may implement Get and Update. For read-only singleton resources, Update may hardcode a 405 response. The Id, List, Create, and Delete methods will never be called.

Collection-style resources must also implement Id. They may implement List, Get, Create, Update, and Delete. For read-only collection resources, Create, Update, and Delete may hardcode a 405 response.

Any resource may implement POST actions. These are dispatched to the Action method, which should switch on the action name.

NB: because golang's type system sucks, any implementation must take care to handle interface{} types correctly. In particular, Id, Create, and Update must be able to handle value parameters that have the same concrete type as returned by New.

type ResponseWriter

type ResponseWriter interface {
	http.ResponseWriter
	http.Flusher

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

	// Written returns whether or not the ResponseWriter has been
	// written.
	Written() bool

	// Size returns the size of the response body.
	Size() int
}

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

func ContextResponseWriter

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

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

func NewResponseWriter

func NewResponseWriter(rw http.ResponseWriter) ResponseWriter

NewResponseWriter creates a ResponseWriter that wraps an http.ResponseWriter.

type SchemaHandler

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

func NewSchemaHandler

func NewSchemaHandler(filePath string) *SchemaHandler

func (*SchemaHandler) ServeHTTP

func (h *SchemaHandler) ServeHTTP(rw http.ResponseWriter, req0 *http.Request, params httprouter.Params)

type Service

type Service interface {
	// AddHandler adds a context-aware middleware handler to the
	// middleware stack. All handlers must be added before Run is
	// called.
	AddHandler(h Handler)

	// AddSingletonResource registers a singleton-style resource
	// (supporting GET and PUT methods only).
	AddSingletonResource(itemPath string, r Resource)

	// AddCollectionResource registers a collection-style resource
	// (supporting GET, POST, PUT, and DELETE methods).
	AddCollectionResource(basePath string, r Resource)

	// Config returns the service's ServiceConfig instance.
	Config() *ServiceConfig

	// Logger returns the service's log.Logger instance.
	Logger() *log.Logger

	// Router returns the service's httprouter.Router instance.
	Router() *httprouter.Router

	// Run is a convenience function that runs the service as an
	// HTTP server. The address is taken from the ServiceConfig
	// passed to NewService.
	Run() error
}

Service is an interface that 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)

type ServiceConfig

type ServiceConfig struct {
	// Addr is the address:port pair that the HTTP server listens on.
	Addr 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 is a struct that holds config values relevant to the service framework.

type StoppableTCPListener

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

func (*StoppableTCPListener) Accept

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

type Version

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

Version is middleware that performs API version selection and enforces the service's min/max supported version constraints.

func NewVersion

func NewVersion(minVersion, maxVersion int) *Version

NewVersion returns a new Version instance.

func (*Version) HandleHTTP

func (v *Version) HandleHTTP(rw http.ResponseWriter, req *http.Request, next http.HandlerFunc)

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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