当构建 web 应用程序,可能对所有的请求会共享一些功能。例如我们每一个请求都会写入日志。
- 打印 http 请求或返回的日志
- 压缩 http 请求的返回值
- 将 http 请求头保持一致
- 实现安全以及权限验证
Middleware-web.jpg
在 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()
}
next.ServeHTTP(w,r)indexabouthttp.Handle("/",logginHandler(indexHandler))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()
}
middlewareFirstmiddlewareSecondmiddlewareSecond
我们通过学习自己动手写了两个拦截器,输出日志和简单权限校验。这也是我们中间件通常要做的事。其实很多时候我们不需要自己做过多事,想喝咖啡我们还有必要去种、烘焙、研磨吗?直接麦当劳了。
"github.com/gorilla/handlers"
func index(w http.ResponseWriter, r *http.Request){
log.Println("Execute index handler")
fmt.Fprintf(w,"welcome")
}
func about(w http.ResponseWriter, r *http.Request){
log.Println("Execute about handler")
fmt.Fprintf(w,"about")
}
我们先定义两个路由器,分别在浏览器输出文本。
logFile, err := os.OpenFile("server.log",os.O_WRONLY|os.O_CREATE|os.O_APPEND,0666)
// logFile, err := os.OpenFile("server.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
server.log
http.Handle("/", handlers.LoggingHandler(logFile, handlers.CompressHandler(indexHandler)))
http.Handle("/about", handlers.LoggingHandler(logFile, handlers.CompressHandler( aboutHandler)))
handlersLoggingHandlerCompressHandler
完整代码如下
package main
import(
"fmt"
"log"
"net/http"
"os"
"github.com/gorilla/handlers"
)
func index(w http.ResponseWriter, r *http.Request){
log.Println("Execute index handler")
fmt.Fprintf(w,"welcome")
}
func about(w http.ResponseWriter, r *http.Request){
log.Println("Execute about handler")
fmt.Fprintf(w,"about")
}
func iconHandler(w http.ResponseWriter, r *http.Request) {
}
func main(){
http.HandleFunc("/favicon.icon",iconHandler)
indexHandler := http.HandlerFunc(index)
aboutHandler := http.HandlerFunc(about)
logFile, err := os.OpenFile("server.log",os.O_WRONLY|os.O_CREATE|os.O_APPEND,0666)
// logFile, err := os.OpenFile("server.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
if err != nil{
panic(err)
}
http.Handle("/", handlers.LoggingHandler(logFile, handlers.CompressHandler(indexHandler)))
http.Handle("/about", handlers.LoggingHandler(logFile, handlers.CompressHandler( aboutHandler)))
server := &http.Server{
Addr: ":8080",
}
log.Println("Listening...")
server.ListenAndServe()
}
::1 - - [24/Apr/2019:05:39:31 +0800] "GET / HTTP/1.1" 200 31
::1 - - [24/Apr/2019:05:39:31 +0800] "GET /favicon.ico HTTP/1.1" 200 31
::1 - - [24/Apr/2019:05:39:38 +0800] "GET /about HTTP/1.1" 200 31
::1 - - [24/Apr/2019:05:39:38 +0800] "GET /favicon.ico HTTP/1.1" 200 31
github.com/justinas/alice
commonHandlers := alice.New(loggingHandler, handlers.CompressHandler)
对我们日志进行整合封装。
package main
import(
"io"
"log"
"net/http"
"os"
"github.com/justinas/alice"
"github.com/gorilla/handlers"
)
func loggingHandler(next http.Handler) http.Handler{
logFile, err := os.OpenFile("server.log",os.O_WRONLY|os.O_CREATE|os.O_APPEND,0666)
if err != nil{
panic(err)
}
return handlers.LoggingHandler(logFile,next)
}
func index(w http.ResponseWriter, r *http.Request){
w.Header().Set(
"Content-Type",
"text/html",
)
io.WriteString(
w,
`<doctype html>
<html>
<head>
<title>Index</title>
</head>
<body>
Hello Zidea!
</body>
</html>`, )
}
func about(w http.ResponseWriter, r *http.Request){
w.Header().Set(
"Content-Type",
"text/html",
)
io.WriteString(
w,
`<doctype html>
<html>
<head>
<title>About</title>
</head>
<body>
about HTTP Middleware
</body>
</html>`, )
}
func iconHandler(w http.ResponseWriter, r *http.Request) {
// http.ServeFile(w, r, "./favicon.ico")
}
func main(){
http.HandleFunc("/favicon.ico", iconHandler)
indexHandler := http.HandlerFunc(index)
aboutHandler := http.HandlerFunc(about)
commonHandlers := alice.New(loggingHandler, handlers.CompressHandler)
http.Handle("/", commonHandlers.ThenFunc(indexHandler))
http.Handle("/about", commonHandlers.ThenFunc(aboutHandler))
server := &http.Server{
Addr: ":8080",
}
log.Println("Listening...")
server.ListenAndServe()
}
::1 - - [24/Apr/2019:05:53:01 +0800] "GET /about HTTP/1.1" 200 110
::1 - - [24/Apr/2019:05:53:05 +0800] "GET /welcome HTTP/1.1" 200 103