router.POST("/login", func(c *gin.Context) {
var user User
err := c.Bind(&user)
if err != nil {
fmt.Println(err)
log.Fatal(err)
}
c.JSON(http.StatusOK, gin.H{
"username": user.Username,
"passwd": user.Passwd,
"age": user.Age,
})
})
多格式渲染
既然请求可以使用不同的content-type,响应也如此。通常响应会有html,text,plain,json和xml等。
gin提供了很优雅的渲染方法。到目前为止,我们已经见识了c.String, c.JSON,c.HTML,下面介绍一下c.XML。
router.GET("/render", func(c *gin.Context) {
contentType := c.DefaultQuery("content_type", "json")
if contentType == "json" {
c.JSON(http.StatusOK, gin.H{
"user": "rsj217",
"passwd": "123",
})
} else if contentType == "xml" {
c.XML(http.StatusOK, gin.H{
"user": "rsj217",
"passwd": "123",
})
}
})
View Code
结果如下:
☁ ~ curl http://127.0.0.1:8000/render\?content_type\=json
{"passwd":"123","user":"rsj217"}
☁ ~ curl http://127.0.0.1:8000/render\?content_type\=xml
<map><user>rsj217</user><passwd>123</passwd></map>%
View Code
重定向
gin对于重定向的请求,相当简单。调用上下文的Redirect方法:
router.GET("/redict/google", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "https://google.com")
})
View Code
分组路由
熟悉Flask的同学应该很了解蓝图分组。Flask提供了蓝图用于管理组织分组api。gin也提供了这样的功能,让你的代码逻辑更加模块化,同时分组也易于定义中间件的使用范围。
v1 := router.Group("/v1")
v1.GET("/login", func(c *gin.Context) {
c.String(http.StatusOK, "v1 login")
})
v2 := router.Group("/v2")
v2.GET("/login", func(c *gin.Context) {
c.String(http.StatusOK, "v2 login")
})
View Code
访问效果如下:
☁ ~ curl http://127.0.0.1:8000/v1/login
v1 login% ☁ ~ curl http://127.0.0.1:8000/v2/login
v2 login%
View Code
middleware中间件
golang的net/http设计的一大特点就是特别容易构建中间件。gin也提供了类似的中间件。需要注意的是中间件只对注册过的路由函数起作用。对于分组路由,嵌套使用中间件,可以限定中间件的作用范围。中间件分为全局中间件,单个路由中间件和群组中间件。
全局中间件
先定义一个中间件函数:
func MiddleWare() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("before middleware")
c.Set("request", "clinet_request")
c.Next()
fmt.Println("before middleware")
}
}
View Code
该函数很简单,只会给c上下文添加一个属性,并赋值。后面的路由处理器,可以根据被中间件装饰后提取其值。需要注意,虽然名为全局中间件,只要注册中间件的过程之前设置的路由,将不会受注册的中间件所影响。只有注册了中间件一下代码的路由函数规则,才会被中间件装饰。
router.Use(MiddleWare())
{
router.GET("/middleware", func(c *gin.Context) {
request := c.MustGet("request").(string)
req, _ := c.Get("request")
c.JSON(http.StatusOK, gin.H{
"middile_request": request,
"request": req,
})
})
}
View Code
使用router装饰中间件,然后在/middlerware
即可读取request的值,注意在router.Use(MiddleWare())
代码以上的路由函数,将不会有被中间件装饰的效果。使用花括号包含被装饰的路由函数只是一个代码规范,即使没有被包含在内的路由函数,只要使用router进行路由,都等于被装饰了。想要区分权限范围,可以使用组返回的对象注册中间件。
☁ ~ curl http://127.0.0.1:8000/middleware
{"middile_request":"clinet_request","request":"clinet_request"}
View Code
如果没有注册就使用MustGet
方法读取c的值将会抛错,可以使用Get方法取而代之。
上面的注册装饰方式,会让所有下面所写的代码都默认使用了router的注册过的中间件。
单个路由中间件
当然,gin也提供了针对指定的路由函数进行注册。
router.GET("/before", MiddleWare(), func(c *gin.Context) {
request := c.MustGet("request").(string)
c.JSON(http.StatusOK, gin.H{
"middile_request": request,
})
})
View Code
把上述代码写在 router.Use(Middleware())之前,同样也能看见/before
被装饰了中间件。
群组中间件
群组的中间件也类似,只要在对于的群组路由上注册中间件函数即可:
authorized := router.Group("/", MyMiddelware())
// 或者这样用:
authorized := router.Group("/")
authorized.Use(MyMiddelware())
{
authorized.POST("/login", loginEndpoint)
}
View Code
群组可以嵌套,因为中间件也可以根据群组的嵌套规则嵌套。
中间件实践
中间件最大的作用,莫过于用于一些记录log,错误handler,还有就是对部分接口的鉴权。下面就实现一个简易的鉴权中间件。
router.GET("/auth/signin", func(c *gin.Context) {
cookie := &http.Cookie{
Name: "session_id",
Value: "123",
Path: "/",
HttpOnly: true,
}
http.SetCookie(c.Writer, cookie)
c.String(http.StatusOK, "Login successful")
})
router.GET("/home", AuthMiddleWare(), func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"data": "home"})
})
View Code
登录函数会设置一个session_id的cookie,注意这里需要指定path为/
,不然gin会自动设置cookie的path为/auth
,一个特别奇怪的问题。/homne
的逻辑很简单,使用中间件AuthMiddleWare注册之后,将会先执行AuthMiddleWare的逻辑,然后才到/home
的逻辑。
AuthMiddleWare的代码如下:
func AuthMiddleWare() gin.HandlerFunc {
return func(c *gin.Context) {
if cookie, err := c.Request.Cookie("session_id"); err == nil {
value := cookie.Value
fmt.Println(value)
if value == "123" {
c.Next()
return
}
}
c.JSON(http.StatusUnauthorized, gin.H{
"error": "Unauthorized",
})
c.Abort()
return
}
}
View Code
从上下文的请求中读取cookie,然后校对cookie,如果有问题,则终止请求,直接返回,这里使用了c.Abort()方法。
In [7]: resp = requests.get('http://127.0.0.1:8000/home')
In [8]: resp.json()
Out[8]: {u'error': u'Unauthorized'}
In [9]: login = requests.get('http://127.0.0.1:8000/auth/signin')
In [10]: login.cookies
Out[10]: <RequestsCookieJar[Cookie(version=0, name='session_id', value='123', port=None, port_specified=False, domain='127.0.0.1', domain_specified=False, domain_initial_dot=False, path='/', path_specified=True, secure=False, expires=None, discard=True, comment=None, comment_url=None, rest={'HttpOnly': None}, rfc2109=False)]>
In [11]: resp = requests.get('http://127.0.0.1:8000/home', cookies=login.cookies)
In [12]: resp.json()
Out[12]: {u'data': u'home'}
View Code
异步协程
golang的高并发一大利器就是协程。gin里可以借助协程实现异步任务。因为涉及异步过程,请求的上下文需要copy到异步的上下文,并且这个上下文是只读的。
router.GET("/sync", func(c *gin.Context) {
time.Sleep(5 * time.Second)
log.Println("Done! in path" + c.Request.URL.Path)
})
router.GET("/async", func(c *gin.Context) {
cCp := c.Copy()
go func() {
time.Sleep(5 * time.Second)
log.Println("Done! in path" + cCp.Request.URL.Path)
}()
})
View Code
在请求的时候,sleep5秒钟,同步的逻辑可以看到,服务的进程睡眠了。异步的逻辑则看到响应返回了,然后程序还在后台的协程处理。
自定义router
gin不仅可以使用框架本身的router进行Run,也可以配合使用net/http本身的功能:
func main() {
router := gin.Default()
http.ListenAndServe(":8080", router)
}
或者
func main() {
router := gin.Default()
s := &http.Server{
Addr: ":8000",
Handler: router,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
s.ListenAndServe()
}
View Code
当然还有一个优雅的重启和结束进程的方案。后面将会探索使用supervisor管理golang的进程。
总结
Gin是一个轻巧而强大的golang web框架。涉及常见开发的功能,我们都做了简单的介绍。关于服务的启动,请求参数的处理和响应格式的渲染,以及针对上传和中间件鉴权做了例子。更好的掌握来自实践,同时gin的源码注释很详细,可以阅读源码了解更多详细的功能和魔法特性。