处理Go语言中的连接重置错误

huangapple go评论119阅读模式
英文:

Handling connection reset errors in Go

问题

在一个普通的Go HTTP处理程序中,如果在仍然向响应写入数据时断开客户端连接,http.ResponseWriter.Write将返回一个带有类似于write tcp 127.0.0.1:60702: connection reset by peer的错误消息。

现在,从syscall包中,我有syscall.ECONNRESET,它具有消息connection reset by peer,所以它们并不完全相同。

我该如何匹配它们,以便在发生这种情况时知道不要引发panic?在其他情况下,我一直在做类似于以下的处理:

if err == syscall.EAGAIN {
  /* 以不同的方式处理错误 */
}

例如,这种处理方式效果很好,但我不能对syscall.ECONNRESET这样做。

更新:

因为我迫切需要一个解决方案,暂时我将使用以下非常不好的方法:

if strings.Contains(err.Error(), syscall.ECONNRESET.Error()) {
    println("it's a connection reset by peer!")
    return
}
英文:

On a plain Go HTTP handler, if I disconnect a client while still writting to the response, http.ResponseWritter.Write will return an error with a message like write tcp 127.0.0.1:60702: connection reset by peer.

Now from the syscall package, I have sysca.ECONNRESET, which has the message connection reset by peer, so they're not exactly the same.

How can I match them, so I know not to panic if it occurs ? On other ocasions I have been doing

if err == syscall.EAGAIN {
  /* handle error differently */
}

for instance, and that worked fine, but I can't do it with syscall.ECONNRESET.

Update:

Because I'm desperate for a solution, for the time being I'll be doing this very dirty hack:

if strings.Contains(err.Error(), syscall.ECONNRESET.Error()) {
	println("it's a connection reset by peer!")
	return
}

答案1

得分: 13

你得到的错误具有底层类型*net.OpError,例如这里构建的。

你应该能够将错误类型断言为其具体类型,像这样:

operr, ok := err.(*net.OpError)

然后访问它的Err字段,该字段应该对应于你需要的系统调用错误:

operr.Err.Error() == syscall.ECONNRESET.Error()
英文:

The error you get has the underlying type *net.OpError, built here, for example.

You should be able to type-assert the error to its concrete type like this:

operr, ok := err.(*net.OpError)

And then access its Err field, which should correspond to the syscall error you need:

operr.Err.Error() == syscall.ECONNRESET.Error()

答案2

得分: 13

@zian的回答比被接受的回答更有用,但是在Go 1.13+版本中,最好避免手动解包错误:

if errors.Is(opErr, syscall.ECONNRESET) {
    fmt.Println("发现了ECONNRESET")
}

这样做的好处是你还可以更普遍地使用它,比如在以下情况之后:

resp, err := http.Get("http://127.0.0.1:4444")

在这里,否则err会有一个额外的包装层(*url.Error),并且会被@zian使用的条件所忽略,除非显式地解包它第三次。

英文:

The answer by @zian is more useful than the accepted answer, but now on Go 1.13+ it is preferable to avoid manually unwrapping the errors:

if errors.Is(opErr,syscall.ECONNRESET) {
	fmt.Println("Found a ECONNRESET")
}

This has the benefit that you can also use it more generally, such as after:

resp, err := http.Get("http://127.0.0.1:4444")

Here this err would otherwise have an extra layer of wrapping (*url.Error) and would be missed by the condition @zian used without explicitly unwrapping it a third time.

答案3

得分: 11

我遇到了这个问题,接受的答案足以指引我正确的方向。然而,它提供的用于检查嵌入在*net.OpError中的错误是否为ECONNRESET的代码并不完整,至少对于Golang 1.9来说是如此。

嵌入在OpError.Err中的错误实际上是*os.SyscallError类型的(https://golang.org/pkg/os/#SyscallError)。由*net.netFD结构体实现的Write()函数(在通过网络发送响应时进行写入)如下所示:

func (fd *netFD) Write(p []byte) (nn int, err error) {
    nn, err = fd.pfd.Write(p)
    runtime.KeepAlive(fd)
    return nn, wrapSyscallError("write", err)
}

以及wrapSyscallError函数:

func wrapSyscallError(name string, err error) error {
    if _, ok := err.(syscall.Errno); ok {
        err = os.NewSyscallError(name, err)
    }
    return err
}

*os.SyscallError结构体中的错误可以直接与syscall.ECONNRESET进行比较。

因此,给定从网络写入返回的错误(例如调用http.ResponseWriter.Write),用于确定该错误是否为ECONNRESET的完整代码块如下所示:

if opErr, ok := err.(*net.OpError); ok {
    if syscallErr, ok := opErr.Err.(*os.SyscallError); ok {
        if syscallErr.Err == syscall.ECONNRESET {
            fmt.Println("Found a ECONNRESET")
        }
    }
}
英文:

I came across this issue and the accepted answer was sufficient to point me in the right direction. However, the code it provides to check if the Error embedded inside *net.OpError is ECONNRESET is not complete, at least not for Golang 1.9.

The error embedded at OpError.Err is actually of type *os.SyscallError (https://golang.org/pkg/os/#SyscallError). The Write() function implemented by struct *net.netFD (which is what's being written to when sending a response over the network) looks like this:

func (fd *netFD) Write(p []byte) (nn int, err error) {
    nn, err = fd.pfd.Write(p)
    runtime.KeepAlive(fd)
    return nn, wrapSyscallError("write", err)
}

And wrapSyscallError:

func wrapSyscallError(name string, err error) error {
    if _, ok := err.(syscall.Errno); ok {
	    err = os.NewSyscallError(name, err)
    }
    return err
}

The error inside the *os.SyscallError struct can be directly compared against syscall.ECONNRESET.

So, given an error returned from a network write (e.g. a call to http.ResponseWritter.Write), the full code block to determine if that error is ECONNRESET is:

if opErr, ok := err.(*net.OpError); ok {
    if syscallErr, ok := opErr.Err.(*os.SyscallError); ok {
	    if syscallErr.Err == syscall.ECONNRESET {
            fmt.Println("Found a ECONNRESET")
        }
    }
}

答案4

得分: 1

@zian - 感谢你对João Pinto(和我)的问题提供了一个很好的解决方案:如何匹配它们,以便在发生时不要惊慌失措?
从Go版本1.13开始,一个改进是使用errors.Is函数,它在“底层”上进行错误解包和顺序测试。例如:

if errors.Is(opErr,syscall.ECONNRESET) {
  fmt.Println("找到一个ECONNRESET")
}

@SteveCoffman - 补充你的好答案,谢谢!

在Go 1.13中处理错误 - Go博客 - Golang

英文:

@zian - thanks for your good solution to João Pinto's (and my) question : How can I match them, so I know not to panic if it occurs ?
As at go version 1.13, an improvement is to use the errors.Is function which does error unwrapping and testing sequentially 'under the hood'. For example :

if errors.Is(opErr,syscall.ECONNRESET) {
	fmt.Println("Found a ECONNRESET")
}

@SteveCoffman - adding to your good answer, cheers!

Working with Errors in Go 1.13 - The Go Blog - Golang

huangapple
  • 本文由 发表于 2013年11月12日 20:37:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/19929386.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定