aboutsummaryrefslogtreecommitdiff
path: root/src/vendor/github.com/alexedwards/stack/README.md
blob: ba689f61fe08492976f1db9cd8eee94f3a20e787 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# Stack <br> [![Build Status](https://travis-ci.org/alexedwards/stack.svg?branch=master)](https://travis-ci.org/alexedwards/stack)  [![Coverage](http://gocover.io/_badge/github.com/alexedwards/stack?0)](http://gocover.io/github.com/alexedwards/stack)  [![GoDoc](http://godoc.org/github.com/alexedwards/stack?status.png)](http://godoc.org/github.com/alexedwards/stack)

Stack provides an easy way to chain your HTTP middleware and handlers together and to pass request-scoped context between them. It's essentially a context-aware version of [Alice](https://github.com/justinas/alice).

[Skip to the example &rsaquo;](#example)

### Usage

#### Making a chain

Middleware chains are constructed with [`stack.New()`](http://godoc.org/github.com/alexedwards/stack#New):

```go
stack.New(middlewareOne, middlewareTwo, middlewareThree)
```

You can also store middleware chains as variables, and then [`Append()`](http://godoc.org/github.com/alexedwards/stack#Chain.Append) to them:

```go
stdStack := stack.New(middlewareOne, middlewareTwo)
extStack := stdStack.Append(middlewareThree, middlewareFour)
```

Your middleware should have the signature `func(*stack.Context, http.Handler) http.Handler`. For example:

```go
func middlewareOne(ctx *stack.Context, next http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    // do something middleware-ish, accessing ctx
    next.ServeHTTP(w, r)
  })
}
```

You can also use middleware with the signature `func(http.Handler) http.Handler` by adapting it with [`stack.Adapt()`](http://godoc.org/github.com/alexedwards/stack#Adapt). For example, if you had the middleware:

```go
func middlewareTwo(next http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    // do something else middleware-ish
    next.ServeHTTP(w, r)
  })
}
```

You can add it to a chain like this:

```go
stack.New(middlewareOne, stack.Adapt(middlewareTwo), middlewareThree)
```

See the [codes samples](#code-samples) for real-life use of third-party middleware with Stack.

#### Adding an application handler

Application handlers should have the signature `func(*stack.Context, http.ResponseWriter, *http.Request)`. You add them to the end of a middleware chain with the [`Then()`](http://godoc.org/github.com/alexedwards/stack#Chain.Then) method. 

So an application handler like this:

```go
func appHandler(ctx *stack.Context, w http.ResponseWriter, r *http.Request) {
   // do something handler-ish, accessing ctx
}
```

Is added to the end of a middleware chain like this:

```go
stack.New(middlewareOne, middlewareTwo).Then(appHandler)
```

For convenience [`ThenHandler()`](http://godoc.org/github.com/alexedwards/stack#Chain.ThenHandler) and [`ThenHandlerFunc()`](http://godoc.org/github.com/alexedwards/stack#Chain.ThenHandlerFunc) methods are also provided. These allow you to finish a chain with a standard `http.Handler` or `http.HandlerFunc` respectively.

For example, you could use a standard `http.FileServer` as the application handler:

```go
fs :=  http.FileServer(http.Dir("./static/"))
http.Handle("/", stack.New(middlewareOne, middlewareTwo).ThenHandler(fs))
```

Once a chain is 'closed' with any of these methods it is converted into a [`HandlerChain`](http://godoc.org/github.com/alexedwards/stack#HandlerChain) object which satisfies the `http.Handler` interface, and can be used with the `http.DefaultServeMux` and many other routers.

#### Using context

Request-scoped data (or *context*) can be passed through the chain by storing it in `stack.Context`. This is implemented as a pointer to a `map[string]interface{}` and scoped to the goroutine executing the current HTTP request. Operations on `stack.Context` are protected by a mutex, so if you need to pass the context pointer to another goroutine (say for logging or completing a background process) it is safe for concurrent use.

Data is added with [`Context.Put()`](http://godoc.org/github.com/alexedwards/stack#Context.Put). The first parameter is a string (which acts as a key) and the second is the value you need to store. For example:

```go
func middlewareOne(ctx *stack.Context, next http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    ctx.Put("token", "c9e452805dee5044ba520198628abcaa")
    next.ServeHTTP(w, r)
  })
}
```

You retrieve data with [`Context.Get()`](http://godoc.org/github.com/alexedwards/stack#Context.Get). Remember to type assert the returned value into the type you're expecting.

```go
func appHandler(ctx *stack.Context, w http.ResponseWriter, r *http.Request) {
  token, ok := ctx.Get("token").(string)
  if !ok {
    http.Error(w, http.StatusText(500), 500)
    return
  }
  fmt.Fprintf(w, "Token is: %s", token)
}
```

Note that `Context.Get()` will return `nil` if a key does not exist. If you need to tell the difference between a key having a `nil` value and it explicitly not existing, please check with [`Context.Exists()`](http://godoc.org/github.com/alexedwards/stack#Context.Exists).

Keys (and their values) can be deleted with [`Context.Delete()`](http://godoc.org/github.com/alexedwards/stack#Context.Delete).    

#### Injecting context

It's possible to inject values into `stack.Context` during a request cycle but *before* the chain starts to be executed. This is useful if you need to inject parameters from a router into the context.

The [`Inject()`](http://godoc.org/github.com/alexedwards/stack#Inject) function returns a new copy of the chain containing the injected context. You should make sure that you use this new copy &ndash; not the original &ndash; for subsequent processing.

Here's an example of a wrapper for injecting [httprouter](https://github.com/julienschmidt/httprouter) params into the context:

```go
func InjectParams(hc stack.HandlerChain) httprouter.Handle {
  return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
    newHandlerChain := stack.Inject(hc, "params", ps)
    newHandlerChain.ServeHTTP(w, r)
  }
}
```

A full example is available in the [code samples](#code-samples).

### Example

```go
package main

import (
  "net/http"
  "github.com/alexedwards/stack"
  "fmt"
)

func main() {
  stk := stack.New(token, stack.Adapt(language))

  http.Handle("/", stk.Then(final))

  http.ListenAndServe(":3000", nil)
}

func token(ctx *stack.Context, next http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    ctx.Put("token", "c9e452805dee5044ba520198628abcaa")
    next.ServeHTTP(w, r)
  })
}

func language(next http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Language", "en-gb")
    next.ServeHTTP(w, r)
  })
}

func final(ctx *stack.Context, w http.ResponseWriter, r *http.Request) {
  token, ok := ctx.Get("token").(string)
  if !ok {
    http.Error(w, http.StatusText(500), 500)
    return
  }
  fmt.Fprintf(w, "Token is: %s", token)
}
```

### Code samples

* [Integrating with httprouter](https://gist.github.com/alexedwards/4d20c505f389597c3360)
* *More to follow*

### TODO 

- Add more code samples (using 3rd party middleware)
- Make a `chain.Merge()` method
- Mirror master in v1 branch (and mention gopkg.in in README)
- Add benchmarks