Golang 微框架 Gin 简介

2023-11-14

框架一直是敏捷开发中的利器,能让开发者很快的上手并做出应用,甚至有的时候,脱离了框架,一些开发者都不会写程序了。成长总不会一蹴而就,从写出程序获取成就感,再到精通框架,快速构造应用,当这些方面都得心应手的时候,可以尝试改造一些框架,或是自己创造一个。

曾经我以为Python世界里的框架已经够多了,后来发现相比golang简直小巫见大巫。golang提供的net/http库已经很好了,对于http的协议的实现非常好,基于此再造框架,也不会是难事,因此生态中出现了很多框架。既然构造框架的门槛变低了,那么低门槛同样也会带来质量参差不齐的框架。

考察了几个框架,通过其github的活跃度,维护的team,以及生产环境中的使用率。发现Gin还是一个可以学习的轻巧框架。

Gin

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

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

首先需要安装,安装比较简单,使用go get即可

go get gopkg.in/gin-gonic/gin.v1
View Code

gin的版本托管再 gopkg的网站上。我在安装的过程中,gokpg卡住了,后来不得不根据gin里的godep的文件,把响应的源码从github上下载,然后copy到对应的目录。

关于golang的包管理和依赖,我们以后再讨论。

Hello World

使用Gin实现Hello world非常简单,创建一个router,然后使用其Run的方法:

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不支持路由正则表达式:

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/不会被匹配。

☁ ~ 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) 
    }
 }
}

访问效果如下:

☁  ~  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编码。

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() }
View Code

使用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 % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 104 100 79 100 25 48555 15365 --:--:-- --:--:-- --:--:-- 79000 
{ "message": "hello", 
"nick": "rsj217", 
"status": { "status": "ok", "status_code": 200 } }

前面我们使用c.String返回响应,顾名思义则返回string类型。content-type是plain或者text。调用c.JSON则返回json数据。其中gin.H封装了生成json的方式,是一个强大的工具。使用golang可以像动态语言一样写字面量的json,对于嵌套json的实现,嵌套gin.H即可。

发送数据给服务端,并不是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参数发送数据给服务器。

文件上传

上传单个文件

前面介绍了基本的发送数据,其中multipart/form-data转用于文件上传。gin文件上传也很方便,和原生的net/http方法类似,不同在于gin把原生的request封装到c.Request中了。

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

    router.POST("/upload", func(c *gin.Context) {
        name := c.PostForm("name")
        fmt.Println(name)
        file, header, err := c.Request.FormFile("upload")
        if err != nil {
            c.String(http.StatusBadRequest, "Bad request")
            return
        }
        filename := header.Filename

        fmt.Println(file, err, filename)

        out, err := os.Create(filename)
        if err != nil {
            log.Fatal(err)
        }
        defer out.Close()
        _, err = io.Copy(out, file)
        if err != nil {
            log.Fatal(err)
        }
        c.String(http.StatusCreated, "upload successful")
    })
    router.Run(":8000")
}

 

 

使用c.Request.FormFile解析客户端文件name属性。如果不传文件,则会抛错,因此需要处理这个错误。一种方式是直接返回。然后使用os的操作,把文件数据复制到硬盘上。

使用下面的命令可以测试上传,注意upload为c.Request.FormFile指定的参数,其值必须要是绝对路径:

curl -X POST http://127.0.0.1:8000/upload -F "upload=@/Users/ghost/Desktop/pic.jpg" -H "Content-Type: multipart/form-data"

上传多个文件

单个文件上传很简单,别以为多个文件就会很麻烦。依葫芦画瓢,所谓多个文件,无非就是多一次遍历文件,然后一次copy数据存储即可。下面只写handler,省略main函数的初始化路由和开启服务器监听了:

router.POST("/multi/upload", func(c *gin.Context) {
        err := c.Request.ParseMultipartForm(200000)
        if err != nil {
            log.Fatal(err)
        }

        formdata := c.Request.MultipartForm 

        files := formdata.File["upload"] 
        for i, _ := range files { /
            file, err := files[i].Open()
            defer file.Close()
            if err != nil {
                log.Fatal(err)
            }

            out, err := os.Create(files[i].Filename)

            defer out.Close()

            if err != nil {
                log.Fatal(err)
            }

            _, err = io.Copy(out, file)

            if err != nil {
                log.Fatal(err)
            }

            c.String(http.StatusCreated, "upload successful")

        }

    })

 

 

与单个文件上传类似,只不过使用了c.Request.MultipartForm得到文件句柄,再获取文件数据,然后遍历读写。

