摒弃世俗浮躁
追求技术精湛

Golang笔记(二):路由和中间件

ServeMux 和 Handler

ServeMux 本质上是一个 HTTP 请求路由器(或者叫多路复用器,Multiplexor)。它把收到的请求与一组预先定义的 URL 路径列表做对比,然后在匹配到路径的时候调用关联的处理器(Handler)。

ServeMux用法

func defaultHandler(w http.ResponseWriter, r *http.Request) {
        // 此处代码省略...
}
func aboutHandler(w http.ResponseWriter, r *http.Request) {
        // 此处代码省略...
}

func main() {
	router := http.NewServeMux()
	router.HandleFunc("/", defaultHandler)
	router.HandleFunc("/about", aboutHandler)
	http.ListenAndServe(":3000", router)
	return
}

ServeMux的局限性

  • 不支持URI路径参数(请求当中的参数无法直接获取,需要字符串分割后获取 或其他方式进行获取)
  • 不支持请求方法过滤(无法直接区分是GET请求还是POST请求,需要手动去判断后再写逻辑)
  • 不支持路由命名(其他页面使用的时候只能写具体的URI,后续维护成本高)

TIPS:标准库里的简单、高效、稳定、兼容性强,但不一定是最好的或是最快的

用gorilla/mux替代ServeMux

执行命令拉取包

$ go get -u github.com/gorilla/mux

获取router实例的时候 不要再去newServeMux了,直接去改成NewRouter

example:

	// router := http.NewServeMux()
	router := mux.NewRouter()
	router.HandleFunc("/", homeHandler).Name("home")
	router.HandleFunc("/about", aboutHandler).Name("about")

注意:gorilla/mux 的路由解析采用的是 精准匹配 规则,而 net/http 包使用的是 长度优先匹配 规则。

  • 精准匹配:指路由只会匹配准确指定的规则,这个比较好理解,也是较常见的匹配方式。
  • 长度优先匹配:一般用在静态路由上(不支持动态元素如正则和 URL 路径参数),优先匹配字符数较多的规则。

使用中间件

func forceHTMLMiddleware(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// 设置Header
		w.Header().Set("Content-Type", "text/html; charset=UTF-8")
		// 继续处理请求
		h.ServeHTTP(w, r)
	})
}

func main() {
	... 省略代码 ...

	// 使用中间件
	router.Use(forceHTMLMiddleware)
}

gorilla/mux末尾斜杠问题处理

默认使用gorilla/mux的时候 访问 http://localhost/about 页面是正常的,但访问 http://localhost/about/却显示404,因为末尾的 / 没有被Router匹配到导致的。
获取路由实例的时候我们可以这样写:

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

这样写看似上面的问题已经得到了解决,其实并没有!当你发起POST请求的时候,会将该请求通过301跳转,导致POST请求变成了GET请求(又引发了另外一个问题)。
我们在StrictSlash doc当中看到了这么一段话:

The re-direct is a HTTP 301 (Moved Permanently). Note that when this is set for routes with a non-idempotent method (e.g. POST, PUT), the subsequent re-directed request will be made as a GET by most clients. Use middleware or client settings to modify this behaviour as needed.

大概意思就是需要让你自己通过使用中间件或者客户端设置来修改这个问题
于是,可以通过下面这个前置中间件来解决该问题:

func removeTrailingSlash(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/" {
			r.URL.Path = strings.TrimSuffix(r.URL.Path, "/")
		}
		next.ServeHTTP(w, r)
	})
}
func main() {
	... 省略代码 ...
	http.ListenAndServe(":3000", removeTrailingSlash(router))
}

gorilla/mux 完整示例

package main

import (
	"fmt"
	"github.com/gorilla/mux"
	"net/http"
	"strings"
)

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:abc@example.com\">abc@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(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// 设置Header
		w.Header().Set("Content-Type", "text/html; charset=UTF-8")
		// 继续处理请求
		h.ServeHTTP(w, r)
	})
}

func removeTrailingSlash(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/" {
			r.URL.Path = strings.TrimSuffix(r.URL.Path, "/")
		}
		next.ServeHTTP(w, r)
	})
}

func main() {
	router := mux.NewRouter()
	router.HandleFunc("/", homeHandler).Name("home")
	router.HandleFunc("/about", aboutHandler).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)

	// 使用中间件
	router.Use(forceHTMLMiddleware)

	homeURL, _ := router.Get("home").URL()
	fmt.Println("homeURL: ", homeURL)
	articlesURL, _ := router.Get("articles.show").URL("id", "1")
	fmt.Println("articlesURL: ", articlesURL)

	http.ListenAndServe(":3000", removeTrailingSlash(router))
	return
}
赞(1) 打赏
未经允许不得转载:时光日记 » Golang笔记(二):路由和中间件

评论 抢沙发

评论前必须登录!

 

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