HttpRouter

寒江蓑笠翁大约 7 分钟

HttpRouter

仓库地址: julienschmidt/httprouter: A high performance HTTP request router that scales well (github.com)open in new window

Gin的路由组件采用的是HttpRouter,这同样也是一个轻量,高性能的路由组件,整个组件只有三个.go文件,代码十分的简洁,其主要有以下特点。

一对一匹配:一个请求只能匹配到零个或一个路由,且有利于SEO优化。

路径自动校正:随意选择喜欢的URL风格,就算多了或者少了一个斜杠,也会自动重定向。如果有大小写错误的话,查找时也会忽略大小写进行正确的重定向。

路由参数自动解析:只要给路径段一个名称,路由器就会把动态值传递给你。由于路由器的设计,路径参数解析的占用非常低廉。

零垃圾:在路由分配与调度的过程中,不会产生任何内存垃圾。

RefstfulAPI支持:路由器的设计鼓励合理的分层的Restful API。

错误处理:可以设置一个错误处理器来处理请求中的异常,路由器会将其捕获并记录,然后重定向到错误页面。

基本用法

就像是springboot一样,一个函数绑定一个URL且对应一个处理器。

package main

import (
   "fmt"
   "github.com/julienschmidt/httprouter"
   "log"
   "net/http"
)

func Hello(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
   fmt.Fprintf(w, "<h1>Hello World!")
}

func main() {
   router := httprouter.New()
   router.GET("/hello", Hello)
   log.Fatal(http.ListenAndServe(":8080", router))
}

随后用浏览器或者任何的接口测试工具输入127.0.0.1:8080,即可看到正确的内容,我们可以看到HttpRouter仅仅只是做了路由,实际上依旧是采用的net/http默认组件,gin也是如此,只不过封装的相对而言要更深一点。

命名参数

package main

import (
   "fmt"
   "github.com/julienschmidt/httprouter"
   "log"
   "net/http"
)

func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
   fmt.Fprintf(w, "<h1>Hello World!")
}

func main() {
   router := httprouter.New()
   router.GET("/hello:name", Hello)
   log.Fatal(http.ListenAndServe(":8080", router))
}

这次的路由后面加了一个:namename就是一个命名参数,可以通过httprouter.Params来访问参数切片,可以通过索引或者ByName(name)来获取参数。同样的,你可以把http.handlerhttp.handlerFunc当作httprouter.handler来使用,路由本身实现了其接口,例如下方的例子。

func Hello(w http.ResponseWriter, r *http.Request) {
    params := httprouter.ParamsFromContext(r.Context())
    //params := r.Context().Value(httprouter.ParamsKey)也可以

    fmt.Fprintf(w, "hello, %s!\n", params.ByName("name"))
}

当方法绑定的路由是/user/:user,下面的几种URL的匹配情况

 /user/gordon              match
 /user/you                 match
 /user/gordon/profile      no match
 /user/                    no match

你不能将/user/new/user/:user注册到同一个请求方法上,每一个请求方法应当是相互独立的。

捕获全部参数

第二种类型是捕获全部参数,顾名思义,他们匹配一切,因此必须位于Pattern的尾部。

Pattern: /src/*filepath

 /src/                     match
 /src/somefile.go          match
 /src/subdir/somefile.go   match

HttpRouter的工作原理是构建大量的前缀树,感兴趣的可以了解:httprouter package - github.com/julienschmidt/httprouter - Go Packagesopen in new window

OPTIONS & CORS

有些人可能会希望修改对于OPTIONS的自动响应并设置一些响应头来适配CORS的预检请求,这些需求可以通过使用Router.GlobalOPTIONShandler来实现。

router.GlobalOPTIONS = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    if r.Header.Get("Access-Control-Request-Method") != "" {
        // 设置CORS响应头
        header := w.Header()
        header.Set("Access-Control-Allow-Methods", r.Header.Get("Allow"))
        header.Set("Access-Control-Allow-Origin", "*")
    }

    // 适配状态码204
    w.WriteHeader(http.StatusNoContent)
})

NOT FOUND 处理器

提示

可能需要关闭Router.HandleMethodNotAllowed,来避免一些问题。

router.NotFound = http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
	//你的逻辑
})

基本校验

package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/julienschmidt/httprouter"
)

func BasicAuth(h httprouter.Handle, requiredUser, requiredPassword string) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
		//获取基本的身份凭据
		user, password, hasAuth := r.BasicAuth()

		if hasAuth && user == requiredUser && password == requiredPassword {
			// 将请求委派给给予的处理器
			h(w, r, ps)
		} else {
			// 否则请求认证
			w.Header().Set("WWW-Authenticate", "Basic realm=Restricted")
			http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
		}
	}
}

func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	fmt.Fprint(w, "Not protected!\n")
}

func Protected(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	fmt.Fprint(w, "Protected!\n")
}

func main() {
	user := "gordon"
	pass := "secret!"

	router := httprouter.New()
	router.GET("/", Index)
	router.GET("/protected/", BasicAuth(Protected, user, pass))

	log.Fatal(http.ListenAndServe(":8080", router))
}