brief introduction
negroni Is a library that focuses on HTTP middleware. It is small, non intrusive, and encourages the use of standard library net/http handlers. This article introduces this library.
Why middleware? There are some logic codes, such as statistics, log, debugging, etc., which are needed in every processor. If you add them one by one, it is too tedious, error prone, and easy to miss. If we want to count processor time consumption, we can add code statistics time consumption in each processor:
package main import ( "fmt" "net/http" "time" ) func index(w http.ResponseWriter, r *http.Request) { start := time.Now() fmt.Fprintf(w, "home page") fmt.Printf("index elasped:%fs", time.Since(start).Seconds()) } func greeting(w http.ResponseWriter, r *http.Request) { start := time.Now() name := r.FormValue("name") if name == "" { name = "world" } fmt.Fprintf(w, "hello %s", name) fmt.Printf("greeting elasped:%fs\n", time.Since(start).Seconds()) } func main() { mux := http.NewServeMux() mux.HandleFunc("/", index) mux.HandleFunc("/greeting", greeting) http.ListenAndServe(":8000", mux) }
But this approach is very inflexible:
- Every time you add a processor, you need to add this part of code. This code has nothing to do with the actual processor logic. It's easy to forget when writing a processor, especially considering all the return paths. Increased coding burden;
- Not conducive to modification: if the statistical code has errors or needs to be adjusted, all processors must be modified;
- Add trouble: to add other statistical logic, you need to change all the processor code.
With the closure of Go language, we can encapsulate the actual processor code into a function, which performs additional logic:
func elasped(h func(w http.ResponseWriter, r *http.Request)) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { path := r.URL.Path start := time.Now() h(w, r) fmt.Printf("path:%s elasped:%fs\n", path, time.Since(start).Seconds()) } } func index(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "home page") } func greeting(w http.ResponseWriter, r *http.Request) { name := r.FormValue("name") if name == "" { name = "world" } fmt.Fprintf(w, "hello %s", name) } func main() { mux := http.NewServeMux() mux.HandleFunc("/", elasped(index)) mux.HandleFunc("/greeting", elasped(greeting)) http.ListenAndServe(":8000", mux) }
We put extra processor independent code in another function. When registering processor functions, we do not directly use the original processor functions, but use the elated function to encapsulate one layer. In fact, functions like elated are middleware. It encapsulates the original processor function and returns a new one. Thus, it is very convenient to insert code before and after the actual processing logic, which is convenient to add, modify and maintain.
Quick use
Install first:
$ go get github.com/urfave/negroni
After use:
package main import ( "fmt" "net/http" "github.com/urfave/negroni" ) func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello World!") }) n := negroni.Classic() n.UseHandler(mux) http.ListenAndServe(":3000", n) }
Negroni is very simple to use, it can be very convenient with http.Handler Use together. negroni.Classic() several common middleware are provided:
- negroni.Recovery : restore panic. If there is panic in the processor code, it will be captured by the middleware, and the program will not exit;
- negroni.Logger : log, recording basic information of request and response;
- negroni.Static : provides static file service in the public directory.
Call n.UseHandler(mux) to apply these middleware to the multiplexer. Run, enter in browser localhost:3000 , view console output:
$ go run main.go [negroni] 2020-06-22T06:48:53+08:00 | 200 | 20.9966ms | localhost:3000 | GET / [negroni] 2020-06-22T06:48:54+08:00 | 200 | 0s | localhost:3000 | GET /favicon.ico
negroni.Handler
Interface negroni.Handler Let's have more flexible control over the execution process of middleware:
type Handler interface { ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) }
Our middleware signature must be func( http.ResponseWriter * http.Request , http.HandlerFunc ), or implement negroni.Handler Interface:
func RandomMiddleware(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { if rand.Int31n(100) <= 50 { fmt.Fprintf(w, "hello from RandomMiddleware") } else { next(w, r) } } func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello World!") }) n := negroni.New() n.Use(negroni.HandlerFunc(RandomMiddleware)) n.UseHandler(mux) http.ListenAndServe(":3000", n) }
In the above code, a random middleware is implemented. Half of the probability is directly returned from the middleware RandomMiddleware, and half of the probability is to execute the actual processor functions. Run the program and refresh the page continuously in the browser localhost:3000 Look at the effect.
Note that actually func (W http.ResponseWriter , r * http.Request , next http.HandlerFunc )It's just a convenient way to write. Used when n.Use is called negroni.HandlerFunc It's a layer of encapsulation, and negroni.HandlerFunc Achieved negroni.Handler Interface:
// src/github.com/urfave/negroni/negroni.go 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) }
net/http, through http.HandlerFunc Package func( http.ResponseWriter * http.Request )So as to realize the interface http.Handler .
negroni.With
If there are multiple middleware, each requires n.Use(), which is a little tedious. Negroni provides a With() method that accepts one or more negroni.Handler Parameter to return a new object:
func Middleware1(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { fmt.Println("Middleware1 begin") next(w, r) fmt.Println("Middleware1 end") } func Middleware2(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { fmt.Println("Middleware2 begin") next(w, r) fmt.Println("Middleware2 end") } func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello World!") }) n := negroni.New() n = n.With( negroni.HandlerFunc(Middleware1), negroni.HandlerFunc(Middleware2), ) n.UseHandler(mux) http.ListenAndServe(":3000", n) }
Run
The Negroni object provides a convenient Run() method to run the server program. It accepts and http.ListenAndServe() same address (Addr) parameter:
func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello World!") }) n := negroni.New() n.UseHandler(mux) n.Run(":3000") }
If no PORT is specified, try using the PORT environment variable. If the PORT environment variable is also not set, the default PORT is used: 8080.
As http.Handler use
Negroni is easy to use in net/http programs, negroni.Negroni Objects can be used directly as http.Handler To the appropriate method:
func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello World!") }) n := negroni.Classic() n.UseHandler(mux) s := &http.Server{ Addr: ":8080", Handler: n, ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, MaxHeaderBytes: 1 << 20, } s.ListenAndServe() }
Built in middleware
negroni has built in some common middleware, which can be used directly.
Static
negroni.Static File service can be provided in the specified directory:
func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "hello world") }) n := negroni.New() n.Use(negroni.NewStatic(http.Dir("./public"))) n.UseHandler(mux) http.ListenAndServe(":3000", n) }
Create the public directory under the program running directory, and then put some files 1.txt, 2.jpg. After the program runs, you can use the browser localhost:3000/1.txt and localhost:3000/2.jpg These files are requested.
In addition, it should be noted that if the corresponding file cannot be found, Static will pass the request to the next middleware or processor function. In the above example, hello world. Enter in browser localhost:3000/none-exist.txt Look at the effect.
Logger
In the quick start, we passed negroni.Classic() this middleware has been used. We can also use it alone, which can record the requested information. We can also call the SetFormat() method to format the log:
func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "hello world") }) n := negroni.New() logger := negroni.NewLogger() logger.SetFormat("[{{.Status}} {{.Duration}}] - {{.Request.UserAgent}}") n.Use(logger) n.UseHandler(mux) http.ListenAndServe(":3000", n) }
The above code sets the log format to [{. Status} {. Duration}] - { Request.UserAgent }}, i.e. response status, elapsed time, and useragent.
Use Chrome browser to request:
[negroni] [200 26.0029ms] - Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36
Recovery
negroni.Recovery panic in subsequent middleware or processor functions can be captured and a response code of 500 can be returned:
func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { panic("internal server error") }) n := negroni.New() n.Use(negroni.NewRecovery()) n.UseHandler(mux) http.ListenAndServe(":3000", n) }
When requested, the stack of panic is displayed in the browser:
This is useful in the development environment, but it cannot be disclosed in the generation environment. At this time, you can set the PrintStack field to false:
func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { panic("internal server error") }) n := negroni.New() r := negroni.NewRecovery() r.PrintStack = false n.Use(r) n.UseHandler(mux) http.ListenAndServe(":3000", n) }
In addition to outputting the panic information in the console and browser, Recovery also provides a hook function, which can report the panic to other services, such as Sentry/Airbrake. Of course, the reported code should be written by yourself 😄.
func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { panic("internal server error") }) n := negroni.New() r := negroni.NewRecovery() r.PanicHandlerFunc = reportToSentry n.Use(r) n.UseHandler(mux) http.ListenAndServe(":3000", n) } func reportToSentry(info *negroni.PanicInformation) { fmt.Println("sent to sentry") }
This function is called when panic occurs after PanicHandlerFunc is set.
We can also set the format of the output, and set the Formatter field as negroni.HTMLPanicFormatter Enables output to be rendered better in the browser:
func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { panic("internal server error") }) n := negroni.New() r := negroni.NewRecovery() r.Formatter = &negroni.HTMLPanicFormatter{} n.Use(r) n.UseHandler(mux) http.ListenAndServe(":3000", n) }
effect:
Third party Middleware
In addition to the built-in middleware, negroni also has many third-party middleware. See here for the complete list: https://github.com/urfave/negroni#third-party-middleware.
We will only introduce one xrequestid , which adds a random Header: X-Request-Id to each request.
To install xrequested:
$ go get github.com/pilu/xrequestid
use:
func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "X-Request-Id is `%s`", r.Header.Get("X-Request-Id")) }) n := negroni.New() n.Use(xrequestid.New(16)) n.UseHandler(mux) n.Run(":3000") }
Add a 16 byte X-Request-Id to each request. The processor function writes the X-Request-Id to the response and displays it in the browser. Run the program, enter in the browser localhost:3000 See the effect.
summary
negroni focuses on middleware and doesn't have many fancy functions. Non intrusiveness makes it easy to use with standard libraries such as net/http and other Web libraries such as gorilla/mux.
If you find a fun and easy-to-use Go language library, you are welcome to submit the issue to GitHub, the daily Go library 😄
reference resources
- negroni GitHub: https://github.com/urfave/negroni
- Go daily GitHub: https://github.com/darjun/go-daily-lib
I
My blog: https://darjun.github.io
Welcome to my WeChat official account, GoUpUp, learn together and make progress together.