golang-gin框架入门

基础

快速入门

  • gin完整支持路由框架
  • 支持全局异常(错误)处理
  • 内置渲染
  • 高可扩展

组件

  • 在gin框架中四个基本组件是:
    • Engine:是web server的根数据结构,也是基础容器;它包含复用器、中间件和配置设置类似SpringBoot的SpringApplication;
    • RouteGroup路由组,保存了路由路径,Radix Tree数据结构;
    • Handler:处理函数、中间件
    • Context一次请求/响应的完整上下文,封装了请求和响应的操作及需要的数据

Engine

基础
  • Engine 是框架的实例,它包含复用器、中间件和配置设置。一般使用New () 或Default () 创建 Engine 实例;
    • 默认包含:Logger和Recover中间件,用于日志和全局异常处理
  • 继承了RouterGroup
  • Engine一般使用Run(ip:port)直接启动
    • 同样支持通过其他方式启动
    • 支持设置server服务属性
golang-gin框架入门_第1张图片
方法
  • Engine的方法大概可以分为
    • 全局处理
      • 异常处理
        • NoMethod:请求的路径的方式不对(405),在开启运行默认处理的时候,会调用这里设置的处理方法
        • NoRoute:没有路由路径(404),会调用这里设置的处理方法
      • 全局绑定
        • Use绑定全局调用链相当于spring的拦截器,其中的Context的Next方法决定拦截前后
        • LoadHtmlGloba&LoadHTMLFiles全局绑定加载的Html,避免重复书写路径(之后只需要书写html文件即可),当多个文件路径的时候可以使用LoadHTMLFiles
        • Delims设置一组用于HTML模板渲染的左右分隔符,默认是{{}}
      • 其他RouterGroup的StaticXXX方法,主要是绑定静态资源
        • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5qnmnKNf-1681483225389)(gin.assets/image-20220610153337575-16548464189554.png)]
    • 运行
      • RunXXX系列都是运行相关,即以什么方式运行服务、另外Handler也是
    • 扩展
      • SetHTMLTemplate:自定义模板解析引擎
      • SetFuncMap:模板解析方法
      • SetTrustedProxies:设置信任的代理的IP地址,默认都信任

RouteGroup和Handler

RouteGroup
  • 非常简单,就是配置各种路由,支持前缀路由(路由组)配置
golang-gin框架入门_第2张图片
Handler
  • 就是一系列处理函数,类似Netty的Handler,自定义;
  • 在gin中,每个路由会绑定HandlerChain,管理handler

