将参数传递给中间件的下一个处理函数。

huangapple go评论86阅读模式
英文:

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)
   ...
}

huangapple
  • 本文由 发表于 2022年2月22日 07:47:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/71214328.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定