扩展HTTP处理程序

时间:2014-02-01 08:57:52

标签: go

我的Go网络应用程序中有一个相当快速且肮脏的错误处理程序,它会引发HTTP错误,记录响应的重要部分并提供错误模板。我想删除重复,我在处理程序中写了几次这样的事情:

err := doSomething()
if err != nil {
    serverError(w, r, err, code)
}

我已经很好地阅读了Error Handling and Go文章,其中包括定义一个返回错误类型/结构的自定义HTTP处理程序类型(甚至返回int,而不是返回错误):

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)

        }
    }
}

但我不确定如何保留现有的中间件功能/包装器,允许我链接这样的中间件: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) {
                // do something
                // h.ServeHTTP(w,r)
            }
     }

从我可以看出,它就像下面的东西(它不起作用)。我收到错误cannot use m(h) (type http.Handler) as type appHandler in assignment: need type assertion。如何在保持中间件本身“按原样”的同时解决这个问题?

您可以在此处找到(简化的)游乐场示例: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)
    })
}

我缺少什么,有更好的方法吗?

1 个答案:

答案 0 :(得分:10)

由于#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))