目前为止,我们的代码中有一段重复性很高的代码:
w.Header().Set("Content-Type", "text/html; charset=utf-8")
使用中间件
我们可以使用中间件来完成该操作。
main.go
package mainimport ("fmt""net/http""github.com/gorilla/mux")func homeHandler(w http.ResponseWriter, r *http.Request) {fmt.Fprint(w, "<h1>Hello, 欢迎来到 goblog!</h1>")}func aboutHandler(w http.ResponseWriter, r *http.Request) {fmt.Fprint(w, "此博客是用以记录编程笔记,如您有反馈或建议,请联系 "+"<a href=\"mailto:summer@example.com\">summer@example.com</a>")}func notFoundHandler(w http.ResponseWriter, r *http.Request) {w.WriteHeader(http.StatusNotFound)fmt.Fprint(w, "<h1>请求页面未找到 :(</h1><p>如有疑惑,请联系我们。</p>")}func articlesShowHandler(w http.ResponseWriter, r *http.Request) {vars := mux.Vars(r)id := vars["id"]fmt.Fprint(w, "文章 ID:"+id)}func articlesIndexHandler(w http.ResponseWriter, r *http.Request) {fmt.Fprint(w, "访问文章列表")}func articlesStoreHandler(w http.ResponseWriter, r *http.Request) {fmt.Fprint(w, "创建新的文章")}func forceHTMLMiddleware(next http.Handler) http.Handler {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {// 1. 设置标头w.Header().Set("Content-Type", "text/html; charset=utf-8")// 2. 继续处理请求next.ServeHTTP(w, r)})}func main() {router := mux.NewRouter()router.HandleFunc("/", homeHandler).Methods("GET").Name("home")router.HandleFunc("/about", aboutHandler).Methods("GET").Name("about")router.HandleFunc("/articles/{id:[0-9]+}", articlesShowHandler).Methods("GET").Name("articles.show")router.HandleFunc("/articles", articlesIndexHandler).Methods("GET").Name("articles.index")router.HandleFunc("/articles", articlesStoreHandler).Methods("POST").Name("articles.store")// 自定义 404 页面router.NotFoundHandler = http.HandlerFunc(notFoundHandler)// 中间件:强制内容类型为 HTMLrouter.Use(forceHTMLMiddleware)// 通过命名路由获取 URL 示例homeURL, _ := router.Get("home").URL()fmt.Println("homeURL: ", homeURL)articleURL, _ := router.Get("articles.show").URL("id", "1")fmt.Println("articleURL: ", articleURL)http.ListenAndServe(":3000", router)}
可以看到我们新增了 forceHTMLMiddleware 中间件方法:
func forceHTMLMiddleware(h http.Handler) http.Handler {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {// 1. 设置标头w.Header().Set("Content-Type", "text/html; charset=utf-8")// 2. 继续处理请求h.ServeHTTP(w, r)})}
然后使用 Gorilla Mux 的 mux.Use() 方法来加载中间件:
router.Use(forceHTMLMiddleware)
浏览器打开 localhost:3000/about ,或者其他存在的页面,查看header
解决路径结尾斜杠问题
访问以下两个链接:
可以看到有 / 的链接会报 404 错误。
我们希望 URL 后面是否加斜杆的情况下,皆使用同一个返回结果。
StrictSlash
对于这个问题 Gorilla Mux 提供了一个 StrictSlash(value bool) 函数,我们先来试试:
在 main 函数中,请将以下这一行:
router := mux.NewRouter()
修改为:
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
...func removeTrailingSlash(next http.Handler) http.Handler {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {r.URL.Path = strings.TrimSuffix(r.URL.Path, "/")next.ServeHTTP(w, r)})}func main() {...http.ListenAndServe(":3000", removeTrailingSlash(router))}Copy
我们使用 strings 包提供的 TrimSuffix(s, suffix string) string 函数来移除 / 后缀,如果不带斜杆后缀的话,r.URL.Path 将会被原封不动地返回。
修改保持后,再次访问 localhost:3000/about/,可见正常的结果
偶然间,我们又发现了另一个问题,访问主页 localhost:3000/ 会出现错误
那是因为我们的 removeTrailingSlash 中这一段:
r.URL.Path = strings.TrimSuffix(r.URL.Path, "/")
会将我们的首页 URL / 给去除了,解决方法是把 / 路径排除在外:
main.go
func removeTrailingSlash(next http.Handler) http.Handler {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {// 1. 除首页以外,移除所有请求路径后面的斜杆if r.URL.Path != "/" {r.URL.Path = strings.TrimSuffix(r.URL.Path, "/")}// 2. 将请求传递下去next.ServeHTTP(w, r)})}
保存修改后,再次访问 localhost:3000/ ,可见正常的结果
