Gin框架简介,路由处理,数据处理,会话控制,中间件,RESTful API,表单格式,Go编程规范。
1 Gin框架简介 gin 是一个使用 Go 语言编写的 Web 后端框架,gin 和 beego 是 Go 语言编写 Web 应用最常用的后端框架。使用 Go 语言开发 Web 应用,对于框架的依赖要比其它编程语言要小。Go 语言内置的 net/http 包简单轻便,性能优良。而且,大多数的 Go 语言 Web 框架都是在其之上进行的封装。
框架是指半成品的应用,一般需要填充少许代码或者无需填充代码就可以运行,只是这样的应用缺少业务逻辑。我们使用框架开发,主要工作就是在框架上补充业务逻辑代码。所以,借助框架进行开发,不仅可以减少开发时间、提高效率,也有助于团队统一编码风格,形成编程规范。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import "github.com/gin-gonic/gin" import "net/http" go run 报错no required module privides package "github.com/xxx" 首先想到go mod ,于是go mod init 报错go mod exists but should not 关掉设置的GOPATH路径 输入go mod init GinFrameLearn 提示输入go mod tidy,输入后成功run 运行main包不会运行其他文件,所以运行的时候,也要把其他文件都带上 go run main.go xxx.go
2 路由处理 路由(route)就是根据 HTTP 请求的 url 路径,设置由哪个函数来处理请求。路由是 web 框架的核心功能。路由通常这样实现:根据路由里的字符 “/”,把路由切分成多个字符串数组,然后构造成树状结构;寻址的时候,先把请求的 URL 按照 “/” 进行切分,然后遍历树进行寻址。
Gin 框架中采用的路由库是基于 httprouter 开发的。Gin 的路由支持 HTTP 的 GET , POST , PUT , DELETE , PATCH , HEAD , OPTIONS 方法的请求,同时还有一个 Any 函数,可以同时支持以上的所有请求。
2.1 参数 web 程序中经常需要处理各种形式的参数,参数是处理 HTTP 请求中很重要的工作,它是前端提交数据的基本形式。gin 框架内置了处理 HTTP 各种参数的方法,包括 API 参数,URL 参数 以及表单参数的处理。
2.1.1 API参数 1 2 3 4 5 6 7 8 9 10 11 12 func main () { engine := gin.Default() engine.GET("/user/:name" , func (context *gin.Context) { name := context.Param("name" ) context.String(http.StatusOK, "name = %s" , name) }) engine.Run(":9091" ) }
2.1.2 URL参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func main () { engine := gin.Default() engine.GET("/user" , func (context *gin.Context) { name := context.Query("name" ) id := context.DefaultQuery("id" , "1" ) context.String(http.StatusOK, "name=%s\nid=%s" , name, id) }) engine.Run(":9091" ) }
2.1.3 表单参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func main () { engine := gin.Default() engine.POST("/form" , func (context *gin.Context) { types := context.DefaultPostForm("type" , "post" ) username := context.PostForm("username" ) password := context.PostForm("password" ) context.String(http.StatusOK, "usr=%s\npwd=%s\ntype%s\n" , username, password, types) }) engine.Run(":9091" ) }
2.2 上传文件 web 程序中有时候需要处理上传文件,通常由前端负责提交文件,后端负责处理或者保存文件。
2.2.1 单个文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func main () { engine := gin.Default() engine.MaxMultipartMemory = 8 << 20 engine.POST("/upload" , func (context *gin.Context) { fileHeader, err := context.FormFile("file" ) if err != nil { context.String(http.StatusBadRequest, "error" ) return } context.SaveUploadedFile(fileHeader, fileHeader.Filename) context.String(http.StatusOK, fileHeader.Filename) }) engine.Run(":9091" ) }
2.2.2 特定文件 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 func main () { engine := gin.Default() engine.MaxMultipartMemory = 8 << 20 engine.POST("/upload" , func (context *gin.Context) { _, header, err := context.Request.FormFile("file" ) if err != nil { context.String(http.StatusBadRequest, "error" ) return } if size := header.Size; size > 2 << 20 { context.String(http.StatusBadRequest, "file too big" ) return } if header.Header.Get("Content-Type" ) != "image/png" { context.String(http.StatusBadRequest, "only png allowed" ) return } context.SaveUploadedFile(header, "./" + header.Filename) context.String(http.StatusOK, header.Filename) }) engine.Run(":9091" ) }
2.2.3 多个文件 1 2 3 4 5 6 7 8 9 10 11 12 13 func main () { engine := gin.Default() engine.MaxMultipartMemory = 8 << 20 engine.POST("/upload" , func (context *gin.Context) { form, _ := context.MultipartForm() files := form.File["files" ] for _, file := range files { context.SaveUploadedFile(file, "./files/" + file.Filename) } context.String(http.StatusOK, "success" ) }) engine.Run(":9091" ) }
2.3 路由分组 根据业务逻辑给一个模块划分一组路由。把一个模块相关的方法都写在一个路由下,主要好处是业务逻辑清晰,便于管理和查找相关的代码。
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 func getHandler (c *gin.Context) { c.String(http.StatusOK, "get" ) } func postHandler (c *gin.Context) { c.String(http.StatusOK, "post" ) } func putHandler (c *gin.Context) { c.String(http.StatusOK, "put" ) } func main () { engine := gin.Default() g1 := engine.Group("/g1" ) { g1.GET("/get" , getHandler) g1.POST("/post" , postHandler) g1.PUT("/put" , putHandler) } g2 := engine.Group("/g2" ) { g2.GET("/get" , getHandler) g2.POST("/post" , postHandler) g2.PUT("/put" , putHandler) } engine.Run(":9091" ) }
2.4 路由拆分和注册 2.4.1 基本路由注册 基本的,在一个文件里写所有的路由注册。
2.4.2 路由拆分成单独的文件或包 规模比较大的时候,将路由注册拆分出来,放到其他文件里。
注册的时候可以返回一个gin.Engine指针。
1 2 3 4 5 gin_demo ├── go .mod ├── go .sum ├── main.go └── routers.go
1 2 3 4 5 6 7 8 9 func setupRouters () *gin.Engine { engine := gin.Default() engine.GET("/" , getHandler) return engine } func getHandler (c *gin.Context) { c.String(http.StatusOK, "success" ) }
或者放到包里,此时要import包。
1 2 3 4 5 6 gin_demo ├── go .mod ├── go .sum ├── main.go └── routers └── routers.go
2.4.3 路由拆分成多个文件 因为不是同一个文件注册,所以不能让每个函数返回一个gin.Engine对象指针,而是在main函数创建gin.Engine,然后当作参数传入。
1 2 3 4 5 6 7 gin_demo ├── go .mod ├── go .sum ├── main.go └── routers ├── blog.go └── shop.go
1 2 3 4 5 6 7 func RegisterBlog (engine *gin.Engine) { engine.GET("/blog/get" , handler) } func handler (context *gin.Context) { context.String(http.StatusOK, "success" ) }
2.4.4 拆分到不同的APP 1 2 3 4 5 6 7 8 9 10 11 12 13 gin_demo ├── app │ ├── blog │ │ ├── handler.go │ │ └── router.go │ └── shop │ ├── handler.go │ └── router.go ├── go .mod ├── go .sum ├── main.go └── routers └── routers.go
3 数据处理 3.1 数据解析和绑定 gin 框架提供了大量的方法,用于将前端提交的数据绑定到结构体。可以绑定的数据类型有form,json,uri,xml。binding:”required”修饰的字段,若接收为空值,则报错,是必须字段。
3.1.1 JSON 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 type User struct { Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required"` } func main () { engine := gin.Default() engine.POST("/" , func (context *gin.Context) { var user User context.ShouldBindJSON(&user) if user.Username != "root" || user.Password != "admin" { context.JSON(http.StatusBadRequest, gin.H{ "status" : "303" , }) return } context.JSON(http.StatusOK, gin.H{ "status" : "200" , }) }) engine.Run(":9091" ) }
3.1.2 表单 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 type User2 struct { Username string `form:"username" binding:"required"` Password string `form:"password" binding:"required"` } func main () { engine := gin.Default() engine.POST("/" , func (context *gin.Context) { var user User2 context.Bind(&user) if user.Username != "root" || user.Password != "admin" { context.JSON(http.StatusOK, gin.H{ "status" : "303" , }) return } context.JSON(http.StatusOK, gin.H{ "status" : "200" , }) }) engine.Run(":9091" ) }
3.1.3 URI 1 2 localhost:9091 /:username/ :password localhost:9091 /root/ admin
3.2 响应格式 gin框架 可以提供多种数据格式的响应,包括json、结构体、XML、YAML 以及 ProtoBuf等格式。
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 func main () { engine := engine.Default() engine.GET("/someJSON" , func (c *gin.Context) { c.JSON(200 , gin.H{"message" : "someJSON" , "status" : 200 }) }) engine.GET("/someStruct" , func (c *gin.Context) { var msg struct { Name string Message string Number int } msg.Name = "root" msg.Message = "message" msg.Number = 123 c.JSON(200 , msg) }) engine.GET("/someXML" , func (c *gin.Context) { c.XML(200 , gin.H{"message" : "abc" }) }) engine.GET("/someYAML" , func (c *gin.Context) { c.YAML(200 , gin.H{"name" : "zhangsan" }) }) engine.GET("/someProtoBuf" , func (c *gin.Context) { reps := []int64 {int64 (1 ), int64 (2 )} label := "label" data := &protoexample.Test{ Label: &label, Reps: reps, } c.ProtoBuf(200 , data) }) engine.Run() }
3.3 模板渲染 1 HTML文档里面 {{.ParamName }} 就是替换的Key。
gin 支持加载 HTML 模板, 然后根据模板参数进行配置并返回相应的数据,本质上就是字符串替换。LoadHTMLGlob() 方法可以加载模板文件。
3.3.1 单个文件 1 2 3 4 5 6 test ├── go.mod├── go.sum ├── main.go └── tem └── index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 func main () { engine := gin.Default() engine.LoadHTMLGlob("tem/*" ) engine.GET("/index" , func (c *gin.Context) { c.HTML(http.StatusOK, "index.html" , gin.H{"title" : "我是测试" , "ce" : "123456" }) }) engine.Run() }
3.3.2 多层目录 1 2 3 4 5 6 7 8 9 test ├── go.mod ├── go.sum ├── main .go └── tem └── user └── index.html engine.LoadHTMLGlob ("tem/**/*" )
3.3.3 引入静态文件 如果需要引入静态文件需要定义一个静态文件目录。
1 engine.Static("/assets" , "./assets" )
4 会话控制 4.1 cookie HTTP 是无状态协议,服务器不能记录浏览器的访问状态,也就是说服务器不能区分两次请求是否由同一个客户端发出。cookie 是解决 HTTP 协议无状态的方案之一。
cookie 是服务器保存在客户端浏览器的数据,每次向服务器发送请求时都会同时将该信息发送给服务器,服务器收到请求后,就可以根据该信息处理请求。cookie 由服务器创建,并发送给浏览器,最终由浏览器保存。gin 框架提供了 cookie 操作的支持。
1 2 3 4 5 6 7 func (c *Context) SetCookie(key, value string , maxAge int , path, domain string , secure, httpOnly bool )maxAge:cookie的生存事件 path:cookie所在目录 domain:cookie所在域 secure httpOnly
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 func main () { engine := gin.Default() engine.GET("/read_cookie" , func (context *gin.Context) { val, _ := context.Cookie("name" ) context.String(200 , "Cookie:%s" , val) }) engine.GET("/write_cookie" , func (context *gin.Context) { context.SetCookie("name" , "Shimin Li" , 24 *60 *60 , "/" , "localhost" , false , true ) }) engine.GET("/clear_cookie" , func (context *gin.Context) { context.SetCookie("name" , "Shimin Li" , -1 , "/" , "localhost" , false , true ) }) engine.Run() }
4.2 session web 应用程序中, 记录客户端的状态除了使用 cookie 外,还经常使用 session。session 是服务器端使用的一种记录客户端状态的机制,存储在服务器上,使用上比 cookie 简单,但会增加服务器的存储压力。
session 是另一种记录客户状态的机制,不同的是 cookie 保存在客户端浏览器中,而 session 保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上,这就是 session。客户端浏览器再次访问时只需要从该 session 中查找该客户的状态就可以了。
如果说 cookie 机制是通过检查客户身上的 “通行证” 来确定客户身份的话,那么 session 机制就是通过检查服务器上的 “客户明细表” 来确认客户身份。session 相当于程序在服务器上建立的一份客户档案,客户来访的时候只需要查询客户档案表就可以了。
go 语言 和 gin 框架都没有单独提供 session 对象或者操作方法。通常我们使用 gorilla/sessions包,它是由第三方提供的 session 操作包。
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 var store = sessions.NewCookieStore([]byte ("session-secret" ))func main () { http.HandleFunc("/save" , SaveSession) http.HandleFunc("/get" , GetSession) err := http.ListenAndServe(":8080" , nil ) if err != nil { fmt.Println("HTTP server failed,err:" , err) return } } func SaveSession (w http.ResponseWriter, r *http.Request) { session, err := store.Get(r, "session-name" ) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } session.Values["foo" ] = "bar" session.Values[42 ] = 43 session.Save(r, w) } func GetSession (w http.ResponseWriter, r *http.Request) { session, err := store.Get(r, "session-name" ) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } foo := session.Values["foo" ] fmt.Println(foo) } func RemoveSession (w http.ResponseWriter, r *http.Request) { session, err := store.Get(r, "session-name" ) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } session.Options.MaxAge = -1 session.Save(r, w) }
5 中间件 gin 框架允许在请求处理过程中,加入用户自己的钩子函数,这个钩子函数就叫中间件。中间件的英文名称为 MiddleWare。gin 中间件常用于处理一些公共业务逻辑,比如登陆校验,耗时统计,日志打印等工作。
5.1 中间件使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 func MiddleWare () gin.HandlerFunc { return func (c *gin.Context) { fmt.Println("调用中间件" ) } } func main () { engine := gin.Default() engine.Use(MiddleWare()) engine.GET("/" , func (c *gin.Context) { fmt.Println("调用路由处理函数" ) c.JSON(200 , gin.H{"request" : "编程宝库 gin框架" }) }) engine.Run() }
1 2 3 [GIN-debug ] Listening and serving HTTP on :8080 调用中间件 调用路由处理函数
说明中间件被调用,而且是在页面处理函数之前执行的。
中间件是被依次调用(按照注册顺序),而且是在页面处理函数之前执行的。
5.2 abort和next gin 框架中间件处理有两个重要的函数 Next() 和 Abort()。
Abort 函数在被调用的函数中阻止后续中间件的执行。例如,你有一个验证当前的请求是否是认证过的 Authorization 中间件。如果验证失败(例如,密码不匹配),调用 Abort 以确保这个请求的其他函数不会被调用。有个细节需要注意,调用 Abort 函数不会停止当前的函数的执行,除非后面跟着 return。第一个中间件使用Abort,则后面的中间件都不会使用。
Next 函数会挂起当前所在的函数,然后调用后面的中间件,待后面中间件执行完毕后,再接着执行当前函数。而且是在页面处理函数结束之后才继续执行中间件的。
6 补充 6.1 RESTful API 6.1.1 API API 即应用程序接口,是指不同的软件系统 或者 软件系统的不同组成部分互相衔接的约定。API 代表了一个系统对外提供的服务能力。WEB API 一般的实现有 RESTful、RPC 等形式。
web 应用程序,一般分为前端和后端两个部分。前后端通信通常需要一种统一机制,以方便不同的前端设备与后端进行通信。这种需求导致了 API 构架的流行,甚至出现 “API First” 的设计思想。
RESTful API 是目前比较成熟的一套 web 应用程序的 API 设计理论,用于 web 前后端的数据交互。
6.1.2 RESTful API架构介绍 RESTful 架构,是目前流行的一种互联网软件架构。它结构清晰、易于理解、扩展方便,得到了越来越多网站的采用。RESTful 架构基于 HTTP、URI、XML、JSON 等标准和协议,具有轻量级、跨平台、跨语言等特点。
REST(Representational State Transfer,表现层状态转化) 这个词,是 Roy Thomas Fielding 在 2000 年的博士论文中提出的。
“本文研究计算机科学两大前沿—-软件和网络—-的交叉点。长期以来,软件研究主要关注软件设计的分类、设计方法的演化,很少客观地评估不同的设计选择对系统行为的影响。而相反地,网络研究主要关注系统之间通信行为的细节、如何改进特定通信机制的表现,常常忽视了一个事实,那就是改变应用程序的互动风格比改变互动协议,对整体表现有更大的影响。我这篇文章的写作目的,就是想在符合架构原理的前提下,理解和评估以网络为基础的应用软件的架构设计,得到一个功能强、性能好、适宜通信的架构。”
RESTful 风格的 API 具有一些天然的优势,例如通过 HTTP 协议降低了客户端的耦合,具有极好的开放性。因此越来越多的开发者使用 RESTful 这种风格设计 API,但是 RESTful 只能算是一个设计思想或理念,不是一个 API 规范,没有一些具体的约束条件。
主语:“资源”Resources
“表现层” 其实指的是 “资源”(Resources)的 “表现层”。所谓 “资源”,就是网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的实在。你可以用一个URI(统一资源定位符)指向它,每种资源对应一个特定的 URI。要获取这个资源,访问它的 URI 就可以,因此 URI 就成了每一个资源的地址或独一无二的识别符。所谓 “上网”,就是与互联网上一系列的 “资源” 互动,调用它的 URI。
“表现层”Representation
“资源” 是一种信息实体,它可以有多种外在表现形式。我们把 “资源” 具体呈现出来的形式,叫做它的 “表现层”(Representation)。比如,文本可以用 txt 格式表现,也可以用 HTML格式、XML格式、JSON 格式表现,甚至可以采用二进制格式;图片可以用JPG格式表现,也可以用 PNG 格式表现。
URI 只代表资源的实体,不代表它的形式。严格地说,有些网址最后的 “.html” 后缀名是不必要的,因为这个后缀名表示格式,属于”表现层”范畴,而 URI 应该只代表”资源”的位置。它的具体表现形式,应该在 HTTP 请求的头信息中用 Accept 和 Content-Type 字段指定,这两个字段才是对”表现层”的描述。
“状态转化”State Transfer
访问一个网站,就代表了客户端和服务器的一个互动过程。在这个过程中,势必涉及到数据和状态的变化。
互联网通信协议HTTP协议,是一个无状态协议。这意味着,所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生”状态转化”(State Transfer)。而这种转化是建立在表现层之上的,所以就是”表现层状态转化”。
客户端用到的手段,只能是HTTP协议。具体来说,就是HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT 用来更新资源,DELETE 用来删除资源。
6.2 编程规范 1 2 3 4 5 包名采用小写的一个单词,尽量不要和标准库冲突。如果包含多个单词,直接连写,中间无需使用下划线连接, 不使用驼峰形式。多个单词的情况下,通常考虑对包按照每个单词进行分层。 正例:admin 、stdmanager、encoding /base64 反例:Admin 、std_manager、encodingBase64、encoding_base64
1 2 3 go 文件的命名采用小写单词,尽量见名思义,看见文件名就可以知道这个文件下的大概内容。测试文件必须以_test.go 结尾。正例:struct Role所在文件为role.go ,对应的测试文件为role_test.go
1 2 3 4 常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。 正例:MAX_STOCK_COUNT 反例:MAX_COUNT
6.3 表单格式 四种:application/json,application/x-www-form-urlencoded,multipart/form-data,text/plain。
application/json
格式在POST
请求里面可以说是最常见 的一种格式。
application/x-www-form-urlencoded
这首表单提交的默认格式,不支持文件类型,它的请求格式是键值对 。
multipart/form-data
表单提交文件 必须要用这种格式,注意不能设置'Content-Type'='multipart/form-data'
,因为你手动设置了它,那么后面这个boundary=浏览器默认boundary
就没了。这个是分界线,服务端是以这个分界线去key
值,如果没有分界线服务端就不知道从从哪个位置开始取key
。
text/plain
是以纯文本格式 (就是一段字符串)发送的.。
gin框架 教程 - 编程宝库 (codebaoku.com)