自定义错误类型
- 我们可以通过查看error的类型,得到是个interface,里面只有一个Eroor,只要实现了Error()的strct,就相当于实现了Error接口,因此我们就可以自定义自己的错误类型了
//自己定义异常处理
type HttpError struct {
}
//实现了Error方法,就相当于实现了这个接口
func (e *HttpError) Error() string {
return "http error found"
}
func main() {
fileWrite()
}
func fileWrite() {
//读取文件
file, err := os.OpenFile("D:/gopath/src/exce/file.txt", os.O_EXCL|os.O_CREATE, 0666)
//自己定义错误 因为不属于os.PathError类型 因此错误
//err = errors.New("error is owner")
//调用函数,发生http错误
err = http()
if err != nil {
//可以通过错误类型来判断
if pathError, ok := err.(*os.PathError); ok {
fmt.Println(pathError.Op, pathError.Path, pathError.Err)
return
} else if httpError, ok := err.(*HttpError); ok {
fmt.Println(httpError)
} else {
panic(err)
}
}
defer file.Close()
buf := bufio.NewWriter(file)
defer buf.Flush()
for i := 0; i <= 100; i++ {
fmt.Fprintln(buf, "A")
}
}
//定义函数返回HTTP错误
func http() *HttpError {
return &HttpError{}
}
使用了自定义错误的话,我们就可以抛出一些自定义的错误,在最外层通过不同的错误类型,做出不同的处理反映,有点类型与Java的try{}catch()
panic错误处理 recover接受错误
panic会导致程序出现宕机,一般发生panic都是程序运行时的错误,我们可以在最先进入栈的defer函数中,使用recover()捕获到panic,然后通过不同的类型,做出不同的处理,在defer函数中,我们可以使用recover接受到pani继续处理错误,下面我们使用一个HTTP服务来实现一个相对完善的错误处理,里面会用的error,panic,recover的组合使用
func main() {
http.HandleFunc("/list/", func(w http.ResponseWriter, r *http.Request) {
//读取文件
path := r.URL.Path[len("list")+2:]
file,err := os.Open("./"+path)
if err != nil {
panic(err)
}
defer file.Close()
body,err := ioutil.ReadAll(file)
if err != nil {
panic(err)
}
fmt.Fprintf(w,string(body))
})
log.Fatal(http.ListenAndServe(":8081",nil))
}
/*
http服务器自带的recover错误处理
defer func() {
if err := recover(); err != nil && err != ErrAbortHandler {
const size = 64 << 10
buf := make([]byte, size)
buf = buf[:runtime.Stack(buf, false)]
c.server.logf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf)
}
if !c.hijacked() {
c.close()
c.setState(c.rwc, StateClosed)
}
}()
*/
上面当我们输出不存在的问题时,会出现panic错误,但是服务器并没有挂掉,是因为HTTP服务使用了上面的recover来接受错误,并且处理,导致服务器并没有挂掉
下面我们使用优雅的错误显示
func main() {
http.HandleFunc("/list/", func(w http.ResponseWriter, r *http.Request) {
//读取文件
path := r.URL.Path[len("list")+2:]
file, err := os.Open("./" + path)
//使用http错误更加优雅的返回
if err != nil {
//判断文件打开的错误类型
var code int
switch {
case os.IsNotExist(err):
code = http.StatusNotFound
case os.IsPermission(err):
code = http.StatusForbidden
default:
code = http.StatusInternalServerError
}
http.Error(w,http.StatusText(code),code)
return
}
defer file.Close()
body, err := ioutil.ReadAll(file)
if err != nil {
panic(err)
}
fmt.Fprintf(w, string(body))
})
log.Fatal(http.ListenAndServe(":8081", nil))
}
继续观察,如果我们发现上面的代码每次都要这样去处理错误,也不符合我们的编码习惯,我们可以将错误统一的方式处理
//定义处理逻辑的函数
type appHandler func(w http.ResponseWriter, r *http.Request) error
//定义处理错误的函数
func wapper(handler appHandler) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
err := handler(w, r)
var code int
switch {
case os.IsNotExist(err):
code = http.StatusNotFound
case os.IsPermission(err):
code = http.StatusForbidden
default:
code = http.StatusInternalServerError
}
http.Error(w, http.StatusText(code), code)
}
}
func main() {
http.HandleFunc("/list/", wapper(handler))
log.Fatal(http.ListenAndServe(":8081", nil))
}
func handler(w http.ResponseWriter, r *http.Request) error {
//读取文件
path := r.URL.Path[len("list")+2:]
file, err := os.Open("./" + path)
if err != nil {
//判断文件打开的错误类型
return err;
}
defer file.Close()
body, err := ioutil.ReadAll(file)
if err != nil {
return err
}
fmt.Fprintf(w, string(body))
return nil;
}
通过上面的函数,我们将错误统一处理放到了一个函数中,我们的逻辑代码只需要返回error即可,但是如果不是我们的错误,而是运行中出现数组越界等错误呢,上面并不能捕获到,因此我们要增加reover接受panic错误
//定义处理逻辑的函数
type appHandler func(w http.ResponseWriter, r *http.Request) error
func wapper(handler appHandler) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
//使用recover接受panic的错误,非常规错误
defer func() {
if r := recover(); r != nil {
log.Printf("panic:%v", r)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
}()
err := handler(w, r)
var code int
switch {
case os.IsNotExist(err):
code = http.StatusNotFound
case os.IsPermission(err):
code = http.StatusForbidden
default:
code = http.StatusInternalServerError
}
http.Error(w, http.StatusText(code), code)
}
}
func main() {
http.HandleFunc("/list/", wapper(handler))
log.Fatal(http.ListenAndServe(":8081", nil))
}
func handler(w http.ResponseWriter, r *http.Request) error {
//读取文件
path := r.URL.Path[len("list")+2:]
file, err := os.Open("./" + path)
if err != nil {
//判断文件打开的错误类型
return err;
}
defer file.Close()
body, err := ioutil.ReadAll(file)
if err != nil {
return err
}
//我们在这里模拟了下标越界这种panic错误
b := []int{1, 2, 3}
fmt.Println(b[4])
fmt.Fprintf(w, string(body))
return nil;
}
上面我们已经处理了error,panic错误,但是我们发现,如果我们抛出的是异常信息,希望用户看到,比如密码不正确等自定义信息,上面的依然不能实现,因此我们需要自定义错误
//定义处理逻辑的函数
type appHandler func(w http.ResponseWriter, r *http.Request) error
//自定义错误信息
type UserFail struct {
Message string `json:"message"`
Code int `json:"code"`
Data interface{} `json:"data"`
}
//定义错误处理函数
func app_fail(code int,msg string) *UserFail {
return &UserFail{
Message:msg,
Code:code,
Data: make(map[string]interface{}),
}
}
func (e *UserFail) Error() string {
bytes, err := json.Marshal(e)
if err == nil {
return string(bytes)
}
return "";
}
func wapper(handler appHandler) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
//使用recover接受panic的错误
defer func() {
if r := recover(); r != nil {
log.Printf("panic:%v", r)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
}()
err := handler(w, r)
//处理用户自定义的错误,并使用json的方式输出
if userErr, ok := err.(*UserFail); ok {
fmt.Fprintln(w,userErr)
return
}
var code int
switch {
case os.IsNotExist(err):
code = http.StatusNotFound
case os.IsPermission(err):
code = http.StatusForbidden
default:
code = http.StatusInternalServerError
}
http.Error(w, http.StatusText(code), code)
}
}
func main() {
http.HandleFunc("/list/", wapper(handler))
log.Fatal(http.ListenAndServe(":8081", nil))
}
func handler(w http.ResponseWriter, r *http.Request) error {
//读取文件
path := r.URL.Path[len("list")+2:]
file, err := os.Open("./" + path)
//抛出用户自定义的错误
return app_fail(403,"无权限访问")
if err != nil {
//判断文件打开的错误类型
return err;
}
defer file.Close()
body, err := ioutil.ReadAll(file)
if err != nil {
return err
}
//b := []int{1, 2, 3}
//fmt.Println(b[4])
fmt.Fprintf(w, string(body))
return nil;
}
通过上面逐渐强化的代码,我们实现了一个简易版本的http的错误处理,使我们更加了解了recover,error,自定义错误,让我们对go的错误处理有了一个大概的认识,大家敢兴趣的话,对http错误处理,可以看看gin等优秀框架的实现