autoroute

package module
v0.0.0-...-3ca5141 Latest Latest
Warning

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

Go to latest
Published: Apr 1, 2020 License: MIT Imports: 12 Imported by: 0

README

autoroute Build Status Documentation

  • document and test the autoroute.Router
  • add examples for common situations
    • authentication
    • metricization
    • generated clients

build wicked fast, automatically documented APIs with Go.

Autoroute works by using reflection to automatically create an http.Handler from any number of Go functions. It does this by using a concept called an autoroute.Codec which dictates how a certain HTTP Content-Type (parsed by mime.ParseMediaType) maps to a certain class of Go functions.

Basic Example

In the general case, we can automatically create a JSON Handler from a function like this

package main

import (
	"log"
	"net/http"
	"strings"

	"github.com/autonaut/autoroute"
)

type SplitStringInput struct {
	String string `json:"string"`
}

type SplitStringOutput struct {
	Split []string `json:"split_string"`
}

func SplitString(ssi *SplitStringInput) *SplitStringOutput {
	return &SplitStringOutput{
		Split: strings.Split(ssi.String, " "),
	}
}

func main() {
	// autoroute includes a powerful Router of its own, that's deeply
	// integrated with autoroute's handlers and provides many mechanisms
	// for avoiding duplicated code. For this example, we're showing off
	// our use of standard net/http interfaces by using the standard library
	// http mux.
	mux := http.NewServeMux()

    // create a new *autoroute.Handler (compatible with http.Handler from the stdlib)
    // even though autoroute uses reflection, it provides a handy set of pre-validations
    // to ensure you know ASAP if you've passed something incorrect.
	h, err := autoroute.NewHandler(SplitString, autoroute.WithCodec(autoroute.JSONCodec))
	if err != nil {
		log.Fatal(err)
	}
	mux.Handle("/", h)

	log.Fatal(http.ListenAndServe(":8080", mux))
}

After we build and run the above program, we can try it out

ian@zuus ~ % curl -XPOST 'http://localhost:8080' -H 'Content-Type: application/json' -d '{"string":"test string split me"}' 
{"split_string":["test","string","split","me"]}

In addition to the happy path, our new handler automatically supports many common use cases, such as

handling incorrect content type (http status code 415):

ian@zuus ~ % curl -w "%{http_code}" -XPOST 'http://localhost:8080' -H 'Content-Type: application/potatoes' -d '{"string":"test string split me"}'
415

limiting input size (this is harder to demo, but you can control it via autoroute.WithMaxSizeBytes(your-max-byte-size-int64) when you create a handler)

Middleware

Autoroute supports running any middleware you can imagine to modify requests along the way. Common use cases for this is to easily apply authentication and authorization rules to many different routes without writing lots of duplicate code.

Autoroute currently ships with two basic middlewares, autoroute.BasicAuthMiddleware and autoroute.SignedHeaderMiddleware. The former of which restricts access to only those with a preset username and password via http basic auth, and the latter validates a whitelist of incoming headers using an internal HMAC (i.e. requires that a api-key header is signed properly before even letting it get to your code).

Look in the examples/middleware folder for a non-trivial example of this.

Codecs and Roadmap

In addition to the default codec (autoroute.JSONCodec), we hope to ship many more codecs soon, such as

  • autoroute.CSVCodec (parse application/csv automatically)
  • autoroute.FormCodec (parse application/x-www-form-urlencoded and multipart/form-data), outputting JSON
  • autoroute.HTMLCodec ^ same as FormCodec, but output text/ html

We also plan to implement a mechanism for returning binary, custom file types (PDFs, Images, etc)

LICENSE

MIT, see LICENSE

Documentation

Index

Constants

View Source
const MimeTypeHeader = "Content-Type"

Variables

View Source
var (
	ErrTooManyInputArgs  = errors.New("autoroute: a function can only have up to three input args")
	ErrTooManyOutputArgs = errors.New("autoroute: a function can only have up to two output args")
)
View Source
var (
	ErrNoFunction    = errors.New("autoroute: not a function passed to NewHandler")
	ErrDecodeFailure = errors.New("autoroute: failure decoding input")
)
View Source
var (
	ErrAlreadyRegistered = errors.New("autoroute: route already registered")
	ErrInvalidMethod     = errors.New("autoroute: not a valid method")
)
View Source
var (
	ErrBadErrorHandlerArgs = errors.New("autoroute: error handlers must have two input args")
)

Functions

func DefaultErrorHandler

func DefaultErrorHandler(w http.ResponseWriter, x error)

DefaultErrorHandler writes json `{"error": "errString"}`

Types

type BasicAuthMiddleware

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

