调用http.ResponseWriter.Write()时检查错误。

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

Check errors when calling http.ResponseWriter.Write()

问题

假设我有这个 HTTP 处理程序:

func SomeHandler(w http.ResponseWriter, r *http.Request) {
    data := GetSomeData()
    _, err := w.Write(data)
}

我应该检查 w.Write 返回的错误吗?我看到的示例都忽略了它并且没有处理。而且,像 http.Error() 这样的函数也没有返回错误供处理。

英文:

Say I have this http handler:

func SomeHandler(w http.ResponseWriter, r *http.Request) {
    data := GetSomeData()
    _, err := w.Write(data)
}

Should I check the error returned by w.Write? Examples I've seen just ignore it and do nothing. Also, functions like http.Error() do not return an error to be handled.

答案1

得分: 26

这取决于你。我的建议是,除非某个方法/函数的文档明确说明它永远不会返回非nil的错误(比如bytes.Buffer.Write()),始终检查错误,并至少记录下来。这样,如果发生错误,就会留下一些标记,以便以后出现问题时进行调查。

对于写入http.ResponseWriter也是如此。

你可能会认为ResponseWriter.Write()只有在发送数据失败(例如连接关闭)时才会返回错误,但事实并非如此。实现http.ResponseWriter的具体类型是未导出的http.response类型,如果你检查未导出的response.write()方法,就会发现它可能因为其他原因返回非nil的错误。

ResponseWriter.Write()可能返回非nil错误的原因:

  • 如果连接被劫持(参见http.Hijacker):http.ErrHijacked
  • 如果指定了内容长度,并且你尝试写入超过该长度的内容:http.ErrContentLength
  • 如果HTTP方法和/或HTTP状态根本不允许有响应体,并且你尝试写入超过0字节的内容:http.ErrBodyNotAllowed
  • 如果向实际连接写入数据失败。

即使你对错误无能为力,记录下来也可能对调试错误非常有帮助。例如,你(或处理程序链中的其他人)劫持了连接,并且稍后尝试写入它;你会得到一个错误(http.ErrHijacked),记录下来将立即揭示原因。

“简单”记录错误的提示

如果你对偶尔出现的错误无能为力,而且它不是“致命错误”,你可以创建并使用一个简单的函数来进行检查和记录,类似于这样:

func logerr(n int, err error) {
    if err != nil {
        log.Printf("Write failed: %v", err)
    }
}

使用它:

logerr(w.Write(data))

“自动记录”错误的提示

如果你甚至不想一直使用logerr()函数,你可以创建一个http.ResponseWriter的包装器,它会“自动”进行记录:

type LogWriter struct {
    http.ResponseWriter
}

func (w LogWriter) Write(p []byte) (n int, err error) {
    n, err = w.ResponseWriter.Write(p)
    if err != nil {
        log.Printf("Write failed: %v", err)
    }
    return
}

使用它:

func SomeHandler(w http.ResponseWriter, r *http.Request) {
    w = LogWriter{w}
    w.Write([]byte("hi"))
}

使用LogWriter作为http.ResponseWriter的包装器,如果写入原始的http.ResponseWriter失败,它将自动记录错误。

这样做的另一个巨大好处是,不需要调用日志记录函数,因此你可以将LogWriter的值“传递”给链中的其他人,每个尝试写入它的人都将被监视和记录,他们不必担心或甚至知道这一点。

但是,在将LogWriter传递给链时必须小心,因为这也有一个缺点:LogWriter的值将不会实现原始http.ResponseWriter可能实现的其他接口,例如http.Hijackerhttp.Pusher

Go Playground上有一个示例,展示了这个过程,并且还展示了LogWriter不会实现其他接口的方式;还展示了一种方法(使用2个“嵌套”的类型断言)如何从LogWriter中获取我们想要的内容(在示例中是http.Pusher)。

英文:

It's up to you. My advice is that unless the documentation of some method / function explicitly states that it never returns a non-nil error (such as bytes.Buffer.Write()), always check the error and the least you can do is log it, so if an error occurs, it will leave some mark which you can investigate should it become a problem later.

This is also true for writing to http.ResponseWriter.

You might think ResponseWriter.Write() may only return errors if sending the data fails (e.g. connection closed), but that is not true. The concrete type that implements http.ResponseWriter is the unexported http.response type, and if you check the unexported response.write() method, you'll see it might return a non-nil error for a bunch of other reasons.

Reasons why ResponseWriter.Write() may return a non-nil error:

  • If the connection was hijacked (see http.Hijacker): http.ErrHijacked
  • If content length was specified, and you attempt to write more than that: http.ErrContentLength
  • If the HTTP method and / or HTTP status does not allow a response body at all, and you attempt to write more than 0 bytes: http.ErrBodyNotAllowed
  • If writing data to the actual connection fails.

Even if you can't do anything with the error, logging it may be of great help debugging the error later on. E.g. you (or someone else in the handler chain) hijacked the connection, and you attempt to write to it later; you get an error (http.ErrHijacked), logging it will reveal the cause immediately.

Tip for "easy" logging errors

If you can't do anything with the occasional error and it's not a "showstopper", you may create and use a simple function that does the check and logging, something like this:

func logerr(n int, err error) {
	if err != nil {
		log.Printf("Write failed: %v", err)
	}
}

Using it:

logerr(w.Write(data))

Tip for "auto-logging" errors

If you don't even want to use the logerr() function all the time, you may create a wrapper for http.ResponseWriter which does this "automatically":

type LogWriter struct {
	http.ResponseWriter
}

func (w LogWriter) Write(p []byte) (n int, err error) {
	n, err = w.ResponseWriter.Write(p)
	if err != nil {
		log.Printf("Write failed: %v", err)
	}
	return
}

Using it:

func SomeHandler(w http.ResponseWriter, r *http.Request) {
	w = LogWriter{w}
	w.Write([]byte("hi"))
}

Using LogWriter as a wrapper around http.ResponseWriter, should writes to the original http.ResponseWriter fail, it will be logged automatically.

This also has the great benefit of not expecting a logger function to be called, so you can pass a value of your LogWriter "down" the chain, and everyone who attempts to write to it will be monitored and logged, they don't have to worry or even know about this.

But care must be taken when passing LogWriter down the chain, as there's also a downside to this: a value of LogWriter will not implement other interfaces the original http.ResponseWriter might also do, e.g. http.Hijacker or http.Pusher.

Here's an example on the Go Playground that shows this in action, and also shows that LogWriter will not implement other interfaces; and also shows a way (using 2 "nested" type assertions) how to still get out what we want from LogWriter (an http.Pusher in the example).

答案2

得分: -1

我想对@icza的解决方案进行补充。你不需要创建日志结构,可以使用简单的函数:

func logWrite(write func([]byte) (int, error), body []byte) {
	_, err := write(body)
	if err != nil {
		log.Printf("写入失败:%v", err)
	}
}

请参考基于@icza代码的这种方法:https://play.golang.org/p/PAetVixCgv4

英文:

I want to add to @icza solution. You don't need to create logging structure, you can use simple function:

func logWrite(write func([]byte) (int, error), body []byte) {
	_, err := write(body)
	if err != nil {
		log.Printf("Write failed: %v", err)
	}
}

Take a look on that approach based on the @icza code: https://play.golang.org/p/PAetVixCgv4

huangapple
  • 本文由 发表于 2017年5月15日 17:41:46
  • 转载请务必保留本文链接:https://go.coder-hub.com/43976140.html
匿名

发表评论

匿名网友

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

确定