假设服务器需要向客户端回复一些数据,并且数据来自本地磁盘上的文件。然后我们写,
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
是由服务器还是客户端引起的?
答案 0 :(得分:6)
io.Copy
在目标Write
上调用io.Writer
。 http.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操作将由内核完成。