首页 > 代码库 > http.Handler 与Go的错误处理

http.Handler 与Go的错误处理

原文地址

?? 在之前我写过一篇关于通过使用http.HandlerFunc来实现一个定制handler类型用来避免一些平常的错误的文章。func MyHandler(w http.ResponseWriter, r *http.Request)的签名经常可以看到。这是一个有用的通用的包含一些基本功能的handler类型,但是和其他事情一样,也有一些不足:

  • 当你想要在一个handler中停止处理的时候,必须记得显示的调用一个return。这个在当你想要跑出一个从定向(301、302),未找到(404)或者服务器端错误(500)的状态的时候是很平常的。如果不这么做可能会引起一些微妙的错误(函数会继续执行),因为函数不需要一个返回值,编译器也不会警告你。
  • 不容易传递额外的参数(例如,数据库连接池,配置)。你最后不得不实用一系列的全局变量(不算太坏,但是跟踪他们会导致难以扩展)或者将他们存到请求上下文中,然后每次都从其取出。这样做很笨重。
  • 一直在不断的重复同样的语句。想要记录数据库包返回的错误?既可以再每个查询方法中调用log.Printf,也可以再每个handler中返回错误。如果你的handler可以返回给一个集中记录错误的函数,并且跑出一个500的错误就更好了。

?? 我以前的方法中使用了func(http.ResponseWriter, *http.Request)签名。这已经被证明是一个简介的方式,但是有个奇怪的地方是,返回一个无错误的状态,例如,200,302,303往往是多余的,因为要么你已经在其他地方设置了,要么就是没用的。例如:

func SomeHandler(w http.ResponseWriter, r *http.Request) (int, error) {    db, err := someDBcall()    if err != nil {        // This makes sense.        return 500, err    }    if user.LoggedIn {        http.Redirect(w, r, "/dashboard", 302)        // Superfluous! Our http.Redirect function handles the 302, not         // our return value (which is effectively ignored).        return 302, nil    }}

看起来还行,但是我们可以做的更好

一些区别

?? 那么我们应该如何改进它?我们先列出代码:

package handler// Error represents a handler error. It provides methods for a HTTP status // code and embeds the built-in error interface.type Error interface {    error    Status() int}// StatusError represents an error with an associated HTTP status code.type StatusError struct {    Code int    Err  error}// Allows StatusError to satisfy the error interface.func (se StatusError) Error() string {    return se.Err.Error()}// Returns our HTTP status code.func (se StatusError) Status() int {    return se.Code}// A (simple) example of our application-wide configuration.type Env struct {    DB   *sql.DB    Port string    Host string}// The Handler struct that takes a configured Env and a function matching// our useful signature.type Handler struct {    *Env    H func(e *Env, w http.ResponseWriter, r *http.Request) error}// ServeHTTP allows our Handler type to satisfy http.Handler.func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {    err := h.H(h.Env, w, r)    if err != nil {        switch e := err.(type) {        case Error:            // We can retrieve the status here and write out a specific            // HTTP status code.            log.Printf("HTTP %d - %s", e.Status(), e)            http.Error(w, e.Error(), e.Status())        default:            // Any error types we don‘t specifically look out for default            // to serving a HTTP 500            http.Error(w, http.StatusText(http.StatusInternalServerError),                http.StatusInternalServerError)        }    }}

上面的代码不言自明,但是要说明一下一些突出的观点:

  • 我们自定义了一个Error类型(接口),他内嵌了Go的内建的error接口,同时提供了一个Status() int方法。
  • 我们提供了一个简单的StatusError类型(结构体),它满足handler.Error的接口。StatusError接受一个HTTP的状态码(int类型),一个可以让我们包装错误用来记录或者查询的error类型。
  • 我们的ServeHTTP方法包好了一个”e := err.(type)”的类型断言,它可以测试我们需要处理的错误,允许我们处理那些特别的错误。在这个例子中,他是只是一个handler.Error类型。其他的错误,例如其他包中的错误想net.Error,或者其他我们定义的额外的错误,如果想要检查,同样也可以检查。

?? 如果我们不想捕捉那些错误,那么default将会默认捕捉到。记住一点,ServeHTTP可以使我们的Handler类型满足http.Handler接口,这样他就可以在任何使用http.Handler的地方使用了,例如Go的net/http包或者所有的其他的第三方框架。这样使得定制的handler更有用,他们用起来很灵活。
?? 注意 net 包处理事情很简单。它又一个net.Error的接口,内嵌了内建的error接口。一些具体的类型实现了它。函数返回的具体类型跟错误的类型相同(DNS错误,解析错误等)。再datastore 包中定义的DBError有一个Query() string 方法,可以很好的解释。

所有示例

?? 它最后是什么样子的?我们是否可以将其分到不同的包中?

package handlerimport (    "net/http")// Error represents a handler error. It provides methods for a HTTP status // code and embeds the built-in error interface.type Error interface {    error    Status() int}// StatusError represents an error with an associated HTTP status code.type StatusError struct {    Code int    Err  error}// Allows StatusError to satisfy the error interface.func (se StatusError) Error() string {    return se.Err.Error()}// Returns our HTTP status code.func (se StatusError) Status() int {    return se.Code}// A (simple) example of our application-wide configuration.type Env struct {    DB   *sql.DB    Port string    Host string}// The Handler struct that takes a configured Env and a function matching// our useful signature.type Handler struct {    *Env    H func(e *Env, w http.ResponseWriter, r *http.Request) error}// ServeHTTP allows our Handler type to satisfy http.Handler.func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {    err := h.H(h.Env, w, r)    if err != nil {        switch e := err.(type) {        case Error:            // We can retrieve the status here and write out a specific            // HTTP status code.            log.Printf("HTTP %d - %s", e.Status(), e)            http.Error(w, e.Error(), e.Status())        default:            // Any error types we don‘t specifically look out for default            // to serving a HTTP 500            http.Error(w, http.StatusText(http.StatusInternalServerError),                http.StatusInternalServerError)        }    }}func GetIndex(env *Env, w http.ResponseWriter, r *http.Request) error {    users, err := env.DB.GetAllUsers()    if err != nil {        // We return a status error here, which conveniently wraps the error        // returned from our DB queries. We can clearly define which errors         // are worth raising a HTTP 500 over vs. which might just be a HTTP         // 404, 403 or 401 (as appropriate). It‘s also clear where our         // handler should stop processing by returning early.        return StatusError{500, err}    }    fmt.Fprintf(w, "%+v", users)    return nil}
main包:
package mainimport (    "net/http"    "github.com/you/somepkg/handler")func main() {    db, err := sql.Open("connectionstringhere")    if err != nil {          log.Fatal(err)    }    // Initialise our app-wide environment with the services/info we need.    env := &handler.Env{        DB: db,        Port: os.Getenv("PORT"),        Host: os.Getenv("HOST"),        // We might also have a custom log.Logger, our         // template instance, and a config struct as fields         // in our Env struct.    }    // Note that we‘re using http.Handle, not http.HandleFunc. The     // latter only accepts the http.HandlerFunc type, which is not     // what we have here.    http.Handle("/", handler.Handler{env, handler.GetIndex})    // Logs the error if ListenAndServe fails.    log.Fatal(http.ListenAndServe(":8000", nil))}
在实际使用时,会将handler和Env放入不同的包中,这里只是为了简单放在了同一个包中。

http.Handler 与Go的错误处理