中间件声明
概述
在 go-zero 中,我们通过 api 语言来声明 HTTP 服务,然后通过 goctl 生成 HTTP 服务代码,在之前我们系统性的介绍了 API 规范。
在 HTTP 开发中,中间件是非常常见的需求,比如我们需要对请求进行鉴权,或者对请求进行日志记录,这些都是非常常见的需求。
中间件声明
假设我们有一个用户服务,我们需要将 user-agent 信息存入到 context 信息中,然后在 logic 层根据 user-agent 做业务处理,我们可以通过 api 语言来声明中间件, 在 api 语言中,我们可以通过 middleware
关键字来声明中间件,中间件的声明格式如下:
syntax = "v1"
type UserInfoRequest {
Id int64 `path:"id"`
}
type UserInfoResponse {
Id int64 `json:"id"`
Name string `json:"name"`
Age int32 `json:"age"`
}
@server(
// 通过 middileware 关键字声明中间件,多个中间件以英文逗号分割,如 UserAgentMiddleware,LogMiddleware
middleware: UserAgentMiddleware
)
service user {
@handler userinfo
get /user/info/:id (UserInfoRequest) returns (UserInfoResponse)
}
在上面的例子中,我们声明了一个中间件 UserAgentMiddleware
,然后在 @server
中通过 middileware
关键字来声明中间件。 我们来看一下生成的中间件代码:
目录结构
.
├── etc
│ └── user.yaml
├── internal
│ ├── config
│ │ └── config.go
│ ├── handler
│ │ ├── routes.go
│ │ └── userinfohandler.go
│ ├── logic
│ │ └── userinfologic.go
│ ├── middleware # 中间件目录
│ │ └── useragentmiddleware.go
│ ├── svc
│ │ └── servicecontext.go
│ └── types
│ └── types.go
├── user.api
└── user.go
8 directories, 10 files
中间件代码(未填充逻辑)
- useragentmiddleware.go
- servicecontext.go
- routes.go
package middleware
import "net/http"
type UserAgentMiddleware struct {
}
func NewUserAgentMiddleware() *UserAgentMiddleware {
return &UserAgentMiddleware{}
}
func (m *UserAgentMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// TODO generate middleware implement function, delete after code implementation
// Passthrough to next handler if need
next(w, r)
}
}
package svc
import (
"demo/user/internal/config"
"demo/user/internal/middleware"
"github.com/zeromicro/go-zero/rest"
)
type ServiceContext struct {
Config config.Config
UserAgentMiddleware rest.Middleware
}
func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
Config: c,
UserAgentMiddleware: middleware.NewUserAgentMiddleware().Handle,
}
}
// Code generated by goctl. DO NOT EDIT.
package handler
import (
"net/http"
"demo/user/internal/svc"
"github.com/zeromicro/go-zero/rest"
)
func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.UserAgentMiddleware},
[]rest.Route{
{
Method: http.MethodGet,
Path: "/user/info/:id",
Handler: userinfoHandler(serverCtx),
},
}...,
),
)
}
你可以看到,中间件的代码是通过 goctl 自动生成的,中间件的代码是一个结构体,结构体中有一个 Handle
方法,这个方法是中间件的核心方法,这个方法接收一个 http.HandlerFunc
类型的参数,然后返回一个 http.HandlerFunc
类型的参数,这个方法的作用是对请求进行处理,然后将请求传递给下一个中间件或者 handler。
你可以在 Handle
方法中对请求进行处理,比如鉴权,日志记录等等,然后将请求传递给下一个中间件或者 handler。
如上需求例子,我们可以在中间件中将 header 中的 User-Agent
信息存到 context中,中间件实现如下:
package middleware
import (
"context"
"net/http"
)
type UserAgentMiddleware struct {
}
func NewUserAgentMiddleware() *UserAgentMiddleware {
return &UserAgentMiddleware{}
}
func (m *UserAgentMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
val := r.Header.Get("User-Agent")
reqCtx := r.Context()
ctx := context.WithValue(reqCtx, "User-Agent", val)
newReq := r.WithContext(ctx)
next(w, newReq)
}
}