简介
Gin的GitHub项目网站如下:
Gin Web Framework - GitHub
主页上是这样介绍的:
Gin is a web framework written in Go (Golang). It features a martini-like API with performance that is up to 40 times faster thanks to httprouter . If you need performance and good productivity, you will love Gin.
Gin 是使用 Go 语言实现的 HTTP Web 框架。接口简单,性能极高。
初次接触Golang,先简单学习一下 Gin 是如何使用的。
net/http库实现Web响应
在不使用Gin Web框架的情况下,我们可以使用Golang自带的net/http库来完成简单的http响应,简单的demo如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package mainimport ( "fmt" "net/http" ) func main () { http.HandleFunc("/hello" , func (w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "<h1>Hello World</h1>" ) }) err := http.ListenAndServe(":9090" , nil ) if err != nil { fmt.Printf("Http Server Failed to Start, err:%v\n" , err) return } }
我们还可以把里面的字符串放在一个文件里,我们定义一个 hello.html文件
1 2 3 4 5 6 7 8 9 10 11 12 <html> <title>hello golang</title> <body> <h1 style='color:red' > hello Golang! </h1> <h1> hello gin! </h1> <img src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1600011052622&di=9aeee5de695a40c8d469f0c3980c2d48&imgtype=0&src=http%3A%2F%2Fa4.att.hudong.com%2F22%2F59%2F19300001325156131228593878903.jpg" > </body> </html>
然后修改刚刚的main.go,使用 ioutil解析文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package mainimport ( "fmt" "io/ioutil" "net/http" ) func sayHello (w http.ResponseWriter, r *http.Request) { html, _ := ioutil.ReadFile("./template/hello.html" ) _, _ = fmt.Fprintln(w, string (html)); } func main () { http.HandleFunc("/hello" , sayHello) err := http.ListenAndServe(":9090" , nil ) if err != nil { fmt.Println("http server failed, err:%v \n" , err) return } }
快速上手
安装
GitHub官方文档中给出了如下的安装方式:
To install Gin package, you need to install Go and set your Go workspace first.
The first need Go installed (version 1.13+ is required ), then you can use the below Go command to install Gin.
1 $ go get -u github.com/gin-gonic/gin
Import it in your code:
1 import "github.com/gin-gonic/gin"
(Optional) Import net/http
. This is required for example if using constants such as http.StatusOK
.
我们直接在项目目录下进行安装:
安装结束后主目录的go.mod依赖如下所示,表示依赖已经正确加入:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 module newsfeed go 1.17 require ( github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-gonic/gin v1.7.7 // indirect github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect github.com/go-playground/validator/v10 v10.10.0 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/ugorji/go/codec v1.2.6 // indirect golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce // indirect golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect golang.org/x/text v0.3.7 // indirect google.golang.org/protobuf v1.27.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect )
简单示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package mainimport ( "github.com/gin-gonic/gin" "net/http" ) func main () { r := gin.Default() r.GET("/ping" , func (c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "message" : "pong" , }) }) r.Run(":9090" ) }
这里的gin.H
其实就是一个map数据结构,以string
类型作为key键:
在net/http库中GoLang直接将常见的状态码进行了映射存储,如我们上面所见到的http.StatusOK
即为200
状态码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 package httpconst ( StatusContinue = 100 StatusSwitchingProtocols = 101 StatusProcessing = 102 StatusEarlyHints = 103 StatusOK = 200 StatusCreated = 201 StatusAccepted = 202 StatusNonAuthoritativeInfo = 203 StatusNoContent = 204 StatusResetContent = 205 StatusPartialContent = 206 StatusMultiStatus = 207 StatusAlreadyReported = 208 StatusIMUsed = 226 StatusMultipleChoices = 300 StatusMovedPermanently = 301 StatusFound = 302 StatusSeeOther = 303 StatusNotModified = 304 StatusUseProxy = 305 _ = 306 StatusTemporaryRedirect = 307 StatusPermanentRedirect = 308 StatusBadRequest = 400 StatusUnauthorized = 401 StatusPaymentRequired = 402 StatusForbidden = 403 StatusNotFound = 404 StatusMethodNotAllowed = 405 StatusNotAcceptable = 406 StatusProxyAuthRequired = 407 StatusRequestTimeout = 408 StatusConflict = 409 StatusGone = 410 StatusLengthRequired = 411 StatusPreconditionFailed = 412 StatusRequestEntityTooLarge = 413 StatusRequestURITooLong = 414 StatusUnsupportedMediaType = 415 StatusRequestedRangeNotSatisfiable = 416 StatusExpectationFailed = 417 StatusTeapot = 418 StatusMisdirectedRequest = 421 StatusUnprocessableEntity = 422 StatusLocked = 423 StatusFailedDependency = 424 StatusTooEarly = 425 StatusUpgradeRequired = 426 StatusPreconditionRequired = 428 StatusTooManyRequests = 429 StatusRequestHeaderFieldsTooLarge = 431 StatusUnavailableForLegalReasons = 451 StatusInternalServerError = 500 StatusNotImplemented = 501 StatusBadGateway = 502 StatusServiceUnavailable = 503 StatusGatewayTimeout = 504 StatusHTTPVersionNotSupported = 505 StatusVariantAlsoNegotiates = 506 StatusInsufficientStorage = 507 StatusLoopDetected = 508 StatusNotExtended = 510 StatusNetworkAuthenticationRequired = 511 ) var statusText = map [int ]string { StatusContinue: "Continue" , StatusSwitchingProtocols: "Switching Protocols" , StatusProcessing: "Processing" , StatusEarlyHints: "Early Hints" , StatusOK: "OK" , StatusCreated: "Created" , StatusAccepted: "Accepted" , StatusNonAuthoritativeInfo: "Non-Authoritative Information" , StatusNoContent: "No Content" , StatusResetContent: "Reset Content" , StatusPartialContent: "Partial Content" , StatusMultiStatus: "Multi-Status" , StatusAlreadyReported: "Already Reported" , StatusIMUsed: "IM Used" , StatusMultipleChoices: "Multiple Choices" , StatusMovedPermanently: "Moved Permanently" , StatusFound: "Found" , StatusSeeOther: "See Other" , StatusNotModified: "Not Modified" , StatusUseProxy: "Use Proxy" , StatusTemporaryRedirect: "Temporary Redirect" , StatusPermanentRedirect: "Permanent Redirect" , StatusBadRequest: "Bad Request" , StatusUnauthorized: "Unauthorized" , StatusPaymentRequired: "Payment Required" , StatusForbidden: "Forbidden" , StatusNotFound: "Not Found" , StatusMethodNotAllowed: "Method Not Allowed" , StatusNotAcceptable: "Not Acceptable" , StatusProxyAuthRequired: "Proxy Authentication Required" , StatusRequestTimeout: "Request Timeout" , StatusConflict: "Conflict" , StatusGone: "Gone" , StatusLengthRequired: "Length Required" , StatusPreconditionFailed: "Precondition Failed" , StatusRequestEntityTooLarge: "Request Entity Too Large" , StatusRequestURITooLong: "Request URI Too Long" , StatusUnsupportedMediaType: "Unsupported Media Type" , StatusRequestedRangeNotSatisfiable: "Requested Range Not Satisfiable" , StatusExpectationFailed: "Expectation Failed" , StatusTeapot: "I'm a teapot" , StatusMisdirectedRequest: "Misdirected Request" , StatusUnprocessableEntity: "Unprocessable Entity" , StatusLocked: "Locked" , StatusFailedDependency: "Failed Dependency" , StatusTooEarly: "Too Early" , StatusUpgradeRequired: "Upgrade Required" , StatusPreconditionRequired: "Precondition Required" , StatusTooManyRequests: "Too Many Requests" , StatusRequestHeaderFieldsTooLarge: "Request Header Fields Too Large" , StatusUnavailableForLegalReasons: "Unavailable For Legal Reasons" , StatusInternalServerError: "Internal Server Error" , StatusNotImplemented: "Not Implemented" , StatusBadGateway: "Bad Gateway" , StatusServiceUnavailable: "Service Unavailable" , StatusGatewayTimeout: "Gateway Timeout" , StatusHTTPVersionNotSupported: "HTTP Version Not Supported" , StatusVariantAlsoNegotiates: "Variant Also Negotiates" , StatusInsufficientStorage: "Insufficient Storage" , StatusLoopDetected: "Loop Detected" , StatusNotExtended: "Not Extended" , StatusNetworkAuthenticationRequired: "Network Authentication Required" , } func StatusText (code int ) string { return statusText[code] }
如果我们使用Postman进行访问,我们会得到如下的结果:
携带请求参数
以下内容引用自:gin请求参数处理
Get请求参数
Get请求url例子:**/path?id=1234&name=Manu&value=**111
获取Get请求参数的常用函数:
func (c *Context) Query (key string) string
func (c *Context) DefaultQuery (key, defaultValue string) string
func (c *Context) GetQuery (key string) (string, bool)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func Handler (c *gin.Context) { name := c.Query("name" ) name := c.DefaultQuery("name" , "sockstack" ) id, ok := c.GetQuery("id" ) if !ok { } }
Post请求参数
获取Post请求参数的常用函数:
func (c *Context) PostForm (key string) string
func (c *Context) DefaultPostForm (key, defaultValue string) string
func (c *Context) GetPostForm (key string) (string, bool)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func Handler (c *gin.Context) { name := c.PostForm("name" ) name := c.DefaultPostForm("name" , "sockstack" ) id, ok := c.GetPostForm("id" ) if !ok { } }
获取URL路径参数
获取URL路径参数,指的是获取 /user/:id 这类型路由绑定的参数,这个例子绑定了一个参数id。
获取url路径参数常用函数:
func (c *Context) Param (key string) string
1 2 3 4 5 6 r := gin.Default() r.GET("/user/:id" , func (c *gin.Context) { id := c.Param("id" ) })
将请求参数绑定到struct对象
前面获取参数的方式都是一个个参数的读取,比较麻烦,Gin框架支持将请求参数自动绑定到一个struct对象,这种方式支持Get/Post请求,也支持http请求body内容为json/xml格式的参数。
例子:
下面例子是将请求参数绑定到User struct对象。
1 2 3 4 5 type User struct { Name string `json:"name" form:"name"` Email string `json:"email" form:"email"` }
通过定义struct字段的标签,定义请求参数和struct字段的关系。
下面对User的Name字段的标签进行说明。
struct标签说明:
标签
说明
json:“name”
数据格式为json格式,并且json字段名为name
form:“name”
表单参数名为name
提示:你可以根据自己的需要选择支持的数据类型,例如需要支持json数据格式,可以这样定义字段标签: json:“name”
下面看下控制器代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 r.POST("/user/:id" , func (c *gin.Context) { u := User{} if c.ShouldBind(&u) == nil { log.Println(u.Name) log.Println(u.Email) } c.String(200 , "Success" ) })
RESTful API
REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移”或“表现层状态转化”。
推荐阅读阮一峰 理解RESTful架构
简单来说,REST的含义就是客户端与Web服务器之间进行交互的时候,使用HTTP协议中的4个请求方法代表不同的动作。
GET
用来获取资源
POST
用来新建资源
PUT
用来更新资源
DELETE
用来删除资源。
只要API程序遵循了REST风格,那就可以称其为RESTful API。目前在前后端分离的架构中,前后端基本都是通过RESTful API来进行交互。
例如,我们现在要编写一个管理书籍的系统,我们可以查询对一本书进行查询、创建、更新和删除等操作,我们在编写程序的时候就要设计客户端浏览器与我们Web服务端交互的方式和路径。按照经验我们通常会设计成如下模式:
请求方法
URL
含义
GET
/book
查询书籍信息
POST
/create_book
创建书籍记录
POST
/update_book
更新书籍信息
POST
/delete_book
删除书籍信息
同样的需求我们按照RESTful API设计如下:
请求方法
URL
含义
GET
/book
查询书籍信息
POST
/book
创建书籍记录
PUT
/book
更新书籍信息
DELETE
/book
删除书籍信息
同上,我们也能完成类似的代码,只需要替换r.GET()
中的GET()
为其他请求即可。
模版渲染
在一些前后端不分离的Web架构中,我们通常需要在后端将一些数据渲染到HTML文档中,从而实现动态的网页(网页的布局和样式大致一样,但展示的内容并不一样)效果。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 type student struct { Name string Age int8 } r.LoadHTMLGlob("templates/*" ) stu1 := &student{Name: "Geektutu" , Age: 20 } stu2 := &student{Name: "Jack" , Age: 22 } r.GET("/arr" , func (c *gin.Context) { c.HTML(http.StatusOK, "arr.tmpl" , gin.H{ "title" : "Gin" , "stuArr" : [2 ]*student{stu1, stu2}, }) })
1 2 3 4 5 6 7 8 9 <html > <body > <p > hello, {{.title}}</p > {{range $index, $ele := .stuArr }} <p > {{ $index }}: {{ $ele.Name }} is {{ $ele.Age }} years old</p > {{ end }} </body > </html >
中间件
默认中间件
在之前的示例中,我们使用了Gin提供的默认函数,返回了默认的路由引擎*Engine
。
我们在这里看到Default()
函数会默认绑定两个已经准备好的中间件,分别是Logger()
和Recovery()
,其中中间件是使用Use()
方法来进行设置的,这个函数可以接受多个HandlerFunc
对象作为中间件。
也就是说HandlerFunc
也就是我们所说的Gin的中间件。
换句话说,在上文中,我们使用的GET()
等方法中后部传入的这个函数即为HandlerFunc
,后面的func(c *gin.Context)
这部分其实就是一个HandlerFunc
。
1 2 3 4 5 r.GET("/ping" , func (c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "message" : "pong" , }) })
自定义中间件
例如实现一个统计请求的执行时间的中间件,我们定义如下中间件HandlerFunc
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func costTime () gin.HandlerFunc { return func (c *gin.Context) { nowTime := time.Now() c.Next() costTime := time.Since(nowTime) url := c.Request.URL.String() fmt.Printf("the request URL %s cost %v\n" , url, costTime) } }
需要说明的是,c.Next()
方法,这个是执行后续中间件请求处理的意思(含没有执行的中间件和我们定义的GET方法处理)
随后我们新建引擎,并设置创建的中间件:
1 2 3 4 5 6 7 8 9 10 11 func main () { r := gin.New() r.Use(costTime()) r.GET("/" , func (c *gin.Context) { c.JSON(200 , "首页" ) }) r.Run(":8080" ) }
终端运行日志如下:
通过自定义中间件,我们可以很方便的拦截请求,来做一些我们需要做的事情,比如日志记录、授权校验、各种过滤等等。