巧妙的洋葱模型
在 golang 中有强大的标准库支持,我们可以很轻松的写出一个 web 服务。如下所示:
func main() {
http.HandleFunc("/", hack)
http.ListenAndServe(":8080", nil)
}
func hack(w http.ResponseWriter, r *http.Request) {
status := struct {
Status string
}{
Status: "OK",
}
json.NewEncoder(w).Encode(status)
}
// Output:
// {"Status":"OK"}
可如果需要在已有的逻辑不改变的基础上加一些逻辑,比如说统一打印一些日志或者统一捕获 panic,要如何处理呢?这些逻辑就是抽象出来的中间件,可能很多个 Handler 共享这些中间件。这时,就不能使用默认的 mux 来注册路由了,而需要使用指定的 mux,在注册路由前,在原有的逻辑中添加这些中间件的处理函数后再进行注册,就可以完美解决这个问题。
func main() {
a := &App{}
a.Handle(http.MethodGet, "/", hack)
http.ListenAndServe(":8080", &a.mux)
}
type Handler func(ctx context.Context, w http.ResponseWriter, r *http.Request) error
type App struct {
mux http.ServeMux
}
func (a *App) Handle(method string, path string, handler Handler) {
h := func(w http.ResponseWriter, r *http.Request) {
// 任意代码
if err := handler(r.Context(), w, r); err != nil {
return
}
// 任意代码
}
pattern := method + " " + path
a.mux.HandleFunc(pattern, h)
}
func hack(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
status := struct {
Status string
}{
Status: "OK",
}
return json.NewEncoder(w).Encode(status)
}
上面只是加了一个中间件进行处理,当有多个中间件时需要一层一层执行,执行流程看起来就像一个洋葱一样。
type Middleware func(Handler) Handler
执行过程
func Logger(handler Handler) Handler {
h := func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
// 打日志
err := handler(r.Context(), w, r)
// 打日志
return err
}
return h
}
可当有这样一个打日志的中间件,在 Middleware 和 Handler 这两个函数签名都不能改变的情况下要如何把 logger 传入进去呢?当然我们可以将这个函数改成一个方法,在 receiver 中保有一个 logger,不过更优雅简洁的做法是通过闭包的手段将 logger 传入。
func Logger(log *log.Logger) Middleware {
m := func(handler Handler) Handler {
h := func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
// 打日志
err := handler(r.Context(), w, r)
// 打日志
return err
}
return h
}
return m
}
完整代码
package main
import (
"context"
"encoding/json"
"log"
"net/http"
"time"
)
func main() {
a := &App{}
a.Handle(http.MethodGet, "/", hack, Logger(log.Default()))
http.ListenAndServe(":8080", &a.mux)
}
func hack(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
status := struct {
Status string
Time string
}{
Status: "OK",
Time: time.Now().String(),
}
return json.NewEncoder(w).Encode(status)
}
type Handler func(ctx context.Context, w http.ResponseWriter, r *http.Request) error
type App struct {
mux http.ServeMux
mw []Middleware
}
func (a *App) Handle(method string, path string, handler Handler, mw ...Middleware) {
handler = wrapMiddleware(mw, handler)
handler = wrapMiddleware(a.mw, handler)
a.handle(method, path, handler)
}
func (a *App) handle(method string, path string, handler Handler) {
h := func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if err := handler(ctx, w, r); err != nil {
// if validateShutdown(err) {
// a.SignalShutdown()
// return
// }
}
}
pattern := method + " " + path
a.mux.HandleFunc(pattern, h)
}
type Middleware func(Handler) Handler
func wrapMiddleware(mw []Middleware, handler Handler) Handler {
for i := len(mw) - 1; i >= 0; i-- {
mwFunc := mw[i]
if mwFunc != nil {
handler = mwFunc(handler)
}
}
return handler
}
func Logger(log *log.Logger) Middleware {
m := func(handler Handler) Handler {
h := func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
// 打日志
log.Println("start...")
err := handler(r.Context(), w, r)
// 打日志
log.Println("end...")
return err
}
return h
}
return m
}
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。