英文:
Writing Per-Handler Middleware
问题
我正在寻找一种方法,将一些重复的逻辑从处理程序中提取出来,并将其放入每个处理程序的中间件中,具体包括CSRF检查、检查现有会话值(例如用于身份验证或预览页面)等。
我已经阅读了一些关于这方面的文章,但很多示例都集中在每个服务器中间件(包装http.Handler
)上:我有一小组需要使用中间件的处理程序。我的其他大部分页面不需要中间件,因此如果我可以避免为这些请求检查会话等,那就更好了。
到目前为止,我的中间件通常看起来像这样:
func checkCSRF(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// 获取会话,根据HTTP方法检查/验证/创建令牌等
// 在检查失败时返回HTTP 403
// 否则调用包装的处理程序 h(w, r)
}
}
然而,在许多情况下,我希望将一个变量传递给包装的处理程序:一个生成的CSRF令牌以传递给模板,或者一个包含表单数据的结构体。一个中间件在用户访问/preview/
URL之前检查会话中是否存在一些保存的表单数据,否则会将其重定向(因为他们没有要预览的内容)。
我希望将该结构体传递给包装的处理程序,以避免在中间件中重复编写刚刚写在中间件中的session.Get/type断言/error检查逻辑。
我可以这样写:
type CSRFHandlerFunc func(w http.ResponseWriter, r *http.Request, t string)
然后像这样编写中间件:
func csrfCheck(h CSRFHandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// 获取会话,根据HTTP方法检查/验证/创建令牌等
// 在检查失败时返回HTTP 403
// 否则调用包装的处理程序并传递令牌 h(w, r, token)
}
但是这引发了一些问题:
- 这是一种实现每个处理程序中间件并传递每个请求变量的明智方式吗?
- 在测试之前(无法访问我的开发机器!),如果我需要用多个中间件包装一个处理程序,我可以假设只需
r.HandleFunc("/path/preview/", checkCSRF(checkExisting(previewHandler)))
吗?我在这里遇到的问题是中间件现在紧密耦合:包装的中间件现在需要接收并传递来自外部中间件的变量。这使得扩展http.HandlerFunc
变得更加棘手/复杂。 - **gorilla/context**在这里是否更合适,并且是否允许我避免编写2-3个自定义处理程序类型(或通用处理程序类型)?如果可以的话,我该如何使用它?或者我可以实现自己的“上下文”映射(并遇到并发访问问题)吗?
在可能的情况下,我试图避免陷入“不要陷入编写库”的陷阱,但是中间件是我在项目的后期可能会添加/构建的东西,我希望第一次就“搞定”。
对此的一些建议将不胜感激。到目前为止,Go在编写Web应用程序方面非常棒,但是在这个阶段,相关示例并不多,因此我在一定程度上依赖于Stack Overflow。
英文:
I'm looking to pull some repetitive logic out of my handlers and put it into some per-handler middleware: specifically things like CSRF checks, checking for an existing session value (i.e. for auth, or for preview pages), etc.
I've read a few articles on this, but many examples focus on a per-server middleware (wrapping http.Handler
): I have a smaller set of handlers that need the middleware. Most of my other pages do not, and therefore if I can avoid checking sessions/etc. for those requests the better.
My middleware, so far, typically looks something like this:
func checkCSRF(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// get the session, check/validate/create the token based on HTTP method, etc.
// return HTTP 403 on a failed check
// else invoke the wrapped handler h(w, r)
}
}
However, in many cases I want to pass a variable to the wrapped handler: a generated CSRF token to pass to the template, or a struct that contains form data—one piece of middleware checks the session for the presence of some saved form data before the user hits a /preview/
URL, else it redirects them away (since they have nothing to preview!).
I'd like to pass that struct along to the wrapped handler to save having to duplicate the session.Get/type assertion/error checking logic I just wrote in the middleware.
I could write something like:
type CSRFHandlerFunc func(w http.ResponseWriter, r *http.Request, t string)
... and then write the middleware like so:
func csrfCheck(h CSRFHandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// get the session, check/validate/create the/a token based on HTTP method, etc.
// return HTTP 403 on a failed check
// else invoke the wrapped handler and pass the token h(w, r, token)
}
... but that raises a few questions:
- Is this a sensible way to implement per-handler middleware and pass per-request variables?
- Prior to testing this (don't have access to my dev machine!), if I need to wrap a handler with multiple pieces of middleware, I assume I can just
r.HandleFunc("/path/preview/", checkCSRF(checkExisting(previewHandler)))
? The issue I'm seeing here is that the middleware is now tightly coupled: the wrapped middleware now needs to receive and then pass on the variable from the outer middleware. This makes extending http.HandlerFunc trickier/more convoluted. - Would gorilla/context fit better here and allow me to avoid writing 2-3 custom handler types (or a generic handler type) — and if so, how would I make use of it? Or could I implement my own "context" map (and run into issues with concurrent access?).
Where possible I'm trying to avoid falling for the "don't get caught writing a library" trap, but middleware is something that I'm likely to add/build on later in the project's life, and I'd like to "get it right" the first time around.
Some guidance on this would be much appreciated. Go's been great so far for writing a web application, but there's not a ton of examples around at this stage in its life and I'm therefore leaning on SO a little.
答案1
得分: 7
如果我正确理解你的问题,你正在寻找一种方便的方法来传递额外的参数给你的中间件,对吗?
现在,重要的是定义这些参数是什么。它们可以是中间件的一些配置值 - 这些值可以在构建Handler类型时设置。而不是NewMyMiddleware(MyHandler)
,你可以使用NewMyMiddleware(MyHandler, "参数")
,这没有问题。
但在你的情况下,似乎你想传递每个请求的参数,比如CSRF令牌。将这些参数传递给处理函数会修改其签名,并偏离标准的Handler[Func]
接口。在这种情况下,你对中间件更紧密耦合的看法是正确的。
你在某种程度上提到了解决方案 - 上下文映射在我看来是一个可行的工具。自己编写一个并不是那么困难 - 你基本上需要一个map[*http.Request]interface{}
和一个RWMutex
来实现安全的并发访问。不过,简单地使用gorilla/context
应该就足够了 - 它看起来是一个(相对)成熟、编写良好且具有良好API的包。
无耻的自荐:如果你正在处理CSRF检查,为什么不试试我的nosurf包呢?
英文:
If I understood your question correctly, you're looking for a convenient way to pass additional parameters to your middleware, right?
Now, it's important to define what those parameters are. They could be some configuration values for your middleware – those can be set when the Handler type is being constructed). Instead of NewMyMiddleware(MyHandler)
, you do NewMyMiddleware(MyHandler, "parameter")
, no problem here.
But in your case it seems like you want to pass per-request parameters, like a CSRF token. Passing those into the handler function would modify its signature and it would deviate from the standard Handler[Func]
interface. You're right about middleware being more tightly coupled in this case.
You kind of mentioned the solution yourself – a context map is, in my opinion, a viable tool for this. It's not that hard to write one yourself – you basically need a map[*http.Request]interface{}
and an RWMutex
for safe concurrent access. Still, simply using gorilla/context
should suffice – it seems like a (relatively) mature, well-written package with a nice API.
Shameless plug: if you're dealing with CSRF checks, why not try out my nosurf package?
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论