在编写HTTP处理程序时,容易在已经回复HTTP请求后忘记返回语句。这可能会导致奇怪的事情产生,我们期望在发生错误后停止处理程序,然而实际中并没有停止。下面的例子描述了这种情况。
func handler(w http.ResponseWriter, req *http.Request) {
err := foo(req)
if err != nil {
http.Error(w, "foo", http.StatusInternalServerError)
}
// ...
}
上述程序在执行foo时如果产生错误,会使用http.Error将错误消息foo和状态码500(http.StatusInternalServerError)返回给客户端。这段代码的问题在于,即使进入if err != nil
分支,程序还会继续往后运行,因为http.Error不会停止处理程序的执行。这会导致什么问题?结合下面的例子,从HTTP层面上来说,在err !=nil
时,返回的内容会包含了错误时内容(foo)和正常时内容(all good).
func handler(w http.ResponseWriter, req *http.Request) {
err := foo(req)
if err != nil {
http.Error(w, "foo", http.StatusInternalServerError)
}
_, _ = w.Write([]byte("all good"))
w.WriteHeader(http.StatusCreated)
}
对于HTTP状态码,只会返回第一个(http.StatusInternalServerError),但是,也会记录一条警告日志,因为存在重复设置状态码。这个警告意味着我们多次写入状态码,提示这是多余的操作。
2021/10/29 16:45:33 http: superfluous response.WriteHeader call
from main.handler (main.go:20)
从程序执行层面来说,主要的影响是本应该停止运行的逻辑会继续运行。例如,如果foo除了返回一个error还会返回一个指针,继续执行意味着可能使用这个空指针(因为存在错误这个指针可能为空),导致程序panic.
修复上述问题方法很简单,就是在执行完http.Error之后添加return语句。由于有return语句,如果执行逻辑进入到if err != nil
之后,函数将停止执行。
func handler(w http.ResponseWriter, req *http.Request) {
err := foo(req)
if err != nil {
http.Error(w, "foo", http.StatusInternalServerError)
return
}
// ...
}
总结,上述HTTP程序中存在的问题可能非常微小,很容易忘记它,以至于这个错误出现的相当频繁。我们需要牢记http.Error不会停止处理程序的执行,必须手动添加return 语句。不过如果程序有好的单元测试覆盖率,这种问题比较容易发现,这也说明了单测的重要性。