使用curl上传

curl -X POST http://127.0.0.1:8000/multi/upload -F "upload=@/Users/ghost/Desktop/pic.jpg" -F "upload=@

表单上传

上面我们使用的都是curl上传,实际上,用户上传图片更多是通过表单,或者ajax和一些requests的请求完成。下面展示一下web的form表单如何上传。

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

首先需要定义一个模板的文件夹。然后调用c.HTML渲染模板,可以通过gin.H给模板传值。至此,无论是String,JSON还是HTML,以及后面的XML和YAML,都可以看到Gin封装的接口简明易用。

创建一个文件夹templates,然后再里面创建html文件upload.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>upload</title>
</head>
<body>
<h3>Single Upload</h3>
<form action="/upload", method="post" enctype="multipart/form-data">
    <input type="text" value="hello gin" />
    <input type="file" name="upload" />
    <input type="submit" value="upload" />
</form>


<h3>Multi Upload</h3>
<form action="/multi/upload", method="post" enctype="multipart/form-data">
    <input type="text" value="hello gin" />
    <input type="file" name="upload" />
    <input type="file" name="upload" />
    <input type="submit" value="upload" />
</form>

</body>
</html>

upload 很简单,没有参数。一个用于单个文件上传,一个用于多个文件上传。

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

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

参数绑定

我们已经见识了x-www-form-urlencoded类型的参数处理,现在越来越多的应用习惯使用JSON来通信,也就是无论返回的response还是提交的request,其content-type类型都是application/json的格式。而对于一些旧的web表单页还是x-www-form-urlencoded的形式,这就需要我们的服务器能改hold住这多种content-type的参数了。

Python的世界里很好解决,毕竟动态语言不需要实现定义数据模型。因此可以写一个装饰器将两个格式的数据封装成一个数据模型。golang中要处理并非易事,好在有gin,他们的model bind功能非常强大。

type User struct {
    Username string `form:"username" json:"username" binding:"required"`
    Passwd   string `form:"passwd" json:"passwd" bdinding:"required"`
    Age      int    `form:"age" json:"age"`
}

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

    router.POST("/login", func(c *gin.Context) {
        var user User
        var err error
        contentType := c.Request.Header.Get("Content-Type")

        switch contentType {
        case "application/json":
            err = c.BindJSON(&user)
        case "application/x-www-form-urlencoded":
            err = c.BindWith(&user, binding.Form)
        }

        if err != nil {
            fmt.Println(err)
            log.Fatal(err)
        }

        c.JSON(http.StatusOK, gin.H{
            "user":   user.Username,
            "passwd": user.Passwd,
            "age":    user.Age,
        })

    })

}

先定义一个User模型结构体,然后针对客户端的content-type,一次使BindJSONBindWith方法。

☁  ~  curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/x-www-form-urlencoded" -d "username=rsj217&passwd=123&age=21" | python -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    79  100    46  100    33  41181  29543 --:--:-- --:--:-- --:--:-- 46000
{
    "age": 21,
    "passwd": "123",
    "username": "rsj217"
}
☁  ~  curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/x-www-form-urlencoded" -d "username=rsj217&passwd=123&new=21" | python -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    78  100    45  100    33  37751  27684 --:--:-- --:--:-- --:--:-- 45000
{
    "age": 0,
    "passwd": "123",
    "username": "rsj217"
}
☁  ~  curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/x-www-form-urlencoded" -d "username=rsj217&new=21" | python -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0curl: (52) Empty reply from server
No JSON object could be decoded

可以看到,结构体中,设置了binding标签的字段(username和passwd),如果没传会抛错误。非banding的字段(age),对于客户端没有传,User结构会用零值填充。对于User结构没有的参数,会自动被忽略。

改成json的效果类似:

☁  ~  curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/json" -d '{"username": "rsj217", "passwd": "123", "age": 21}' | python -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    96  100    46  100    50  32670  35511 --:--:-- --:--:-- --:--:-- 50000
{
    "age": 21,
    "passwd": "123",
    "username": "rsj217"
}
☁  ~  curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/json" -d '{"username": "rsj217", "passwd": "123", "new": 21}' | python -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    95  100    45  100    50  49559  55066 --:--:-- --:--:-- --:--:-- 50000
{
    "age": 0,
    "passwd": "123",
    "username": "rsj217"
}
☁  ~  curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/json" -d '{"username": "rsj217", "new": 21}' | python -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0curl: (52) Empty reply from server
No JSON object could be decoded
☁  ~  curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/json" -d '{"username": "rsj217", "passwd": 123, "new": 21}' | python -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0curl: (52) Empty reply from server
No JSON object could be decoded
View Code

