周末想认真理解一下http传参,包含GET办法的传参、POST办法的传参,索性把GIN也捡起来了一点
根底
1.Gin.Use和Gin.Default的区别
//Default加了两个中间件Gin.Use(Logger(),Recovery())
有打印日志和从Panic中复原的能力
Logger中间件会将日志写入gin.DefaultWriter,即便配置了GIN_MODE=release
Recovery中间件会recover任何panic,如果有panic的话,会写入500响应码
2.Gin怎么分组
gin.Group对路由进行分组
参数解决
Gin怎么拿到URL中的参数
用ID举例子
Param
C.Param("id")// 这个时候路由要这么写 id就成为了不定参数GET("/:id")//这时候url中的申请格局就应该如下http://localhost:8080/id=xx//如果有多的参数则应该是http://localhost:8080/id=xx&name=xxGET("/:id/:name")//当咱们想解决URL中所有的参数的时候//比如说有name id type等等//这个时候路由就该这么写c.Param("all")GET("/*all")
在URL中传参时必须传某个param该怎么做?
type Produdct struct { ID int `uri:"id" binding:"required" Name string `uri:"name" binding:"required"}// uri也是tag反对的一个键值对之一var p Product err := c.ShouldBindUri(&p)// 如果这里err有问题了,那么很显然就是某个param没有传
struct tag
在很多我的项目代码外面,很容易看到有一些构造体的定义是相似上面这个构造体的
type Location struct { Longitude float32 `json:"lon,omitempty"` Latitude float32 `json:"lat,omitempty"`}
字段前面会有一个标签,这个标签通常是由反引号给括起来的
Go提供了可通过发射发现的构造体标签,这些在标签库json/xml中失去了宽泛的应用,orm框架也反对了构造体标签,下面的这个例子就是因为encoding/json反对json构造体标签,每种标签都有本人的非凡规定
不过所有标签都遵循一个总体规定,这个规定是不能更改的,具体格局如下key1:"value1" key2:"value2" key3:"value3"...
构造体标签能够有多个键值对,键与值要用冒号宰割,值要应用双引号括起来,多个键值对之间应用一个空格进行宰割
而一个值中要传递多个信息,不同库的实现是不一样的,在encoding/json中,多值应用逗号进行宰割
例如上面的例子json:"lon,omitempty"
在gorm中,多值应用分号进行分隔`gorm:"column:id;primaryKey"
构造体标签的具体作用机会如下
在编译阶段和成员进行关联,以字符串的模式进行关联,在运行阶段能够通过反射读取进去
在Go我的项目的编译阶段是不会对struct tag做非法键值对的查看的,如果咱们不小心写错了,就会很难被发现,这个时候咱们就能够应用go vet
工具,帮忙咱们做CI查看
上面是Go反对的struct tag类型
自定义构造体标签
构造体标签是能够随便写的,只有合乎语法规定。然而一些库没有反对该标签的状况下,随便写的标签是没有任何意义的,如果想要咱们的标签变得有意义,就须要咱们提供解析办法。能够通过反射的办法获取标签,上面是一个例子
type test struct { Name string `cheng:"name"` ID int `cheng:"id"` Type int `cheng:"type"`}func getStructTag(obj interface{}) { t := reflect.TypeOf(obj) for i := 0; i < t.NumField(); i++ { value := t.Field(i) tag := value.Tag.Get("cheng") fmt.Println("get tag is", tag) }}func main() { t := test{ Name: "yuyating", ID: 2021212345, Type: 1, } getStructTag(t)}输入后果get tag is nameget tag is idget tag is type
Post-body参数的类型
在POST申请中,常见的body数据类型包含
- application/x-www-form-urlencoded: 这是最常见的POST申请数据格式,实用于简略的表单数据。在这种格局下,数据以键值对的模式呈现,每个键值对之间应用&符号宰割,例如:key1=value1&key2=value2
- multipart/form-data:实用于上传文件或者二进制数据,通常用于文件上传性能。在这种格局下,数据被宰割成多个局部,每个局部有本人的Content-Type,例如Content-Type:image/jpeg。这种格局下,数据以肯定的边界符宰割,每个局部之间以该边界符宰割
- application/json:实用于发送JSON格局的数据,在这种格局下,数据以JSON格局组织,例如{"key1":"value1","key2":"value2"}
- text/xml: 实用于发送XML格局的数据,在这种格局下,数据以XML格局组织,例如<?xml version ="1.0" encoding="UTF-8"?><root><key1>value1</key1><key2>
个别状况下,POST申请的body数据类型是须要依据API设计要求而抉择的。如果混用不同类型的数据格式,服务器端可能无奈正确解析申请的数据,导致申请失败。
ShouldBind办法
ShouldBind用于绑定申请中的参数,将其转换成Go构造体或者map类型,该办法的参数类型绑定程序为
- 如果是query string,则依照form表单的格局进行解析
- 如果是post表单数据,则依照form表单的进行解析
- 如果是json格局,依照json格局解析
- 如果是xml格局,依照XML格局解析
- 如果是protobuf格局,依照protobuf格局解析
在绑定参数的时候,Gin会依据申请的Content-Type主动抉择核实的参数绑定形式,能够通过ShouldBind办法来实现主动绑定。例如,如果申请的Content-Type为application/json,则Gin会主动将申请体中的JSON数据解析为Go构造体
为什么query string会依照form表单进行解析呢?form表单不是放在body里的吗?
尽管form表单数据通常被放在POST申请中的body外面,然而在HTTP申请中,form表单数据也能够以query string的模式呈现在url中。在这种状况下,query string中的键值对与form表单中的键值对是雷同的,都是由键和值组成的键值对,通过&符号进行宰割
因而,Gin在解析query string时会依照form表单的格局进行解析,行将query string中的键值对解析为Go构造体或者Map类型。这样就可能通过Gin的ShouldBind办法对立解决query string和form表单数据,进步了代码复用性和可读性
须要留神的是,在将query string解析为Go构造体或者map类型的时候,须要将URL编码本义的字符进行解码。例如将%20转换为空格。Gin会主动进行这一步操作,不须要手动进行解码。
Gin中间件
Gin容许开发者在解决申请的过程中加上本人的钩子函数,这个钩子函数就被称为中间件,中间件适宜解决一些公共的业务逻辑,比方登录认证、权限校验、数据分页、记录日志、耗时统计等等。
在JAVA等面向对象编程语言中,面向切面编程(AOP)的思维和中间件是相似的
而拦截器(interceptor)的思维和中间件也是相似的
AOP和MiddleWare、Interceptor都是用于改善软件系统架构的技术,但它们的实现和指标有所不同
相同点
- 都是通过将特定性能从次要业务逻辑中分离出来,以改善零碎的可维护性和可扩展性
- 都是在零碎中插入特定代码来实现所需性能的(hook)
- 都能够进步代码的复用性,缩小反复代码的编写
不同点
- AOP关注的是切面,即与业务逻辑无关的横切关注点,如安全性、日志记录、性能检测等等,它们被成为切面,AOP应用依赖注入和动静代理等特定的技术,实现这些切面
- 中间件关注的是不同零碎组件之间的通信和交互,是一种软件层,为应用程序提供根底服务,如消息传递、数据传输和近程调用等等
AOP更关注于解决代码层面的问题,中间件则更关注于解决零碎层面的问题
拦截器通常只在特定的代码门路或者逻辑流中执行,例如在特定的web申请或者调用特定的办法的时候,通常由程序自身实现,通过代码中的特定注解或配置来申明和应用,旨在通过拦挡申请和响应来解决和批改它们,以实现特定的性能,如安全性、性能检测和日志记录等等
应用中间件
//注册全局中间件 c.Use(middleware())//注册某一条路由的中间件 r.GET("/xxx",MiddleWare(),handler)
留神:在中间件或者handler中启用新的goroutine的时候,不能应用原始的上下文c *gin.Context,必须应用其只读正本c.Copy()
c.Next和c.Abort
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain { finalSize := len(group.Handlers) + len(handlers) if finalSize >= int(abortIndex) { // 这里有一个最大限度 panic("too many handlers") } mergedHandlers := make(HandlersChain, finalSize) copy(mergedHandlers, group.Handlers) copy(mergedHandlers[len(group.Handlers):], handlers) return mergedHandlers}一个路由的中间件函数和处理函数联合到一起成为一条解决链条实质上就是一个由HandlerFunc组成的切片Next:func (c *Context) Next() { c.index++ for c.index < int8(len(c.handlers)) { c.handlers[c.index](c) c.index++ }}通过索引遍历HandlersChain链条,从而实现顺次调用该路由的每一个函数Abort:func (c *Context) Abort() { c.index = abortIndex // 间接将索引置为最大限度值,从而退出循环}
Next:
Abort:
中断整个调用链条,从以后函数返回
咱们的handlers也是HandleFunc类型,所以如果一条路由的专用middleware,调用了c.Next,其实就是间接跳到了handlers中去执行
优雅关机
优雅关机的应用场景,咱们不能让一个我的项目随便的退出,因为这个时候可能还有申请没有解决完,如果你一个信号、或者一个stop按键可能间接让程序进行,那么显然这个我的项目是不合格的。正确做法是应该解决完所有申请、开释对应资源之后,再进行程序
上面是一个例子
// 把 run 放在子协程中执行go func() { r.Run()}()// 一个信号通道exit:=make(chan os.Signal)// 监听通道中有没有这两种信号signal.Notify(exit,syscall.SIGINT,syscall.SIGTERM)<-exitlog.Println("process exit")
本文由mdnice多平台公布