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

Posted

技术标签:

【中文标题】调用 http.ResponseWriter.Write() 时检查错误【英文标题】:Check errors when calling http.ResponseWriter.Write() 【发布时间】:2017-10-14 00:58:42 【问题描述】:

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

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

我应该检查w.Write返回的错误吗?我见过的例子只是忽略它,什么也不做。此外,http.Error() 之类的函数不会返回要处理的错误。

【问题讨论】:

“我应该检查返回的错误...” 答案取决于您的要求。如果发生错误,您打算如何反应?如果您检查错误并且在错误时您所做的只是“不知道现在该做什么......让我们登录到 /dev/null 并将其称为已处理。”那么您可以很好地忽略错误。可以就一般问题给出一般建议。你的不一般。 【参考方案1】:

这取决于你。我的建议是,除非某些方法/函数的文档明确声明它永远不会返回非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),记录它会立即揭示原因。

“简单”记录错误提示

如果您对偶尔出现的错误无能为力,并且它不是“showstopper”,您可以创建并使用一个简单的函数来进行检查和记录,如下所示:

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 = LogWriterw
    w.Write([]byte("hi"))

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

这还有一个很大的好处,就是不期望调用记录器函数,因此您可以将LogWriter 的值“向下”传递给链,并且尝试写入它的每个人都将被监视和记录,他们不必担心,甚至不必知道这一点。

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

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

【讨论】:

根据我在一些帖子中看到的内容,可以放弃“自动记录错误提示”选项。可以在这里找到解释:blog.merovius.de/2017/07/30/… 虽然它已经在答案中提到了“但是通过时必须小心......”我只是想把链接放在这里给那些想知道究竟是什么问题的人,所以答案是 +1。【参考方案2】:

我想添加到@icza 解决方案。你不需要创建日志结构,你可以使用简单的函数:

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

根据@icza 代码查看该方法:https://play.golang.org/p/PAetVixCgv4

【讨论】:

以上是关于调用 http.ResponseWriter.Write() 时检查错误的主要内容,如果未能解决你的问题,请参考以下文章

java三种调用方式(同步调用/回调/异步调用)

LINUX系统调用

引用调用 vs 复制调用调用

RPC 调用和 HTTP 调用的区别

js方法调用

深入理解Java虚拟机——方法调用(解析调用)