当构建 web 应用程序,可能对所有的请求会共享一些功能。例如我们每一个请求都会写入日志。
- 打印 http 请求或返回的日志
- 压缩 http 请求的返回值
- 将 http 请求头保持一致
-
实现安全以及权限验证
在 go 语言 net/http 标准库中提供了中间件类似的函数 StripPrefix 和 TimeoutHandler。
如何自己编写一个中间件呢?答案是
- 这个函数接收一个 http.Handler 作为参数,目的是可使用其他中间件或应用处理器,调用其他中间件或应用的方法就是调用其 HttpServe 的方法。
- 并且将 http.Handler 作为返回值,这样其他中间件可以使用 http.Handler 作为输入的参数,达到链式调用。
func middlewareHandler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 在执行应用处理器前编译中间件的逻辑
next.ServeHTTP(w, r)
// 在执行应用处理器后将会执行的编译中间件的逻辑
})
}
从上面示例我们可以看出 go 语言是支持高阶函数,通过向下传递 http.Handler 来实现中间件。
package main
import (
"fmt"
"log"
"net/http"
"time"
)
func logginHandler(next http.Handler) http.Handler{
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
start := time.Now()
log.Printf("Started %s %s",r.Method, r.URL.Path)
next.ServeHTTP(w,r)
log.Printf("Completed %s in %v", r.URL.Path, time.Since(start))
})
}
func index(w http.ResponseWriter,r *http.Request ){
log.Println("Executing index handler")
fmt.Fprintf(w,"welcome!")
}
func about(w http.ResponseWriter, r *http.Request){
log.Println("Executing about handler")
fmt.Fprintf(w,"Go Middleware")
}
func iconHandler(w http.ResponseWriter, r *http.Request){
}
func main() {
http.HandleFunc("/favicon.ico", iconHandler)
indexHandler := http.HandlerFunc(index)
aboutHandler := http.HandlerFunc(about)
http.Handle("/",logginHandler(indexHandler))
http.Handle("/about",logginHandler(aboutHandler))
server := &http.Server{
Addr:":8080",
}
log.Println("Listening...")
server.ListenAndServe()
}
- 上面的代码关键是 logginHandler 这个日志处理器,作为中间件会拦截请求,进行处理也就是输出日志,通过
next.ServeHTTP(w,r)
然后将请求向下进行传递 - 这里创建两个简单路由处理器
index
和about
。都是向客户端输出不同的文本。 -
http.Handle("/",logginHandler(indexHandler))
通过 logginHandler 函数进行包裹,从容我们日志中间件可以拦截到发起的请求进行一些业务逻辑,业务逻辑可以位于next.ServeHTTP(w,r)
之前或之后。通过下面的输出我们可以清晰可见代码执行的顺序。
Listening...
Started GET /
Executing index handler
Completed / in 41.129µs
Started GET /
Executing index handler
Completed / in 50.475µs
Started GET /about
Executing about handler
Completed /about in 49.483µs
许多书都提供代码示例,个人建议是不要 copy 过来运行看效果,自己手 coding 就可以发现一些问题,同时可以思考为什么要这样编写代码。
通过上面示例我们了解如何写一个简单中间件,这个个人看来和 nodejs 实现中间件没有什么差别。实际开发中我们往往会有多个中间件来执行业务,那么这些中间件执行顺序也是我们值得考虑的问题。
package main
import(
"fmt"
"log"
"net/http"
)
func middlewareFirst(next http.Handler) http.Handler{
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
log.Println("MiddlewareFirst - before Handler")
next.ServeHTTP(w,r)
log.Println("MiddlewareFirst - after Handler")
})
}
func middlewareSecond(next http.Handler) http.Handler{
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
log.Println("MiddlewareSecond - before Handler")
if r.URL.Path == "/message"{
if r.URL.Query().Get("password") == "123"{
log.Println("Authorized to system...")
next.ServeHTTP(w,r)
}else{
log.Println("Failed to authorize to the system")
return
}
}else{
next.ServeHTTP(w,r)
}
log.Println("MiddlewareScond - after Handler")
})
}
func index(w http.ResponseWriter, r *http.Request){
log.Println("Executed index Handler")
fmt.Fprintf(w,"welcome")
}
func message(w http.ResponseWriter, r *http.Request){
log.Println("Executed message Handler")
fmt.Fprintf(w,"message...")
}
func iconHandler(w http.ResponseWriter, r *http.Request){
}
func main() {
http.HandleFunc("/favicon",iconHandler)
indexHandler := http.HandlerFunc(index)
messageHandler := http.HandlerFunc(message)
http.Handle("/",middlewareFirst(middlewareSecond(indexHandler)))
http.Handle("/message",middlewareFirst(middlewareSecond(messageHandler)))
server := &http.Server{
Addr:":8080",
}
log.Println("Listen...")
server.ListenAndServe()
}
在上面代码中其实也没有什么特别之处,就是我们创建了两个middlewareFirst
和middlewareSecond
两个中间件并且有一定先后顺序,然后在 middlewareSecond
中写了一个对访问权限校验的逻辑。大家可以尝试地去运行一下。
有疑问加站长微信联系(非本文作者)