Golang 微框架 Gin 简介

Gin

  Gin是一个golang的微框架,封装比较优雅,API友好,源码注释比较明确,已经发布了1.0版本。具备快速灵活,容错方便等特色。其实对于golang而言,web框架的依赖要远比Python,Java之类的要小。自身的net/http足够简单,性能也很是不错。框架更像是一些经常使用函数或者工具的集合。借助框架开发,不只能够省去不少经常使用的封装带来的时间,也有助于团队的编码风格和造成规范。html

下面就Gin的用法作一个简单的介绍。python

首先须要安装,安装比较简单,使用go get便可:git

go get -u github.com/gin-gonic/gin

Hello World

使用Gin实现Hello world很是简单,建立一个router,而后使用其Run的方法:github

import (
    "gopkg.in/gin-gonic/gin.v1"
    "net/http"
)

func main(){
    
    router := gin.Default()

    router.GET("/", func(c *gin.Context) {
        c.String(http.StatusOK, "Hello World")
    })
    router.Run(":8000")
}
简单几行代码,就能实现一个web服务。使用gin的Default方法建立一个路由handler。而后经过HTTP方法绑定路由规则和路由函数。不一样于net/http库的路由函数,gin进行了封装,把request和response都封装到gin.Context的上下文环境。最后是启动路由的Run方法监听端口。麻雀虽小,五脏俱全。固然,除了GET方法,gin也支持POST,PUT,DELETE,OPTION等经常使用的restful方法。

restful路由

gin的路由来自httprouter库。所以httprouter具备的功能,gin也具备,不过gin不支持路由正则表达式:golang

func main(){
    router := gin.Default()
    
    router.GET("/user/:name", func(c *gin.Context) {
        name := c.Param("name")
        c.String(http.StatusOK, "Hello %s", name)
    })
}

冒号:加上一个参数名组成路由参数。可使用c.Params的方法读取其值。固然这个值是字串string。诸如/user/rsj217,和/user/hello均可以匹配,而/user//user/rsj217/不会被匹配。web

☁  ~  curl http://127.0.0.1:8000/user/rsj217
Hello rsj217%~  curl http://127.0.0.1:8000/user/rsj217/
404 page not found%~  curl http://127.0.0.1:8000/user/
404 page not found%

除了:,gin还提供了*号处理参数,*号能匹配的规则就更多。正则表达式

func main(){
    router := gin.Default()
    
    router.GET("/user/:name/*action", func(c *gin.Context) {
        name := c.Param("name")
        action := c.Param("action")
        message := name + " is " + action
        c.String(http.StatusOK, message)
    })
}

访问效果以下typescript

☁  ~  curl http://127.0.0.1:8000/user/rsj217/
rsj217 is /%~  curl http://127.0.0.1:8000/user/rsj217/中国
rsj217 is /中国%

query string参数与body参数

web提供的服务一般是client和server的交互。其中客户端向服务器发送请求,除了路由参数,其余的参数无非两种,查询字符串query string和报文体body参数。所谓query string,即路由用,用?之后链接的key1=value2&key2=value2的形式的参数。固然这个key-value是通过urlencode编码。json

query string

对于参数的处理,常常会出现参数不存在的状况,对因而否提供默认值,gin也考虑了,而且给出了一个优雅的方案:服务器

func main(){
    router := gin.Default()
    router.GET("/welcome", func(c *gin.Context) {
        firstname := c.DefaultQuery("firstname", "Guest")
        lastname := c.Query("lastname")

        c.String(http.StatusOK, "Hello %s %s", firstname, lastname)
    })
  router.Run()
}

使用c.DefaultQuery方法读取参数,其中当参数不存在的时候,提供一个默认值。使用Query方法读取正常参数,当参数不存在的时候,返回空字串:

☁  ~  curl http://127.0.0.1:8000/welcome
Hello Guest %~  curl http://127.0.0.1:8000/welcome\?firstname\=中国
Hello 中国 %~  curl http://127.0.0.1:8000/welcome\?firstname\=中国\&lastname\=天朝
Hello 中国 天朝%~  curl http://127.0.0.1:8000/welcome\?firstname\=\&lastname\=天朝
Hello  天朝%~  curl http://127.0.0.1:8000/welcome\?firstname\=%E4%B8%AD%E5%9B%BD
Hello 中国 %

之因此使用中文,是为了说明urlencode。注意,当firstname为空字串的时候,并不会使用默认的Guest值,空值也是值,DefaultQuery只做用于key不存在的时候,提供默认值。

body

http的报文体传输数据就比query string稍微复杂一点,常见的格式就有四种。例如application/jsonapplication/x-www-form-urlencoded, application/xmlmultipart/form-data。后面一个主要用于图片上传。json格式的很好理解,urlencode其实也不难,无非就是把query string的内容,放到了body体里,一样也须要urlencode。默认状况下,c.PostFROM解析的是x-www-form-urlencodedfrom-data的参数。

func main(){
    router := gin.Default()
    router.POST("/form_post", func(c *gin.Context) {
        message := c.PostForm("message")
        nick := c.DefaultPostForm("nick", "anonymous")

        c.JSON(http.StatusOK, gin.H{
            "status":  gin.H{
                "status_code": http.StatusOK,
                "status":      "ok",
            },
            "message": message,
            "nick":    nick,
        })
    })
}

