一:golang自带路由介绍

golang自带路由库 http.ServerMux ,实际上是一个 map[string]Handler,是请求的url路径和该url路径对于的一个处理函数的映射关系。这个实现比较简单,有一些缺点:

  1. 不支持参数设定,例如/user/:uid 这种泛型类型匹配
  2. 无法很友好的支持REST模式,无法限制访问方法(POST,GET等)
  3. 也不支持正则

    二:gorilla/mux路由

    github地址:https://github.com/gorilla/mux http://www.gorillatoolkit.org/pkg/mux https://github.com/gorilla/mux#examples

上面所指出来的glang自带路由的缺点,gorilla/mux 都具备,而且还兼容 http.ServerMux。除了支持路径正则,命名路由,还支持中间件等等功能。所以mux是一个短小精悍,功能很全的路由。

1. 普通路由

示例 demo1.go

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/gorilla/mux"
  5. "net/http"
  6. )
  7. func main() {
  8. r := mux.NewRouter()
  9. //普通路由
  10. r.HandleFunc("/", IndexHandler)
  11. r.HandleFunc("/products", ProductsHandler)
  12. http.ListenAndServe(":8080", r)
  13. }
  14. func IndexHandler(w http.ResponseWriter, r *http.Request) {
  15. w.WriteHeader(http.StatusOK)
  16. fmt.Fprintf(w, "hello world")
  17. }
  18. func ProductsHandler(w http.ResponseWriter, r *http.Request) {
  19. w.WriteHeader(http.StatusOK)
  20. fmt.Fprintf(w, "hello, Products")
  21. }

上面mux的普通路由是不是似曾相识,跟golang标准库用法一样

在浏览器访问:http://localhost:8080/products
输出:hello, Products

2. 参数路由

参数路由,可以是普通路由,还可以是正则匹配
示例 demo2.go:

  1. package main
  2. import (
  3. "net/http"
  4. "fmt"
  5. "github.com/gorilla/mux"
  6. )
  7. //路由参数
  8. func main() {
  9. r := mux.NewRouter()
  10. //1. 普通路由参数
  11. // r.HandleFunc("/articles/{title}", TitleHandler)
  12. //2. 正则路由参数,下面例子中限制为英文字母
  13. r.HandleFunc("/articles/{title:[a-z]+}", TitleHandler)
  14. http.ListenAndServe(":8080", r)
  15. }
  16. //https://github.com/gorilla/mux#examples
  17. func TitleHandler(w http.ResponseWriter, r *http.Request) {
  18. vars := mux.Vars(r) // 获取参数
  19. w.WriteHeader(http.StatusOK)
  20. fmt.Fprintf(w, "title: %v\n", vars["title"])
  21. }

第1个普通路由参数,就是啥参数都可以,不管是字母,数字,还是中文等
第2个正则路由参数,限制了只能是英文字母,否则会报 404 page not found

3. 路由匹配 Matching Routes

https://github.com/gorilla/mux#matching-routes
我们也可以限制路由或者子路由。

3.1 匹配host

  1. r := mux.NewRouter()
  2. //只匹配 www.example.com
  3. r.Host("www.example.com")
  4. // 动态匹配子路由
  5. r.Host("{subdomain:[a-z]+}.example.com")

3.2 更多的一些其他匹配

见下面的更多匹配的例子:

  1. r := mux.NewRouter()
  2. r.PathPrefix("/products/") //前缀匹配
  3. r.Methods("GET", "POST") //请求方法匹配
  4. r.Schemes("https") //schemes
  5. r.Headers("X-Requested-With", "XMLHttpRequest") //header 匹配
  6. r.Queries("key", "value") //query的值匹配
  7. // 用户自定义方法 匹配
  8. r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool {
  9. return r.ProtoMajor == 0
  10. })

把上面的联合起来在一个单独的route里

  1. r.HandleFunc("/products", ProductsHandler).
  2. Host("www.example.com").
  3. Methods("GET").
  4. Schemes("http")

