kami

package module
v2.2.1+incompatible Latest Latest
Warning

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

Go to latest
Published: Aug 25, 2017 License: MIT Imports: 16 Imported by: 0

README

kami GoDoc CircleCI

import "github.com/guregu/kami" or import "gopkg.in/guregu/kami.v2"

kami (神) is a tiny web framework using context for request context and httptreemux for routing. It includes a simple system for running hierarchical middleware before and after requests, in addition to log and panic hooks. Graceful restart via einhorn is also supported.

kami is designed to be used as central registration point for your routes, middleware, and context "god object". You are encouraged to use the global functions, but kami supports multiple muxes with kami.New().

You are free to mount kami.Handler() wherever you wish, but a helpful kami.Serve() function is provided.

Here is a presentation about the birth of kami, explaining some of the design choices.

Both context and x/net/context are supported.

Example

A contrived example using kami and context to localize greetings.

Skip ⏩

// Our webserver
package main

import (
	"fmt"
	"net/http"
	"context"

	"github.com/guregu/kami"

	"github.com/my-github/greeting" // see package greeting below
)

func greet(ctx context.Context, w http.ResponseWriter, r *http.Request) {
	hello := greeting.FromContext(ctx)
	name := kami.Param(ctx, "name")
	fmt.Fprintf(w, "%s, %s!", hello, name)
}

func main() {
	ctx := context.Background()
	ctx = greeting.WithContext(ctx, "Hello") // set default greeting
	kami.Context = ctx                       // set our "god context", the base context for all requests

	kami.Use("/hello/", greeting.Guess) // use this middleware for paths under /hello/
	kami.Get("/hello/:name", greet)     // add a GET handler with a parameter in the URL
	kami.Serve()                        // gracefully serve with support for einhorn and systemd
}
// Package greeting stores greeting settings in context.
package greeting

import (
	"net/http"
	"context"

	"golang.org/x/text/language"
)

// For more information about context and why we're doing this,
// see https://blog.golang.org/context
type ctxkey int

var key ctxkey = 0

var greetings = map[language.Tag]string{
	language.AmericanEnglish: "Yo",
	language.Japanese:        "こんにちは",
}

// Guess is kami middleware that examines Accept-Language and sets
// the greeting to a better one if possible.
func Guess(ctx context.Context, w http.ResponseWriter, r *http.Request) context.Context {
	if tag, _, err := language.ParseAcceptLanguage(r.Header.Get("Accept-Language")); err == nil {
		for _, t := range tag {
			if g, ok := greetings[t]; ok {
				ctx = WithContext(ctx, g)
				return ctx
			}
		}
	}
	return ctx
}

// WithContext returns a new context with the given greeting.
func WithContext(ctx context.Context, greeting string) context.Context {
	return context.WithValue(ctx, key, greeting)
}

// FromContext retrieves the greeting from this context,
// or returns an empty string if missing.
func FromContext(ctx context.Context) string {
	hello, _ := ctx.Value(key).(string)
	return hello
}