Context

  • 在go和go框架中,上下文是一个非常核心的概念,上下文具有线程安全和局部可见的特点,当然和原生Context有点不同

  • 上下文对象同样是gin的基础,在gin中context方法非常的多

    • //GET系列
      Get(key string) (value any, exists bool)
      MustGet(key string) any
      GetString(key string) (s string)
      GetBool(key string) (b bool)
      GetInt(key string) (i int)
      GetInt64(key string) (i64 int64)
      GetUint(key string) (ui uint)
      GetUint64(key string) (ui64 uint64)
      GetFloat64(key string) (f64 float64)
      GetTime(key string) (t time.Time)
      GetDuration(key string) (d time.Duration)
      GetStringSlice(key string) (ss []string)
      GetStringMap(key string) (sm map[string]any)
      GetStringMapString(key string) (sms map[string]string)
      GetStringMapStringSlice(key string) (smss map[string][]string)
      GetQuery(key string) (string, bool)
      GetQueryArray(key string) (values []string, ok bool)
      GetQueryMap(key string) (map[string]string, bool)
      GetHeader(key string) string
      GetRawData() ([]byte, error)
      
      
      //params
      Param(key string) string
      AddParam(key string, value string)
      
      
      
      //form
      PostForm(key string) (value string)
      DefaultPostForm(key string, defaultValue string) string
      GetPostForm(key string) (string, bool)
      PostFormArray(key string) (values []string)
      initFormCache()
      GetPostFormArray(key string) (values []string, ok bool)
      PostFormMap(key string) (dicts map[string]string)
      GetPostFormMap(key string) (map[string]string, bool)
      FormFile(name string) (*multipart.FileHeader, error)
      MultipartForm() (*multipart.Form, error)
      
      
      
      //获取cookie
      Cookie(name string) (string, error)
      
      
      //query查询是否存在
      Query(key string) (value string)
      DefaultQuery(key string, defaultValue string) string
      QueryArray(key string) (values []string)
      initQueryCache()
      QueryMap(key string) (dicts map[string]string)
      
      
      
      //handler处理器链
      reset()
      Copy() *Context
      HandlerName() string
      HandlerNames() []string
      Handler() HandlerFunc
      FullPath() string
      	//下一个
      Next()
      	//终止
      IsAborted() bool
      Abort()
      AbortWithStatus(code int)
      AbortWithStatusJSON(code int, jsonObj any)
      AbortWithError(code int, err error) *Error
      Error(err error) *Error
      Set(key string, value any)
      
      
      
      //参数继续引擎绑定,必须成功(BindXXXX),可以失败(ShouldBindXXX)
      Bind(obj any) error
      BindJSON(obj any) error
      BindXML(obj any) error
      BindQuery(obj any) error
      BindYAML(obj any) error
      BindTOML(obj interface{}) error
      BindHeader(obj any) error
      BindUri(obj any) error
      MustBindWith(obj any, b binding.Binding) error
      BindWith(obj any, b binding.Binding) error
      ShouldBind(obj any) error
      ShouldBindJSON(obj any) error
      ShouldBindXML(obj any) error
      ShouldBindQuery(obj any) error
      ShouldBindYAML(obj any) error
      ShouldBindTOML(obj interface{}) error
      ShouldBindHeader(obj any) error
      ShouldBindUri(obj any) error
      ShouldBindWith(obj any, b binding.Binding) error
      ShouldBindBodyWith(obj any, bb binding.BindingBody) (err error)
      
      
      
      //响应
      	//重定向
      Redirect(code int, location string)
      	//header、code、message、cookie
      Status(code int)
      Header(key string, value string)
      SetSameSite(samesite http.SameSite)
      SetCookie(name string, value string, maxAge int, path string, domain string, secure bool, httpOnly bool)
      Render(code int, r render.Render)
      	//data,将数据转为对应格式的data在写入resp
      HTML(code int, name string, obj any)
      IndentedJSON(code int, obj any)
      SecureJSON(code int, obj any)
      JSONP(code int, obj any)
      JSON(code int, obj any)
      AsciiJSON(code int, obj any)
      PureJSON(code int, obj any)
      XML(code int, obj any)
      YAML(code int, obj any)
      TOML(code int, obj interface{})
      ProtoBuf(code int, obj any)
      String(code int, format string, values ...any)
      DataFromReader(code int, contentLength int64, contentType string, reader io.Reader, extraHeaders map[string]string)
      File(filepath string)
      FileFromFS(filepath string, fs http.FileSystem)
      FileAttachment(filepath string, filename string)
      Stream(step func(w io.Writer) bool) bool
      
      
      
      //信息
      ClientIP() string
      RemoteIP() string
      ContentType() string
      IsWebsocket() bool
      SSEvent(name string, message any)
      Negotiate(code int, config Negotiate)
      NegotiateFormat(offered ...string) string
      SetAccepted(formats ...string)
      SaveUploadedFile(file *multipart.FileHeader, dst string) error
      
      //go Context
      Deadline() (deadline time.Time, ok bool)
      Done() <-chan struct{}
      Err() error
      Value(key any) any
      
方法
  • 信息获取
    • Get系列
      • 可以指定类型获取参数、除了MustGet,其他在没有该参数不会有painc,而是返回false,可以通过GetQuery查询是否存在该参数
      • 获取信息
        • GetHeader:获取头部信息
        • GetBody:获取body的数据
    • Form系列
      • 获取表单数据
        • golang-gin框架入门_第3张图片
    • Params:获取参数
    • 解析获取:即解析成指定的结构体/数据结构
      • 必须解析成功:BindXXX
      • 可以解析失败:ShouldBindXXX
  • 响应
    • 重定向
      • Redirect
    • 信息
      • SetCookie、Handler、SetStatus等
    • 返回的数据
      • HTML、JSON等
  • 信息和go的Context

使用

  • Engine:基本使用和iris没有很大的区别,即通过engine绑定host、路由、handler
  • Context:和iris的iris.Context一样,gin有gin.Context
  • gin支持自动解析(数据绑定),而不需要每个获取
  • gin封装了返回数据,可以使用框架的JSON格式
  • 支持路由组、restFul风格参数、最佳适配、静态资源和静态资源路由绑定
  • 支持中间件(和iris处理逻辑一样)
  • 支持模板引擎
  • 集成swagger-go
    • https://github.com/swaggo/swag/blob/master/README_zh-CN.md
