go doc/articles/wiki: "编写Web应用程序"示例写入响应头两次

6rqinv9w  于 4个月前  发布在  Go
关注(0)|答案(5)|浏览(107)

你正在使用的Go版本是什么( go version )?

go1.11

这个问题在最新版本的发布中是否重现?

是的

你正在使用什么操作系统和处理器架构( go env )?

GOARCH="amd64"
GOBIN="/home/jabenze/go/bin"
GOCACHE="/home/jabenze/.cache/go-build"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
GOPATH="/home/jabenze/go"
GOPROXY=""
GORACE=""
GOROOT="/usr/lib/go"
GOTMPDIR=""
GOTOOLDIR="/usr/lib/go/pkg/tool/linux_amd64"
GCCGO="gccgo"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build876174949=/tmp/go-build -gno-record-gcc-switches"

你做了什么?

按照"编写Web应用程序"教程,从这里开始,直到错误处理部分:https://golang.org/doc/articles/wiki/#tmp_9。
查看函数 renderTemplate ,如下所示:

func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
    t, err := template.ParseFiles(tmpl + ".html")
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    err = t.Execute(w, p)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
    }
}

修改 Page 数据结构,通过删除一个字段(或修改模板以引用不存在的字段),使得以下行: err = t.Execute(w, p) 导致创建一个错误。观察应用程序发送的响应代码。下一行: http.Error(w, err.Error(), http.StatusInternalServerError) 意味着响应代码为500,但是应用程序返回了200。
这是因为 t.Execute 调用了 w.Write ,由于 w.WriteHeader 尚未被调用,导致对 w.WriteHeader(200) 的隐式调用。对 w.WriteHeader 的进一步调用,例如 http.Error 内的调用将被忽略。
在所有错误的情况下,无法发送500错误代码,因为 w.Write 本身可以返回错误,在某个时候,只需简单地记录错误并返回而不设置错误代码即可。可选地,示例可以渲染到 bytes.Buffer ,以便捕获模板渲染错误。

你期望看到什么?

基于代码,500错误代码

你看到了什么?

错误代码200。

r8xiu3jd

r8xiu3jd1#

这是一个有效的问题,很好的发现!感谢你编写的报告和良好的分析。
我们应该讨论如何解决它。正如你所说,一个选项是先将模板渲染到 bytes.Buffer ,然后在所有可能失败的操作都成功后,只将 buf 复制到 rw 。然而,这可能与教程的其他部分不太相符。
还有其他方法可以解决这个问题,重要的是要提出一个在“编写Web应用程序”教程的背景下最有意义的解决方案,以及读者在那之前期望了解或接触到的内容。
/cc @adg 作为文章的原始作者,我认为。

cx6n0qe3

cx6n0qe32#

字节缓冲区渲染的唯一问题是,如果教程要涵盖适当的错误处理,我们仍然会遇到同样的问题。

buf := &bytes.Buffer{}
if err := t.Execute(buf, p); err != nil {
   http.Error(w, err.Error(), http.StatusInternalServerError)
   return
}

if err := w.Write(buf.Bytes()); err != nil {
   // can't call http.Error here for the same reason as before.  We've caught 
   // template problems, but we still can't do exhaustive error handling with http.Error
}

对于教程的建议可能是直接将模板渲染错误写入 ResponseWriterlog ,然后在教程中添加一个注解或简短的说明,说明为什么我们不能写入两次头部。这是正确的代码,不会使教程变得过于复杂。
不知道是否有我遗漏的更简单的解决方案。

pgccezyw

pgccezyw3#

我对于教程的建议可能仅仅是继续直接写入ResponseWriter并记录模板渲染错误。
这就是我想说的。模板渲染错误对应用程序作者来说,而不是用户,并且通常只应该在程序开发期间发生。在这里记录它将是理想的,并且几句话解释这一点实际上会改善教程。

vuv7lop3

vuv7lop34#

https://golang.org/cl/136757提到了这个问题:doc/articles/wiki: remove misleading code in tutorial

2eafrhcq

2eafrhcq5#

我意识到这个问题仍然带有"需要决策"的标签,但由于到目前为止所有人都同意,所以我继续写了一些东西来解决它。我不是最好的作家,但至少这是一个不错的开始。

相关问题