stack

package module
v2.0.0-...-d204c95 Latest Latest
Warning

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

Go to latest
Published: Jul 23, 2015 License: MIT Imports: 5 Imported by: 0

README

stack

Codeship Status for go-on/stack

Build status

Package stack creates a fast and flexible middleware stack for http.Handlers.

Features

  • small and simple
  • based on http.Handler interface; nicely integrates with net/http
  • middleware stacks are http.Handlers too and may be embedded
  • has a solution for per request context sharing
  • easy debugging
  • low memory footprint
  • fast
  • no dependency apart from the standard library
  • easy to create adapters / wrappers for 3rd party middleware

Status

Not ready for general consumption yet. API may change at any time.

Benchmarks (Go 1.4)

The overhead of n writes to http.ResponseWriter via n wrappers vs n writes in a loop within a single http.Handler on my laptop

  BenchmarkServing2Simple       10000000     201 ns/op 1.00x 
  BenchmarkServing2Wrappers      5000000     250 ns/op 1.24x
  
  BenchmarkServing50Simple        300000    4833 ns/op 1.00x
  BenchmarkServing50Wrappers      200000    6503 ns/op 1.35x
  
  BenchmarkServing100Simple       200000    9810 ns/op 1.00x
  BenchmarkServing100Wrappers     100000   13489 ns/op 1.38x
  
  BenchmarkServing500Simple        30000   48970 ns/op 1.00x
  BenchmarkServing500Wrappers      20000   75562 ns/op 1.54x
  
  BenchmarkServing1000Simple       20000   98737 ns/op 1.00x
  BenchmarkServing1000Wrappers     10000  163671 ns/op 1.66x

Accepted middleware

  // Functions
  func(next http.Handler) http.Handler
  func(http.ResponseWriter, *http.Request)
  func(http.ResponseWriter, *http.Request, next http.Handler)
  func(stack.Contexter, http.ResponseWriter, *http.Request)
  func(stack.Contexter, http.ResponseWriter, *http.Request, next http.Handler)

  // Interfaces
  ServeHTTP(http.ResponseWriter,*http.Request)                                      // http.Handler
  Wrap(http.Handler) http.Handler                                                   // stack.Wrapper
  ServeHTTP(http.ResponseWriter, *http.Request, next http.Handler)                  // stack.Middleware
  ServeHTTP(stack.Contexter, http.ResponseWriter, *http.Request)                    // stack.ContextHandler
  ServeHTTP(stack.Contexter, http.ResponseWriter, *http.Request, next http.Handler) // stack.ContextMiddleware

  // 3rd party middleware (via stack/adapter)
  Martini
  Negroni

  // 3rd party integrations
  abbot/go-http-auth (Basic and Digest Authentication)
  justinas/nosurf (CSRF protection)
  gorilla/sessions

Batteries included

Middleware can be found in the sub package stack/mw.

Router

A simple router is included in the sub package router.

Usage

  // define middleware
  func middleware(w http.ResponseWriter, r *http.Request, next http.Handler) {
    w.Write([]byte("middleware speaking\n"))
    next.ServeHTTP(w,r)
  }

  // define app
  func app(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("app here"))
  }  

  // define stack
  var s Stack
  s.UseFunc(middleware)
  // add other middleware for each supported type there is a UseXXX method
  
  // use it
  http.Handle("/", s.WrapFunc(app))

Sharing Context

Context is shared by wrapping the http.ResponseWriter with another one that also implements the Contexter interface. This new ResponseWriter is then passed to the middleware. In order to use the context the middleware must have the Contexter as first parameter. Context data can be any type that has a pointer method implementing the Recoverer interface. Each type can only be saved once per request.

  // define context data type, may be any type
  type Name string

  // implement Recoverer interface
  // Recover replaces the value of m with the value of val
  func (n *Name) Recover(val interface{}) {
    *n = *(val.(*Name)) // will never panic
  }

  // define middleware that uses context
  func middleware(ctx stack.Contexter, w http.ResponseWriter, r *http.Request, next http.Handler) {
    var n Name = "Hulk"
    ctx.Set(&n)
    next.ServeHTTP(w, r)
  }

  // define app that uses context
  func app(ctx stack.Contexter, w http.ResponseWriter, r *http.Request) {
    var n Name
    ctx.Get(&n)
    w.Write([]byte("name is " + string(n)))
  }

  // define stack
  s := stack.New()
  s.UseFuncWithContext(middleware)
  // add other middleware for each supported type there is a UseXXXWithContext method
       

  // make sure to call WrapWithContext or WrapFuncWithContext to create the context object
  http.Handle("/", s.WrapFuncWithContext(app))

Original ResponseWriter