//实例
package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
)

//结构体解析
type User struct {
	Name     string `json:"userName"`
	Password string `json:"password"`
	Other    string `json:"other"`
}


//统一封装返回
type Result struct {
	Code    int
	Message string
	Data    interface{}
}

func init() {

}

func login(c *gin.Context) {
	var user User
    //绑定JSON解析
	err := c.BindJSON(&user)
	if err != nil {
		return
	}
	fmt.Println(user)
	result := Result{
		Code:    200,
		Message: "SUCCESS",
		Data:    "ok",
	}
    //自动将JSON作为返回参数
	c.JSON(200, &result)
    //执行下一条语句
	c.Next()
}

func printHello(c *gin.Context) {
    //获取路径参数
	fmt.Println(c.Param("hello"))
	c.Next()
}

func printUser(c *gin.Context) {
	fmt.Println("user" + c.RemoteIP())
    c.Next()
}

func globalPrint(c *gin.Context) {
	fmt.Println("global")
	c.Next()
}

func main() {
	engine := gin.New()
    //绑定全局处理器
	engine.Use(globalPrint)
    //绑定静态资源
	engine.Static("/img", "./resource/static/img/*")
	//绑定全局Html前缀路径
    engine.LoadHTMLGlob("web/route/resource/web/*")
	engine.GET("/", func(context *gin.Context) {
        //html,通过模板引擎gin.H渲染  ---->//在index默认通过{{.name}}获取,可以自定义标识符替换{{}}
		context.HTML(200, "index.html", gin.H{
			"name": "lili",
		})
	})
    //绑定路由组
	group := engine.Group("/user", printUser)
    //:hello为restful风格的参数传递
	group.GET("/say/:hello", printHello)
	//表单提交
	engine.POST("/login", login)
    //运行
	err := engine.Run(":8888")
	if err != nil {
		fmt.Println("error")
	}
}

实现

golang-gin框架入门_第4张图片

  • 很明显,gin框架就像SpringMVC一样,其实现主要包括
    • 路由注册和保存路由算法
    • 请求处理的过程
      • 执行的过程(调度的过程)
      • Request数据解析
      • Response数据绑定
      • 模板引擎渲染
    • 组件间的关系

启动

Engine

  • 实现了Http库的Handler接口,所以可以作为Handler直接使用http库
    • golang-gin框架入门_第5张图片
type Engine struct {
	RouterGroup

	//是否自动重定向(即/a/,自动重定向为/a;默认为true
	RedirectTrailingSlash bool

	// 是否对书写(多余/、字母大小写不符)的自动重定向
	RedirectFixedPath bool

	// 是否对请求的方法不正确(405),通过NotMethod处理
	HandleMethodNotAllowed bool

	// 请求IP;默认为true
	ForwardedByClientIP bool


	// 开启路径参数
	UseRawPath bool
	// 路径参数转移;默认为true
	UnescapePathValues bool
	// 以从 URL 中解析参数,即使带有额外的斜杠。
	RemoveExtraSlash bool

	//允许的IP请求头
	RemoteIPHeaders []string
	// 信任的请求的平台
	TrustedPlatform string

	// 传入的参数最大占用空间
	MaxMultipartMemory int64

	//是否支持HTTP2
	UseH2C bool

	// Context是否可以回退
	ContextWithFallback bool

    //公共请求和响应设置(模板引擎、对象池、参数等)
	delims           render.Delims
	secureJSONPrefix string
	HTMLRender       render.HTMLRender
	FuncMap          template.FuncMap
	allNoRoute       HandlersChain
	allNoMethod      HandlersChain
	noRoute          HandlersChain
	noMethod         HandlersChain
	pool             sync.Pool
	trees            methodTrees
	maxParams        uint16
	maxSections      uint16
	trustedProxies   []string
	trustedCIDRs     []*net.IPNet
}

过程

New方法

  • 无论是通过Default还是New获取Engine都会调用New方法
    • Default绑定了默认的日志和全局异常处理
  • New方法
    • 绑定默认的Engine参数
    • 注入根RouteGroup

Run方法

  • 就是调用内置的http开启监听:http.ListenAndServe
func (engine *Engine) Run(addr ...string) (err error) {
	address := resolveAddress(addr)
    //注入engine作为http的handler,即作为监听的端口的所有请求的handler;
    	//engine.Handler()方法就是返回http1.x或者http2.0的engine
	err = http.ListenAndServe(address, engine.Handler())
}
  • 到这里启动基本完成,
    • 下面介绍启动前注入路由
    • 请求到达后的处理

