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 handler

import (
    "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 main

import (
    "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放入不同的包中,這里只是為了簡單放在了同一個包中。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM