Avoid checking date header of web requests by properly applying generic handlers (#2914)
parent
73982c8cb6
commit
df59967f59
@ -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,443 @@ |
||||
# 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 | |
||||
|
||||
## 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,99 @@ |
||||
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 { |
||||
return &responseWriter{ |
||||
ResponseWriter: rw, |
||||
} |
||||
} |
||||
|
||||
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) CloseNotify() <-chan bool { |
||||
return rw.ResponseWriter.(http.CloseNotifier).CloseNotify() |
||||
} |
||||
|
||||
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 { |
||||
flusher.Flush() |
||||
} |
||||
} |
@ -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