与get处理query参数同样,post方法也提供了处理默认参数的状况。同理,若是参数不存在,将会获得空字串。

☁  ~  curl -X POST http://127.0.0.1:8000/form_post -H "Content-Type:application/x-www-form-urlencoded" -d "message=hello&nick=rsj217" | python -m json.tool

{
    "message": "hello",
    "nick": "rsj217",
    "status": {
        "status": "ok",
        "status_code": 200
    }
}

发送数据给服务端,并非post方法才行,put方法同样也能够。同时querystring和body也不是分开的,两个同时发送也能够:

func main(){
    router := gin.Default()
    
    router.PUT("/post", func(c *gin.Context) {
        id := c.Query("id")
        page := c.DefaultQuery("page", "0")
        name := c.PostForm("name")
        message := c.PostForm("message")
        fmt.Printf("id: %s; page: %s; name: %s; message: %s \n", id, page, name, message)
        c.JSON(http.StatusOK, gin.H{
            "status_code": http.StatusOK,
        })
    })
}

上面的例子,展现了同时使用查询字串和body参数发送数据给服务器。

表单

咱们先要写一个表单页面,所以须要引入gin如何render模板。前面咱们见识了c.String和c.JSON。下面就来看看c.HTML方法。

首先须要定义一个模板的文件夹。而后调用c.HTML渲染模板,能够经过gin.H给模板传值。至此,不管是String,JSON仍是HTML,以及后面的XML和YAML,均可以看到Gin封装的接口简明易用。

建立一个文件夹templates,而后再里面建立html文件login.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>upload</title>
</head>
<body>
<h3>Login</h3>
<form action="/form_post", method="post" >
    <input type="text" name="name" />
    <input type="text" name="password" />
    <input type="submit" value="提交" />
</form>

</body>
</html>

使用LoadHTMLGlob定义模板文件路径。

router.LoadHTMLGlob("templates/*")
    router.GET("/login", func(c *gin.Context) {
        c.HTML(http.StatusOK, "login.html", gin.H{})
    })

重定向

gin对于重定向的请求,至关简单。调用上下文的Redirect方法:

  router.GET("/redict/google", func(c *gin.Context) {
        c.Redirect(http.StatusMovedPermanently, "https://google.com")
    })

分组路由

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")
    })

访问效果以下:

☁  ~  curl http://127.0.0.1:8000/v1/login
v1 login%~  curl http://127.0.0.1:8000/v2/login
v2 login%

 

middleware中间件

golang的net/http设计的一大特色就是特别容易构建中间件。gin也提供了相似的中间件。须要注意的是中间件只对注册过的路由函数起做用。对于分组路由,嵌套使用中间件,能够限定中间件的做用范围。中间件分为全局中间件,单个路由中间件和群组中间件。

全局中间件

先定义一个中间件函数:

//全局中间件
func MiddleWare() gin.HandlerFunc  {
    return func(c *gin.Context) {
        c.Set("salt", "qingdao")
        c.Next()
    }
}

该函数很简单,只会给c上下文添加一个属性,并赋值。后面的路由处理器,能够根据被中间件装饰后提取其值。须要注意,虽然名为全局中间件,只要注册中间件的过程以前设置的路由,将不会受注册的中间件所影响。只有注册了中间件一下代码的路由函数规则,才会被中间件装饰。

//使用中间件
router.Use(MiddleWare())
{
    router.GET("/middleware", func(c *gin.Context) {
        salt, _ := c.Get("salt")
        c.JSON(http.StatusOK, gin.H{
            "salt":salt,
        })
    })
}

使用router装饰中间件,而后在/middlerware便可读取request的值,注意在router.Use(MiddleWare())代码以上的路由函数,将不会有被中间件装饰的效果。

使用花括号包含被装饰的路由函数只是一个代码规范,即便没有被包含在内的路由函数,只要使用router进行路由,都等于被装饰了。想要区分权限范围,可使用组返回的对象注册中间件。

☁  ~  curl  http://127.0.0.1:8000/middleware
{"salt":"qingdao"}

上面的注册装饰方式,会让全部下面所写的代码都默认使用了router的注册过的中间件。

单个路由中间件

固然,gin也提供了针对指定的路由函数进行注册。

router.GET("/before", MiddleWare(), func(c *gin.Context) {
        request := c.MustGet("request").(string)
        c.JSON(http.StatusOK, gin.H{
            "middile_request": request,
        })
    })

群组中间件

群组的中间件也相似,只要在对于的群组路由上注册中间件函数便可:

authorized := router.Group("/", MyMiddelware())
// 或者这样用:
authorized := router.Group("/")
authorized.Use(MyMiddelware())
{
    authorized.POST("/login", loginEndpoint)
}

群组能够嵌套,由于中间件也能够根据群组的嵌套规则嵌套。

中间件实践

中间件最大的做用,莫过于用于一些记录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"})
    })

登陆函数会设置一个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
    }
}

从上下文的请求中读取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'}
相关文章
相关标签/搜索