使用json还需要注意一点,json是有数据类型的,因此对于 {"passwd": "123"}{"passwd": 123}是不同的数据类型,解析需要符合对应的数据类型,否则会出错。

当然,gin还提供了更加高级方法,c.Bind,它会更加content-type自动推断是bind表单还是json的参数。

 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的源码注释很详细,可以阅读源码了解更多详细的功能和魔法特性。



作者:人世间
链接:http://www.jianshu.com/p/a31e4ee25305
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。










 



 















 

 





转载于:https://www.cnblogs.com/xielideboke/p/golang.html

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Golang 微框架 Gin 简介 的相关文章

  • linux找不到root用户,Linux中root用户找不到JAVA_HOME

    使用DOS比较两个txt文件的差异 将两个文件放入到同一个文件夹下 DOS下提供了FC命令 点击开始 gt 运行 gt 输入cmd 进入DOS下 进入指定目录 输入FC a txt b txt进行比较 下面会显示出之间的差异 lbrack
  • 02.SVN 忽略不需要提交的文件

    我使用的是 TortoiseSVN 每次提交到 svn 上的时候我不想提交 idea 文件夹 不需要列出 服务器也不需要存 因为是编译器的文件 多人维护项目时 总会显示修改 最好的避免类似问题的方法是添加参考文件到该项目的忽略列表 这样就不
  • ApkScan-PKID查壳工具+脱壳(搬运)

    Android APK 查壳工具免费通道 链接 https pan baidu com s 1rDfsEvqQwhUmep1UBLUwSQ 密码 wefd看众多小伙伴需要脱壳工具 现在补上1 Zjdroid github https git
  • 计算机网络(谢希仁-第八版)第五章习题全解

    5 01 试说明运输层在协议栈中的地位和作用 运输层的通信和网络层的通信有什么重要的区别 为什么运输层是必不可少的 地位和作用 从通信和信息处理的角度看 运输层向它上面的应用层提供通信服务 它属于面向通信部分的最高层 同时也是用户功能的最低
  • 线程安全threadsafe的四种策略

    线程之间的 竞争条件 作用于同一个mutable数据上的多个线程 彼此之间存在对该数据的访问竞争并导致interleaving 导致post condition可能被违反 这是不安全的 线程安全 ADT或方法在多线程中要执行正确 一 Con
  • MyBatis Log 插件无法显示SQL语句的原因

    MyBatis Log是IDEA一款下载量非常高的插件 该插件可以对控制台打印的日志进行解析 然后将对应的SQL语句整理并拼接好对应的参数 非常方便 有时插件却无法打印SQL 总的来说 有如下三种原因 1 项目的日志等级过高 修改日志等级为
  • mysql的jdbc的url

    driver com mysql jdbc Driver url jdbc mysql localhost 3306 数据库名 useSSL true useUnicode true characterEncoding utf8 usern
  • 交互设计师

    岗位职责 对产品进行行为设计和界面设计 行为设计是指各种用户操作后的效果设计 Web的操作以点击为主 点击操作又可以分为 表单提交 类和 跳转链接 类两种 除点击外 还涉及到拖拽操作等 界面设计包括 页面布局 内容展示等众多界面展现 例如
  • java获取集合里重复出现的元素及其出现次数

    1 代码如下 package com qingfeng ArrayList import java util import java util stream Collectors author wfj since 2020 6 11 pub
  • 网站服务器正常,有的外部http访问会出现503

    Linux系统相关内核参数配置异常 将 etc sysctl conf下 net ipv4 tcp tw recycle 0 net ipv4 tcp timestamps 0 执行 sysctl p 使配置生效 net ipv4 tcp
  • python安装opencv安装出错_在CentOS上安装带有python模块的OpenCV出现错误

    我回答我自己的问题 希望遭受同样问题的人能在短时间内找到解决问题的方法 在 1 首先 用yum更新所有的页面包 在安装OpenCV时 我发现了几个由依赖性问题引起的bug 在sudo yum update skip broken 2 使用
  • js将时间搓转换成字符串格式

    var timestamp 1425553097 var d new Date timestamp 1000 根据时间戳生成的时间对象 var date d getFullYear d getMonth 1 d getDate d getH
  • 网状结构(图)图的存储(邻接矩阵、邻接表)、图的遍历(深度DFS、广度BFS)、图的最短路径

    图 多对多关系 是一种网状数据结构 图是由非空的顶点集合和一个描述顶点之间关系的集合组成 其定义 Graph V E V x x 某个数据对象 E
  • dashicons.min.css,WordPress前端使用跟后台的Dashicons图标字体?

    很多站长都喜欢在站点菜单或其他地方添加一些图标字体 常用的就是添加Font Awesome图标和阿里巴巴矢量库图标 其实我们使用的 wordpress 本身就有一套管理员使用的官方图标字体 Dashicons 登录我们站点后台就能看到这些图
  • 视频码率(Bitrate),帧率(FPS)和分辨率的联系与区别

    一 视频码率 码率就是数据传输时单位时间传送的数据位数 一般我们用的单位是kbps即千位每秒 也就是取样率 并不等同与采样率 采样率的单位是Hz 表示每秒采样的次数 单位时间内取样率越大 精度就越高 处理出来的文件就越接近原始文件 但是文件
  • matlab里的function编程,MATLAB 元编程介绍

    这篇文章对 Matlab 中的元编程进行了简单的介绍 Matlab 是一个古老而又高度专业化的语言 由于这一原因 缺乏很多在现代或者通用语言中拥有的特性 然而 用一些简单的工具 我们可以发现 Matlab 也可以足够灵活去进行非常简单的元编
  • HTML、CSS与JavaScript之间的区别与联系;HTML与HTML5之间的区别、CSS与CSS3之间的区别

    HTML与CSS和JavaScript之间的关系与区别 HTML CSS JS JavaScript 之间是相互依赖 共同构建了网页的展示和交互 HTML HyperText Markup Language 是超文本标记语言 CSS Cas
  • Qtcreator中常用快捷键总结

    转载自 https blog csdn net wuli dear wang article details 82143152 Qtcreator中常用快捷键总结 F1 查看帮助 F2 跳转到函数定义 和Ctrl 鼠标左键一样的效果 Shi
  • 入门系列之Kubernetes部署

    欢迎大家前往腾讯云 社区 获取更多腾讯海量技术实践干货哦 本文由林岑影 发表于云 社区专栏 容器实例服务 Container Instance Service CIS 可以帮您在云上快捷 灵活的部署容器 让您专注于构建程序和使用容器而非管理
  • 五分钟带你一步一步去了解Spring Boot是如何实现自动配置

    前言 在使用传统的 Spring 去做 Java EE 应用开发时 在项目中会出现大量的 XMl配置文件 使 Java EE 项目变得笨重且繁琐 从而导致开发和部署上的效率降低 Spring Boot 的出现就是简化 Spring 应用的搭

