Go 网络开发中的常用的应用层协议库都是基于 net 标准库开发的,比如 gorilla/websocket 和 go-grpc,还有最常用的 http 库。
当请求一个不存在的 host 时会返回一个错误,如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| $ cat main.go package main
import ( "fmt" "net/http" )
func main() { if _, err := http.Get("http://unknownhost.example"); err != nil { fmt.Println(err) } } $ go run main.go Get "http://unknownhost.example": dial tcp: lookup unknownhost.example: no such host
|
这个错误本质其实是 *net.DNSError
,在 Go 早期开发中,会经常使用接口断言匹配错误。
比如这样 if v, ok := err.(*net.DNSError);ok {}
的形式。
如果这样使用的话,实际上不会匹配断言匹配到,在 debug 模式可以看到最后返回其实是 *url.Error
,然后内部才实际包裹了 (*net.DNSError)
错误,并实现 Unwarp() error
方法。
1 2 3 4 5 6
| type Error struct { Op string URL string Err error }
|
在 go 1.13 之后,errors.As
可以进行解包错误,那么我们就可以直接使用下面方式即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package main
import ( "errors" "fmt" "net" "net/http" )
func main() { if _, err := http.Get("http://unknownhost.example"); err != nil { if v := (*net.DNSError)(nil); errors.As(err, &v) { fmt.Println("can't found", v.Name) } } }
|
除了 *net.DNSError 之外,还有下面内置错误结构体:
- net.InvalidAddrError
- net.UnknownNetworkError
- net.AddrError
- net.DNSConfigError
上面错误除了实现了 Error() 接口,还实现了 Timeout() bool
和 Temporary() bool
接口,也就是实现了 net.Error
接口
1 2 3 4 5 6
| type Error interface { error Timeout() bool Temporary() bool }
|
同时也实现了 Unwarp()
接口,用于错误解包。那么我们就可以区分请求的错误是网络错误,还是服务器错误。
比如在 jsonrpc 协议中定义了 Error 结构体,使用下面方式进行判断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| type JsonError struct { Code int `json:"code"` Msg string `json:"message"` Data interface{} `json:"data"` }
func (e *JsonError) Error() string { return e.Msg }
func main() { if _, err := jsonrpc.Call("http://jsonrpc.example"); err != nil { if v := (net.Error)(nil); errors.As(err, &v) { fmt.Println("is timeout?", v.Timeout()) return }
if v := (*Error)(nil); errors.As(err, &v) { fmt.Println("jsonrpc error: Code:", v.Code, "Msg", v.Msg) } } }
|
如果按照以前的方式不断通过接口断言的解包错误的形式,那么代码很快会形成类似”回调地狱“的模样。
所以,在实际应用层开发上,直接可以使用 fmt.Errorf("Stack: %w",err)
包装错误,最后调用方最后使用 errros.As
或 errros.Is
解包按需处理错误。