目前为止,我们的代码中有一段重复性很高的代码:

  1. w.Header().Set("Content-Type", "text/html; charset=utf-8")

这是设置内容类型的标头,以便浏览器能正常解析页面。

使用中间件

我们可以使用中间件来完成该操作。
main.go

  1. package main
  2. import (
  3. "fmt"
  4. "net/http"
  5. "github.com/gorilla/mux"
  6. )
  7. func homeHandler(w http.ResponseWriter, r *http.Request) {
  8. fmt.Fprint(w, "<h1>Hello, 欢迎来到 goblog!</h1>")
  9. }
  10. func aboutHandler(w http.ResponseWriter, r *http.Request) {
  11. fmt.Fprint(w, "此博客是用以记录编程笔记,如您有反馈或建议,请联系 "+
  12. "<a href=\"mailto:summer@example.com\">summer@example.com</a>")
  13. }
  14. func notFoundHandler(w http.ResponseWriter, r *http.Request) {
  15. w.WriteHeader(http.StatusNotFound)
  16. fmt.Fprint(w, "<h1>请求页面未找到 :(</h1><p>如有疑惑,请联系我们。</p>")
  17. }
  18. func articlesShowHandler(w http.ResponseWriter, r *http.Request) {
  19. vars := mux.Vars(r)
  20. id := vars["id"]
  21. fmt.Fprint(w, "文章 ID:"+id)
  22. }
  23. func articlesIndexHandler(w http.ResponseWriter, r *http.Request) {
  24. fmt.Fprint(w, "访问文章列表")
  25. }
  26. func articlesStoreHandler(w http.ResponseWriter, r *http.Request) {
  27. fmt.Fprint(w, "创建新的文章")
  28. }
  29. func forceHTMLMiddleware(next http.Handler) http.Handler {
  30. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  31. // 1. 设置标头
  32. w.Header().Set("Content-Type", "text/html; charset=utf-8")
  33. // 2. 继续处理请求
  34. next.ServeHTTP(w, r)
  35. })
  36. }
  37. func main() {
  38. router := mux.NewRouter()
  39. router.HandleFunc("/", homeHandler).Methods("GET").Name("home")
  40. router.HandleFunc("/about", aboutHandler).Methods("GET").Name("about")
  41. router.HandleFunc("/articles/{id:[0-9]+}", articlesShowHandler).Methods("GET").Name("articles.show")
  42. router.HandleFunc("/articles", articlesIndexHandler).Methods("GET").Name("articles.index")
  43. router.HandleFunc("/articles", articlesStoreHandler).Methods("POST").Name("articles.store")
  44. // 自定义 404 页面
  45. router.NotFoundHandler = http.HandlerFunc(notFoundHandler)
  46. // 中间件:强制内容类型为 HTML
  47. router.Use(forceHTMLMiddleware)
  48. // 通过命名路由获取 URL 示例
  49. homeURL, _ := router.Get("home").URL()
  50. fmt.Println("homeURL: ", homeURL)
  51. articleURL, _ := router.Get("articles.show").URL("id", "1")
  52. fmt.Println("articleURL: ", articleURL)
  53. http.ListenAndServe(":3000", router)
  54. }

可以看到我们新增了 forceHTMLMiddleware 中间件方法:

  1. func forceHTMLMiddleware(h http.Handler) http.Handler {
  2. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  3. // 1. 设置标头
  4. w.Header().Set("Content-Type", "text/html; charset=utf-8")
  5. // 2. 继续处理请求
  6. h.ServeHTTP(w, r)
  7. })
  8. }

然后使用 Gorilla Mux 的 mux.Use() 方法来加载中间件:

  1. router.Use(forceHTMLMiddleware)

浏览器打开 localhost:3000/about ,或者其他存在的页面,查看header

解决路径结尾斜杠问题

访问以下两个链接:

可以看到有 / 的链接会报 404 错误。
我们希望 URL 后面是否加斜杆的情况下,皆使用同一个返回结果。

StrictSlash

对于这个问题 Gorilla Mux 提供了一个 StrictSlash(value bool) 函数,我们先来试试:
main 函数中,请将以下这一行:

  1. router := mux.NewRouter()

修改为:

  1. router := mux.NewRouter().StrictSlash(true)

浏览器再次访问 localhost:3000/about/
可以看到 URL 被校正了,跳转速度太快,我们再次试试看。
我们打开 Chrome 的控制台,查看请求,注意把 Disable cache 打钩
以看到当请求 about/ 时产生了两个请求,第一个是 301 跳转,第二个是跳转到的 about 去掉斜杆的链接。
浏览器在处理 301 请求时,会缓存起来。后续的 about/ 浏览器都会自动去请求 about 链接,也就是说两次请求只会在第一次的时候发生。
这个解决方案看起来不错,然而有一个严重的问题 —— 当请求方式为 POST 的时候,遇到服务端的 301 跳转,将会变成 GET 方式。很明显,这并非所愿,我们需要一个更好的方案。

兼容 POST 请求

我们需要在 URL 进入 Gorilla Mux 路由解析之前,就将后面的 / 去掉。
像这种针对所有请求的操作,你第一时间想到的可能是用中间件处理,然而因为执行顺序的问题,Gorilla Mux 会先匹配路由,再执行中间件,故使用中间件永远会返回 404.
解决方法很简单,那就是写一个函数把 Gorilla Mux 包起来,在这个函数中我们先对进来的请求做处理,然后再传给 Gorilla Mux 去解析。
接下来新增 removeTrailingSlash() 函数并调用:
main.go

  1. .
  2. .
  3. .
  4. func removeTrailingSlash(next http.Handler) http.Handler {
  5. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  6. r.URL.Path = strings.TrimSuffix(r.URL.Path, "/")
  7. next.ServeHTTP(w, r)
  8. })
  9. }
  10. func main() {
  11. .
  12. .
  13. .
  14. http.ListenAndServe(":3000", removeTrailingSlash(router))
  15. }Copy

我们使用 strings 包提供的 TrimSuffix(s, suffix string) string 函数来移除 / 后缀,如果不带斜杆后缀的话,r.URL.Path 将会被原封不动地返回。
修改保持后,再次访问 localhost:3000/about/,可见正常的结果
偶然间,我们又发现了另一个问题,访问主页 localhost:3000/ 会出现错误
那是因为我们的 removeTrailingSlash 中这一段:

  1. r.URL.Path = strings.TrimSuffix(r.URL.Path, "/")

会将我们的首页 URL / 给去除了,解决方法是把 / 路径排除在外:
main.go

  1. func removeTrailingSlash(next http.Handler) http.Handler {
  2. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  3. // 1. 除首页以外,移除所有请求路径后面的斜杆
  4. if r.URL.Path != "/" {
  5. r.URL.Path = strings.TrimSuffix(r.URL.Path, "/")
  6. }
  7. // 2. 将请求传递下去
  8. next.ServeHTTP(w, r)
  9. })
  10. }

保存修改后,再次访问 localhost:3000/ ,可见正常的结果