资源管理, 通俗的讲: 就是连接数据需要关闭, 操作文件的时候, 打开文件, 必须记得关闭; 对一个进程上锁的时候, 需要释放锁...; 错误表示程序中发生了异常情况。 假设我们正在尝试打开一个文件,但该文件在文件系统中不存在。 这是一种异常情况,表示为错误。
defer 调用
资源管理, 通俗的讲: 就是连接数据需要关闭, 操作文件的时候, 打开文件, 必须记得关闭; 对一个进程上锁的时候, 需要释放锁
-
确保调用在函数结束时发生
func tryDefer() { defer fmt.println(1) fmt.println(2) } // 输出结果 2, 1
说明调用
defer
的时候是在函数结束时发生func tryDefer() { defer fmt.println(1) defer fmt.println(2) fmt.println(3) } // 输出结果 3, 2, 1
说明
defer
类似于一个栈, 先进后出, 所以输出 3, 2, 1func tryDefer() { defer fmt.println(1) defer fmt.println(2) fmt.println(3) panic("error occurred") fmt.println(4) } // 输出结果 3, 2, 1 panic: error occurred
说明
defer
的优先级高于panic
,return
. 即使代码中有return
,panic
也无法阻止defer
语句的执行func tryDefer2() { for i := 0; i< 100; i++ { defer fmt.Println(i) if i == 30 { panic("printed too many") } } } // 输出结果 30, 29 ...0
说明
defer
先进后出
-
Open/Close
使用defer
斐波那契数列写入文件
func writeFile(filename string) { // 创建文件 file, err := os.Create(filename) if err != nil { panic(err) } // 关闭文件 defer file.Close() writer := bufio.NewWriter(file) defer writer.Flush() // 导入 // 调用 斐波那契数列函数, 注意引入包的位置 var f = fib.Fibonacci() for i := 0; i < 20; i++ { fmt.println(writer, f()) } } // 主函数调用, 即可生成文件
Lock/Unlock
使用defer
PrintHeader/PrintFooter
使用defer
错误处理概念
错误表示程序中发生了异常情况。 假设我们正在尝试打开一个文件,但该文件在文件系统中不存在。 这是一种异常情况,表示为错误。
func writeFile(filename string) {
file, err := os.OpenFile(filename, os.O_EXCL|os.O_CREATE, 0666)
if err != nil {
panic(err)
}
defer file.Close()
writer := bufio.NewWriter(file)
defer writer.Flush() // 导入
var f = fib.Fibonacci()
for i := 0; i < 20; i++ {
fmt.Fprintln(writer, f())
}
}
-
os.O_CREATE
文件不存在则会创建 -
os.O_EXCL
与 O_CREATE 一起使用,文件不能存在。
panic
异常会终止程序运行, 且返回异常格式太生硬, 所以需要手动对异常处理下
if err != nil {
fmt.println("file already exists")
// 异常, 程序终止
return
}
error 是什么东西
- 查看源码
//错误内置接口类型为常规接口
// 表示错误条件,nil 值表示没有错误。
type error interface {
Error() string
}
- 上面的异常处理又可以优化下
if err != nil {
fmt.Println("Error:", err,Error())
}
- 手动设置
error
err = errors.New("this is error handling")
if err != nil {
if pathError, ok := err.(*os.PathError); !ok {
panic(err)
} else {
fmt.Printf("%s, %s, %s\n", pathError.Op, pathError.Path, pathError.Err)
}
}
服务器统一处理异常
实现统一的错误处理服务(一)
-
新建目录
filelistingserver
, 新建web.go
package main import ( "io/ioutil" "net/http" "os" ) func main() { http.HandleFunc("/list/", func(writer http.ResponseWriter, request *http.Request){ path := request.URL.Path[len("/list/"):] // 截取之后才取得真实路径 file, err := os.Open(path) // 打开文件 if err != nil { // 抛出异常 panic(err) } defer file.Close() // 关闭资源 all, err : = ioutil.ReadAll(file) if err != nil { panic(err) } writer.Write(all) }) // 起监听服务 err := http.ListenAndServe("8888", nil) if err != nil { panic(err) } }
-
封装
将上面的业务代码处理提取出来, 放到单独一文件里; 新建目录-->新建文件
handle.go
package filelisting import ( "io/ioutil" "net/http" "os" ) func HandleFileList (writer http.ResponseWriter, request *http.Request) error { path := request.URL.Path[len("/list/"):] // 路径 file, err := os.Open(path) if err != nil { return err } defer file.Close() all, err := ioutil.ReadAll(file) if err != nil { return err } writer.Write(all) }
遇到异常, 直接抛出异常, 在调用层单独处理异常
-
修改
web.go
文件package main import ( "net/http" "os" "github.com/StudyGo/errorhandling/filelistingserver/filelisting" "github.com/gpmgo/gopm/modules/log" ) // 定义结构体 type appHandle func(writer http.ResponseWriter, request *http.Request) error // 异常处理, 包装; 输入一个函数, 输出一个函数, func errWrapper(handler appHandle) func(http.ResponseWriter, *http.Request) { return func(writer http.ResponseWriter, request *http.Request) { err := handler(writer, request) if err != nil { log.Warn("Error handling request: %s", err.Error()) code := http.StatusOK switch { case os.IsNotExist(err): code = http.StatusNotFound case os.IsPermission(err): code = http.StatusForbidden default: code = http.StatusInternalServerError } http.Error(writer, http.StatusText(code), code) } } } func main() { http.HandleFunc("/list/", errWrapper(filelisting.HandleFileList)) err := http.ListenAndServe(":8888", nil) if err != nil { panic(err) } }
实现统一的错误处理服务(二)
基于错误处理服务(一)修改; 这么一种情况,
handle
文件的创建者将文件路径写成固定值/list/
, 调用这个函数的人, 给的参数是/
路径. 实测:http://127.0.0.1:8888/abc
网页直接崩溃, 并没有抛出上面封装的error
- 修改
web.go
文件
func errWrapper(handler appHandle) func(http.ResponseWriter, *http.Request) {
return func(writer http.ResponseWriter, request *http.Request) {
// 核心 Start, 对 error 进行封装处理
defer func() {
r := recover()
log.Error("panic: %v", r)
http.Error(writer, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}()
// 核心End
err := handler(writer, request)
if err != nil {
log.Warn("Error handling request: %s", err.Error())
code := http.StatusOK
switch {
case os.IsNotExist(err):
code = http.StatusNotFound
case os.IsPermission(err):
code = http.StatusForbidden
default:
code = http.StatusInternalServerError
}
http.Error(writer, http.StatusText(code), code)
}
}
}
- 对
handle.go
文件再次修改
package filelisting
import (
"errors"
"io/ioutil"
"net/http"
"os"
"strings"
)
const prefix = "/list/"
type userError string
func (e userError) Error() string {
return e.Message()
}
func (e userError) Message() string {
return string(e)
}
func HandleFileList (writer http.ResponseWriter, request *http.Request) error {
// 对路径进行校对, 不正确直接返回
if strings.Index(request.URL.Path, prefix) != 0{
return errors.New("path must start with "+ prefix)
}
path := request.URL.Path[len(prefix):] // 路径
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
all, err := ioutil.ReadAll(file)
if err != nil {
return err
}
writer.Write(all)
return nil
}
- 对
web.go
文件进行修改
package main
import (
"net/http"
"os"
"github.com/StudyGo/errorhandling/filelistingserver/filelisting"
"github.com/gpmgo/gopm/modules/log"
)
type appHandle func(writer http.ResponseWriter, request *http.Request) error
func errWrapper(handler appHandle) func(http.ResponseWriter, *http.Request) {
return func(writer http.ResponseWriter, request *http.Request) {
defer func() {
if r := recover(); r != nil {
log.Error("panic: %v", r)
http.Error(writer, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
}()
err := handler(writer, request)
if err != nil {
log.Warn("Error handling request: %s", err.Error())
// 改动在这里, 可以实现给用户展示, 你想让他看到的信息, 相对友好点的信息
if userErr, ok := err.(userError); ok {
http.Error(writer, userErr.Message(), http.StatusBadRequest)
return
}
code := http.StatusOK
switch {
case os.IsNotExist(err):
code = http.StatusNotFound
case os.IsPermission(err):
code = http.StatusForbidden
default:
code = http.StatusInternalServerError
}
http.Error(writer, http.StatusText(code), code)
}
}
}
type userError interface {
error
Message() string
}
func main() {
http.HandleFunc("/", errWrapper(filelisting.HandleFileList))
err := http.ListenAndServe(":8888", nil)
if err != nil {
panic(err)
}
}
给用户提示相对友好的信息, 并不是统一返回 Internet error
panic 和 recover
panic
- 停止当前函数执行
- 一直向上返回, 执行每一层的
defer
- 如果没有遇见
recover
, 程序退出
recover
- 仅在
defer
调用中使用 - 可以获取
panic
的值 - 如果无法处理, 可以重新
panic
示例
新建 recover.go
package main
import (
"fmt"
)
func tryRecover() {
// 匿名函数
defer func() {
r := recover()
if err, ok := r.(error); ok {
fmt.Println("Error occurred: ", err)
} else {
panic(fmt.Sprintf("I don't know what to do: %v", r))
}
}()
//panic(errors.New("this is an error recover"))
//b := 0
//a := 5 / b
//fmt.Println(a)
panic(123445)
}
func main() {
tryRecover()
}
error vs panic
- 意料之中的: 使用
error
- 意料之外的: 使用
panic
. 如数组越界