To get access to the original ResponseWriter there are several methods. Here is an example of using the original ResponseWriter to type assert it to a http.Flusher.

  // access the original http.ResponseWriter to flush
  func flush(ctx stack.Contexter, rw http.ResponseWriter, req *http.Request, next http.Handler) {
    var o stack.ResponseWriter
    ctx.Get(&o)

    // shortcut for the above
    // o := stack.ReclaimResponseWriter(rw)

    // type assertion of the original ResponseWriter
    if fl, is := o.ResponseWriter.(http.Flusher); is {
      fl.Flush()
    }

    // alternative shortcut for flushing, respecting an underlying ResponseWriter
    // stack.Flush(rw)
  }
  // shortcuts for underlying ResponseWriters

  Flush                 // flush a ResponseWriter if it is a http.Flusher
  CloseNotify           // returns a channel to notify if ResponseWriter is a http.CloseNotifier
  Hijack                // allows to hijack a connection if ResponseWriter is a http.Hijacker
  ReclaimResponseWriter // get the original ResponseWriter from a ResponseWriter with context

Other ResponseWriters

The package stack.responsewriter provides some ResponseWriter wrappers that help with development of middleware and also support context sharing via embedding of a stack.Contexter if it is available.

Server

A simple ready-to-go server is inside the server subpackage.


package main

import (
  "github.com/go-on/stack/server"
  "github.com/go-on/stack/mw"
)

func main() {
  s := server.New()
  s.INDEX("hu", mw.TextString("hu"))
  s.ListenAndServe(":8080")
}

Credits

Initial inspiration came from Christian Neukirchen's rack for ruby some years ago.

Adapters come from carbocation/interpose

Documentation

Overview

Package stack creates a fast and flexible middleware stack for http.Handlers.

Accepted middleware

See UseXXX methods of Stack

Batteries included

Middleware can be found in the sub package stack/middleware and stack/thirdparty.

Credits

Initial inspiration came from Christian Neukirchen's rack for ruby some years ago.

