英文:
Extending HTTP Handlers
问题
我在我的Go Web应用程序中有一个相当快速且简单的错误处理程序,它会引发HTTP错误,记录响应的重要部分并提供错误模板。我想要消除在处理程序中写类似以下代码的重复部分:
err := doSomething()
if err != nil {
serverError(w, r, err, code)
}
我已经仔细阅读了《错误处理和Go》的文章,该文章介绍了定义一个自定义的HTTP处理程序类型,该类型返回一个错误类型/结构(甚至返回int,err):
type appHandler func(http.ResponseWriter, *http.Request) *appError
type appError struct {
code int
Err error
}
// 确保appHandler满足http.Handler接口
func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if err := fn(w, r); err != nil {
switch err.Code {
case http.StatusNotFound:
http.NotFound(w, r)
case http.StatusInternalServerError:
http.Error(w, "message", http.StatusInternalServerError)
default:
http.Error(w, "message", err.Code)
}
}
}
但是,我不确定如何保留现有的中间件功能/包装器,以便我可以像这样链接中间件:r.HandleFunc("/route", use(myHandler, middleware1, middleware2))
,其中use
和我的中间件如下所示:
func use(h http.HandlerFunc, middleware ...func(http.HandlerFunc) http.HandlerFunc) http.HandlerFunc {
for _, m := range middleware {
h = m(h)
}
return h
}
func AntiCSRF(h http.HandlerFunc) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 做一些事情
// h.ServeHTTP(w,r)
}
}
根据我的理解,可能是以下代码(不起作用)。我得到一个错误,说cannot use m(h) (type http.Handler) as type appHandler in assignment: need type assertion
。在保持中间件本身“不变”的同时,如何解决这个问题?
你可以在这里找到一个(简化的)playground示例:http://play.golang.org/p/Cmmo-wK2Af
r.Handle("/route", use(myHandler, middleware.NoCache)) // 虚构的示例!
func use(h myHandlerType?, middleware ...func(http.Handler) http.Handler) http.Handler {
for _, m := range middleware {
h = m(h)
}
return h
}
func myHandler(w http.ResponseWriter, r *http.Request) *appError {
// 极端虚构的示例
name := "Matt"
_, err := fmt.Fprintf(w, "Hi %s", name)
if err != nil {
return &appError{500, err}
}
return nil
}
func contrivedMiddleware(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "max-age=0, private, must-revalidate")
w.Header().Set("X-Accel-Expires", "0")
h.ServeHTTP(w, r)
})
}
我漏掉了什么?有没有更好的方法来做到这一点?
英文:
I have a fairly quick-and-dirty error handler in my Go web app that raises a HTTP error, logs the important parts of the response and serves an error template. I'd like to remove the repetition where I'm writing something like this a few too many times in a handler:
err := doSomething()
if err != nil {
serverError(w, r, err, code)
}
I've had a good read of the Error Handling and Go article which covers defining a custom HTTP handler type that returns a error type/struct like this (or even returning int, err instead):
type appHandler func(http.ResponseWriter, *http.Request) *appError
type appError struct {
code int
Err error
}
// Ensures appHandler satisfies the http.Handler interface
func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if err := fn(w, r); err != nil {
switch err.Code {
case http.StatusNotFound:
http.NotFound(w, r)
case http.StatusInternalServerError:
http.Error(w, "message", http.StatusInternalServerError)
default:
http.Error(w, "message", err.Code)
}
}
}
But I'm not sure how to retain my existing middleware functionality/wrapper that allows me to chain middleware like this: r.HandleFunc("/route", use(myHandler, middleware1, middleware2))
where use
and my middleware look like this:
func use(h http.HandlerFunc, middleware ...func(http.HandlerFunc) http.HandlerFunc) http.HandlerFunc {
for _, m := range middleware {
h = m(h)
}
return h
}
func AntiCSRF(h http.HandlerFunc) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// do something
// h.ServeHTTP(w,r)
}
}
From what I can figure, it'd be something like the below (which doesn't work). I'm getting an error saying cannot use m(h) (type http.Handler) as type appHandler in assignment: need type assertion
. How do I resolve this whilst still keeping the middleware itself "as is"?
You can find a (simplified) playground example here: http://play.golang.org/p/Cmmo-wK2Af
r.Handle("/route", use(myHandler, middleware.NoCache)) // Contrived example!
func use(h myHandlerType?, middleware ...func(http.Handler) http.Handler) http.Handler {
for _, m := range middleware {
h = m(h)
}
return h
}
func myHandler(w http.ResponseWriter, r *http.Request) *appError {
// Extremely contrived example
name := "Matt"
_, err := fmt.Fprintf(w, "Hi %s", name)
if err != nil {
return &appError{500, err}
}
return nil
}
func contrivedMiddleware(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "max-age=0, private, must-revalidate")
w.Header().Set("X-Accel-Expires", "0")
h.ServeHTTP(w, r)
})
}
What am I missing and is there a better way to do this?
答案1
得分: 12
我已经通过#go-nuts上的'cronos'的帮助解决了这个问题。
这个解决方案允许我使用自定义的处理程序类型、链式中间件,并避免了重复包装处理程序(即appHandler(myHandler)、中间件...)的问题:
type appHandler func(http.ResponseWriter, *http.Request) *appError
type appError struct {
Code int
Error error
}
func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if e := fn(w, r); e != nil {
switch e.Code {
case http.StatusNotFound:
notFound(w, r)
case http.StatusInternalServerError:
serverError(w, r, e.Error, e.Code)
default:
serverError(w, r, e.Error, e.Code)
}
}
}
func use(h appHandler, middleware ...func(http.Handler) http.Handler) http.Handler {
var res http.Handler = h
for _, m := range middleware {
res = m(res)
}
return res
}
func someMiddleware(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "max-age=0, private, must-revalidate")
w.Header().Set("X-Accel-Expires", "0")
h.ServeHTTP(w, r)
})
}
func myHandler(w http.ResponseWriter, r *http.Request) *appError {
err := doSomething()
if err != nil {
return &appError{500, err}
}
// render your template, etc.
return nil
}
路由看起来像这样:r.Handle("/route", use(myHandler, someMiddleware))
你可以根据需要修改appHandler
以返回任何你想要的内容,向appError
添加额外的字段等等。如果你想将中间件应用于所有路由,你的中间件也可以包装你的路由器,例如http.Handle("/", someMiddleware(r))
。
英文:
I've managed to solve this thanks to the help of 'cronos' on #go-nuts.
The solution allows me to use a custom handler type, chain middleware and avoid the repetition of having to wrap handlers (i.e. appHandler(myHandler)), middleware...):
type appHandler func(http.ResponseWriter, *http.Request) *appError
type appError struct {
Code int
Error error
}
func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if e := fn(w, r); e != nil {
switch e.Code {
case http.StatusNotFound:
notFound(w, r)
case http.StatusInternalServerError:
serverError(w, r, e.Error, e.Code)
default:
serverError(w, r, e.Error, e.Code)
}
}
}
func use(h appHandler, middleware ...func(http.Handler) http.Handler) http.Handler {
var res http.Handler = h
for _, m := range middleware {
res = m(res)
}
return res
}
func someMiddleware(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "max-age=0, private, must-revalidate")
w.Header().Set("X-Accel-Expires", "0")
h.ServeHTTP(w, r)
})
}
func myHandler(w http.ResponseWriter, r *http.Request) *appError {
err := doSomething()
if err != nil {
return &appError{500, err}
}
// render your template, etc.
return nil
}
With routes looking like this: r.Handle("/route", use(myHandler, someMiddleware))
You can obviously modify appHandler
to return whatever you like, add additional fields to appError
and so on. Your middleware is also able to wrap your router if you want to apply it to all routes - i.e. http.Handle("/", someMiddleware(r))
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论