原文地址

   在之前我写过一篇关于通过使用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. 查看那个进程占用了端口号(LINUX与AIX)

    在LINUX中: netstat命令 [root@limt ~]# netstat -tulp Active Internet connections (only servers) Proto Rec ...

  2. 基于Solr的HBase多条件查询测试

    背景: 某电信项目中采用HBase来存储用户终端明细数据,供前台页面即时查询.HBase无可置疑拥有其优势,但其本身只对rowkey支持毫秒级 的快 速检索,对于多字段的组合查询却无能为力.针对HBa ...

  3. 查看linux中某个端口(port)是否被占用(netstat,lsof)

    查看linux中某个端口(port)是否被占用(netstat,lsof) netstat命令可以显示网络连接,路由表,接口状态,伪装连接,网络链路信息和组播成员组等信息.命令格式:netstat [ ...

  4. 【android design】android常用设计资源

    一.概述 大部分程序员擅长开发,但是对于设计却知之甚少.这直接导致,程序员在初期开发出来的应用(大多为兴趣或实用导向)中看不中用.因此,有必要搜集整合一些设计资源,这样既能减轻程序员在设计上所耗费的时 ...

  5. [LintCode] Coins in a Line 一条线上的硬币

    There are n coins in a line. Two players take turns to take one or two coins from right side until t ...

  6. 将普通工程转为mvn标准工程(main resources)

    It is sometimes required to change the default source folder working on the java project. One best e ...

  7. SQL sp_executesql【转】

    execute相信大家都用的用熟了,简写为exec,除了用来执行存储过程,一般都用来执行动态Sql sp_executesql,sql2005中引入的新的系统存储过程,也是用来处理动态sql的, 如: ...

  8. DBA日常工作内容和职责

    1.统计数据库总容量 按表空间分别统计: 总容量(单位为M): 2.计算用户下所有对象大小 3计算USERS表空间的大小 4计算该USERS表空间对象大小 ---------------------- ...

  9. BizTalk动手实验(九)业务规则引擎使用

    1 课程简介 通过本课程熟悉业务规则引擎(BRE)的使用(本环境为Windows 2008 32位操作系统环境 + Visual Studio 2010 + BizTalk 210) 2 准备工作 1 ...

  10. IOS第一天多线程-05GCD队列的使用

    ************** // // HMViewController.m // 08-GCD02-队列的使用(了解) // // Created by apple on 14-9-15. // ...