go程序设计笔记(三)错误处理

171 阅读5分钟

自定义错误类型

  1. 我们可以通过查看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等优秀框架的实现