func NewBasicAuthMiddleware

func NewBasicAuthMiddleware(user, pwd string) *BasicAuthMiddleware

func (*BasicAuthMiddleware) Before

func (bam *BasicAuthMiddleware) Before(r *http.Request, h *Handler) error

type Codec

type Codec interface {
	Mime() string
	ValidFn(fn reflect.Value) error
	HandleRequest(*CodecRequestArgs)
}

A Codec implements pluggable, mime-type based serialization and deserialization for autoroute based Handlers

var JSONCodec Codec = jsonCodec{}

JSONCodec implements autoroute functionality for the mime type application/json and functions that have up to three input args and two output args. a function with three input args must look like func(context.Context, autoroute.Header, anyStructOrPointer) with two it can be func(context.Context, autoroute.Header) or func(context.Context, anyStructOrPointer) with one it can be any of func(context.Context), func(autoroute.Header), or func(anyStructOrPointer) in terms of output values, a function can return `(anyStructOrPointer, error)`, `(anyStructOrPointer)`, (error), or nothing. the JSONCodec will attempt to decode values in two ways 1. use encoding/json on the request body 2. decode the URL parameters of a GET request into the struct and it will always JSON encode the output value.

type CodecRequestArgs

type CodecRequestArgs struct {
	ResponseWriter http.ResponseWriter
	Request        *http.Request
	ErrorHandler   ErrorHandler

	Header Header

	// Our nice bit of reflection stuff to work with
	HandlerFn                     reflect.Value
	HandlerType                   reflect.Type
	InputArgCount, OutputArgCount int

	MaxSizeBytes int64
}

CodecRequestArgs is passed to a Codec when it matches the mime type of a given request

type ErrorHandler

type ErrorHandler func(w http.ResponseWriter, e error)

An ErrorHandler is responsible for writing an error back to the calling http client

func (ErrorHandler) Handle

func (eh ErrorHandler) Handle(w http.ResponseWriter, errorValue reflect.Value)

Handle is a convienience method on ErrorHandler that allows it to call itself with a reflect.Value

type Handler

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

Handler wraps a function and uses reflection and pluggable codecs to automatically create a fast, safe http.Handler from the function

func NewHandler

func NewHandler(x interface{}, opts ...HandlerOption) (*Handler, error)

NewHandler creates an http.Handler from a function that fits a codec-specified layout.

func (*Handler) ServeHTTP

func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request)

type HandlerOption

type HandlerOption func(h *Handler)

func WithCodec

func WithCodec(c Codec) HandlerOption

func WithErrorHandler

func WithErrorHandler(errH ErrorHandler) HandlerOption

func WithMaxSizeBytes

func WithMaxSizeBytes(maxSizeBytes int64) HandlerOption

func WithMiddleware

func WithMiddleware(m Middleware) HandlerOption

func WithName

func WithName(n string) HandlerOption
type Header map[string]string

func (Header) Get

func (h Header) Get(v string) string

type Middleware

type Middleware interface {
	// Before can modify an incoming request in the middleware chain
	Before(r *http.Request, h *Handler) error
}

Middleware is used for things such as authentication / authorization controls checking specific headers of a request etc

type MiddlewareError

type MiddlewareError struct {
	StatusCode int
	Err        error
}

func (MiddlewareError) Error

func (mwe MiddlewareError) Error() string

type Router

type Router struct {
	NotFoundHandler http.Handler
	// contains filtered or unexported fields
}

Router implements an autoroute aware grouping of autoroute.Handler's

func NewRouter

func NewRouter(handlerOptions ...HandlerOption) (*Router, error)

func (*Router) Register

func (ro *Router) Register(method string, path string, x interface{}, extraOptions ...HandlerOption) error

func (*Router) ServeHTTP

func (ro *Router) ServeHTTP(w http.ResponseWriter, r *http.Request)

type SignedHeadersMiddleware

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

SignedHeadersMiddleware validates that all incoming headers are signed using a certain key if they're set as a header outgoing, they'll also be signed on the way out. this works great for cookies.

func NewSignedHeadersMiddleware

func NewSignedHeadersMiddleware(headers []string, key string) *SignedHeadersMiddleware

func (*SignedHeadersMiddleware) Before

func (shm *SignedHeadersMiddleware) Before(r *http.Request, h *Handler) error

func (*SignedHeadersMiddleware) Sign

func (shm *SignedHeadersMiddleware) Sign(value string) (string, error)

func (*SignedHeadersMiddleware) Verify

func (shm *SignedHeadersMiddleware) Verify(value string) (string, error)

Directories

Path Synopsis
examples
internal

Jump to

Keyboard shortcuts

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