使用io.Copy进行响应时,谁应该对错误负责?

时间:2014-09-29 09:45:26

标签: io go

假设服务器需要向客户端回复一些数据,并且数据来自本地磁盘上的文件。然后我们写,

n, err := io.Copy(w, f)  // w is the ResponseWriter and f is the *os.File

我的想法是,io.Copy()首先编写标题,然后将数据从f复制到w

err不是nil(比如unexpected EOF)时,客户端仍然会获得状态代码200,尽管响应正文包含错误。

可能是本地磁盘坏了,或者客户端的网络坏了。我们怎样才能确定 err是由服务器还是客户端引起的?

2 个答案:

答案 0 :(得分:6)

io.Copy在目标Write上调用io.Writerhttp.ResponseWriter关于Write方法的文档指定了此行为:

// Write writes the data to the connection as part of an HTTP reply.
// If WriteHeader has not yet been called, Write calls WriteHeader(http.StatusOK)
// before writing the data.  If the Header does not contain a
// Content-Type line, Write adds a Content-Type set to the result of passing
// the initial 512 bytes of written data to DetectContentType.
Write([]byte) (int, error)

这意味着它将首先调用WriteHeader

// WriteHeader sends an HTTP response header with status code.
// If WriteHeader is not called explicitly, the first call to Write
// will trigger an implicit WriteHeader(http.StatusOK).
// Thus explicit calls to WriteHeader are mainly used to
// send error codes.
WriteHeader(int)

所以是的,如果您的HD在Write操作期间失败,那么您已经写了200 OK个响应,但是,如果您的回复指定Content-Length,则客户端将会当你的回答长度不匹配时,就知道出了什么问题。

在HTTP 1.1和分块传输编码的情况下,理论上你可以在HTTP预告片中的响应之后指定一个失败头。遗憾的是,任何当前最常用的Web浏览器都不支持HTTP预告片。

来自@OneOfOne的贡献:io.Copy的错误不会指定哪一端失败;如果服务器或客户端。

  

因此,我们不能指出错误应该记录为4xx或5xx,对吗?

如果您正在记录HTTP状态标题,请记录您作为响应发送客户端的内容;不应该是什么。

答案 1 :(得分:4)

当直接从文件复制到响应编写器时,告诉客户端出错的唯一方法是发送一个不完整的响应主体。

要强制服务器发送不完整的响应正文,请在复制正文之前指定内容长度:

 w.Header().Set("Content-Length", strconv.Itoa(fileLen))

处理程序应该在复制正文后返回,错误与否。

服务器检查处理程序是否写入了内容长度标头中指定的字节数。如果处理程序没有写入该字节数,则服务器将关闭连接。

客户端可以在读取完整正文之前检测到连接已关闭。许多HTTP客户端库将在此方案中报告错误。

如果在开始编写响应之前将文件缓冲在内存中,则可以设置响应状态代码以指示错误。如果文件很大,您可能不想缓冲。

处理程序难以检测io.Copy是否因读取文件时出错或写入客户端时出错而失败。考虑到所涉及的可能代码路径的数量(不同的操作系统,不论是否为TLS,io.Copy中的可选优化,......),io.Copy返回的潜在错误很多。在文件和客户端错误之间,错误甚至可能不是唯一的。

在复制文件之前指定内容长度还有其他好处:当内容长度已知时,服务器始终使用最有效的传输编码(身份编码)。在某些操作系统上,io.Copy操作将由内核完成。