# 使用 HTTP Middleware 使用 HTTP Middleware 可以有效的对 HTTP 的请求体,响应体进行拦截,做一些自定义的操作。 在 go-zero 中使用 HTTP Middleware 共有两种方式。 1. 全局 Middleware 配置 1. 路由组 Middleware 配置 接下来以快速开始中最基础的 greet 项目做演示。 # 准备工作 首先准备一个最基本的 greet 演示项目。以下代码均由 goctl 生成,未进行编辑。 项目结构如下: ```bash $ tree . ├── etc │ └── greet-api.yaml ├── go.mod ├── go.sum ├── greet.api ├── greet.go └── internal ├── config │ └── config.go ├── handler │ ├── greethandler.go │ └── routes.go ├── logic │ └── greetlogic.go ├── svc │ └── servicecontext.go └── types └── types.go ``` greet/greet.go: ```go package main import ( "flag" "fmt" "greet/internal/config" "greet/internal/handler" "greet/internal/svc" "github.com/tal-tech/go-zero/core/conf" "github.com/tal-tech/go-zero/rest" ) var configFile = flag.String("f", "etc/greet-api.yaml", "the config file") func main() { flag.Parse() var c config.Config conf.MustLoad(*configFile, &c) ctx := svc.NewServiceContext(c) server := rest.MustNewServer(c.RestConf) defer server.Stop() handler.RegisterHandlers(server, ctx) fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port) server.Start() } ``` 我们先在 Greet 方法中加入一行日志输出,用来代表我们的业务处理逻辑 greet/internal/logic/greetlogic.go : ```go package logic import ( "context" "greet/internal/svc" "greet/internal/types" "github.com/tal-tech/go-zero/core/logx" ) type GreetLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext } func NewGreetLogic(ctx context.Context, svcCtx *svc.ServiceContext) GreetLogic { return GreetLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } func (l *GreetLogic) Greet(req types.Request) (*types.Response, error) { // 在这里加入业务逻辑,我们用打印日志来代表业务逻辑 l.Infof("name: %v", req.Name) return &types.Response{}, nil } ``` # 全局 Middleware 配置 首先我们创建一个 Middleware 方法 ```go func middlewareDemoFunc(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { logx.Info("request ... ") next(w, r) logx.Info("reponse ... ") } } ``` 然后将其注册到 go-zero 的 rest 中 ```go func main() { ··· server := rest.MustNewServer(c.RestConf) defer server.Stop() server.Use( middlewareDemoFunc, ) ··· server.Start() } ``` 启动项目,并使用 curl 进行请求,终端输出如下: curl: ```bash $ curl -i http://localhost:8888/from/you HTTP/1.1 200 OK Content-Type: application/json Date: Fri, 23 Oct 2020 07:42:48 GMT Content-Length: 14 {"message":""} ``` server: ```bash $ go run greet.go -f etc/greet-api.yaml Starting server at 0.0.0.0:8888... {"@timestamp":"2020-10-23T15:42:48.113+08","level":"info","content":"request..."} {"@timestamp":"2020-10-23T15:42:48.113+08","level":"info","content":"name: you","trace":"373939c095f9c52f","span":"0"} {"@timestamp":"2020-10-23T15:42:48.113+08","level":"info","content":"reponse..."} {"@timestamp":"2020-10-23T15:42:48.113+08","level":"info","content":"200 - /from/you - [::1]:52864 - curl/7.64.1 - 0.3ms","trace":"373939c095f9c52f","span":"0"} ``` # 路由组 Middleware 配置 路由组 Middleware 只对特定的路由有效,使用路由组 Middleware 只需我们在注册路由时使用 `rest.WithMiddleware()` 或 `rest.WithMiddlewares()` 方法传入我们定义的 Middleware 即可。 goctl 支持生成路由组 Middleware 在 goctl 中可使用以下语法来定义路由组 Middleware greet/greet.api : ```protobuf ··· @server( middleware: GreetMiddleware1, GreetMiddleware2 ) service greet-api { @handler GreetHandler get /from/:name(Request) returns (Response); } ··· ``` **使用 goctl 命令重新生成路由代码** ```bash $ goctl api go -api ./greet.api -dir . etc/greet-api.yaml exists, ignored generation internal/config/config.go exists, ignored generation greet.go exists, ignored generation internal/svc/servicecontext.go exists, ignored generation internal/types/types.go exists, overwrite it? Enter to overwrite or Ctrl-C to cancel... internal/handler/greethandler.go exists, ignored generation internal/handler/routes.go exists, overwrite it? Enter to overwrite or Ctrl-C to cancel... internal/logic/greetlogic.go exists, ignored generation Done. ``` 具体代码如下: 没有使用 Middleware 的路由组 greet/internal/handler/routes.go: ```go ··· func RegisterHandlers(engine *rest.Server, serverCtx *svc.ServiceContext) { engine.AddRoutes( []rest.Route{ { Method: http.MethodGet, Path: "/from/:name", Handler: greetHandler(serverCtx), }, }, ) } ··· ``` 加入 Middleware 的路由组 greet/internal/handler/routes.go: ```go ··· func RegisterHandlers(engine *rest.Server, serverCtx *svc.ServiceContext) { engine.AddRoutes( rest.WithMiddlewares( []rest.Middleware{serverCtx.GreetMiddleware1, serverCtx.GreetMiddleware2}, []rest.Route{ { Method: http.MethodGet, Path: "/from/:name", Handler: greetHandler(serverCtx), }, }..., ), ) ) ··· ``` **注意:** goctl 命令行工具不会覆盖 greet/internal/svc/servicecontext.go 文件,而使用 goctl 定义的 Middleware 方法在 greet/internal/svc/servicecontext.go 中,因此我们需要手动修改 greet/internal/svc/servicecontext.go 文件,添加我们所需要的 `serverCtx.GreetMiddleware1`, `serverCtx.GreetMiddleware2` 方法。在首次生成代码的新项目中,由于不存在 internal/svc/servicecontext.go 文件,goctl 会生成 Middleware 方法的声明,具体实现需要自己来完成。 这里的 `serverCtx.GreetMiddleware1`, `serverCtx.GreetMiddleware2` 需要我们自己进行实现。 我们简单实现一下。 ```go package svc import ( "greet/internal/config" "net/http" "github.com/tal-tech/go-zero/core/logx" "github.com/tal-tech/go-zero/rest" ) type ServiceContext struct { Config config.Config GreetMiddleware1 rest.Middleware GreetMiddleware2 rest.Middleware } func NewServiceContext(c config.Config) *ServiceContext { return &ServiceContext{ Config: c, GreetMiddleware1: greetMiddleware1, GreetMiddleware2: greetMiddleware2, } } func greetMiddleware1(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { logx.Info("greetMiddleware1 request ... ") next(w, r) logx.Info("greetMiddleware1 reponse ... ") } } func greetMiddleware2(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { logx.Info("greetMiddleware2 request ... ") next(w, r) logx.Info("greetMiddleware2 reponse ... ") } } ``` 启动服务并使用 curl 进行请求,可以观察到如下输出: curl: ```bash $ curl -i http://localhost:8888/from/you HTTP/1.1 200 OK Content-Type: application/json Date: Fri, 23 Oct 2020 08:14:27 GMT Content-Length: 14 {"message":""} ``` server: ```bash $ go run greet.go -f etc/greet-api.yaml Starting server at 0.0.0.0:8888... {"@timestamp":"2020-10-23T16:14:27.655+08","level":"info","content":"greetMiddleware1 request ... "} {"@timestamp":"2020-10-23T16:14:27.655+08","level":"info","content":"greetMiddleware2 request ... "} {"@timestamp":"2020-10-23T16:14:27.655+08","level":"info","content":"name: you","trace":"102aee4a4a935210","span":"0"} {"@timestamp":"2020-10-23T16:14:27.655+08","level":"info","content":"greetMiddleware2 reponse ... "} {"@timestamp":"2020-10-23T16:14:27.655+08","level":"info","content":"greetMiddleware1 reponse ... "} {"@timestamp":"2020-10-23T16:14:27.655+08","level":"info","content":"200 - /from/you - [::1]:53701 - curl/7.64.1 - 0.3ms","trace":"102aee4a4a935210","span":"0"} ```