3.3 子路由匹配

Subrouter() 可以设置子路由

  1. r := mux.NewRouter()
  2. s := r.Host("www.example.com").Subrouter()
  3. s.HandleFunc("/products/", ProductsHandler)
  4. s.HandleFunc("/products/{key}", ProductHandler)
  5. s.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)

3.4 多个路由匹配的顺序

如果有多个路由添加到路由器里面,那么匹配顺序是怎么样?按照添加的先后顺序匹配。比如有2个路由都匹配了,那么优先匹配第一个路由。

  1. r := mux.NewRouter()
  2. r.HandleFunc("/specific", specificHandler)
  3. r.PathPrefix("/").Handler(catchAllHandler)

4. 设置路由前缀

PathPrefix() 设置路由前缀

  1. r := mux.NewRouter()
  2. //PathPrefix() 可以设置路由前缀
  3. product := r.PathPrefix("/products").HandleFunc("/", ProductsHandler)

路由前缀一般情况下不会单独使用,而是和子路由结合起来用,实现路由分组

5. 分组路由

可以根据前面的子路由和路由前缀的功能,综合运用就可以设置分组路由了
实例:grouprouter.go

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/gorilla/mux"
  5. "net/http"
  6. )
  7. //子路由, 分组路由
  8. func main() {
  9. r := mux.NewRouter()
  10. //PathPrefix() 可以设置路由前缀,设置路由前缀为products
  11. products := r.PathPrefix("/products").Subrouter()
  12. //"http://localhost:8080/products/", 最后面的斜线一定要,不然路由不正确,页面出现404
  13. products.HandleFunc("/", ProductsHandler)
  14. //"http://localhost:8080/products/{key}"
  15. products.HandleFunc("/{key}", ProductHandler)
  16. users := r.PathPrefix("/users").Subrouter()
  17. // "/users"
  18. users.HandleFunc("/", UsersHandler)
  19. // "/users/id/参数/name/参数"
  20. users.HandleFunc("/id/{id}/name/{name}", UserHandler)
  21. http.ListenAndServe(":8080", r)
  22. }
  23. func ProductsHandler(w http.ResponseWriter, r *http.Request) {
  24. w.WriteHeader(http.StatusOK)
  25. fmt.Fprintf(w, "%s", "products")
  26. }
  27. func ProductHandler(w http.ResponseWriter, r *http.Request) {
  28. vars := mux.Vars(r) //获取路由的值
  29. fmt.Fprintf(w, "key: %s", vars["key"])
  30. }
  31. func UsersHandler(w http.ResponseWriter, r *http.Request) {
  32. fmt.Fprintf(w, " %s \r\n", "users handler")
  33. }
  34. func UserHandler(w http.ResponseWriter, r *http.Request) {
  35. vars := mux.Vars(r) //获取值
  36. id := vars["id"]
  37. name := vars["name"]
  38. fmt.Fprintf(w, "id: %s, name: %s \r\n", id, name)
  39. }

6. 路由中间

https://github.com/gorilla/mux#middleware
Mux middlewares are defined using the de facto standard type: 在mux中路由中间件的定义

type MiddlewareFunc func(http.Handler) http.Handler

示例1:middleware1.go

  1. package main
  2. import (
  3. "fmt"
  4. "net/http"
  5. "github.com/gorilla/mux"
  6. )
  7. func main() {
  8. r := mux.NewRouter()
  9. r.HandleFunc("/", handler)
  10. r.Use(loggingMiddleware)
  11. http.ListenAndServe(":8080", r)
  12. }
  13. func loggingMiddleware(next http.Handler) http.Handler {
  14. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  15. //Do stuff here
  16. fmt.Println(r.RequestURI)
  17. fmt.Fprintf(w, "%s\r\n", r.URL)
  18. // Call the next handler, which can be another middleware in the chain, or the final handler.
  19. next.ServeHTTP(w, r)
  20. })
  21. }
  22. func handler(w http.ResponseWriter, r *http.Request) {
  23. w.Write([]byte("handle middleware"))
  24. fmt.Println("print handler")
  25. }

