Skip to content

Commit

Permalink
Merge pull request #25 from guregu/newctx
Browse files Browse the repository at this point in the history
Support 1.7 context package
  • Loading branch information
guregu authored Aug 23, 2016
2 parents ebf8880 + 8d1d846 commit 096aa33
Show file tree
Hide file tree
Showing 16 changed files with 1,263 additions and 332 deletions.
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
## kami [![GoDoc](https://godoc.org/github.com/guregu/kami?status.svg)](https://godoc.org/github.com/guregu/kami) [![CircleCI](https://circleci.com/gh/guregu/kami.svg?style=svg)](https://circleci.com/gh/guregu/kami)
`import "github.com/guregu/kami"` [or](http://gopkg.in) `import "gopkg.in/guregu/kami.v1"`
`import "github.com/guregu/kami"` [or](http://gopkg.in) `import "gopkg.in/guregu/kami.v2"`

kami (神) is a tiny web framework using [x/net/context](https://blog.golang.org/context) for request context and [httptreemux](https://github.com/dimfeld/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 a tiny web framework using [context](https://blog.golang.org/context) for request context and [httptreemux](https://github.com/dimfeld/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](http://go-talks.appspot.com/github.com/guregu/slides/kami/kami.slide), explaining some of the design choices.

Both `context` and `x/net/context` are supported.

### Example
A contrived example using kami and x/net/context to localize greetings.
A contrived example using kami and context to localize greetings.

[Skip :fast_forward:](#usage)

Expand All @@ -22,9 +24,9 @@ package main
import (
"fmt"
"net/http"
"context"

"github.com/guregu/kami"
"golang.org/x/net/context"

"github.com/my-github/greeting" // see package greeting below
)
Expand Down Expand Up @@ -52,8 +54,8 @@ package greeting

import (
"net/http"
"context"

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

Expand Down
79 changes: 79 additions & 0 deletions globalmux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package kami

import (
"net/http"

"github.com/dimfeld/httptreemux"
)

var (
routes = newRouter()
enable405 = true
)

func init() {
// set up the default 404/405 handlers
NotFound(nil)
MethodNotAllowed(nil)
}

func newRouter() *httptreemux.TreeMux {
r := httptreemux.New()
r.PathSource = httptreemux.URLPath
r.RedirectBehavior = httptreemux.Redirect307
r.RedirectMethodBehavior = map[string]httptreemux.RedirectBehavior{
"GET": httptreemux.Redirect301,
}
return r
}

// Handler returns an http.Handler serving registered routes.
func Handler() http.Handler {
return routes
}

// Handle registers an arbitrary method handler under the given path.
func Handle(method, path string, handler HandlerType) {
routes.Handle(method, path, bless(wrap(handler)))
}

// Get registers a GET handler under the given path.
func Get(path string, handler HandlerType) {
Handle("GET", path, handler)
}

// Post registers a POST handler under the given path.
func Post(path string, handler HandlerType) {
Handle("POST", path, handler)
}

// Put registers a PUT handler under the given path.
func Put(path string, handler HandlerType) {
Handle("PUT", path, handler)
}

// Patch registers a PATCH handler under the given path.
func Patch(path string, handler HandlerType) {
Handle("PATCH", path, handler)
}

// Head registers a HEAD handler under the given path.
func Head(path string, handler HandlerType) {
Handle("HEAD", path, handler)
}

// Head registers a OPTIONS handler under the given path.
func Options(path string, handler HandlerType) {
Handle("OPTIONS", path, handler)
}

// Delete registers a DELETE handler under the given path.
func Delete(path string, handler HandlerType) {
Handle("DELETE", path, handler)
}

// EnableMethodNotAllowed enables or disables automatic Method Not Allowed handling.
// Note that this is enabled by default.
func EnableMethodNotAllowed(enabled bool) {
enable405 = enabled
}
85 changes: 85 additions & 0 deletions globalmux_17.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// +build go1.7

package kami

import (
"context"
"net/http"

"github.com/dimfeld/httptreemux"
"github.com/zenazn/goji/web/mutil"
)

var (
// Context is the root "god object" from which every request's context will derive.
Context = context.Background()

// 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)
)

// NotFound registers a special handler for unregistered (404) paths.
// If handle is nil, use the default http.NotFound behavior.
func NotFound(handler HandlerType) {
// set up the default handler if needed
// we need to bless this so middleware will still run for a 404 request
if handler == nil {
handler = HandlerFunc(func(_ context.Context, w http.ResponseWriter, r *http.Request) {
http.NotFound(w, r)
})
}

h := bless(wrap(handler))
routes.NotFoundHandler = func(w http.ResponseWriter, r *http.Request) {
h(w, r, nil)
}
}

// MethodNotAllowed registers a special handler for automatically responding
// to invalid method requests (405).
func MethodNotAllowed(handler HandlerType) {
if handler == nil {
handler = HandlerFunc(func(_ context.Context, w http.ResponseWriter, r *http.Request) {
http.Error(w,
http.StatusText(http.StatusMethodNotAllowed),
http.StatusMethodNotAllowed,
)
})
}

h := bless(wrap(handler))
routes.MethodNotAllowedHandler = func(w http.ResponseWriter, r *http.Request, methods map[string]httptreemux.HandlerFunc) {
if !enable405 {
routes.NotFoundHandler(w, r)
return
}
h(w, r, nil)
}
}

// bless creates a new kamified handler using the global mux and middleware.
func bless(h ContextHandler) httptreemux.HandlerFunc {
k := kami{
handler: h,
base: &Context,
middleware: defaultMW,
panicHandler: &PanicHandler,
logHandler: &LogHandler,
}
return k.handle
}

// Reset changes the root Context to context.Background().
// It removes every handler and all middleware.
func Reset() {
Context = context.Background()
PanicHandler = nil
LogHandler = nil
defaultMW = newWares()
routes = newRouter()
NotFound(nil)
MethodNotAllowed(nil)
}
85 changes: 85 additions & 0 deletions globalmux_old.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// +build !go1.7

package kami

import (
"net/http"

"github.com/dimfeld/httptreemux"
"github.com/zenazn/goji/web/mutil"
"golang.org/x/net/context"
)

var (
// Context is the root "god object" from which every request's context will derive.
Context = context.Background()

// 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)
)

// NotFound registers a special handler for unregistered (404) paths.
// If handle is nil, use the default http.NotFound behavior.
func NotFound(handler HandlerType) {
// set up the default handler if needed
// we need to bless this so middleware will still run for a 404 request
if handler == nil {
handler = HandlerFunc(func(_ context.Context, w http.ResponseWriter, r *http.Request) {
http.NotFound(w, r)
})
}

h := bless(wrap(handler))
routes.NotFoundHandler = func(w http.ResponseWriter, r *http.Request) {
h(w, r, nil)
}
}

// MethodNotAllowed registers a special handler for automatically responding
// to invalid method requests (405).
func MethodNotAllowed(handler HandlerType) {
if handler == nil {
handler = HandlerFunc(func(_ context.Context, w http.ResponseWriter, r *http.Request) {
http.Error(w,
http.StatusText(http.StatusMethodNotAllowed),
http.StatusMethodNotAllowed,
)
})
}

h := bless(wrap(handler))
routes.MethodNotAllowedHandler = func(w http.ResponseWriter, r *http.Request, methods map[string]httptreemux.HandlerFunc) {
if !enable405 {
routes.NotFoundHandler(w, r)
return
}
h(w, r, nil)
}
}

// bless creates a new kamified handler using the global mux and middleware.
func bless(h ContextHandler) httptreemux.HandlerFunc {
k := kami{
handler: h,
base: &Context,
middleware: defaultMW,
panicHandler: &PanicHandler,
logHandler: &LogHandler,
}
return k.handle
}

// Reset changes the root Context to context.Background().
// It removes every handler and all middleware.
func Reset() {
Context = context.Background()
PanicHandler = nil
LogHandler = nil
defaultMW = newWares()
routes = newRouter()
NotFound(nil)
MethodNotAllowed(nil)
}
61 changes: 61 additions & 0 deletions handler_17.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// +build go1.7

package kami

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

netcontext "golang.org/x/net/context"
)

// 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 HandlerType interface{}

// ContextHandler is like http.Handler but supports context.
type ContextHandler interface {
ServeHTTPContext(context.Context, http.ResponseWriter, *http.Request)
}

// OldContextHandler is like ContextHandler but uses the old x/net/context.
type OldContextHandler interface {
ServeHTTPContext(netcontext.Context, http.ResponseWriter, *http.Request)
}

// HandlerFunc is like http.HandlerFunc with context.
type HandlerFunc func(context.Context, http.ResponseWriter, *http.Request)

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

// wrap tries to turn a HandlerType into a ContextHandler
func wrap(h HandlerType) ContextHandler {
switch x := h.(type) {
case ContextHandler:
return x
case func(context.Context, http.ResponseWriter, *http.Request):
return HandlerFunc(x)
case func(netcontext.Context, http.ResponseWriter, *http.Request):
return HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
x(ctx, w, r)
})
case http.Handler:
return HandlerFunc(func(_ context.Context, w http.ResponseWriter, r *http.Request) {
x.ServeHTTP(w, r)
})
case func(http.ResponseWriter, *http.Request):
return HandlerFunc(func(_ context.Context, w http.ResponseWriter, r *http.Request) {
x(w, r)
})
}
panic(fmt.Errorf("unsupported HandlerType: %T", h))
}
2 changes: 2 additions & 0 deletions handler.go → handler_old.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// +build !go1.7

package kami

import (
Expand Down
Loading

0 comments on commit 096aa33

Please sign in to comment.