随机推荐

  • 计算机原码反码补码的理解和作用

    摘要 本文旨在深入探讨整数在计算机中的二进制表示形式以及涉及的源码 反码和补码的运算原理 我们将介绍二进制数的基本概念 解释源码 反码和补码的定义和转换规则 并探讨在运算过程中它们的作用和区别 通过详细的解释和示例 我们希望读者能够全面理解
  • Vue项目引用百度地图并实现搜索定位等功能

    Tip 本篇文章为案例分析 技术点较多 所以篇幅较长 认真阅览的你一定会学到很多知识 前言 百度地图开放平台 给开发者们提供了丰富的地图功能与服务 使我们的项目中可以轻松地实现地图定位 地址搜索 路线导航等功能 本文给大家介绍如何在vue项
  • 继续发布VS2005下DataGridView 的多种样式列控件

    VS2005已经发布好久了 但对DataGridView 的使用 在网上的资料还比较少 DataGridView 无论是美观与功能方面都是DataGrid所不能比的 应该说DataGridView 不是DataGrid的升级 因为DataG
  • 大范围自动化时序软件LiCSBAS安装教程

    1 配置虚拟环境 1 1创建虚拟环境 注意随着版本的提升 python现在是3 9 装不上 需要降python改为3 8版本即可 conda create n licsbas python 3 8 13 1 2 安装LiCSBAS所需要的库
  • 华为ensp模拟校园网/企业网实例(精品拓扑图)

    文章简介 本文用华为ensp对企业网络进行了规划和模拟 也同样适用于校园 医院等场景 如有需要可联系作者 可以根据定制化需求做修改 作者简介 网络工程师 希望能认识更多的小伙伴一起交流 可私信或QQ号 1686231613 目录 摘 要 第
  • 华为发布数字资产继承功能

    在华为开发者大会2023 HDC Together 上 华为常务董事 终端BG CEO 智能汽车解决方案BU CEO余承东正式发布了数字资产继承功能 HarmonyOS提供了安全便捷的数字资产继承路径 在鸿蒙世界中 我们每个人在每台设备 应
  • AI技术进阶的75道面试题

    本文转载自AI科技大本营 整理 AI科技大本营 出品 AI科技大本营 公众号id rgznai100 导语 正值求职 跳槽季 无论你是换工作还是找实习 没有真本事都是万万不行的 可是如何高效率复习呢 之前我们给大家推荐了一份 Python
  • 如何把 Git Submodule 变成普通文件夹

    转载于此文章 记录一下 感觉最重要的是把原来的submodule中的 git删除掉 先删除 Git Submodule 删除所有相关的git配置文件即可 再把 submodule 中的文件添加到主仓库 删除 Git submodule 的命
  • MATLAB求矩阵最大、最小值

    矩阵求最大 最小值用max min函数 max A min A 返回行向量 求每列最大 最小值 max A B min A B 返回一个A B中比较大 较小元素组成的矩阵 max A dim min A dim dim 1 比较A的列 di
  • PowerOJ 2543: 赛场布置

    题目链接 对于每个点 它可以选择男或者女 如果要加上的贡献 那么相邻的一定得是异性才可以 所以 对相邻的 我们可以考虑成 然后 我们对于点坐标的的奇偶性分别讨论即可 当然 还需要考虑的贡献 然后就是全选减去最少割去的即可 include
  • iOS面试题(2.类变量的@protected,@private,@public,@package声明各有什么含义)拓展:常用框架和第三方框架

    2 类变量的 protected private public package声明各有什么含义 private 作用范围只能在自身类 protected 作用范围在自身类和继承自己的子类 默认 public 作用范围最大 可以在任何地方被访
  • nginx匹配以XXX结尾的

    匹配以do结尾的所有文件 如http 192 168 126 168 8080 delivery transportPlanData do startRelease 2019 07 06 endRelease 2019 07 06 sear
  • 初识Composer

    文章目录 依赖管理工具Composer 参考 1 简介 2 安装Composer 3 声明和安装依赖 4 自动加载 5 模块仓库 依赖管理工具Composer 参考 Composer 中文网 Packagist 中国全量镜像官方 地址htt
  • cygwin的git vscode中的使用

    背景 需要用到cygwin 编辑器是vscode 版本 vscode 版本1 55 cygwin版本2 11 2 1 cygcheck c cygwin cygwin的git2 17 cygcheck c git vscode报错 open
  • 算法篇-------贪心2

    文章目录 题目1 活动选择 题目2 无重叠区间 题目3 最多可以参加的会议数目 题目4 去除重复字母 题目5 移掉K位数字 题目6 拼接最大数 题目1 活动选择 有n个需要在同一天使用同一个教室的活动a1 a2 an 教室同一时刻只能由一个
  • linux指令_龙红云

    一 基础指令 1 ls指令 ls 列出当前目录下的所有文件 文件夹的名字 ls root 列出root下的所有文件 文件夹的名字 ls l 以详细列表的形式展示 ls la ls a 显示所有文件 文件夹 包含了隐藏文件 文件夹 ls lh
  • Spring学习总结

    因为是学习总结 所以参考了很多资料做的博客 如果有侵权 请联系我 写的不对的欢迎指出 Spring 开源的轻量级框架 Spring核心 IOC 控制反转控 制反转还有一个名字叫做DI Dependency Injection 中文意思叫依赖
  • Python轻量级Web框架Flask(9)——图书馆项目

    1 项目要求 创建一个项目 用来说明出版社 书籍和作者的关系 作者和书籍之间的关系 1对多 一本书由一个作者完成 一本书可以有多个创作者 出版社和书籍之间的关系 多对多 一个出版社可以出版多本书 一本书可以由多个出版社出版 要求 1 在书籍
  • React项目开发常用API

    记录一下React项目开发常用API 作为后续开发的参考 路由配置 配置文件形式 router index js import React lazy Suspense from react Suspense配合lazy实现懒加载 const
  • Golang 微框架 Gin 简介

    框架一直是敏捷开发中的利器 能让开发者很快的上手并做出应用 甚至有的时候 脱离了框架 一些开发者都不会写程序了 成长总不会一蹴而就 从写出程序获取成就感 再到精通框架 快速构造应用 当这些方面都得心应手的时候 可以尝试改造一些框架 或是自己