中间件声明

概述

在 go-zero 中,我们通过 api 语言来声明 HTTP 服务,然后通过 goctl 生成 HTTP 服务代码,在之前我们系统性的介绍了 API 规范

在 HTTP 开发中,中间件是非常常见的需求,比如我们需要对请求进行鉴权,或者对请求进行日志记录,这些都是非常常见的需求。

中间件声明

假设我们有一个用户服务,我们需要将 user-agent 信息存入到 context 信息中,然后在 logic 层根据 user-agent 做业务处理,我们可以通过 api 语言来声明中间件, 在 api 语言中,我们可以通过 middleware 关键字来声明中间件,中间件的声明格式如下:

  1. syntax = "v1"
  2. type UserInfoRequest {
  3. Id int64 `path:"id"`
  4. }
  5. type UserInfoResponse {
  6. Id int64 `json:"id"`
  7. Name string `json:"name"`
  8. Age int32 `json:"age"`
  9. }
  10. @server(
  11. // 通过 middileware 关键字声明中间件,多个中间件以英文逗号分割,如 UserAgentMiddleware,LogMiddleware
  12. middleware: UserAgentMiddleware
  13. )
  14. service user {
  15. @handler userinfo
  16. get /user/info/:id (UserInfoRequest) returns (UserInfoResponse)
  17. }

在上面的例子中,我们声明了一个中间件 UserAgentMiddleware,然后在 @server 中通过 middileware 关键字来声明中间件。 我们来看一下生成的中间件代码:

目录结构

  1. .
  2. ├── etc
  3. └── user.yaml
  4. ├── internal
  5. ├── config
  6. └── config.go
  7. ├── handler
  8. ├── routes.go
  9. └── userinfohandler.go
  10. ├── logic
  11. └── userinfologic.go
  12. ├── middleware # 中间件目录
  13. └── useragentmiddleware.go
  14. ├── svc
  15. └── servicecontext.go
  16. └── types
  17. └── types.go
  18. ├── user.api
  19. └── user.go
  20. 8 directories, 10 files

中间件代码(未填充逻辑)

  • useragentmiddleware.go
  • servicecontext.go
  • routes.go
  1. package middleware
  2. import "net/http"
  3. type UserAgentMiddleware struct {
  4. }
  5. func NewUserAgentMiddleware() *UserAgentMiddleware {
  6. return &UserAgentMiddleware{}
  7. }
  8. func (m *UserAgentMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
  9. return func(w http.ResponseWriter, r *http.Request) {
  10. // TODO generate middleware implement function, delete after code implementation
  11. // Passthrough to next handler if need
  12. next(w, r)
  13. }
  14. }
  1. package svc
  2. import (
  3. "demo/user/internal/config"
  4. "demo/user/internal/middleware"
  5. "github.com/zeromicro/go-zero/rest"
  6. )
  7. type ServiceContext struct {
  8. Config config.Config
  9. UserAgentMiddleware rest.Middleware
  10. }
  11. func NewServiceContext(c config.Config) *ServiceContext {
  12. return &ServiceContext{
  13. Config: c,
  14. UserAgentMiddleware: middleware.NewUserAgentMiddleware().Handle,
  15. }
  16. }
  1. // Code generated by goctl. DO NOT EDIT.
  2. package handler
  3. import (
  4. "net/http"
  5. "demo/user/internal/svc"
  6. "github.com/zeromicro/go-zero/rest"
  7. )
  8. func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
  9. server.AddRoutes(
  10. rest.WithMiddlewares(
  11. []rest.Middleware{serverCtx.UserAgentMiddleware},
  12. []rest.Route{
  13. {
  14. Method: http.MethodGet,
  15. Path: "/user/info/:id",
  16. Handler: userinfoHandler(serverCtx),
  17. },
  18. }...,
  19. ),
  20. )
  21. }

你可以看到,中间件的代码是通过 goctl 自动生成的,中间件的代码是一个结构体,结构体中有一个 Handle 方法,这个方法是中间件的核心方法,这个方法接收一个 http.HandlerFunc 类型的参数,然后返回一个 http.HandlerFunc 类型的参数,这个方法的作用是对请求进行处理,然后将请求传递给下一个中间件或者 handler。

你可以在 Handle 方法中对请求进行处理,比如鉴权,日志记录等等,然后将请求传递给下一个中间件或者 handler。

如上需求例子,我们可以在中间件中将 header 中的 User-Agent 信息存到 context中,中间件实现如下:

  1. package middleware
  2. import (
  3. "context"
  4. "net/http"
  5. )
  6. type UserAgentMiddleware struct {
  7. }
  8. func NewUserAgentMiddleware() *UserAgentMiddleware {
  9. return &UserAgentMiddleware{}
  10. }
  11. func (m *UserAgentMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
  12. return func(w http.ResponseWriter, r *http.Request) {
  13. val := r.Header.Get("User-Agent")
  14. reqCtx := r.Context()
  15. ctx := context.WithValue(reqCtx, "User-Agent", val)
  16. newReq := r.WithContext(ctx)
  17. next(w, newReq)
  18. }
  19. }