Add web framework dependencies for pat and negroni
parent
b68d88fda9
commit
77fe2532d5
@ -0,0 +1,27 @@
|
|||||||
|
Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@ -0,0 +1,10 @@
|
|||||||
|
context
|
||||||
|
=======
|
||||||
|
[![Build Status](https://travis-ci.org/gorilla/context.png?branch=master)](https://travis-ci.org/gorilla/context)
|
||||||
|
|
||||||
|
gorilla/context is a general purpose registry for global request variables.
|
||||||
|
|
||||||
|
> Note: gorilla/context, having been born well before `context.Context` existed, does not play well
|
||||||
|
> with the shallow copying of the request that [`http.Request.WithContext`](https://golang.org/pkg/net/http/#Request.WithContext) (added to net/http Go 1.7 onwards) performs. You should either use *just* gorilla/context, or moving forward, the new `http.Request.Context()`.
|
||||||
|
|
||||||
|
Read the full documentation here: http://www.gorillatoolkit.org/pkg/context
|
@ -0,0 +1,143 @@
|
|||||||
|
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package context
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
mutex sync.RWMutex
|
||||||
|
data = make(map[*http.Request]map[interface{}]interface{})
|
||||||
|
datat = make(map[*http.Request]int64)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Set stores a value for a given key in a given request.
|
||||||
|
func Set(r *http.Request, key, val interface{}) {
|
||||||
|
mutex.Lock()
|
||||||
|
if data[r] == nil {
|
||||||
|
data[r] = make(map[interface{}]interface{})
|
||||||
|
datat[r] = time.Now().Unix()
|
||||||
|
}
|
||||||
|
data[r][key] = val
|
||||||
|
mutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns a value stored for a given key in a given request.
|
||||||
|
func Get(r *http.Request, key interface{}) interface{} {
|
||||||
|
mutex.RLock()
|
||||||
|
if ctx := data[r]; ctx != nil {
|
||||||
|
value := ctx[key]
|
||||||
|
mutex.RUnlock()
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
mutex.RUnlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOk returns stored value and presence state like multi-value return of map access.
|
||||||
|
func GetOk(r *http.Request, key interface{}) (interface{}, bool) {
|
||||||
|
mutex.RLock()
|
||||||
|
if _, ok := data[r]; ok {
|
||||||
|
value, ok := data[r][key]
|
||||||
|
mutex.RUnlock()
|
||||||
|
return value, ok
|
||||||
|
}
|
||||||
|
mutex.RUnlock()
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAll returns all stored values for the request as a map. Nil is returned for invalid requests.
|
||||||
|
func GetAll(r *http.Request) map[interface{}]interface{} {
|
||||||
|
mutex.RLock()
|
||||||
|
if context, ok := data[r]; ok {
|
||||||
|
result := make(map[interface{}]interface{}, len(context))
|
||||||
|
for k, v := range context {
|
||||||
|
result[k] = v
|
||||||
|
}
|
||||||
|
mutex.RUnlock()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
mutex.RUnlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllOk returns all stored values for the request as a map and a boolean value that indicates if
|
||||||
|
// the request was registered.
|
||||||
|
func GetAllOk(r *http.Request) (map[interface{}]interface{}, bool) {
|
||||||
|
mutex.RLock()
|
||||||
|
context, ok := data[r]
|
||||||
|
result := make(map[interface{}]interface{}, len(context))
|
||||||
|
for k, v := range context {
|
||||||
|
result[k] = v
|
||||||
|
}
|
||||||
|
mutex.RUnlock()
|
||||||
|
return result, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete removes a value stored for a given key in a given request.
|
||||||
|
func Delete(r *http.Request, key interface{}) {
|
||||||
|
mutex.Lock()
|
||||||
|
if data[r] != nil {
|
||||||
|
delete(data[r], key)
|
||||||
|
}
|
||||||
|
mutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear removes all values stored for a given request.
|
||||||
|
//
|
||||||
|
// This is usually called by a handler wrapper to clean up request
|
||||||
|
// variables at the end of a request lifetime. See ClearHandler().
|
||||||
|
func Clear(r *http.Request) {
|
||||||
|
mutex.Lock()
|
||||||
|
clear(r)
|
||||||
|
mutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear is Clear without the lock.
|
||||||
|
func clear(r *http.Request) {
|
||||||
|
delete(data, r)
|
||||||
|
delete(datat, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Purge removes request data stored for longer than maxAge, in seconds.
|
||||||
|
// It returns the amount of requests removed.
|
||||||
|
//
|
||||||
|
// If maxAge <= 0, all request data is removed.
|
||||||
|
//
|
||||||
|
// This is only used for sanity check: in case context cleaning was not
|
||||||
|
// properly set some request data can be kept forever, consuming an increasing
|
||||||
|
// amount of memory. In case this is detected, Purge() must be called
|
||||||
|
// periodically until the problem is fixed.
|
||||||
|
func Purge(maxAge int) int {
|
||||||
|
mutex.Lock()
|
||||||
|
count := 0
|
||||||
|
if maxAge <= 0 {
|
||||||
|
count = len(data)
|
||||||
|
data = make(map[*http.Request]map[interface{}]interface{})
|
||||||
|
datat = make(map[*http.Request]int64)
|
||||||
|
} else {
|
||||||
|
min := time.Now().Unix() - int64(maxAge)
|
||||||
|
for r := range data {
|
||||||
|
if datat[r] < min {
|
||||||
|
clear(r)
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mutex.Unlock()
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearHandler wraps an http.Handler and clears request values at the end
|
||||||
|
// of a request lifetime.
|
||||||
|
func ClearHandler(h http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer Clear(r)
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1,88 @@
|
|||||||
|
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package context stores values shared during a request lifetime.
|
||||||
|
|
||||||
|
Note: gorilla/context, having been born well before `context.Context` existed,
|
||||||
|
does not play well > with the shallow copying of the request that
|
||||||
|
[`http.Request.WithContext`](https://golang.org/pkg/net/http/#Request.WithContext)
|
||||||
|
(added to net/http Go 1.7 onwards) performs. You should either use *just*
|
||||||
|
gorilla/context, or moving forward, the new `http.Request.Context()`.
|
||||||
|
|
||||||
|
For example, a router can set variables extracted from the URL and later
|
||||||
|
application handlers can access those values, or it can be used to store
|
||||||
|
sessions values to be saved at the end of a request. There are several
|
||||||
|
others common uses.
|
||||||
|
|
||||||
|
The idea was posted by Brad Fitzpatrick to the go-nuts mailing list:
|
||||||
|
|
||||||
|
http://groups.google.com/group/golang-nuts/msg/e2d679d303aa5d53
|
||||||
|
|
||||||
|
Here's the basic usage: first define the keys that you will need. The key
|
||||||
|
type is interface{} so a key can be of any type that supports equality.
|
||||||
|
Here we define a key using a custom int type to avoid name collisions:
|
||||||
|
|
||||||
|
package foo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gorilla/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
type key int
|
||||||
|
|
||||||
|
const MyKey key = 0
|
||||||
|
|
||||||
|
Then set a variable. Variables are bound to an http.Request object, so you
|
||||||
|
need a request instance to set a value:
|
||||||
|
|
||||||
|
context.Set(r, MyKey, "bar")
|
||||||
|
|
||||||
|
The application can later access the variable using the same key you provided:
|
||||||
|
|
||||||
|
func MyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// val is "bar".
|
||||||
|
val := context.Get(r, foo.MyKey)
|
||||||
|
|
||||||
|
// returns ("bar", true)
|
||||||
|
val, ok := context.GetOk(r, foo.MyKey)
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
And that's all about the basic usage. We discuss some other ideas below.
|
||||||
|
|
||||||
|
Any type can be stored in the context. To enforce a given type, make the key
|
||||||
|
private and wrap Get() and Set() to accept and return values of a specific
|
||||||
|
type:
|
||||||
|
|
||||||
|
type key int
|
||||||
|
|
||||||
|
const mykey key = 0
|
||||||
|
|
||||||
|
// GetMyKey returns a value for this package from the request values.
|
||||||
|
func GetMyKey(r *http.Request) SomeType {
|
||||||
|
if rv := context.Get(r, mykey); rv != nil {
|
||||||
|
return rv.(SomeType)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMyKey sets a value for this package in the request values.
|
||||||
|
func SetMyKey(r *http.Request, val SomeType) {
|
||||||
|
context.Set(r, mykey, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
Variables must be cleared at the end of a request, to remove all values
|
||||||
|
that were stored. This can be done in an http.Handler, after a request was
|
||||||
|
served. Just call Clear() passing the request:
|
||||||
|
|
||||||
|
context.Clear(r)
|
||||||
|
|
||||||
|
...or use ClearHandler(), which conveniently wraps an http.Handler to clear
|
||||||
|
variables at the end of a request lifetime.
|
||||||
|
|
||||||
|
The Routers from the packages gorilla/mux and gorilla/pat call Clear()
|
||||||
|
so if you are using either of them you don't need to clear the context manually.
|
||||||
|
*/
|
||||||
|
package context
|
@ -0,0 +1,27 @@
|
|||||||
|
Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@ -0,0 +1,70 @@
|
|||||||
|
pat
|
||||||
|
===
|
||||||
|
[![GoDoc](https://godoc.org/github.com/gorilla/pat?status.svg)](https://godoc.org/github.com/gorilla/pat)
|
||||||
|
[![Build Status](https://travis-ci.org/gorilla/pat.svg?branch=master)](https://travis-ci.org/gorilla/pat)
|
||||||
|
|
||||||
|
### How to use?
|
||||||
|
|
||||||
|
pat is pretty simple. The documentation lives [here](http://www.gorillatoolkit.org/pkg/pat).
|
||||||
|
|
||||||
|
### Install
|
||||||
|
With a properly configured Go toolchain:
|
||||||
|
```sh
|
||||||
|
go get github.com/gorilla/pat
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
Here's an example of a RESTful api:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/pat"
|
||||||
|
)
|
||||||
|
|
||||||
|
func homeHandler(wr http.ResponseWriter, req *http.Request) {
|
||||||
|
wr.WriteHeader(http.StatusOK)
|
||||||
|
wr.Write([]byte("Yay! We're home, Jim!"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAllTheThings(wr http.ResponseWriter, req *http.Request) {
|
||||||
|
wr.WriteHeader(http.StatusOK)
|
||||||
|
wr.Write([]byte("Look, Jim! Get all the things!"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func putOneThing(wr http.ResponseWriter, req *http.Request) {
|
||||||
|
wr.WriteHeader(http.StatusOK)
|
||||||
|
wr.Write([]byte("Look, Jim! Put one thing!"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteOneThing(wr http.ResponseWriter, req *http.Request) {
|
||||||
|
wr.WriteHeader(http.StatusOK)
|
||||||
|
wr.Write([]byte("Look, Jim! Delete one thing!"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
router := pat.New()
|
||||||
|
|
||||||
|
router.Get("/things", getAllTheThings)
|
||||||
|
|
||||||
|
router.Put("/things/{id}", putOneThing)
|
||||||
|
router.Delete("/things/{id}", deleteOneThing)
|
||||||
|
|
||||||
|
router.Get("/", homeHandler)
|
||||||
|
|
||||||
|
http.Handle("/", router)
|
||||||
|
|
||||||
|
log.Print("Listening on 127.0.0.1:8000...")
|
||||||
|
log.Fatal(http.ListenAndServe(":8000", nil))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Notice how the routes descend? That's because Pat will take the first route
|
||||||
|
that matches. For your own testing, take the line ```router.Get("/",
|
||||||
|
homeHandler)``` and put it above the other routes and run the example. When you
|
||||||
|
try to curl any of the routes, you'll only get what the homeHandler returns.
|
||||||
|
Design your routes carefully.
|
@ -0,0 +1,67 @@
|
|||||||
|
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package gorilla/pat is a request router and dispatcher with a pat-like
|
||||||
|
interface. It is an alternative to gorilla/mux that showcases how it can
|
||||||
|
be used as a base for different API flavors. Package pat is documented at:
|
||||||
|
|
||||||
|
http://godoc.org/github.com/bmizerany/pat
|
||||||
|
|
||||||
|
Let's start registering a couple of URL paths and handlers:
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := pat.New()
|
||||||
|
r.Get("/products", ProductsHandler)
|
||||||
|
r.Get("/articles", ArticlesHandler)
|
||||||
|
r.Get("/", HomeHandler)
|
||||||
|
http.Handle("/", r)
|
||||||
|
}
|
||||||
|
|
||||||
|
Here we register three routes mapping URL paths to handlers. This is
|
||||||
|
equivalent to how http.HandleFunc() works: if an incoming GET request matches
|
||||||
|
one of the paths, the corresponding handler is called passing
|
||||||
|
(http.ResponseWriter, *http.Request) as parameters.
|
||||||
|
|
||||||
|
Note: gorilla/pat matches path prefixes, so you must register the most
|
||||||
|
specific paths first.
|
||||||
|
|
||||||
|
Note: differently from pat, these methods accept a handler function, and not an
|
||||||
|
http.Handler. We think this is shorter and more convenient. To set an
|
||||||
|
http.Handler, use the Add() method.
|
||||||
|
|
||||||
|
Paths can have variables. They are defined using the format {name} or
|
||||||
|
{name:pattern}. If a regular expression pattern is not defined, the matched
|
||||||
|
variable will be anything until the next slash. For example:
|
||||||
|
|
||||||
|
r := pat.New()
|
||||||
|
r.Get("/articles/{category}/{id:[0-9]+}", ArticleHandler)
|
||||||
|
r.Get("/articles/{category}/", ArticlesCategoryHandler)
|
||||||
|
r.Get("/products/{key}", ProductHandler)
|
||||||
|
|
||||||
|
The names are used to create a map of route variables which are stored in the
|
||||||
|
URL query, prefixed by a colon:
|
||||||
|
|
||||||
|
category := req.URL.Query().Get(":category")
|
||||||
|
|
||||||
|
As in the gorilla/mux package, other matchers can be added to the registered
|
||||||
|
routes and URLs can be reversed as well. To build a URL for a route, first
|
||||||
|
add a name to it:
|
||||||
|
|
||||||
|
r.Get("/products/{key}", ProductHandler).Name("product")
|
||||||
|
|
||||||
|
Then you can get it using the name and generate a URL:
|
||||||
|
|
||||||
|
url, err := r.GetRoute("product").URL("key", "transmogrifier")
|
||||||
|
|
||||||
|
...and the result will be a url.URL with the following path:
|
||||||
|
|
||||||
|
"/products/transmogrifier"
|
||||||
|
|
||||||
|
Check the mux documentation for more details about URL building and extra
|
||||||
|
matchers:
|
||||||
|
|
||||||
|
http://gorilla-web.appspot.com/pkg/mux/
|
||||||
|
*/
|
||||||
|
package pat
|
@ -0,0 +1,126 @@
|
|||||||
|
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package pat
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gorilla/context"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
// New returns a new router.
|
||||||
|
func New() *Router {
|
||||||
|
return &Router{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Router is a request router that implements a pat-like API.
|
||||||
|
//
|
||||||
|
// pat docs: http://godoc.org/github.com/bmizerany/pat
|
||||||
|
type Router struct {
|
||||||
|
mux.Router
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add registers a pattern with a handler for the given request method.
|
||||||
|
func (r *Router) Add(meth, pat string, h http.Handler) *mux.Route {
|
||||||
|
return r.NewRoute().PathPrefix(pat).Handler(h).Methods(meth)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Options registers a pattern with a handler for OPTIONS requests.
|
||||||
|
func (r *Router) Options(pat string, h http.HandlerFunc) *mux.Route {
|
||||||
|
return r.Add("OPTIONS", pat, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete registers a pattern with a handler for DELETE requests.
|
||||||
|
func (r *Router) Delete(pat string, h http.HandlerFunc) *mux.Route {
|
||||||
|
return r.Add("DELETE", pat, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Head registers a pattern with a handler for HEAD requests.
|
||||||
|
func (r *Router) Head(pat string, h http.HandlerFunc) *mux.Route {
|
||||||
|
return r.Add("HEAD", pat, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get registers a pattern with a handler for GET requests.
|
||||||
|
func (r *Router) Get(pat string, h http.HandlerFunc) *mux.Route {
|
||||||
|
return r.Add("GET", pat, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post registers a pattern with a handler for POST requests.
|
||||||
|
func (r *Router) Post(pat string, h http.HandlerFunc) *mux.Route {
|
||||||
|
return r.Add("POST", pat, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put registers a pattern with a handler for PUT requests.
|
||||||
|
func (r *Router) Put(pat string, h http.HandlerFunc) *mux.Route {
|
||||||
|
return r.Add("PUT", pat, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Patch registers a pattern with a handler for PATCH requests.
|
||||||
|
func (r *Router) Patch(pat string, h http.HandlerFunc) *mux.Route {
|
||||||
|
return r.Add("PATCH", pat, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeHTTP dispatches the handler registered in the matched route.
|
||||||
|
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
|
// Clean path to canonical form and redirect.
|
||||||
|
if p := cleanPath(req.URL.Path); p != req.URL.Path {
|
||||||
|
w.Header().Set("Location", p)
|
||||||
|
w.WriteHeader(http.StatusMovedPermanently)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var match mux.RouteMatch
|
||||||
|
var handler http.Handler
|
||||||
|
if matched := r.Match(req, &match); matched {
|
||||||
|
handler = match.Handler
|
||||||
|
registerVars(req, match.Vars)
|
||||||
|
}
|
||||||
|
if handler == nil {
|
||||||
|
if r.NotFoundHandler == nil {
|
||||||
|
r.NotFoundHandler = http.NotFoundHandler()
|
||||||
|
}
|
||||||
|
handler = r.NotFoundHandler
|
||||||
|
}
|
||||||
|
if !r.KeepContext {
|
||||||
|
defer context.Clear(req)
|
||||||
|
}
|
||||||
|
handler.ServeHTTP(w, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// registerVars adds the matched route variables to the URL query.
|
||||||
|
func registerVars(r *http.Request, vars map[string]string) {
|
||||||
|
parts, i := make([]string, len(vars)), 0
|
||||||
|
for key, value := range vars {
|
||||||
|
parts[i] = url.QueryEscape(":"+key) + "=" + url.QueryEscape(value)
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
q := strings.Join(parts, "&")
|
||||||
|
if r.URL.RawQuery == "" {
|
||||||
|
r.URL.RawQuery = q
|
||||||
|
} else {
|
||||||
|
r.URL.RawQuery += "&" + q
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleanPath returns the canonical path for p, eliminating . and .. elements.
|
||||||
|
// Borrowed from the net/http package.
|
||||||
|
func cleanPath(p string) string {
|
||||||
|
if p == "" {
|
||||||
|
return "/"
|
||||||
|
}
|
||||||
|
if p[0] != '/' {
|
||||||
|
p = "/" + p
|
||||||
|
}
|
||||||
|
np := path.Clean(p)
|
||||||
|
// path.Clean removes trailing slash except for root;
|
||||||
|
// put the trailing slash back if necessary.
|
||||||
|
if p[len(p)-1] == '/' && np != "/" {
|
||||||
|
np += "/"
|
||||||
|
}
|
||||||
|
return np
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
# Change Log
|
||||||
|
|
||||||
|
**ATTN**: This project uses [semantic versioning](http://semver.org/).
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
### Added
|
||||||
|
- `Recovery.ErrorHandlerFunc` for custom error handling during recovery
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- `Written()` correct returns `false` if no response header has been written
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Set default status to `0` in the case that no handler writes status -- was
|
||||||
|
previously `200` (in 0.2.0, before that it was `0` so this reestablishes that
|
||||||
|
behavior)
|
||||||
|
- Catch `panic`s thrown by callbacks provided to the `Recovery` handler
|
||||||
|
|
||||||
|
## [0.2.0] - 2016-05-10
|
||||||
|
### Added
|
||||||
|
- Support for variadic handlers in `New()`
|
||||||
|
- Added `Negroni.Handlers()` to fetch all of the handlers for a given chain
|
||||||
|
- Allowed size in `Recovery` handler was bumped to 8k
|
||||||
|
- `Negroni.UseFunc` to push another handler onto the chain
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Set the status before calling `beforeFuncs` so the information is available to them
|
||||||
|
- Set default status to `200` in the case that no handler writes status -- was previously `0`
|
||||||
|
- Panic if `nil` handler is given to `negroni.Use`
|
||||||
|
|
||||||
|
## 0.1.0 - 2013-07-22
|
||||||
|
### Added
|
||||||
|
- Initial implementation.
|
||||||
|
|
||||||
|
[Unreleased]: https://github.com/urfave/negroni/compare/v0.2.0...HEAD
|
||||||
|
[0.2.0]: https://github.com/urfave/negroni/compare/v0.1.0...v0.2.0
|
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014 Jeremy Saenz
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
@ -0,0 +1,444 @@
|
|||||||
|
# Negroni
|
||||||
|
[![GoDoc](https://godoc.org/github.com/urfave/negroni?status.svg)](http://godoc.org/github.com/urfave/negroni)
|
||||||
|
[![Build Status](https://travis-ci.org/urfave/negroni.svg?branch=master)](https://travis-ci.org/urfave/negroni)
|
||||||
|
[![codebeat](https://codebeat.co/badges/47d320b1-209e-45e8-bd99-9094bc5111e2)](https://codebeat.co/projects/github-com-urfave-negroni)
|
||||||
|
|
||||||
|
**Notice:** This is the library formerly known as
|
||||||
|
`github.com/codegangsta/negroni` -- Github will automatically redirect requests
|
||||||
|
to this repository, but we recommend updating your references for clarity.
|
||||||
|
|
||||||
|
Negroni is an idiomatic approach to web middleware in Go. It is tiny,
|
||||||
|
non-intrusive, and encourages use of `net/http` Handlers.
|
||||||
|
|
||||||
|
If you like the idea of [Martini](https://github.com/go-martini/martini), but
|
||||||
|
you think it contains too much magic, then Negroni is a great fit.
|
||||||
|
|
||||||
|
Language Translations:
|
||||||
|
* [German (de_DE)](translations/README_de_de.md)
|
||||||
|
* [Português Brasileiro (pt_BR)](translations/README_pt_br.md)
|
||||||
|
* [简体中文 (zh_cn)](translations/README_zh_cn.md)
|
||||||
|
* [繁體中文 (zh_tw)](translations/README_zh_tw.md)
|
||||||
|
* [日本語 (ja_JP)](translations/README_ja_JP.md)
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
After installing Go and setting up your
|
||||||
|
[GOPATH](http://golang.org/doc/code.html#GOPATH), create your first `.go` file.
|
||||||
|
We'll call it `server.go`.
|
||||||
|
|
||||||
|
<!-- { "interrupt": true } -->
|
||||||
|
``` go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/urfave/negroni"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
fmt.Fprintf(w, "Welcome to the home page!")
|
||||||
|
})
|
||||||
|
|
||||||
|
n := negroni.Classic() // Includes some default middlewares
|
||||||
|
n.UseHandler(mux)
|
||||||
|
|
||||||
|
http.ListenAndServe(":3000", n)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then install the Negroni package (**NOTE**: >= **go 1.1** is required):
|
||||||
|
|
||||||
|
```
|
||||||
|
go get github.com/urfave/negroni
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run your server:
|
||||||
|
|
||||||
|
```
|
||||||
|
go run server.go
|
||||||
|
```
|
||||||
|
|
||||||
|
You will now have a Go `net/http` webserver running on `localhost:3000`.
|
||||||
|
|
||||||
|
## Is Negroni a Framework?
|
||||||
|
|
||||||
|
Negroni is **not** a framework. It is a middleware-focused library that is
|
||||||
|
designed to work directly with `net/http`.
|
||||||
|
|
||||||
|
## Routing?
|
||||||
|
|
||||||
|
Negroni is BYOR (Bring your own Router). The Go community already has a number
|
||||||
|
of great http routers available, and Negroni tries to play well with all of them
|
||||||
|
by fully supporting `net/http`. For instance, integrating with [Gorilla Mux]
|
||||||
|
looks like so:
|
||||||
|
|
||||||
|
``` go
|
||||||
|
router := mux.NewRouter()
|
||||||
|
router.HandleFunc("/", HomeHandler)
|
||||||
|
|
||||||
|
n := negroni.New(Middleware1, Middleware2)
|
||||||
|
// Or use a middleware with the Use() function
|
||||||
|
n.Use(Middleware3)
|
||||||
|
// router goes last
|
||||||
|
n.UseHandler(router)
|
||||||
|
|
||||||
|
http.ListenAndServe(":3001", n)
|
||||||
|
```
|
||||||
|
|
||||||
|
## `negroni.Classic()`
|
||||||
|
|
||||||
|
`negroni.Classic()` provides some default middleware that is useful for most
|
||||||
|
applications:
|
||||||
|
|
||||||
|
* [`negroni.Recovery`](#recovery) - Panic Recovery Middleware.
|
||||||
|
* [`negroni.Logger`](#logger) - Request/Response Logger Middleware.
|
||||||
|
* [`negroni.Static`](#static) - Static File serving under the "public"
|
||||||
|
directory.
|
||||||
|
|
||||||
|
This makes it really easy to get started with some useful features from Negroni.
|
||||||
|
|
||||||
|
## Handlers
|
||||||
|
|
||||||
|
Negroni provides a bidirectional middleware flow. This is done through the
|
||||||
|
`negroni.Handler` interface:
|
||||||
|
|
||||||
|
``` go
|
||||||
|
type Handler interface {
|
||||||
|
ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If a middleware hasn't already written to the `ResponseWriter`, it should call
|
||||||
|
the next `http.HandlerFunc` in the chain to yield to the next middleware
|
||||||
|
handler. This can be used for great good:
|
||||||
|
|
||||||
|
``` go
|
||||||
|
func MyMiddleware(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||||
|
// do some stuff before
|
||||||
|
next(rw, r)
|
||||||
|
// do some stuff after
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And you can map it to the handler chain with the `Use` function:
|
||||||
|
|
||||||
|
``` go
|
||||||
|
n := negroni.New()
|
||||||
|
n.Use(negroni.HandlerFunc(MyMiddleware))
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also map plain old `http.Handler`s:
|
||||||
|
|
||||||
|
``` go
|
||||||
|
n := negroni.New()
|
||||||
|
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
// map your routes
|
||||||
|
|
||||||
|
n.UseHandler(mux)
|
||||||
|
|
||||||
|
http.ListenAndServe(":3000", n)
|
||||||
|
```
|
||||||
|
|
||||||
|
## `Run()`
|
||||||
|
|
||||||
|
Negroni has a convenience function called `Run`. `Run` takes an addr string
|
||||||
|
identical to [`http.ListenAndServe`](https://godoc.org/net/http#ListenAndServe).
|
||||||
|
|
||||||
|
<!-- { "interrupt": true } -->
|
||||||
|
``` go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/urfave/negroni"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
n := negroni.Classic()
|
||||||
|
n.Run(":8080")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In general, you will want to use `net/http` methods and pass `negroni` as a
|
||||||
|
`Handler`, as this is more flexible, e.g.:
|
||||||
|
|
||||||
|
<!-- { "interrupt": true } -->
|
||||||
|
``` go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/urfave/negroni"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
fmt.Fprintf(w, "Welcome to the home page!")
|
||||||
|
})
|
||||||
|
|
||||||
|
n := negroni.Classic() // Includes some default middlewares
|
||||||
|
n.UseHandler(mux)
|
||||||
|
|
||||||
|
s := &http.Server{
|
||||||
|
Addr: ":8080",
|
||||||
|
Handler: n,
|
||||||
|
ReadTimeout: 10 * time.Second,
|
||||||
|
WriteTimeout: 10 * time.Second,
|
||||||
|
MaxHeaderBytes: 1 << 20,
|
||||||
|
}
|
||||||
|
log.Fatal(s.ListenAndServe())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Route Specific Middleware
|
||||||
|
|
||||||
|
If you have a route group of routes that need specific middleware to be
|
||||||
|
executed, you can simply create a new Negroni instance and use it as your route
|
||||||
|
handler.
|
||||||
|
|
||||||
|
``` go
|
||||||
|
router := mux.NewRouter()
|
||||||
|
adminRoutes := mux.NewRouter()
|
||||||
|
// add admin routes here
|
||||||
|
|
||||||
|
// Create a new negroni for the admin middleware
|
||||||
|
router.PathPrefix("/admin").Handler(negroni.New(
|
||||||
|
Middleware1,
|
||||||
|
Middleware2,
|
||||||
|
negroni.Wrap(adminRoutes),
|
||||||
|
))
|
||||||
|
```
|
||||||
|
|
||||||
|
If you are using [Gorilla Mux], here is an example using a subrouter:
|
||||||
|
|
||||||
|
``` go
|
||||||
|
router := mux.NewRouter()
|
||||||
|
subRouter := mux.NewRouter().PathPrefix("/subpath").Subrouter().StrictSlash(true)
|
||||||
|
subRouter.HandleFunc("/", someSubpathHandler) // "/subpath/"
|
||||||
|
subRouter.HandleFunc("/:id", someSubpathHandler) // "/subpath/:id"
|
||||||
|
|
||||||
|
// "/subpath" is necessary to ensure the subRouter and main router linkup
|
||||||
|
router.PathPrefix("/subpath").Handler(negroni.New(
|
||||||
|
Middleware1,
|
||||||
|
Middleware2,
|
||||||
|
negroni.Wrap(subRouter),
|
||||||
|
))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Bundled Middleware
|
||||||
|
|
||||||
|
### Static
|
||||||
|
|
||||||
|
This middleware will serve files on the filesystem. If the files do not exist,
|
||||||
|
it proxies the request to the next middleware. If you want the requests for
|
||||||
|
non-existent files to return a `404 File Not Found` to the user you should look
|
||||||
|
at using [http.FileServer](https://golang.org/pkg/net/http/#FileServer) as
|
||||||
|
a handler.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
<!-- { "interrupt": true } -->
|
||||||
|
``` go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/urfave/negroni"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
fmt.Fprintf(w, "Welcome to the home page!")
|
||||||
|
})
|
||||||
|
|
||||||
|
// Example of using a http.FileServer if you want "server-like" rather than "middleware" behavior
|
||||||
|
// mux.Handle("/public", http.FileServer(http.Dir("/home/public")))
|
||||||
|
|
||||||
|
n := negroni.New()
|
||||||
|
n.Use(negroni.NewStatic(http.Dir("/tmp")))
|
||||||
|
n.UseHandler(mux)
|
||||||
|
|
||||||
|
http.ListenAndServe(":3002", n)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Will serve files from the `/tmp` directory first, but proxy calls to the next
|
||||||
|
handler if the request does not match a file on the filesystem.
|
||||||
|
|
||||||
|
### Recovery
|
||||||
|
|
||||||
|
This middleware catches `panic`s and responds with a `500` response code. If
|
||||||
|
any other middleware has written a response code or body, this middleware will
|
||||||
|
fail to properly send a 500 to the client, as the client has already received
|
||||||
|
the HTTP response code. Additionally, an `ErrorHandlerFunc` can be attached
|
||||||
|
to report 500's to an error reporting service such as Sentry or Airbrake.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
<!-- { "interrupt": true } -->
|
||||||
|
``` go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/urfave/negroni"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
panic("oh no")
|
||||||
|
})
|
||||||
|
|
||||||
|
n := negroni.New()
|
||||||
|
n.Use(negroni.NewRecovery())
|
||||||
|
n.UseHandler(mux)
|
||||||
|
|
||||||
|
http.ListenAndServe(":3003", n)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Will return a `500 Internal Server Error` to each request. It will also log the
|
||||||
|
stack traces as well as print the stack trace to the requester if `PrintStack`
|
||||||
|
is set to `true` (the default).
|
||||||
|
|
||||||
|
Example with error handler:
|
||||||
|
|
||||||
|
``` go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/urfave/negroni"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
panic("oh no")
|
||||||
|
})
|
||||||
|
|
||||||
|
n := negroni.New()
|
||||||
|
recovery := negroni.NewRecovery()
|
||||||
|
recovery.ErrorHandlerFunc = reportToSentry
|
||||||
|
n.Use(recovery)
|
||||||
|
n.UseHandler(mux)
|
||||||
|
|
||||||
|
http.ListenAndServe(":3003", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func reportToSentry(error interface{}) {
|
||||||
|
// write code here to report error to Sentry
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Logger
|
||||||
|
|
||||||
|
This middleware logs each incoming request and response.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
<!-- { "interrupt": true } -->
|
||||||
|
``` go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/urfave/negroni"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
fmt.Fprintf(w, "Welcome to the home page!")
|
||||||
|
})
|
||||||
|
|
||||||
|
n := negroni.New()
|
||||||
|
n.Use(negroni.NewLogger())
|
||||||
|
n.UseHandler(mux)
|
||||||
|
|
||||||
|
http.ListenAndServe(":3004", n)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Will print a log similar to:
|
||||||
|
|
||||||
|
```
|
||||||
|
[negroni] Started GET /
|
||||||
|
[negroni] Completed 200 OK in 145.446µs
|
||||||
|
```
|
||||||
|
|
||||||
|
on each request.
|
||||||
|
|
||||||
|
## Third Party Middleware
|
||||||
|
|
||||||
|
Here is a current list of Negroni compatible middlware. Feel free to put up a PR
|
||||||
|
linking your middleware if you have built one:
|
||||||
|
|
||||||
|
| Middleware | Author | Description |
|
||||||
|
| -----------|--------|-------------|
|
||||||
|
| [binding](https://github.com/mholt/binding) | [Matt Holt](https://github.com/mholt) | Data binding from HTTP requests into structs |
|
||||||
|
| [cloudwatch](https://github.com/cvillecsteele/negroni-cloudwatch) | [Colin Steele](https://github.com/cvillecsteele) | AWS cloudwatch metrics middleware |
|
||||||
|
| [cors](https://github.com/rs/cors) | [Olivier Poitrey](https://github.com/rs) | [Cross Origin Resource Sharing](http://www.w3.org/TR/cors/) (CORS) support |
|
||||||
|
| [csp](https://github.com/awakenetworks/csp) | [Awake Networks](https://github.com/awakenetworks) | [Content Security Policy](https://www.w3.org/TR/CSP2/) (CSP) support |
|
||||||
|
| [delay](https://github.com/jeffbmartinez/delay) | [Jeff Martinez](https://github.com/jeffbmartinez) | Add delays/latency to endpoints. Useful when testing effects of high latency |
|
||||||
|
| [New Relic Go Agent](https://github.com/yadvendar/negroni-newrelic-go-agent) | [Yadvendar Champawat](https://github.com/yadvendar) | Official [New Relic Go Agent](https://github.com/newrelic/go-agent) (currently in beta) |
|
||||||
|
| [gorelic](https://github.com/jingweno/negroni-gorelic) | [Jingwen Owen Ou](https://github.com/jingweno) | New Relic agent for Go runtime |
|
||||||
|
| [Graceful](https://github.com/tylerb/graceful) | [Tyler Bunnell](https://github.com/tylerb) | Graceful HTTP Shutdown |
|
||||||
|
| [gzip](https://github.com/phyber/negroni-gzip) | [phyber](https://github.com/phyber) | GZIP response compression |
|
||||||
|
| [JWT Middleware](https://github.com/auth0/go-jwt-middleware) | [Auth0](https://github.com/auth0) | Middleware checks for a JWT on the `Authorization` header on incoming requests and decodes it|
|
||||||
|
| [logrus](https://github.com/meatballhat/negroni-logrus) | [Dan Buch](https://github.com/meatballhat) | Logrus-based logger |
|
||||||
|
| [oauth2](https://github.com/goincremental/negroni-oauth2) | [David Bochenski](https://github.com/bochenski) | oAuth2 middleware |
|
||||||
|
| [onthefly](https://github.com/xyproto/onthefly) | [Alexander Rødseth](https://github.com/xyproto) | Generate TinySVG, HTML and CSS on the fly |
|
||||||
|
| [permissions2](https://github.com/xyproto/permissions2) | [Alexander Rødseth](https://github.com/xyproto) | Cookies, users and permissions |
|
||||||
|
| [prometheus](https://github.com/zbindenren/negroni-prometheus) | [Rene Zbinden](https://github.com/zbindenren) | Easily create metrics endpoint for the [prometheus](http://prometheus.io) instrumentation tool |
|
||||||
|
| [render](https://github.com/unrolled/render) | [Cory Jacobsen](https://github.com/unrolled) | Render JSON, XML and HTML templates |
|
||||||
|
| [RestGate](https://github.com/pjebs/restgate) | [Prasanga Siripala](https://github.com/pjebs) | Secure authentication for REST API endpoints |
|
||||||
|
| [secure](https://github.com/unrolled/secure) | [Cory Jacobsen](https://github.com/unrolled) | Middleware that implements a few quick security wins |
|
||||||
|
| [sessions](https://github.com/goincremental/negroni-sessions) | [David Bochenski](https://github.com/bochenski) | Session Management |
|
||||||
|
| [stats](https://github.com/thoas/stats) | [Florent Messa](https://github.com/thoas) | Store information about your web application (response time, etc.) |
|
||||||
|
| [VanGoH](https://github.com/auroratechnologies/vangoh) | [Taylor Wrobel](https://github.com/twrobel3) | Configurable [AWS-Style](http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html) HMAC authentication middleware |
|
||||||
|
| [xrequestid](https://github.com/pilu/xrequestid) | [Andrea Franz](https://github.com/pilu) | Middleware that assigns a random X-Request-Id header to each request |
|
||||||
|
| [mgo session](https://github.com/joeljames/nigroni-mgo-session) | [Joel James](https://github.com/joeljames) | Middleware that handles creating and closing mgo sessions per request |
|
||||||
|
| [digits](https://github.com/bamarni/digits) | [Bilal Amarni](https://github.com/bamarni) | Middleware that handles [Twitter Digits](https://get.digits.com/) authentication |
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
[Alexander Rødseth](https://github.com/xyproto) created
|
||||||
|
[mooseware](https://github.com/xyproto/mooseware), a skeleton for writing a
|
||||||
|
Negroni middleware handler.
|
||||||
|
|
||||||
|
## Live code reload?
|
||||||
|
|
||||||
|
[gin](https://github.com/codegangsta/gin) and
|
||||||
|
[fresh](https://github.com/pilu/fresh) both live reload negroni apps.
|
||||||
|
|
||||||
|
## Essential Reading for Beginners of Go & Negroni
|
||||||
|
|
||||||
|
* [Using a Context to pass information from middleware to end handler](http://elithrar.github.io/article/map-string-interface/)
|
||||||
|
* [Understanding middleware](https://mattstauffer.co/blog/laravel-5.0-middleware-filter-style)
|
||||||
|
|
||||||
|
## About
|
||||||
|
|
||||||
|
Negroni is obsessively designed by none other than the [Code
|
||||||
|
Gangsta](https://codegangsta.io/)
|
||||||
|
|
||||||
|
[Gorilla Mux]: https://github.com/gorilla/mux
|
||||||
|
[`http.FileSystem`]: https://godoc.org/net/http#FileSystem
|
@ -0,0 +1,25 @@
|
|||||||
|
// Package negroni is an idiomatic approach to web middleware in Go. It is tiny, non-intrusive, and encourages use of net/http Handlers.
|
||||||
|
//
|
||||||
|
// If you like the idea of Martini, but you think it contains too much magic, then Negroni is a great fit.
|
||||||
|
//
|
||||||
|
// For a full guide visit http://github.com/urfave/negroni
|
||||||
|
//
|
||||||
|
// package main
|
||||||
|
//
|
||||||
|
// import (
|
||||||
|
// "github.com/urfave/negroni"
|
||||||
|
// "net/http"
|
||||||
|
// "fmt"
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// func main() {
|
||||||
|
// mux := http.NewServeMux()
|
||||||
|
// mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
// fmt.Fprintf(w, "Welcome to the home page!")
|
||||||
|
// })
|
||||||
|
//
|
||||||
|
// n := negroni.Classic()
|
||||||
|
// n.UseHandler(mux)
|
||||||
|
// n.Run(":3000")
|
||||||
|
// }
|
||||||
|
package negroni
|
@ -0,0 +1,35 @@
|
|||||||
|
package negroni
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ALogger interface
|
||||||
|
type ALogger interface {
|
||||||
|
Println(v ...interface{})
|
||||||
|
Printf(format string, v ...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logger is a middleware handler that logs the request as it goes in and the response as it goes out.
|
||||||
|
type Logger struct {
|
||||||
|
// ALogger implements just enough log.Logger interface to be compatible with other implementations
|
||||||
|
ALogger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLogger returns a new Logger instance
|
||||||
|
func NewLogger() *Logger {
|
||||||
|
return &Logger{log.New(os.Stdout, "[negroni] ", 0)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||||
|
start := time.Now()
|
||||||
|
l.Printf("Started %s %s", r.Method, r.URL.Path)
|
||||||
|
|
||||||
|
next(rw, r)
|
||||||
|
|
||||||
|
res := rw.(ResponseWriter)
|
||||||
|
l.Printf("Completed %v %s in %v", res.Status(), http.StatusText(res.Status()), time.Since(start))
|
||||||
|
}
|
@ -0,0 +1,133 @@
|
|||||||
|
package negroni
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handler handler is an interface that objects can implement to be registered to serve as middleware
|
||||||
|
// in the Negroni middleware stack.
|
||||||
|
// ServeHTTP should yield to the next middleware in the chain by invoking the next http.HandlerFunc
|
||||||
|
// passed in.
|
||||||
|
//
|
||||||
|
// If the Handler writes to the ResponseWriter, the next http.HandlerFunc should not be invoked.
|
||||||
|
type Handler interface {
|
||||||
|
ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandlerFunc is an adapter to allow the use of ordinary functions as Negroni handlers.
|
||||||
|
// If f is a function with the appropriate signature, HandlerFunc(f) is a Handler object that calls f.
|
||||||
|
type HandlerFunc func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc)
|
||||||
|
|
||||||
|
func (h HandlerFunc) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||||
|
h(rw, r, next)
|
||||||
|
}
|
||||||
|
|
||||||
|
type middleware struct {
|
||||||
|
handler Handler
|
||||||
|
next *middleware
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m middleware) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
m.handler.ServeHTTP(rw, r, m.next.ServeHTTP)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap converts a http.Handler into a negroni.Handler so it can be used as a Negroni
|
||||||
|
// middleware. The next http.HandlerFunc is automatically called after the Handler
|
||||||
|
// is executed.
|
||||||
|
func Wrap(handler http.Handler) Handler {
|
||||||
|
return HandlerFunc(func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||||
|
handler.ServeHTTP(rw, r)
|
||||||
|
next(rw, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Negroni is a stack of Middleware Handlers that can be invoked as an http.Handler.
|
||||||
|
// Negroni middleware is evaluated in the order that they are added to the stack using
|
||||||
|
// the Use and UseHandler methods.
|
||||||
|
type Negroni struct {
|
||||||
|
middleware middleware
|
||||||
|
handlers []Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new Negroni instance with no middleware preconfigured.
|
||||||
|
func New(handlers ...Handler) *Negroni {
|
||||||
|
return &Negroni{
|
||||||
|
handlers: handlers,
|
||||||
|
middleware: build(handlers),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Classic returns a new Negroni instance with the default middleware already
|
||||||
|
// in the stack.
|
||||||
|
//
|
||||||
|
// Recovery - Panic Recovery Middleware
|
||||||
|
// Logger - Request/Response Logging
|
||||||
|
// Static - Static File Serving
|
||||||
|
func Classic() *Negroni {
|
||||||
|
return New(NewRecovery(), NewLogger(), NewStatic(http.Dir("public")))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Negroni) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
n.middleware.ServeHTTP(NewResponseWriter(rw), r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use adds a Handler onto the middleware stack. Handlers are invoked in the order they are added to a Negroni.
|
||||||
|
func (n *Negroni) Use(handler Handler) {
|
||||||
|
if handler == nil {
|
||||||
|
panic("handler cannot be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
n.handlers = append(n.handlers, handler)
|
||||||
|
n.middleware = build(n.handlers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UseFunc adds a Negroni-style handler function onto the middleware stack.
|
||||||
|
func (n *Negroni) UseFunc(handlerFunc func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc)) {
|
||||||
|
n.Use(HandlerFunc(handlerFunc))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UseHandler adds a http.Handler onto the middleware stack. Handlers are invoked in the order they are added to a Negroni.
|
||||||
|
func (n *Negroni) UseHandler(handler http.Handler) {
|
||||||
|
n.Use(Wrap(handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UseHandler adds a http.HandlerFunc-style handler function onto the middleware stack.
|
||||||
|
func (n *Negroni) UseHandlerFunc(handlerFunc func(rw http.ResponseWriter, r *http.Request)) {
|
||||||
|
n.UseHandler(http.HandlerFunc(handlerFunc))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run is a convenience function that runs the negroni stack as an HTTP
|
||||||
|
// server. The addr string takes the same format as http.ListenAndServe.
|
||||||
|
func (n *Negroni) Run(addr string) {
|
||||||
|
l := log.New(os.Stdout, "[negroni] ", 0)
|
||||||
|
l.Printf("listening on %s", addr)
|
||||||
|
l.Fatal(http.ListenAndServe(addr, n))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a list of all the handlers in the current Negroni middleware chain.
|
||||||
|
func (n *Negroni) Handlers() []Handler {
|
||||||
|
return n.handlers
|
||||||
|
}
|
||||||
|
|
||||||
|
func build(handlers []Handler) middleware {
|
||||||
|
var next middleware
|
||||||
|
|
||||||
|
if len(handlers) == 0 {
|
||||||
|
return voidMiddleware()
|
||||||
|
} else if len(handlers) > 1 {
|
||||||
|
next = build(handlers[1:])
|
||||||
|
} else {
|
||||||
|
next = voidMiddleware()
|
||||||
|
}
|
||||||
|
|
||||||
|
return middleware{handlers[0], &next}
|
||||||
|
}
|
||||||
|
|
||||||
|
func voidMiddleware() middleware {
|
||||||
|
return middleware{
|
||||||
|
HandlerFunc(func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {}),
|
||||||
|
&middleware{},
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
package negroni
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"runtime/debug"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Recovery is a Negroni middleware that recovers from any panics and writes a 500 if there was one.
|
||||||
|
type Recovery struct {
|
||||||
|
Logger ALogger
|
||||||
|
PrintStack bool
|
||||||
|
ErrorHandlerFunc func(interface{})
|
||||||
|
StackAll bool
|
||||||
|
StackSize int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRecovery returns a new instance of Recovery
|
||||||
|
func NewRecovery() *Recovery {
|
||||||
|
return &Recovery{
|
||||||
|
Logger: log.New(os.Stdout, "[negroni] ", 0),
|
||||||
|
PrintStack: true,
|
||||||
|
StackAll: false,
|
||||||
|
StackSize: 1024 * 8,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rec *Recovery) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
if rw.Header().Get("Content-Type") == "" {
|
||||||
|
rw.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||||
|
}
|
||||||
|
|
||||||
|
rw.WriteHeader(http.StatusInternalServerError)
|
||||||
|
|
||||||
|
stack := make([]byte, rec.StackSize)
|
||||||
|
stack = stack[:runtime.Stack(stack, rec.StackAll)]
|
||||||
|
|
||||||
|
f := "PANIC: %s\n%s"
|
||||||
|
rec.Logger.Printf(f, err, stack)
|
||||||
|
|
||||||
|
if rec.PrintStack {
|
||||||
|
fmt.Fprintf(rw, f, err, stack)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rec.ErrorHandlerFunc != nil {
|
||||||
|
func() {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
rec.Logger.Printf("provided ErrorHandlerFunc panic'd: %s, trace:\n%s", err, debug.Stack())
|
||||||
|
rec.Logger.Printf("%s\n", debug.Stack())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
rec.ErrorHandlerFunc(err)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
next(rw, r)
|
||||||
|
}
|
@ -0,0 +1,113 @@
|
|||||||
|
package negroni
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ResponseWriter is a wrapper around http.ResponseWriter that provides extra information about
|
||||||
|
// the response. It is recommended that middleware handlers use this construct to wrap a responsewriter
|
||||||
|
// if the functionality calls for it.
|
||||||
|
type ResponseWriter interface {
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
// Status returns the status code of the response or 200 if the response has
|
||||||
|
// not been written (as this is the default response code in net/http)
|
||||||
|
Status() int
|
||||||
|
// Written returns whether or not the ResponseWriter has been written.
|
||||||
|
Written() bool
|
||||||
|
// Size returns the size of the response body.
|
||||||
|
Size() int
|
||||||
|
// Before allows for a function to be called before the ResponseWriter has been written to. This is
|
||||||
|
// useful for setting headers or any other operations that must happen before a response has been written.
|
||||||
|
Before(func(ResponseWriter))
|
||||||
|
}
|
||||||
|
|
||||||
|
type beforeFunc func(ResponseWriter)
|
||||||
|
|
||||||
|
// NewResponseWriter creates a ResponseWriter that wraps an http.ResponseWriter
|
||||||
|
func NewResponseWriter(rw http.ResponseWriter) ResponseWriter {
|
||||||
|
nrw := &responseWriter{
|
||||||
|
ResponseWriter: rw,
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := rw.(http.CloseNotifier); ok {
|
||||||
|
return &responseWriterCloseNotifer{nrw}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nrw
|
||||||
|
}
|
||||||
|
|
||||||
|
type responseWriter struct {
|
||||||
|
http.ResponseWriter
|
||||||
|
status int
|
||||||
|
size int
|
||||||
|
beforeFuncs []beforeFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *responseWriter) WriteHeader(s int) {
|
||||||
|
rw.status = s
|
||||||
|
rw.callBefore()
|
||||||
|
rw.ResponseWriter.WriteHeader(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *responseWriter) Write(b []byte) (int, error) {
|
||||||
|
if !rw.Written() {
|
||||||
|
// The status will be StatusOK if WriteHeader has not been called yet
|
||||||
|
rw.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
size, err := rw.ResponseWriter.Write(b)
|
||||||
|
rw.size += size
|
||||||
|
return size, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *responseWriter) Status() int {
|
||||||
|
return rw.status
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *responseWriter) Size() int {
|
||||||
|
return rw.size
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *responseWriter) Written() bool {
|
||||||
|
return rw.status != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *responseWriter) Before(before func(ResponseWriter)) {
|
||||||
|
rw.beforeFuncs = append(rw.beforeFuncs, before)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||||
|
hijacker, ok := rw.ResponseWriter.(http.Hijacker)
|
||||||
|
if !ok {
|
||||||
|
return nil, nil, fmt.Errorf("the ResponseWriter doesn't support the Hijacker interface")
|
||||||
|
}
|
||||||
|
return hijacker.Hijack()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *responseWriter) callBefore() {
|
||||||
|
for i := len(rw.beforeFuncs) - 1; i >= 0; i-- {
|
||||||
|
rw.beforeFuncs[i](rw)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *responseWriter) Flush() {
|
||||||
|
flusher, ok := rw.ResponseWriter.(http.Flusher)
|
||||||
|
if ok {
|
||||||
|
if !rw.Written() {
|
||||||
|
// The status will be StatusOK if WriteHeader has not been called yet
|
||||||
|
rw.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
flusher.Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type responseWriterCloseNotifer struct {
|
||||||
|
*responseWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *responseWriterCloseNotifer) CloseNotify() <-chan bool {
|
||||||
|
return rw.ResponseWriter.(http.CloseNotifier).CloseNotify()
|
||||||
|
}
|
@ -0,0 +1,88 @@
|
|||||||
|
package negroni
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Static is a middleware handler that serves static files in the given
|
||||||
|
// directory/filesystem. If the file does not exist on the filesystem, it
|
||||||
|
// passes along to the next middleware in the chain. If you desire "fileserver"
|
||||||
|
// type behavior where it returns a 404 for unfound files, you should consider
|
||||||
|
// using http.FileServer from the Go stdlib.
|
||||||
|
type Static struct {
|
||||||
|
// Dir is the directory to serve static files from
|
||||||
|
Dir http.FileSystem
|
||||||
|
// Prefix is the optional prefix used to serve the static directory content
|
||||||
|
Prefix string
|
||||||
|
// IndexFile defines which file to serve as index if it exists.
|
||||||
|
IndexFile string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStatic returns a new instance of Static
|
||||||
|
func NewStatic(directory http.FileSystem) *Static {
|
||||||
|
return &Static{
|
||||||
|
Dir: directory,
|
||||||
|
Prefix: "",
|
||||||
|
IndexFile: "index.html",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Static) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||||
|
if r.Method != "GET" && r.Method != "HEAD" {
|
||||||
|
next(rw, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
file := r.URL.Path
|
||||||
|
// if we have a prefix, filter requests by stripping the prefix
|
||||||
|
if s.Prefix != "" {
|
||||||
|
if !strings.HasPrefix(file, s.Prefix) {
|
||||||
|
next(rw, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
file = file[len(s.Prefix):]
|
||||||
|
if file != "" && file[0] != '/' {
|
||||||
|
next(rw, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f, err := s.Dir.Open(file)
|
||||||
|
if err != nil {
|
||||||
|
// discard the error?
|
||||||
|
next(rw, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
fi, err := f.Stat()
|
||||||
|
if err != nil {
|
||||||
|
next(rw, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to serve index file
|
||||||
|
if fi.IsDir() {
|
||||||
|
// redirect if missing trailing slash
|
||||||
|
if !strings.HasSuffix(r.URL.Path, "/") {
|
||||||
|
http.Redirect(rw, r, r.URL.Path+"/", http.StatusFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
file = path.Join(file, s.IndexFile)
|
||||||
|
f, err = s.Dir.Open(file)
|
||||||
|
if err != nil {
|
||||||
|
next(rw, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
fi, err = f.Stat()
|
||||||
|
if err != nil || fi.IsDir() {
|
||||||
|
next(rw, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
http.ServeContent(rw, r, file, fi.ModTime(), f)
|
||||||
|
}
|
Loading…
Reference in New Issue