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放入不同的包中,这里只是为了简单放在了同一个包中。
http.Handler 与Go的错误处理的更多相关文章
- Linux环境下段错误的产生原因及调试方法小结(转)
最近在Linux环境下做C语言项目,由于是在一个原有项目基础之上进行二次开发,而且 项目工程庞大复杂,出现了不少问题,其中遇到最多.花费时间最长的问题就是著名的“段错误”(Segmentation F ...
- 【转】 svn 错误 以及 中文翻译
直接Ctrl+F 搜索你要找的错 # # Simplified Chinese translation for subversion package # This file is distribute ...
- Linux环境下段错误的产生原因及调试方法小结
转载自http://www.cnblogs.com/panfeng412/archive/2011/11/06/2237857.html 最近在Linux环境下做C语言项目,由于是在一个原有项目基础之 ...
- Linux下的段错误(Segmentation fault)
Linux开发中常见段错误问题原因分析 1 使用非法的内存地址(指针),包括使用未经初始化及已经释放的指针.不存在的地址.受系统保护的地址,只读的地址等,这一类也是最常见和最好解决的段错误问题,使用G ...
- 【转】【调试技巧】Linux环境下段错误的产生原因及调试方法小结
本文转自:http://www.cnblogs.com/panfeng412/archive/2011/11/06/segmentation-fault-in-linux.html 1. 段错误是什么 ...
- linux中段错误的处理
在 Linux环境下做C语言项目,由于是在一个原有项目基础之上进行二次开发,而且项目工程庞大复杂,出现了不少问题,其中遇到最多.花费时间最长的问题就是著名的“段错误”(Segmentation Fau ...
- Linux环境下段错误的产生原因及调试方法小结【转】
转自:http://www.cnblogs.com/panfeng412/archive/2011/11/06/2237857.html 最近在Linux环境下做C语言项目,由于是在一个原有项目基础之 ...
- Linux段错误及GDB Coredump调试方法
最近在Linux环境下做C语言项目,由于是在一个原有项目基础之上进行二次开发,而且项目工程庞大复杂,出现了不少问题,其中遇到最多.花费时间最长的问题就是著名的“段错误”(Segmentation Fa ...
- SVN错误信息汇总
svn错误信息 # # Simplified Chinese translation for subversion package # This file is distributed under ...
随机推荐
- HDU 4750 Count The Pairs(并查集)
题目链接 没有发现那个点,无奈. #include <cstdio> #include <cstring> #include <cmath> #include &l ...
- C语言break和continue
break和continue C语言中有有两种结束循环的关键字:break和continue #include <stdio.h> #include <stdlib.h> in ...
- JS 弹出模态窗口解决方案
最近在项目中使用弹出模态窗口,功能要求: (1)模态窗口选择项目 (2)支持选择返回事件处理 在IE中有showModalDialog 方法,可以很好的解决该问题,但是在Chrome中和FF中就有问题 ...
- ORACLE 查看锁
SELECT object_name, machine, s.sid, s.serial# FROM gv$locked_object l, dba_objects o, gv$session s W ...
- Odoo attrs X2many 类型的过滤
有童鞋在群里问到 attrs 中的 many2many类型的字段该如何进行domain过滤,其实非常简单: Many2many的字段在js中获取的值的格式为[[6,false,[]]] 所以attrs ...
- mvn install
mvn install:install-file \ -DgroupId=com.weibo.datasys.weistreamng \ -DartifactId=weistreamng-092-fr ...
- Iconfont矢量图标平台全面升级
阿里UX矢量图标库今天全新发布上线了,这次升级相对于老版本做了30多项功能和体验上的改善:赶快来体验一下吧!! 请猛戳:www.iconfont.cn 请猛戳:www.iconfont.cn 设计师p ...
- PHP filesystem attack vectors
http://www.ush.it/2009/02/08/php-filesystem-attack-vectors/ On Apr 07, 2008 I spoke with Kuza55 and ...
- vbox下Oracle Enterprise liunx5.4虚拟机安装10G RAC实验(一)
1.配置第一个虚拟机 1.1 安装后的登录界面 1.2 第1台机器(单数据配置方面) 1.2.1 验证安装包 1.2.2 修改内核参数 1.2.3添加安全限制 1.2.4关闭防火墙 1.2.5添加用户 ...
- 非静态的字段、方法或属性“System.Web.UI.Page.ClientScript...”要求对象引用 (封装注册脚本)
在写项目时想对asp.net的注册前台脚本事件进行封装,就添加了一个BasePage.cs页面,但一直报错‘非静态的字段.方法或属性“System.Web.UI.Page.ClientScript.. ...