dowjgrm6787 2014-02-01 08:57
浏览 22
已采纳

扩展HTTP处理程序

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条回答 默认 最新

  • doulun5683 2014-02-02 13:35
    关注

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

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥20 RL+GNN解决人员排班问题时梯度消失
  • ¥15 统计大规模图中的完全子图问题
  • ¥15 使用LM2596制作降压电路,一个能运行,一个不能
  • ¥60 要数控稳压电源测试数据
  • ¥15 能帮我写下这个编程吗
  • ¥15 ikuai客户端l2tp协议链接报终止15信号和无法将p.p.p6转换为我的l2tp线路
  • ¥15 phython读取excel表格报错 ^7个 SyntaxError: invalid syntax 语句报错
  • ¥20 @microsoft/fetch-event-source 流式响应问题
  • ¥15 ogg dd trandata 报错
  • ¥15 高缺失率数据如何选择填充方式