Adapters come from carbocation/interpose (https://github.com/carbocation/interpose/blob/master/adaptors)

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func CloseNotify

func CloseNotify(rw http.ResponseWriter) (ch <-chan bool, ok bool)

CloseNotify is the same for http.CloseNotifier as Flush is for http.Flusher ok tells if it was a CloseNotifier

func Flush

func Flush(rw http.ResponseWriter) (ok bool)

Flush is a helper that flushes the buffer in the underlying response writer if it is a http.Flusher. The http.ResponseWriter might also be a Contexter if it allows the retrieval of the underlying ResponseWriter. Ok returns if the underlying ResponseWriter was a http.Flusher

func Hijack

func Hijack(rw http.ResponseWriter) (c net.Conn, brw *bufio.ReadWriter, err error, ok bool)

Hijack is the same for http.Hijacker as Flush is for http.Flusher ok tells if it was a Hijacker

func ReclaimResponseWriter

func ReclaimResponseWriter(rw http.ResponseWriter) http.ResponseWriter

ReclaimResponseWriter is a helper that expects the given http.ResponseWriter to either be the original http.ResponseWriter or a Contexter which has saved a wrack.ResponseWriter. In either case it returns the underlying http.ResponseWriter

Types

type ContextHandler

type ContextHandler interface {
	ServeHTTP(ctx Contexter, wr http.ResponseWriter, req *http.Request)
}

ContextHandler is like a http.Handler that needs to be able to store and retrieve context bound to the request

func HandlerToContextHandler

func HandlerToContextHandler(h http.Handler) ContextHandler

type ContextHandlerFunc

type ContextHandlerFunc func(ctx Contexter, wr http.ResponseWriter, req *http.Request)

func (ContextHandlerFunc) ServeHTTP

func (c ContextHandlerFunc) ServeHTTP(wr http.ResponseWriter, req *http.Request)

type ContextMiddleware

type ContextMiddleware interface {
	ServeHTTP(ctx Contexter, wr http.ResponseWriter, req *http.Request, next http.Handler)
}

ContextMiddleware is like Middleware that needs to be able to store and retrieve context bound to the request

type Contexter

type Contexter interface {

	// Set the given Recoverer, replaces the value of the same type
	// Set may be run on the same Contexter concurrently
	Set(Recoverer)

	// Get a given Recoverer. If there is a Recoverer of this type
	// inside the Contexter, the given Recoverers Recover method is called with the stored value and true is returned.
	// If no Recoverer of the same type could be found, false is returned
	// Get may be run on the same Contexter concurrently
	Get(Recoverer) (has bool)

	// Del deletes a value of the given type.
	// Del may be run on the same Contexter concurrently
	Del(Recoverer)

	// Transaction runs the given function inside a transaction. A TransactionContexter is passed to the
	// given function that might be used to call the Set, Get and Del methods inside the transaction.
	// However that methods must not be used concurrently
	Transaction(func(TransactionContexter))
}

Contexter stores and retrieves per request data. Only one value per type can be stored.

Example
//go:build go1.1
// +build go1.1

package main

import (
	"fmt"
	"github.com/go-on/stack"
	"net/http"
)

type Val string

// Recover replaces the value of m with the value of val
func (m *Val) Recover(val interface{}) {
	*m = *(val.(*Val)) // will never panic
}

func setVal(ctx stack.Contexter, rw http.ResponseWriter, req *http.Request, next http.Handler) {
	var val Val = "some value"
	ctx.Set(&val)
	next.ServeHTTP(rw, req)
}

func writeVal(ctx stack.Contexter, rw http.ResponseWriter, req *http.Request, next http.Handler) {
	var val Val
	found := ctx.Get(&val)
	if !found {
		fmt.Println("no value found")
	} else {
		fmt.Printf("value: %s\n", string(val))
	}
	next.ServeHTTP(rw, req)
}

func main() {
	var s stack.Stack
	s.UseFuncWithContext(writeVal)
	s.UseFuncWithContext(setVal)
	s.UseFuncWithContext(writeVal)

	r, _ := http.NewRequest("GET", "/", nil)
	s.HandlerWithContext().ServeHTTP(nil, r)

}
Output:

no value found
value: some value

type Middleware

type Middleware interface {
	// ServeHTTP serves the request and may write to the given writer. Also may invoke the next handler
	ServeHTTP(writer http.ResponseWriter, request *http.Request, next http.Handler)
}

Middleware is like a http.Handler that is part of a chain of handlers handling the request

type Recoverer

type Recoverer interface {
	// Recover must be defined on a pointer
	// and changes the the value of the pointer
	// to the value the replacement is pointing to
	// Example
	//
	// type S string
	// func (s *S) Recover(valPtr interface{}) {
	// 	*s = *(valPtr.(*S))
	// }
	Recover(valPtr interface{})
}

Recoverer recovers its value from a given value inside a request context

type ResponseWriter

type ResponseWriter struct {
	http.ResponseWriter
}

ResponseWriter is used to store the underlying http.ResponseWriter inside the Contexter

func (*ResponseWriter) Recover

func (rw *ResponseWriter) Recover(repl interface{})

type Stack

type Stack []func(http.Handler) http.Handler

Stack is a stack of middlewares that handle http requests

Example
//go:build go1.1
// +build go1.1

package main

import (
	"fmt"
	"net/http"

	"github.com/go-on/stack"
)

/*
This example illustrates 3 ways to write and use middleware.

For sharing context, look at example_context_test.go.
*/

type print1 string

func (p print1) ServeHTTP(wr http.ResponseWriter, req *http.Request, next http.Handler) {
	fmt.Print(p)
	next.ServeHTTP(wr, req)
}

type print2 string

func (p print2) Wrap(next http.Handler) http.Handler {
	var f http.HandlerFunc
	f = func(rw http.ResponseWriter, req *http.Request) {
		fmt.Print(p)
		next.ServeHTTP(rw, req)
	}
	return f
}

type print3 string

func (p print3) ServeHTTP(wr http.ResponseWriter, req *http.Request) {
	fmt.Println(p)
}

func main() {
	var s stack.Stack
	s.Use(print1("ready..."))
	s.UseWrapper(print2("steady..."))
	s.UseHandler(print3("go!"))
	r, _ := http.NewRequest("GET", "/", nil)
	s.Handler().ServeHTTP(nil, r)

}
Output:

ready...steady...go!

func New

func New() (s *Stack)

func (*Stack) Concat

func (s *Stack) Concat(st *Stack) *Stack

Concat returns a new stack that has the middleware of the current stack concatenated with the middleware of the given stack

func (*Stack) ConcatWithHandler

func (s *Stack) ConcatWithHandler(h http.Handler) *Stack

ConcatWithHandler returns a new stack that has the middleware concatenated with a handler

func (*Stack) Handler

func (s *Stack) Handler() http.Handler

func (*Stack) HandlerWithContext

func (s *Stack) HandlerWithContext() http.Handler

func (*Stack) Use

func (s *Stack) Use(mw Middleware) *Stack

Use adds the given middleware to the middleware stack

func (*Stack) UseFunc

func (s *Stack) UseFunc(fn func(wr http.ResponseWriter, req *http.Request, next http.Handler)) *Stack

UseFunc adds the given function to the middleware stack

func (*Stack) UseFuncWithContext

func (s *Stack) UseFuncWithContext(fn func(ctx Contexter, wr http.ResponseWriter, req *http.Request, next http.Handler)) *Stack

UseFuncWithContext adds the given function to the middleware stack

func (*Stack) UseHandler

func (s *Stack) UseHandler(mw http.Handler) *Stack

UseHandler adds the given handler as middleware to the stack. the handler will be called before the next middleware

func (*Stack) UseHandlerFunc

func (s *Stack) UseHandlerFunc(fn func(wr http.ResponseWriter, req *http.Request)) *Stack

UseHandlerFunc is like UseHandler but for http.HandlerFunc

func (*Stack) UseHandlerFuncWithContext

func (s *Stack) UseHandlerFuncWithContext(fn func(c Contexter, w http.ResponseWriter, r *http.Request)) *Stack

UseHandlerFuncWithContext adds the given function as middleware to the stack. the handler will be called before the next middleware

func (*Stack) UseHandlerWithContext

func (s *Stack) UseHandlerWithContext(mw ContextHandler) *Stack

UseHandlerWithContext adds the given context handler as middleware to the stack. the handler will be called before the next middleware

func (*Stack) UseWithContext

func (s *Stack) UseWithContext(mw ContextMiddleware) *Stack

UseWithContext adds the context middleware to the middleware stack

func (*Stack) UseWrapper

func (s *Stack) UseWrapper(mw Wrapper) *Stack

UseWrapper adds the given wrapper to the middleware stack

func (*Stack) UseWrapperFunc

func (s *Stack) UseWrapperFunc(mw func(http.Handler) http.Handler) *Stack

UseWrapperFunc adds the given function to the middleware stack

func (*Stack) Wrap

func (s *Stack) Wrap(next http.Handler) http.Handler

Wrap wraps the stack around the next handler and returns the resulting handler

func (*Stack) WrapFunc

func (s *Stack) WrapFunc(fn func(wr http.ResponseWriter, req *http.Request)) http.Handler

func (*Stack) WrapFuncWithContext

func (s *Stack) WrapFuncWithContext(fn func(ctx Contexter, wr http.ResponseWriter, req *http.Request)) http.Handler

func (*Stack) WrapWithContext

func (s *Stack) WrapWithContext(app ContextHandler) http.Handler

WrapWithContext wraps the stack around the given app and returns a handler to run the stack that adds context to the http.ResponseWriter. It should be used instead of Wrap for the outermost stack and only there

type TransactionContexter

type TransactionContexter interface {
	// Set the given Recoverer, replaces the value of the same type
	// Set may NOT be run on the same TransactionContexter concurrently
	Set(Recoverer)

	// Get a given Recoverer. If there is a Recoverer of this type
	// inside the Contexter, the given Recoverers Recover method is called with the stored value and true is returned.
	// If no Recoverer of the same type could be found, false is returned
	// Get may NOT be run on the same TransactionContexter concurrently
	Get(Recoverer) (has bool)

	// Del deletes a value of the given type.
	// Del may NOT be run on the same TransactionContexter concurrently
	Del(Recoverer)
}

TransactionContexter stores and retrieves per request data via a hidden Contexter. It is meant to be used in the Transaction method of a Contexter. Only one TransactionContexter might be used at the same time for the same Contexter. No method of a TransactionContexter might be used concurrently

type Wrapper

type Wrapper interface {
	Wrap(http.Handler) http.Handler
}

Wrapper wraps an http.Handler returning another http.Handler

Directories

Path Synopsis
Package adapter_martini is an adapter for martini middleware to be used with github.com/go-on/stack.
Package adapter_martini is an adapter for martini middleware to be used with github.com/go-on/stack.
Package adapter_negroni is an adapter for negroni middleware to be used with github.com/go-on/stack.
Package adapter_negroni is an adapter for negroni middleware to be used with github.com/go-on/stack.
mw
Package mw provides middleware for the github.com/go-on/stack package
Package mw provides middleware for the github.com/go-on/stack package
Package responsewriter provides some ResponseWriter wrappers that help with development of middleware and also support context sharing via embedding of a stack.Contexter if it is available.
Package responsewriter provides some ResponseWriter wrappers that help with development of middleware and also support context sharing via embedding of a stack.Contexter if it is available.
Package rest provides a simple and opinionated rest rest Principles 1.
Package rest provides a simple and opinionated rest rest Principles 1.
third-party
stackhttpauth
Package stackhttpauth provides wrappers based on the github.com/abbot/go-http-auth package.
Package stackhttpauth provides wrappers based on the github.com/abbot/go-http-auth package.
stacknosurf
Package stacknosurf provides wrappers based on the github.com/justinas/nosurf package.
Package stacknosurf provides wrappers based on the github.com/justinas/nosurf package.
stacksession
Package stacksession provides wrappers based on the github.com/gorilla/sessions.
Package stacksession provides wrappers based on the github.com/gorilla/sessions.

Jump to

Keyboard shortcuts

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