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

使用io.Copy响应时,谁应对错误负责?,io,go,Io,Go,假设服务器需要向客户机响应一些数据,并且数据来自本地磁盘上的文件。然后我们写 n, err := io.Copy(w, f) // w is the ResponseWriter and f is the *os.File 我的想法是,io.Copy()首先写入一个头,然后将数据从f复制到w 当err不是nil(比如意外的EOF)时,客户机仍然会获得状态代码200,尽管响应主体包含错误的内容 可能本地磁盘坏了,或者客户端网络坏了。我们如何确定 错误是由服务器还是客户端引起的?io.Copy在

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

n, err := io.Copy(w, f)  // w is the ResponseWriter and f is the *os.File
我的想法是,
io.Copy()
首先写入一个头,然后将数据从
f
复制到
w

err
不是
nil
(比如
意外的EOF
)时,客户机仍然会获得状态代码200,尽管响应主体包含错误的内容

可能本地磁盘坏了,或者客户端网络坏了。我们如何确定

错误是由服务器还是客户端引起的?

io.Copy
在目标
io.Writer
上调用
Write
s关于
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在
写入
操作期间失败,那么您已经编写了
200 OK
响应,但是,如果您的响应指定了
内容长度
,当您的响应长度不匹配时,客户端将知道有问题

在HTTP 1.1和分块传输编码的情况下,从理论上讲,您可以在HTTP尾部中的响应之后指定失败标头。遗憾的是,目前使用最多的web浏览器都不支持HTTP拖尾

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


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


如果您正在记录HTTP状态头,则记录您作为响应发送给客户端的内容;不应该是这样的。

当直接从文件复制到响应编写器时,告诉客户端有问题的唯一方法是发送不完整的响应正文

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

 w.Header().Set("Content-Length", strconv.Itoa(fileLen))
不管是否有错误,处理程序都应该在复制主体后返回

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

客户端可以检测到在读取完整正文之前连接已关闭。在这种情况下,许多HTTP客户端库都会报告错误

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

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


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

err我更喜欢你的答案,请随意提及
io.Copy
不会指定哪一端失败。我删除了我的答案。因此,我们不能指出错误应该记录为4xx或5xx,对吗?你也应该考虑使用<代码> http.ServFiels来返回内容。如果你因为某种原因不能,或者如果它神奇地做了超过你想要/需要的事情,那么看看它是如何实现的。您可以看到它如何处理错误问题,并再次将该处理复制到您的代码中,假设您不能自己使用
ServeFile
。如果我使用io.copy向客户端响应文件,那么我也使用
分块编码
。如果我没有指定
内容长度
,那么客户端就无法检测到响应主体的不完整性。根据您的解释,即使在
分块编码
时,也可以指定
内容长度
,但这两个标题字段会冲突吗?