英文:
Pass argument down to next handle func from middleware
问题
我想为整个 API 创建一个可重用的验证中间件。在这里,验证是通过 govalidator 进行的,所以我只需要传递验证规则和一个 DTO(数据传输对象),将请求映射到其中。
这是 ValidationMiddleware
函数的代码:
func ValidationMiddleware(next http.HandlerFunc, validationRules govalidator.MapData, dto interface{}) http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
utils.SetResponseHeaders(rw)
opts := govalidator.Options{
Request: r,
Data: &dto,
Rules: validationRules,
RequiredDefault: true,
}
v := govalidator.New(opts)
err := v.ValidateJSON()
if err != nil {
fmt.Println("Middleware found an error")
err := utils.ErrorWrapper{
StatusCode: 400,
Type: "Bad Request",
Message: err,
}
err.ThrowError(rw)
return
}
next(rw, r)
}
}
你想要将这个结构体传递给下一个 HandleFunc
,以避免尝试第二次解析请求体。你可以通过在 ValidationMiddleware
函数中创建一个闭包来实现。在闭包中,你可以访问 dto
变量,并将其传递给下一个处理程序。修改 ValidationMiddleware
函数如下:
func ValidationMiddleware(next http.HandlerFunc, validationRules govalidator.MapData, dto interface{}) http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
// ... 省略其他代码 ...
// 将 dto 传递给下一个处理程序
r = r.WithContext(context.WithValue(r.Context(), "dto", dto))
next(rw, r)
}
}
然后,在下一个处理程序中,你可以通过从请求上下文中获取 dto
值来访问它。例如:
func addItem(rw http.ResponseWriter, r *http.Request) {
// 从请求上下文中获取 dto
dto := r.Context().Value("dto").(dto.CreateItem)
// 使用 dto 进行处理
// ... 省略其他代码 ...
}
这样,你就可以在下一个处理程序中使用传递的结构体 dto
了。
英文:
I would like to make a reusable middleware for validation throughout my API. Here, validation is done through govalidator, so I just need to pass the validation rules and a DTO where the request is mapped into.
func ValidationMiddleware(next http.HandlerFunc, validationRules govalidator.MapData, dto interface{}) http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
utils.SetResponseHeaders(rw)
opts := govalidator.Options{
Request: r,
Data: &dto,
Rules: validationRules,
RequiredDefault: true,
}
v := govalidator.New(opts)
err := v.ValidateJSON()
if err != nil {
fmt.Println("Middleware found an error")
err := utils.ErrorWrapper{
StatusCode: 400,
Type: "Bad Request",
Message: err,
}
err.ThrowError(rw)
return
}
next(rw, r)
}
}
This is how the HandleFunc
looks like:
var rules govalidator.MapData = govalidator.MapData{
"name": []string{"required"},
"description": []string{"required"},
"price": []string{"required", "float"},
}
func RegisterItemsRouter(router *mux.Router) {
itemsRouter := router.PathPrefix("/inventory").Subrouter()
itemsRouter.HandleFunc("/", middleware.ValidationMiddleware(addItem, rules, dto.CreateItem{
Name: "",
Description: "",
Price: govalidator.Float64{},
})).Methods("POST")
}
If no errors are found, govalidator parses the request body into the dto struct, so I would like to pass this down into the next handler and avoid trying to parse the body a second time.
How can I pass this struct down to the next HandleFunc?
答案1
得分: 1
从代码中看,似乎你将请求传递给了验证器选项,验证器从中读取并验证请求体。这会引发几个问题:HTTP请求只能读取一次,所以要么验证器以某种方式返回未解组的对象,要么你必须在验证之前读取请求体。
采用第二种解决方案,首先你的验证器必须知道要解组的对象的类型:
func ValidationMiddleware(next http.HandlerFunc, factory func() interface{}, validationRules govalidator.MapData, dto interface{}) http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
newInstance:=factory()
data, err:=ioutil.ReadAll(r.Body)
json.Unmarshal(data,newInstance)
// 在这里验证newInstance
r.WithContext(context.WithValue(r.Context(),"data",newInstance))
next(wr,r)
}
}
其中,func factory
是一个创建将用于请求解组的对象实例的函数:
func RegisterItemsRouter(router *mux.Router) {
itemsRouter := router.PathPrefix("/inventory").Subrouter()
itemsRouter.HandleFunc("/", middleware.ValidationMiddleware(addItem, func() interface{} { return &TheStruct{}}, rules, dto.CreateItem{
Name: "",
Description: "",
Price: govalidator.Float64{},
})).Methods("POST")
}
这样,当有新的请求到来时,将创建一个新的 TheStruct
实例并进行解组,然后进行验证。如果验证通过,它将被放置在上下文中,以便下一个中间件或处理程序可以获取它:
func handler(wr http.ResponseWriter,r *http.Request) {
item:=r.Context().Value("data").(*TheStruct)
...
}
英文:
From the code, it appears like you pass the request to the validator options, and the validator reads and validates the body from that. This poses several problems: An HTTP request can only be read once, so either the validator somehow returns you that unmarshaled object, or you have to read the body before validating it.
Going for the second solution, the first thing is your validator has to know the type of the object it has to unmarshal to:
func ValidationMiddleware(next http.HandlerFunc, factory func() interface{}, validationRules govalidator.MapData, dto interface{}) http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
newInstance:=factory()
data, err:=ioutil.ReadAll(r.Body)
json.Unmarshal(data,newInstance)
// Validate newInstance here
r.WithContext(context.WithValue(r.Context(),"data",newInstance))
next(wr,r)
}
}
Where the func factory
is a function that creates an instance of the object that will be unmarshaled for a request:
func RegisterItemsRouter(router *mux.Router) {
itemsRouter := router.PathPrefix("/inventory").Subrouter()
itemsRouter.HandleFunc("/", middleware.ValidationMiddleware(addItem, func() interface{} { return &TheStruct{}}, rules, dto.CreateItem{
Name: "",
Description: "",
Price: govalidator.Float64{},
})).Methods("POST")
}
This way, when a new request comes, a new instance of TheStruct
will be created and unmarshaled, then validated. If the validation is ok, it will be placed into the context, so the next middleware or the handler can get it:
func handler(wr http.ResponseWriter,r *http.Request) {
item:=r.Context().Value("data").(*TheStruct)
...
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论