路由

压缩前缀树

  • 压缩前缀树就是:通过合并节点实现的前缀树
    • 前缀树:即字典树,一般只需要:final、value、children数组/map(以及全量单词),显然前缀树的增删改查都是logn
    • 合并节点:将以当前节点为根的子树中所有节点都是只有一个节点的子节点合并为一个节点

路由表的压缩前缀树

//压缩前缀树节点
type node struct {
	path      string	//value
	indices   string	//节点与子节点的分裂的第一个字符
	wildChild bool		//是否为参数节点
	nType     nodeType	//节点类型:根节点、参数节点、通配符、普通节点
	priority  uint32	//handler数量
	children  []*node 	//子节点
	handlers  HandlersChain	//处理器链
	fullPath  string	//全量路径
}

//封装的获取到压缩前缀书查询结果
type nodeValue struct {
	handlers HandlersChain
	params   *Params
	tsr      bool
	fullPath string
}

路由生成

  • 在gin中使用动态路由策略,所以实现路由没有使用hash表,而是压缩前缀树
  • 路由生成主要包括
    • 路由路径生成:即将可能的前缀路由合并到当前路由
    • 处理器链:将当前路由的handler加入到处理器链
    • 加入路由表
      • 获取方法的根路由
      • 压缩前缀书方法加入路由到路由表
//生成路由
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
	//路由路径(加入前缀)
    absolutePath := group.calculateAbsolutePath(relativePath)
	//处理器链
    handlers = group.combineHandlers(handlers)
	//加入路由表
    group.engine.addRoute(httpMethod, absolutePath, handlers)
	return group.returnObj()
}


//获取根路由
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
	//根据方法类型获取根路径
	root := engine.trees.get(method)
	if root == nil {
		root = new(node)
		root.fullPath = "/"
		engine.trees = append(engine.trees, methodTree{method: method, root: root})
	}
    //加入路由表
	root.addRoute(path, handlers)
}

//添加路由路径
func (n *node) addRoute(path string, handlers HandlersChain) {
	fullPath := path
	n.priority++

	// 空树,直接使用压缩前缀即可
	if len(n.path) == 0 && len(n.children) == 0 {
		n.insertChild(path, fullPath, handlers)
		n.nType = root
		return
	}

	parentFullPathIndex := 0

walk:
	for {
		//最长公共路径(排除通配符和参数匹配)
		i := longestCommonPrefix(path, n.path)

		//生成节点
		if i < len(n.path) {
			child := node{
				//...
			}
            //修改n
             n.children = []*node{&child}
			// []byte for proper unicode char conversion, see #65
			n.indices = bytesconv.BytesToString([]byte{n.path[i]})
			n.path = path[:i]
			n.handlers = nil
			n.wildChild = false
			n.fullPath = fullPath[:parentFullPathIndex+i]
		}

		// 去除参数、通配符、多余/
		if i < len(path) {
			path = path[i:]
			c := path[0]

			// '/' after param
			if n.nType == param && c == '/' && len(n.children) == 1 {
				parentFullPathIndex += len(n.path)
				n = n.children[0]
				n.priority++
				continue walk
			}

			// Check if a child with the next path byte exists
			for i, max := 0, len(n.indices); i < max; i++ {
				if c == n.indices[i] {
					parentFullPathIndex += len(n.path)
					i = n.incrementChildPrio(i)
					n = n.children[i]
					continue walk
				}
			}
			if c != ':' && c != '*' && n.nType != catchAll {
				n.indices += bytesconv.BytesToString([]byte{c})
				child := &node{
					fullPath: fullPath,
				}
				n.addChild(child)
				n.incrementChildPrio(len(n.indices) - 1)
				n = child
			} 
            //插入子节点
			n.insertChild(path, fullPath, handlers)
			return
		}

		n.handlers = handlers
		n.fullPath = fullPath
		return
	}
}

路由查找

  • 路由查找非常简单
    • 找到压缩前缀树根节点
    • 找到相应的路由节点

请求处理

  • gin使用了原生的http库,实现了其中handler的ServeHTTP方法,通过handleHTTPRequest统一处理请求
  • gin使用了对象池技术,减少了对象的创建和释放
  • **handleHTTPRequest**主要就是根据请求方法+路径确定处理器链和参数、然后调用处理器链进行处理
    • 根据需要:对于无路由或者请求方法不正确进行处理

你可能感兴趣的:(golang,gin)