示例2:middleware2.go

在来看一个复杂点的例子:

  1. package main
  2. import (
  3. "fmt"
  4. "net/http"
  5. "strings"
  6. "github.com/gorilla/mux"
  7. )
  8. type authMiddleware struct {
  9. tokenUsers map[string]string
  10. }
  11. func (amw *authMiddleware) Populate() {
  12. amw.tokenUsers = make(map[string]string)
  13. amw.tokenUsers["000"] = "user0"
  14. amw.tokenUsers["aaa"] = "userA"
  15. amw.tokenUsers["05ft"] = "randomUser"
  16. amw.tokenUsers["deadbeef"] = "user0"
  17. }
  18. func (amw *authMiddleware) Middleware(next http.Handler) http.Handler {
  19. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  20. token := strings.Trim(r.Header.Get("X-Session-Token"), " ")
  21. if token == "" {
  22. fmt.Fprintf(w, "token is error \r\n")
  23. }
  24. if user, found := amw.tokenUsers[token]; found {
  25. //we found the token in out map
  26. fmt.Printf("Authenticated user: %s\n", user)
  27. fmt.Fprintf(w, "Authenticated user: %s\n", user)
  28. // Pass down the request to the next middleware (or final handler)
  29. next.ServeHTTP(w, r)
  30. } else {
  31. // Write an error and stop the handler chain
  32. http.Error(w, "Forbidden", http.StatusForbidden)
  33. }
  34. })
  35. }
  36. func main() {
  37. r := mux.NewRouter()
  38. r.HandleFunc("/", handler)
  39. amw := authMiddleware{}
  40. amw.Populate()
  41. r.Use(amw.Middleware)
  42. http.ListenAndServe(":8080", r)
  43. }
  44. func handler(w http.ResponseWriter, r *http.Request) {
  45. w.Write([]byte("handler"))
  46. }

7. Walking Routes 遍历注册的所有路由

  1. package main
  2. import (
  3. "fmt"
  4. "net/http"
  5. "strings"
  6. "github.com/gorilla/mux"
  7. )
  8. func handler(w http.ResponseWriter, r *http.Request) {
  9. return
  10. }
  11. //https://github.com/gorilla/mux#walking-routes
  12. func main() {
  13. r := mux.NewRouter()
  14. r.HandleFunc("/", handler)
  15. r.HandleFunc("/products", handler).Methods("POST")
  16. r.HandleFunc("/articles", handler).Methods("GET")
  17. r.HandleFunc("/articles/{id}", handler).Methods("GET", "PUT")
  18. r.HandleFunc("/authors", handler).Queries("surname", "{surname}")
  19. err := r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
  20. pathTemplate, err := route.GetPathTemplate()
  21. if err == nil {
  22. fmt.Println("ROUTE:", pathTemplate)
  23. }
  24. pathRegexp, err := route.GetPathRegexp()
  25. if err == nil {
  26. fmt.Println("Path regexp:", pathRegexp)
  27. }
  28. queriesTemplates, err := route.GetQueriesTemplates()
  29. if err == nil {
  30. fmt.Println("Queries templates:", strings.Join(queriesTemplates, ","))
  31. }
  32. queriesRegexps, err := route.GetQueriesRegexp()
  33. if err == nil {
  34. fmt.Println("Queries regexps:", strings.Join(queriesRegexps, ","))
  35. }
  36. methods, err := route.GetMethods()
  37. if err == nil {
  38. fmt.Println("Methods:", strings.Join(methods, ","))
  39. }
  40. fmt.Println()
  41. return nil
  42. })
  43. if err != nil {
  44. fmt.Println(err)
  45. }
  46. http.Handle("/", r)
  47. http.ListenAndServe(":8080", nil)
  48. }

8. 其他示例

请求方法限制

