Leapcell: The Best of Serverless Web Hosting
Introduction to the Negroni Library
Negroni is a library that focuses on HTTP middleware. It is small in size and non-intrusive, and it encourages the use of handlers from the standard library net/http
. This article will provide a detailed introduction to this library.
I. Why Use Middleware
During the development process, there are some common logical codes, such as statistics, logging, debugging, etc., and these logics may be required in each handler. If these codes are added to each handler one by one, it is not only cumbersome but also prone to errors and omissions.
Take the example of counting the time consumption of a handler. If the code for counting the time consumption is added to each handler, the example is as follows:
package main
import (
"fmt"
"net/http"
"time"
)
func index(w http.ResponseWriter, r *http.Request) {
start := time.Now()
fmt.Fprintf(w, "leapcell 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)
}
However, this approach has many disadvantages:
- Lack of flexibility: Every time a new handler is added, this part of the code for counting time consumption needs to be added, and these codes have no direct relationship with the actual handler logic.
- Prone to omission: It is easy to forget to add such codes when writing handlers, especially when considering all possible return paths, which increases the coding burden.
- Not conducive to modification: If there is an error in the statistical code or it needs to be adjusted, all the handlers involved must be modified.
- Difficult to add new logic: If other statistical logics need to be added, the code of all handlers also needs to be changed.
By utilizing the closure feature of the Go language, the actual handler code can be encapsulated in a function, and additional logic can be executed in this function. It is shown as follows:
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, "leapcell 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)
}
In this example, the additional code that has nothing to do with the handler is placed in the elasped
function. When registering the handler function, instead of directly using the original handler function, it is encapsulated with the elasped
function. In fact, a function like elasped
is a middleware. It encapsulates the original handler function and returns a new handler function, which is convenient for inserting code before and after the actual processing logic, and is beneficial for addition, modification, and maintenance.
II. Quick Start with Negroni
(I) Installation
Execute the following command to install the Negroni library:
$ go get github.com/urfave/negroni
(II) Usage Example
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(":8080", n)
}
Negroni is relatively easy to use and can work conveniently with http.Handler
. negroni.Classic()
provides several commonly used middleware:
-
negroni.Recovery: It is used to recover from
panic
that occurs during the program's running process. If apanic
occurs in the handler code, this middleware will catch the exception and prevent the program from exiting. - negroni.Logger: It is responsible for recording basic information about requests and responses, implementing the logging function.
-
negroni.Static: It can provide static file services in the
public
directory.
By calling n.UseHandler(mux)
, these middleware are applied to the multiplexer mux
. After running the program, enter localhost:3000
in the browser, and you can see the following similar output in the console:
[negroni] 2025-03-22T18:48:53+08:00 | 200 | 10.9966ms | localhost:8080 | GET /
III. In-depth Features of Negroni
(I) negroni.Handler Interface
The negroni.Handler
interface provides more flexible control over the execution process of middleware. The interface is defined as follows:
type Handler interface {
ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc)
}
The signature of the written middleware must be func(http.ResponseWriter, *http.Request, http.HandlerFunc)
, or implement the negroni.Handler
interface. For example:
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(":8080", n)
}
The above code implements a random middleware. There is a 50% probability that the response will be directly returned from the RandomMiddleware
middleware, and there is a 50% probability that the actual handler function will be executed. After running the program, refresh localhost:8080
in the browser continuously to observe the effect.
In fact, func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc)
is a convenient way of writing. When calling n.Use
, it is encapsulated with negroni.HandlerFunc
, and negroni.HandlerFunc
implements the negroni.Handler
interface. The following is the relevant code:
// 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)
}
Similarly, in net/http
, func(http.ResponseWriter, *http.Request)
is also encapsulated with http.HandlerFunc
to implement the http.Handler
interface.
(II) negroni.With Method
When there are multiple middleware, it is cumbersome to add them one by one using the n.Use()
method. Negroni provides the With()
method, which accepts one or more negroni.Handler
parameters and returns a new object. The example is as follows:
func Middleware1(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
fmt.Println("Middleware A begin")
next(w, r)
fmt.Println("Middleware A end")
}
func Middleware2(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
fmt.Println("Middleware B begin")
next(w, r)
fmt.Println("Middleware B 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(":8080", n)
}
(III) Run Method
The Negroni object provides the Run()
method, which is used to conveniently run the server program. This method accepts the same address (Addr) parameter as http.ListenAndServe()
. The example is as follows:
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(":8080")
}
If the port is not specified, the Run()
method will try to use the PORT
environment variable. If the PORT
environment variable is not set either, the default port :8080
will be used.
(IV) Using as http.Handler
Negroni can be easily used in net/http
programs, and the negroni.Negroni
object can be directly passed as an http.Handler
to the corresponding method. The example is as follows:
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()
}
IV. Built-in Middleware
(I) Static
The negroni.Static
middleware can provide file services in the specified directory. The example code is as follows:
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(":8080", n)
}
Create a public
directory in the program's running directory and put some files (such as 1.txt
, 2.jpg
) in it. After the program runs, you can access localhost:8080/1.txt
and localhost:8080/2.jpg
in the browser to request these files. It should be particularly noted that if the corresponding file cannot be found, the Static
middleware will pass the request to the next middleware or handler function. For example, when entering localhost:3000/none - exist.txt
in the browser, you will see the response of hello world
.
(II) Logger
In the "Quick Start" section, this middleware has been used through negroni.Classic()
. It can also be used alone to record request information, and the SetFormat()
method can be called to set the log format. The example is as follows:
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(":8080", n)
}
The above code sets the log format to [{{.Status}} {{.Duration}}] - {{.Request.UserAgent}}
, that is, it records the response status, time consumption, and UserAgent
.
V. Third-party Middleware
In addition to the built-in middleware, Negroni also has many third-party middleware. For the complete list, please refer to: https://github.com/urfave/negroni?tab=readme-ov-file#third-party-middleware.
VI. Conclusion
Negroni focuses on middleware functionality and does not have too many cumbersome and rarely used fancy features. Its non-intrusive design feature enables it to work seamlessly with the standard library net/http
and other Web libraries (such as gorilla/mux
). This feature provides great convenience for developers when building HTTP services. Developers can flexibly select and combine middleware according to actual needs to achieve various functions such as logging, error handling, and request statistics, without the need for large-scale changes to the existing code architecture.
Overall, Negroni is a very valuable library in Go language HTTP service development. For those who pursue efficient, flexible, and easy-to-maintain Web application development, Negroni is undoubtedly an excellent tool worthy of in-depth understanding and widespread application.
Leapcell: The Best of Serverless Web Hosting
Finally, I would like to introduce the best platform for deploying Go services: Leapcell
🚀 Build with Your Favorite Language
Develop effortlessly in JavaScript, Python, Go, or Rust.
🌍 Deploy Unlimited Projects for Free
Only pay for what you use—no requests, no charges.
⚡ Pay-as-You-Go, No Hidden Costs
No idle fees, just seamless scalability.
🔹 Follow us on Twitter: @LeapcellHQ
Top comments (0)