資源管理, 通俗的講: 就是連接數(shù)據(jù)需要關(guān)閉, 操作文件的時候, 打開文件, 必須記得關(guān)閉; 對一個進(jìn)程上鎖的時候, 需要釋放鎖...; 錯誤表示程序中發(fā)生了異常情況。 假設(shè)我們正在嘗試打開一個文件,但該文件在文件系統(tǒng)中不存在劳殖。 這是一種異常情況桃犬,表示為錯誤。
defer 調(diào)用
資源管理, 通俗的講: 就是連接數(shù)據(jù)需要關(guān)閉, 操作文件的時候, 打開文件, 必須記得關(guān)閉; 對一個進(jìn)程上鎖的時候, 需要釋放鎖
-
確保調(diào)用在函數(shù)結(jié)束時發(fā)生
func tryDefer() { defer fmt.println(1) fmt.println(2) } // 輸出結(jié)果 2, 1
說明調(diào)用
defer
的時候是在函數(shù)結(jié)束時發(fā)生func tryDefer() { defer fmt.println(1) defer fmt.println(2) fmt.println(3) } // 輸出結(jié)果 3, 2, 1
說明
defer
類似于一個棧, 先進(jìn)后出, 所以輸出 3, 2, 1func tryDefer() { defer fmt.println(1) defer fmt.println(2) fmt.println(3) panic("error occurred") fmt.println(4) } // 輸出結(jié)果 3, 2, 1 panic: error occurred
說明
defer
的優(yōu)先級高于panic
,return
. 即使代碼中有return
,panic
也無法阻止defer
語句的執(zhí)行func tryDefer2() { for i := 0; i< 100; i++ { defer fmt.Println(i) if i == 30 { panic("printed too many") } } } // 輸出結(jié)果 30, 29 ...0
說明
defer
先進(jìn)后出
-
Open/Close
使用defer
斐波那契數(shù)列寫入文件
func writeFile(filename string) { // 創(chuàng)建文件 file, err := os.Create(filename) if err != nil { panic(err) } // 關(guān)閉文件 defer file.Close() writer := bufio.NewWriter(file) defer writer.Flush() // 導(dǎo)入 // 調(diào)用 斐波那契數(shù)列函數(shù), 注意引入包的位置 var f = fib.Fibonacci() for i := 0; i < 20; i++ { fmt.println(writer, f()) } } // 主函數(shù)調(diào)用, 即可生成文件
Lock/Unlock
使用defer
PrintHeader/PrintFooter
使用defer
錯誤處理概念
錯誤表示程序中發(fā)生了異常情況。 假設(shè)我們正在嘗試打開一個文件,但該文件在文件系統(tǒng)中不存在。 這是一種異常情況维咸,表示為錯誤。
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() // 導(dǎo)入
var f = fib.Fibonacci()
for i := 0; i < 20; i++ {
fmt.Fprintln(writer, f())
}
}
-
os.O_CREATE
文件不存在則會創(chuàng)建 -
os.O_EXCL
與 O_CREATE 一起使用惠爽,文件不能存在癌蓖。
panic
異常會終止程序運(yùn)行, 且返回異常格式太生硬, 所以需要手動對異常處理下
if err != nil {
fmt.println("file already exists")
// 異常, 程序終止
return
}
error 是什么東西
- 查看源碼
//錯誤內(nèi)置接口類型為常規(guī)接口
// 表示錯誤條件,nil 值表示沒有錯誤疆股。
type error interface {
Error() string
}
- 上面的異常處理又可以優(yōu)化下
if err != nil {
fmt.Println("Error:", err,Error())
}
- 手動設(shè)置
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)
}
}
服務(wù)器統(tǒng)一處理異常
實(shí)現(xiàn)統(tǒng)一的錯誤處理服務(wù)(一)
-
新建目錄
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/"):] // 截取之后才取得真實(shí)路徑 file, err := os.Open(path) // 打開文件 if err != nil { // 拋出異常 panic(err) } defer file.Close() // 關(guān)閉資源 all, err : = ioutil.ReadAll(file) if err != nil { panic(err) } writer.Write(all) }) // 起監(jiān)聽服務(wù) err := http.ListenAndServe("8888", nil) if err != nil { panic(err) } }
-
封裝
將上面的業(yè)務(wù)代碼處理提取出來, 放到單獨(dú)一文件里; 新建目錄-->新建文件
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) }
遇到異常, 直接拋出異常, 在調(diào)用層單獨(dú)處理異常
-
修改
web.go
文件package main import ( "net/http" "os" "github.com/StudyGo/errorhandling/filelistingserver/filelisting" "github.com/gpmgo/gopm/modules/log" ) // 定義結(jié)構(gòu)體 type appHandle func(writer http.ResponseWriter, request *http.Request) error // 異常處理, 包裝; 輸入一個函數(shù), 輸出一個函數(shù), 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) } }
實(shí)現(xiàn)統(tǒng)一的錯誤處理服務(wù)(二)
基于錯誤處理服務(wù)(一)修改; 這么一種情況,
handle
文件的創(chuàng)建者將文件路徑寫成固定值/list/
, 調(diào)用這個函數(shù)的人, 給的參數(shù)是/
路徑. 實(shí)測:http://127.0.0.1:8888/abc
網(wǎng)頁直接崩潰, 并沒有拋出上面封裝的error
- 修改
web.go
文件
func errWrapper(handler appHandle) func(http.ResponseWriter, *http.Request) {
return func(writer http.ResponseWriter, request *http.Request) {
// 核心 Start, 對 error 進(jìn)行封裝處理
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 {
// 對路徑進(jìn)行校對, 不正確直接返回
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
文件進(jìn)行修改
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())
// 改動在這里, 可以實(shí)現(xiàn)給用戶展示, 你想讓他看到的信息, 相對友好點(diǎn)的信息
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)
}
}
給用戶提示相對友好的信息, 并不是統(tǒng)一返回 Internet error
panic 和 recover
panic
- 停止當(dāng)前函數(shù)執(zhí)行
- 一直向上返回, 執(zhí)行每一層的
defer
- 如果沒有遇見
recover
, 程序退出
recover
- 僅在
defer
調(diào)用中使用 - 可以獲取
panic
的值 - 如果無法處理, 可以重新
panic
示例
新建 recover.go
package main
import (
"fmt"
)
func tryRecover() {
// 匿名函數(shù)
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
. 如數(shù)組越界