demo3.go:

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/gorilla/mux"
  5. "net/http"
  6. )
  7. // 请求方法的限制, Methods()
  8. func main() {
  9. r := mux.NewRouter()
  10. r.HandleFunc("/products", ProductsHandler).Methods("GET", "POST")
  11. r.Handle("/products/{id}", &ProductsIdHandler{}).Methods("GET")
  12. http.ListenAndServe(":8080", r)
  13. }
  14. func ProductsHandler(w http.ResponseWriter, r *http.Request) {
  15. w.WriteHeader(http.StatusOK)
  16. fmt.Fprintf(w, "hello, products! ")
  17. }
  18. type ProductsIdHandler struct{}
  19. func (handler *ProductsIdHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  20. vars := mux.Vars(r)
  21. w.WriteHeader(http.StatusOK)
  22. fmt.Fprintf(w, "products id: %s", vars["id"])
  23. }

请求头限制

在路由定义中可以通过Headers() 方法来限制设置请求头的匹配。
demo4.go

  1. package main
  2. import (
  3. "fmt"
  4. "net/http"
  5. "github.com/gorilla/mux"
  6. )
  7. // 请求头的限制,用Headers() 来限制
  8. func main() {
  9. r := mux.NewRouter()
  10. r.HandleFunc("/products", func(w http.ResponseWriter, r *http.Request) {
  11. header := "Request-Limit-Test"
  12. fmt.Fprintf(w, "contain headers: %s = %s \n", header, r.Header[header])
  13. }).Headers("Request-Limit-Test", "RequestLimitTest").Methods("POST")
  14. http.ListenAndServe(":8080", r)
  15. }

自定义匹配规则

用 MatcherFunc() 来自定义规则
示例 demo5.go:**

  1. package main
  2. import (
  3. "fmt"
  4. "net/http"
  5. "github.com/gorilla/mux"
  6. )
  7. //自定义匹配 MatcherFunc()
  8. func main() {
  9. r := mux.NewRouter()
  10. r.HandleFunc("/products/matcher", func(w http.ResponseWriter, r *http.Request) {
  11. fmt.Fprintf(w, "FormValue: %s ", r.FormValue("func"))
  12. }).MatcherFunc(func(req *http.Request, match *mux.RouteMatch) bool {
  13. b := false
  14. if req.FormValue("func") == "matcherfunc" {
  15. b = true
  16. }
  17. return b
  18. })
  19. http.ListenAndServe(":8080", r)
  20. }

在浏览器中:http://127.0.0.1:8080/products/matcher?func=matcherfunc
输出:FormValue: matcherfunc

命名路由 Registered URLs

namerouter.go

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/gorilla/mux"
  5. // "log"
  6. "net/http"
  7. )
  8. // 命名路由 Name(), 获取路由URL, URL()
  9. func main() {
  10. r := mux.NewRouter()
  11. r.HandleFunc("/products/{category}/{id:[0-9]+}", ProductHandler).Name("product")
  12. //获取路由的URL
  13. url1, err := r.Get("product").URL()
  14. fmt.Println(err) //error: mux: number of parameters must be multiple of 2, got [/]
  15. if err == nil {
  16. fmt.Println("get URL: \r\n", url1)
  17. }
  18. //获取路由的url后,也可以拼装你需要的URL
  19. url2, err := r.Get("product").URL("category", "tech", "id", "13")
  20. if err == nil {
  21. fmt.Println("new url: ", url2) //new url: /products/tech/13
  22. }
  23. http.ListenAndServe(":8080", r)
  24. }
  25. func ProductHandler(w http.ResponseWriter, r *http.Request) {
  26. w.WriteHeader(http.StatusOK)
  27. vars := mux.Vars(r)
  28. fmt.Fprintf(w, "url: %s, category: %s, id: %s", r.URL, vars["category"], vars["id"])
  29. //浏览器: http://localhost:8080/products/id/23
  30. //output
  31. //url: /products/id/23, category: id, id: 23
  32. }

根据命名的路由来获取路由URL r.Get(“product”).URL()

三:参考

https://github.com/gorilla/mux