Usage

  • Set up routes using kami.Get("/path", handler), kami.Post(...), etc. You can use named parameters or wildcards in URLs like /hello/:name/edit or /files/*path, and access them using the context kami gives you: kami.Param(ctx, "name"). See the routing rules and routing priority. The following kinds of handlers are accepted:
    • types that implement kami.ContextHandler
    • func(context.Context, http.ResponseWriter, *http.Request)
    • types that implement http.Handler
    • func(http.ResponseWriter, *http.Request)
  • All contexts that kami uses are descended from kami.Context: this is the "god object" and the namesake of this project. By default, this is context.Background(), but feel free to replace it with a pre-initialized context suitable for your application.
  • Builds targeting Google App Engine will automatically wrap the "god object" Context with App Engine's per-request Context.
  • Add middleware with kami.Use("/path", kami.Middleware). Middleware runs before requests and can stop them early. More on middleware below.
  • Add afterware with kami.After("/path", kami.Afterware). Afterware runs after requests.
  • Set kami.Cancel to true to automatically cancel all request's contexts after the request is finished. Unlike the standard library, kami does not cancel contexts by default.
  • You can provide a panic handler by setting kami.PanicHandler. When the panic handler is called, you can access the panic error with kami.Exception(ctx).
  • You can also provide a kami.LogHandler that will wrap every request. kami.LogHandler has a different function signature, taking a WriterProxy that has access to the response status code, etc.
  • Use kami.Serve() to gracefully serve your application, or mount kami.Handler() somewhere convenient.

Middleware

type Middleware func(context.Context, http.ResponseWriter, *http.Request) context.Context

Middleware differs from a HandlerType in that it returns a new context. You can take advantage of this to build your context by registering middleware at the approriate paths. As a special case, you may return nil to halt execution of the middleware chain.

Middleware is hierarchical. For example, a request for /hello/greg will run middleware registered under the following paths, in order:

  1. /
  2. /hello/
  3. /hello/greg

Within a path, middleware is run in the order of registration.

func init() {
	kami.Use("/", Login)
	kami.Use("/private/", LoginRequired)
}

// Login returns a new context with the appropiate user object inside
func Login(ctx context.Context, w http.ResponseWriter, r *http.Request) context.Context {
	if u, err := user.GetByToken(ctx, r.FormValue("auth_token")); err == nil {
		ctx = user.NewContext(ctx, u)
	}
	return ctx
}

// LoginRequired stops the request if we don't have a user object
func LoginRequired(ctx context.Context, w http.ResponseWriter, r *http.Request) context.Context {
	if _, ok := user.FromContext(ctx); !ok {
		w.WriteHeader(http.StatusForbidden)
		// ... render 503 Forbidden page
		return nil
	}
	return ctx
}	
Named parameters, wildcards, and middleware

Named parameters and wildcards in middleware are supported now. Middleware registered under a path with a wildcard will run after all hierarchical middleware.

kami.Use("/user/:id/edit", CheckAdminPermissions)
Vanilla net/http middleware

kami can use vanilla http middleware as well. kami.Use accepts functions in the form of func(next http.Handler) http.Handler. Be advised that kami will run such middleware in sequence, not in a chain. This means that standard loggers and panic handlers won't work as you expect. You should use kami.LogHandler and kami.PanicHandler instead.

The following example uses goji/httpauth to add HTTP Basic Authentication to paths under /secret/.

import (
	"github.com/goji/httpauth"
	"github.com/guregu/kami"
)

func main() {
	kami.Use("/secret/", httpauth.SimpleBasicAuth("username", "password"))
	kami.Get("/secret/message", secretMessageHandler)
	kami.Serve()
}
Afterware
type Afterware func(context.Context, mutil.WriterProxy, *http.Request) context.Context
func init() {
	kami.After("/", cleanup)
}

Running after the request handler, afterware is useful for cleaning up. Afterware is like a mirror image of middleware. Afterware also runs hierarchically, but in the reverse order of middleware. Wildcards are evaluated before hierarchical afterware.

For example, a request for /hello/greg will run afterware registered under the following paths:

  1. /hello/greg
  2. /hello/
  3. /

This gives afterware under specific paths the ability to use resources that may be closed by /.

Unlike middleware, afterware returning nil will not stop the remaining afterware from being evaluated.

kami.After("/path", afterware) supports many different types of functions, see the docs for kami.AfterwareType for more details.

Independent stacks with *kami.Mux

kami was originally designed to be the "glue" between multiple packages in a complex web application. The global functions and kami.Context are an easy way for your packages to work together. However, if you would like to use kami as an embedded server within another app, serve two separate kami stacks on different ports, or otherwise would like to have an non-global version of kami, kami.New() may come in handy.

Calling kami.New() returns a fresh *kami.Mux, a completely independent kami stack. Changes to kami.Context, paths registered with kami.Get() et al, and global middleware registered with kami.Use() will not affect a *kami.Mux.

Instead, with mux := kami.New() you can change mux.Context, call mux.Use(), mux.Get(), mux.NotFound(), etc.

*kami.Mux implements http.Handler, so you may use it however you'd like!

// package admin is an admin panel web server plugin
package admin

import (
	"net/http"
	"github.com/guregu/kami"
)

// automatically mount our secret admin stuff
func init() {
	mux := kami.New()
	mux.Context = adminContext
	mux.Use("/", authorize)
	mux.Get("/admin/memstats", memoryStats)
	mux.Post("/admin/die", shutdown)
	//  ...
	http.Handle("/admin/", mux)
}

License

MIT

Acknowledgements

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// Context is the root "god object" from which every request's context will derive.
	Context = context.Background()
	// Cancel will, if true, automatically cancel the context of incoming requests after they finish.
	Cancel bool

	// PanicHandler will, if set, be called on panics.
	// You can use kami.Exception(ctx) within the panic handler to get panic details.
	PanicHandler HandlerType
	// LogHandler will, if set, wrap every request and be called at the very end.
	LogHandler func(context.Context, mutil.WriterProxy, *http.Request)
)

Functions

func After

func After(path string, aw AfterwareType)

After registers afterware to run after middleware and the request handler has run. Afterware is like middleware, but everything is in reverse. Afterware will be executed hierarchically, starting with wildcards and then the most specific path, ending with /. Afterware under the same path will be executed in the opposite order of registration.

func Delete

func Delete(path string, handler HandlerType)

Delete registers a DELETE handler under the given path.

func EnableMethodNotAllowed

func EnableMethodNotAllowed(enabled bool)

EnableMethodNotAllowed enables or disables automatic Method Not Allowed handling. Note that this is enabled by default.

func Exception

func Exception(ctx context.Context) interface{}

Exception gets the "v" in panic(v). The panic details. Only PanicHandler will receive a context you can use this with.

func Get

func Get(path string, handler HandlerType)

Get registers a GET handler under the given path.

func Handle

func Handle(method, path string, handler HandlerType)

Handle registers an arbitrary method handler under the given path.

func Handler

func Handler() http.Handler

Handler returns an http.Handler serving registered routes.

func Head(path string, handler HandlerType)

Head registers a HEAD handler under the given path.

func MethodNotAllowed

func MethodNotAllowed(handler HandlerType)

MethodNotAllowed registers a special handler for automatically responding to invalid method requests (405).

func NotFound

func NotFound(handler HandlerType)

NotFound registers a special handler for unregistered (404) paths. If handle is nil, use the default http.NotFound behavior.

func Options

func Options(path string, handler HandlerType)

Head registers a OPTIONS handler under the given path.

func Param

func Param(ctx context.Context, name string) string

Param returns a request path parameter, or a blank string if it doesn't exist. For example, with the path /v2/papers/:page use kami.Param(ctx, "page") to access the :page variable.

func Patch

func Patch(path string, handler HandlerType)

Patch registers a PATCH handler under the given path.

func Post

func Post(path string, handler HandlerType)

Post registers a POST handler under the given path.

func Put

func Put(path string, handler HandlerType)

Put registers a PUT handler under the given path.

func Reset

func Reset()

Reset changes the root Context to context.Background(). It removes every handler and all middleware.

func Serve

func Serve()

Serve starts kami with reasonable defaults. The bind address can be changed by setting the GOJI_BIND environment variable, or by setting the "bind" command line flag. Serve detects einhorn and systemd for you. It works exactly like zenazn/goji.

func ServeListener

func ServeListener(listener net.Listener)

ServeListener is like Serve, but runs kami on top of an arbitrary net.Listener.

func ServeTLS

func ServeTLS(config *tls.Config)

ServeTLS is like Serve, but enables TLS using the given config.

func SetParam

func SetParam(ctx context.Context, name string, value string) context.Context

SetParam will set the value of a path parameter in a given context. This is intended for testing and should not be used otherwise.

func Use

func Use(path string, mw MiddlewareType)

Use registers middleware to run for the given path. Middleware will be executed hierarchically, starting with the least specific path. Middleware under the same path will be executed in order of registration. You may use wildcards in the path. Wildcard middleware will be run last, after all hierarchical middleware has run.

Adding middleware is not threadsafe.

WARNING: kami middleware is run in sequence, but standard middleware is chained; middleware that expects its code to run after the next handler, such as standard loggers and panic handlers, will not work as expected. Use kami.LogHandler and kami.PanicHandler instead. Standard middleware that does not call the next handler to stop the request is supported.

Types

type Afterware

Afterware is a function that will run after middleware and the request. Afterware takes the request context and returns a new context, but unlike middleware, returning nil won't halt execution of other afterware.

type AfterwareType

type AfterwareType interface{}

Afterware represents types that kami can convert to Afterware. The following concrete types are accepted:

  • Afterware
  • func(context.Context, mutil.WriterProxy, *http.Request) context.Context
  • func(context.Context, http.ResponseWriter, *http.Request) context.Context
  • func(context.Context, *http.Request) context.Context
  • func(context.Context) context.Context
  • Middleware types

The old x/net/context is also supported.

type ContextHandler

type ContextHandler interface {
	ServeHTTPContext(context.Context, http.ResponseWriter, *http.Request)
}

ContextHandler is like http.Handler but supports context.

type HandlerFunc

type HandlerFunc func(context.Context, http.ResponseWriter, *http.Request)

HandlerFunc is like http.HandlerFunc with context.

func (HandlerFunc) ServeHTTPContext

func (h HandlerFunc) ServeHTTPContext(ctx context.Context, w http.ResponseWriter, r *http.Request)

type HandlerType

type HandlerType interface{}

HandlerType is the type of Handlers and types that kami internally converts to ContextHandler. In order to provide an expressive API, this type is an alias for interface{} that is named for the purposes of documentation, however only the following concrete types are accepted:

  • types that implement http.Handler
  • types that implement ContextHandler
  • func(http.ResponseWriter, *http.Request)
  • func(context.Context, http.ResponseWriter, *http.Request)

type Middleware

Middleware is a function that takes the current request context and returns a new request context. You can use middleware to build your context before your handler handles a request. As a special case, middleware that returns nil will halt middleware and handler execution (LogHandler will still run).

type MiddlewareType

type MiddlewareType interface{}

MiddlewareType represents types that kami can convert to Middleware. kami will try its best to convert standard, non-context middleware. See the Use function for important information about how kami middleware is run. The following concrete types are accepted:

  • Middleware
  • func(context.Context, http.ResponseWriter, *http.Request) context.Context
  • func(http.ResponseWriter, *http.Request) context.Context
  • func(http.Handler) http.Handler [* see Use docs]
  • func(http.ContextHandler) http.ContextHandler [* see Use docs]
  • http.Handler [read only]
  • func(http.ResponseWriter, *http.Request) [read only]

The old x/net/context is also supported.

type Mux

type Mux struct {
	// Context is the root "god object" for this mux,
	// from which every request's context will derive.
	Context context.Context
	// Cancel will, if true, automatically cancel the context of incoming requests after they finish.
	Cancel bool
	// PanicHandler will, if set, be called on panics.
	// You can use kami.Exception(ctx) within the panic handler to get panic details.
	PanicHandler HandlerType
	// LogHandler will, if set, wrap every request and be called at the very end.
	LogHandler func(context.Context, mutil.WriterProxy, *http.Request)
	// contains filtered or unexported fields
}

Mux is an independent kami router and middleware stack. Manipulating it is not threadsafe.

func New

func New() *Mux

New creates a new independent kami router and middleware stack. It is totally separate from the global kami.Context and middleware stack.

func (Mux) After

func (m Mux) After(path string, afterware AfterwareType)

After registers middleware to run for the given path after normal middleware added with Use has run. See the global After function's documents for information on how middleware works.

func (*Mux) Delete

func (m *Mux) Delete(path string, handler HandlerType)

Delete registers a DELETE handler under the given path.

func (*Mux) EnableMethodNotAllowed

func (m *Mux) EnableMethodNotAllowed(enabled bool)

EnableMethodNotAllowed enables or disables automatic Method Not Allowed handling. Note that this is enabled by default.

func (*Mux) Get

func (m *Mux) Get(path string, handler HandlerType)

Get registers a GET handler under the given path.

func (*Mux) Handle

func (m *Mux) Handle(method, path string, handler HandlerType)

Handle registers an arbitrary method handler under the given path.

func (*Mux) Head

func (m *Mux) Head(path string, handler HandlerType)

Head registers a HEAD handler under the given path.

func (*Mux) MethodNotAllowed

func (m *Mux) MethodNotAllowed(handler HandlerType)

MethodNotAllowed registers a special handler for automatically responding to invalid method requests (405).

func (*Mux) NotFound

func (m *Mux) NotFound(handler HandlerType)

NotFound registers a special handler for unregistered (404) paths. If handle is nil, use the default http.NotFound behavior.

func (*Mux) Options

func (m *Mux) Options(path string, handler HandlerType)

Options registers a OPTIONS handler under the given path.

func (*Mux) Patch

func (m *Mux) Patch(path string, handler HandlerType)

Patch registers a PATCH handler under the given path.

func (*Mux) Post

func (m *Mux) Post(path string, handler HandlerType)

Post registers a POST handler under the given path.

func (*Mux) Put

func (m *Mux) Put(path string, handler HandlerType)

Put registers a PUT handler under the given path.

func (*Mux) Serve

func (m *Mux) Serve()

Serve starts serving this mux with reasonable defaults. The bind address can be changed by setting the GOJI_BIND environment variable, or by setting the "--bind" command line flag. Serve detects einhorn and systemd for you. It works exactly like zenazn/goji. Only one mux may be served at a time.

func (*Mux) ServeHTTP

func (m *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP handles an HTTP request, running middleware and forwarding the request to the appropriate handler. Implements the http.Handler interface for easy composition with other frameworks.

func (*Mux) ServeListener

func (m *Mux) ServeListener(listener net.Listener)

ServeListener is like Serve, but runs kami on top of an arbitrary net.Listener.

func (*Mux) ServeTLS

func (m *Mux) ServeTLS(config *tls.Config)

ServeTLS is like Serve, but enables TLS using the given config.

func (Mux) Use

func (m Mux) Use(path string, mw MiddlewareType)

Use registers middleware to run for the given path. See the global Use function's documents for information on how middleware works.

Directories

Path Synopsis
Package treemux is a generic treemux ripped from httptreemux.
Package treemux is a generic treemux ripped from httptreemux.

Jump to

Keyboard shortcuts

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