编辑页面 wiki 需要能编辑页面。让我们创建两个新的处理程序:一个命名editHandler为显示"编辑页面"表单,另一个命名saveHandler为保存通过表单输入的数据。 首先,我们将它们添加到main(): func main() { http.HandleFunc("/view/", viewHandler) http.HandleFunc("/edit/", editHandler) http.HandleFunc("/save/", saveHandler) log.Fatal(http.ListenAndServe(":8080", nil)) } editHandler函数加载页面(或者,如果它不存在,则创建一个空Page结构),并显示一个 HTML 表单。 func editHandler(w http.ResponseWriter, r *http.Request) { title := r.URL.Path[len("/edit/"):] p, err := loadPage(title) if err != nil { p = &Page{Title: title} } fmt.Fprintf(w, "Editing %s
"+ "", p.Title, p.Title, p.Body) } 这个函数可以正常工作,但所有硬编码的 HTML 都很丑陋。当然,还有更好的方法。 html/template包 该html/template包是 Go 标准库的一部分。我们可以使用html/template将 HTML 保存在单独的文件中,允许我们更改编辑页面的布局,而无需修改底层 Go 代码。 首先,我们必须添加html/template到导入列表中。我们也不会再使用fmt了,所以我们必须删除它。 import ( "html/template" "os" "net/http" ) 让我们创建一个包含 HTML 表单的模板文件。打开一个名为 的新文件edit.html,并添加以下行:Editing {{.Title}}
修改editHandler以使用模板,而不是硬编码的 HTML: func editHandler(w http.ResponseWriter, r *http.Request) { title := r.URL.Path[len("/edit/"):] p, err := loadPage(title) if err != nil { p = &Page{Title: title} } t, _ := template.ParseFiles("edit.html") t.Execute(w, p) } template.ParseFiles函数将读取内容 edit.html并返回一个*template.Template。 t.Execute方法执行模板,将生成的 HTML 写入http.ResponseWriter。.Title和.Body标识符指的是 p.Title和p.Body。 模板指令用双花括号括起来。该printf "%s" .Body指令是一个函数调用,它以字符串而不是字节流的形式输出,与fmt.Printf一致。html/template包有助于确保模板操作仅生成安全且外观正确的 HTML。例如,它会自动转义任何大于号 ( >),将其替换为 > ,以确保用户数据不会破坏 HTML 表单。 由于我们现在正在使用模板,让我们为 viewHandler调用创建一个模板view.html:{{.Title}}
[edit] {{printf "%s" .Body}} 相应修改viewHandler: func viewHandler(w http.ResponseWriter, r *http.Request) { title := r.URL.Path[len("/view/"):] p, _ := loadPage(title) t, _ := template.ParseFiles("view.html") t.Execute(w, p) } 请注意,我们在两个处理程序中使用了几乎完全相同的模板代码。让我们通过将模板代码移动到它自己的函数来删除这个重复: func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) { t, _ := template.ParseFiles(tmpl + ".html") t.Execute(w, p) } 并修改处理程序以使用该功能: func viewHandler(w http.ResponseWriter, r *http.Request) { title := r.URL.Path[len("/view/"):] p, _ := loadPage(title) renderTemplate(w, "view", p) }func editHandler(w http.ResponseWriter, r *http.Request) { title := r.URL.Path[len("/edit/"):] p, err := loadPage(title) if err != nil { p = &Page{Title: title} } renderTemplate(w, "edit", p) } 如果我们在main中注释掉我们未实现的保存处理程序的注册 ,我们可以再次构建和测试我们的程序。 处理不存在的页面 如果你访问/view/APageThatDoesntExistloadPage呢?您将看到一个包含 HTML 的页面。这是因为它忽略了错误返回值, 并继续尝试填写没有数据的模板。相反,如果请求的页面不存在,它应该将客户端重定向到编辑页面,以便可以创建内容: func viewHandler(w http.ResponseWriter, r *http.Request) { title := r.URL.Path[len("/view/"):] p, err := loadPage(title) if err != nil { http.Redirect(w, r, "/edit/"+title, http.StatusFound) return } renderTemplate(w, "view", p) } http.Redirect函数向 HTTP 响应 添加 HTTP 状态代码 http.StatusFound(302) 和一个新的位置。 保存页面 saveHandler函数将处理位于编辑页面上的表单的提交。取消main注释中的相关行后 ,让我们实现处理程序: func saveHandler(w http.ResponseWriter, r *http.Request) { title := r.URL.Path[len("/save/"):] body := r.FormValue("body") p := &Page{Title: title, Body: []byte(body)} p.save() http.Redirect(w, r, "/view/"+title, http.StatusFound) } 页面标题(在 URL 中提供)和表单的唯一字段 Body存储在一个新的Page. 然后调用该save()方法将数据写入文件,并将客户端重定向到/view/页面。 返回的值FormValue是string类型。我们必须将该值转换为[]byte,然后才能将其放入Page结构中。我们用[]byte(body)来执行转换。 错误处理 在我们的程序中有几个地方会忽略错误。这是不好的做法,尤其是因为当确实发生错误时,程序会出现意外行为。更好的解决方案是处理错误并将错误消息返回给用户。这样,如果出现问题,服务器将完全按照我们想要的方式运行,并且可以通知用户。 首先,让我们处理以下错误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) } } http.Error函数发送指定的 HTTP 响应代码(在本例中为"内部服务器错误")和错误消息。 现在让我们修复saveHandler: func saveHandler(w http.ResponseWriter, r *http.Request) { title := r.URL.Path[len("/save/"):] body := r.FormValue("body") p := &Page{Title: title, Body: []byte(body)} err := p.save() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } http.Redirect(w, r, "/view/"+title, http.StatusFound) } p.save()期间发生的任何错误都会报告给用户。