轻量级 Web 框架 Gin 结构分析

【Go】Gin Web 服务框架 一. Gin Gin是Golang的一个web框架,封装优雅API友好,源码注释比较明确。借助Gin框架开发web服务,不仅可以省去很多常用的封装带来的时间,也有助于团队的编码风格和形成规范。 详细参考Github项目地址 https://github.com/gin-gonic/gin 安装Gin go get -u github.com/gin-gonic/gin 使用Gin import ... 阅读详情

Go 语言最流行了两个轻量级 Web 框架分别是 Gin 和 Echo,这两个框架大同小异,都是插件式轻量级框架,背后都有一个开源小生态来提供各式各样的小插件,这两个框架的性能也都非常好,裸测起来跑的飞快。本节我们只讲 Gin 的实现原理和使用方法,Gin 起步比 Echo 要早,市场占有率要高一些,生态也丰富一些。

go get -u github.com/gin-gonic/gin
640?wx_fmt=png

Hello World

Gin 框架的 Hello World 只需要 10 行代码,比大多数动态脚本语言稍微多几行。

package main

import "github.com/gin-gonic/gin"

func main() {
    r := gin.Default()
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "pong",
        })
    })
    r.Run() // listen and serve on 0.0.0.0:8080
}

type H map[string]interface{}

gin.Engine

Engine 是 Gin 框架最重要的数据结构,它是框架的入口。我们通过 Engine 对象来定义服务路由信息、组装插件、运行服务。正如 Engine 的中文意思「引擎」一样,它就是框架的核心发动机,整个 Web 服务的都是由它来驱动的。

发动机属于精密设备,构造非常复杂,不过 Engine 对象很简单,因为引擎最重要的部分 —— 底层的 HTTP 服务器使用的是 Go 语言内置的 http server,Engine 的本质只是对内置的 HTTP 服务器的包装,让它使用起来更加便捷。

gin.Default() 函数会生成一个默认的 Engine 对象,里面包含了 2 个默认的常用插件,分别是 Logger 和 Recovery,Logger 用于输出请求日志,Recovery 确保单个请求发生 panic 时记录异常堆栈日志,输出统一的错误响应。

func Default() *Engine {
    engine := New()
    engine.Use(Logger(), Recovery())
    return engine
}

路由树

在 Gin 框架中,路由规则被分成了最多 9 棵前缀树,每一个 HTTP Method对应一棵「前缀树」,树的节点按照 URL 中的 / 符号进行层级划分,URL 支持 :name 形式的名称匹配,还支持 *subpath 形式的路径通配符 。

// 匹配单节点 named
pattern = /book/:id
match /book/123
nomatch /book/123/10
nomatch /book/

// 匹配子节点 catchAll mode
/book/*subpath
match /book/
match /book/123
match /book/123/10
640?wx_fmt=png

每个节点都会挂接若干请求处理函数构成一个请求处理链 HandlersChain。当一个请求到来时,在这棵树上找到请求 URL 对应的节点,拿到对应的请求处理链来执行就完成了请求的处理。

type Engine struct {
  ...
  trees methodTrees
  ...
}

type methodTrees []methodTree

type methodTree struct {
    method string
    root   *node  // 树根
}

type node struct {
  path string // 当前节点的路径
  ...
  handlers HandlersChain // 请求处理链
  ...
}

type HandlerFunc func(*Context)

type HandlersChain []HandlerFunc

func (e *Engine) addRoute(method, path string, handlers HandlersChain)

gin.RouterGroup

RouterGroup 是对路由树的包装,所有的路由规则最终都是由它来进行管理。Engine 结构体继承了 RouterGroup ,所以 Engine 直接具备了 RouterGroup 所有的路由管理功能。这是为什么在 Hello World 的例子中,可以直接使用 Engine 对象来定义路由规则。同时 RouteGroup 对象里面还会包含一个 Engine 的指针,这样 Engine 和 RouteGroup 就成了「你中有我我中有你」的关系。

type Engine struct {
  RouterGroup
  ...
}

type RouterGroup struct {
  ...
  engine *Engine
  ...
}

GET(string, ...HandlerFunc) IRoutes
POST(string, ...HandlerFunc) IRoutes
DELETE(string, ...HandlerFunc) IRoutes
PATCH(string, ...HandlerFunc) IRoutes
PUT(string, ...HandlerFunc) IRoutes
OPTIONS(string, ...HandlerFunc) IRoutes
HEAD(string, ...HandlerFunc) IRoutes
// 匹配所有 HTTP Method
Any(string, ...HandlerFunc) IRoutes

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

    v1 := router.Group("/v1")
    {
        v1.POST("/login", loginEndpoint)
        v1.POST("/submit", submitEndpoint)
        v1.POST("/read", readEndpoint)
    }

    v2 := router.Group("/v2")
    {
        v2.POST("/login", loginEndpoint)
        v2.POST("/submit", submitEndpoint)
        v2.POST("/read", readEndpoint)
    }

    router.Run(":8080")
}

gin.Context

这个对象里保存了请求的上下文信息,它是所有请求处理器的入口参数。

type HandlerFunc func(*Context)

type Context struct {
  ...
  Request *http.Request // 请求对象
  Writer ResponseWriter // 响应对象
  Params Params // URL匹配参数
  ...
  Keys map[string]interface{} // 自定义上下文信息
  ...
}

// 获取 URL 匹配参数  /book/:id
func (c *Context) Param(key string) string
// 获取 URL 查询参数 /book?id=123&page=10
func (c *Context) Query(key string) string
// 获取 POST 表单参数
func (c *Context) PostForm(key string) string
// 获取上传的文件对象
func (c *Context) FormFile(name string) (*multipart.FileHeader, error)
// 获取请求Cookie
func (c *Context) Cookie(name string) (string, error) 
...

func (c *Context) JSON(code int, obj interface{})
func (c *Context) Protobuf(code int, obj interface{})
func (c *Context) YAML(code int, obj interface{})
...
// 自定义渲染
func (c *Context) Render(code int, r render.Render)

// 渲染器通用接口
type Render interface {
    Render(http.ResponseWriter) error
    WriteContentType(w http.ResponseWriter)
}

type ResponseWriter interface {
 // 容纳所有的响应头
 Header() Header
 // 写Body
 Write([]byte) (int, error)
 // 写Header
 WriteHeader(statusCode int)
}

插件与请求链

我们编写业务代码时一般也就是一个处理函数,为什么路由节点需要挂接一个函数链呢?

type node struct {
  path string // 当前节点的路径
  ...
  handlers HandlersChain // 请求处理链
  ...
}
type HandlerFunc func(*Context)
type HandlersChain []HandlerFunc

type Context struct {
  ...
  index uint8 // 当前的业务逻辑位于函数链的位置
  handlers HandlersChain // 函数链
  ...
}

// 挨个调用链条中的处理函数
func (c *Context) Next() {
    c.index++
    for s := int8(len(c.handlers)); c.index < s; c.index++ {
        c.handlers[c.index](c)
    }
}

640?wx_fmt=png

Gin 还支持 Abort() 方法中断请求链的执行,它的原理是将 Context.index 调整到一个比较大的数字,这样 Next() 方法中的调用循环就会立即结束。需要注意的 Abort() 方法并不是通过 panic 的方式中断执行流,执行 Abort() 方法之后,当前函数内后面的代码逻辑还会继续执行。

const abortIndex = 127
func (c *Context) Abort() {
    c.index = abortIndex
}

func SomePlugin(c *Context) {
  ...
  if condition {
    c.Abort()
    // continue executing
  }
  ...
}

640?wx_fmt=png

要是你学过 Python 语言,这种嵌套结构很容易让人联想到装饰器 decorator。如果所有的插件都使用嵌套执行流,那么就会变成了下面这张图

640?wx_fmt=png

RouterGroup 提供了 Use() 方法来注册插件,因为 RouterGroup 是一层套一层,不同层级的路由可能会注册不一样的插件,最终不同的路由节点挂接的处理函数链也不尽相同。

func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
    group.Handlers = append(group.Handlers, middleware...)
    return group.returnObj()
}

// 注册 Get 请求
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
    return group.handle("GET", relativePath, handlers)
}

func (g *RouterGroup) handle(method, path string, handlers HandlersChain) IRoutes {
 // 合并URL (RouterGroup有URL前缀)
    absolutePath := group.calculateAbsolutePath(relativePath)
    // 合并处理链条
 handlers = group.combineHandlers(handlers)
    // 注册路由树
 group.engine.addRoute(httpMethod, absolutePath, handlers)
    return group.returnObj()
}

HTTP 错误

当 URL 请求对应的路径不能在路由树里找到时,就需要处理 404 NotFound 错误。当 URL 的请求路径可以在路由树里找到,但是 Method 不匹配,就需要处理 405 MethodNotAllowed 错误。Engine 对象为这两个错误提供了处理器注册的入口

func (engine *Engine) NoMethod(handlers ...HandlerFunc)
func (engine *Engine) NoRoute(handlers ...HandlerFunc)

注意这两个错误处理器是定义在 Engine 全局对象上,而不是 RouterGroup。对于非 404 和 405 错误,需要用户自定义插件来处理。对于 panic 抛出来的异常需要也需要使用插件来处理。

静态文件服务

RouterGroup 对象里定义了下面三个用来服务静态文件的方法

// 服务单个静态文件
StaticFile(relativePath, filePath string) IRoutes
// 服务静态文件目录
Static(relativePath, dirRoot string) IRoutes
// 服务虚拟静态文件系统
StaticFS(relativePath string, fs http.FileSystem) IRoutes

它不同于错误处理器,静态文件服务挂在 RouterGroup 上,支持嵌套。这三个方法中 StaticFS 方法比较特别,它对文件系统进行了抽象,你可以提供一个基于网络的静态文件系统,也可以提供一个基于内存的静态文件系统。FileSystem 接口也很简单,提供一个路径参数返回一个实现了 File 接口的文件对象。不同的虚拟文件系统使用不同的代码来实现 File 接口。

type FileSystem interface {
 Open(path string) (File, error)
}

type File interface {
 io.Closer
 io.Reader
 io.Seeker
 Readdir(count int) ([]os.FileInfo, error)
 Stat() (os.FileInfo, error)
}

表单处理

当请求参数数量比较多时,使用 Context.Query() 和 Context.PostForm() 方法来获取参数就会显得比较繁琐。Gin 框架也支持表单处理,将表单参数和结构体字段进行直接映射。

package main

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

type LoginForm struct {
    User     string `form:"user" binding:"required"`
    Password string `form:"password" binding:"required"`
}

func main() {
    router := gin.Default()
    router.POST("/login", func(c *gin.Context) {
  var form LoginForm
        if c.ShouldBind(&form) == nil {
            if form.User == "user" && form.Password == "password" {
                c.JSON(200, gin.H{"status": "you are logged in"})
            } else {
                c.JSON(401, gin.H{"status": "unauthorized"})
            }
        }
    })
    router.Run(":8080")
}

Context.ShouldBind 方法遇到校验不通过时,会返回一个错误对象告知调用者校验失败的原因。它支持多种数据绑定类型,如 XML、JSON、Query、Uri、MsgPack、Protobuf等,根据请求的 Content-Type 头来决定使用何种数据绑定方法。

func (c *Context) ShouldBind(obj interface{}) error {
 // 获取绑定器
    b := binding.Default(c.Request.Method, c.ContentType())
    // 执行绑定
 return c.ShouldBindWith(obj, b)
}

Context.ShouldBind 是比较柔和的校验方法,它只负责校验,并将校验结果以返回值的形式传递给上层。Context 还有另外一个比较暴力的校验方法 Context.Bind,它和 ShouldBind 的调用形式一摸一样,区别是当校验错误发生时,它会调用 Abort() 方法中断调用链的执行,向客户端返回一个 HTTP 400 Bad Request 错误。

HTTPS

Gin 不支持 HTTPS,官方建议是使用 Nginx 来转发 HTTPS 请求到 Gin。

640?wx_fmt=png

GIN与Echo:选择正确Go框架的指南 本指南将详细介绍每个框架的特性、速度、社区热度以及它们各自擅长的项目类型。最后,您将能够为您的下一个Web项目选择完美的框架! 阅读详情

相关推荐

golang——Gin框架及路由介绍

gin框架中采用的路由库是基于httprouter做的支持Restful风格的API,意思是"表面层状态转化",是一个互联网应用程序的API设计理念:URL定位资源。可以创建路由组,为了管理相同的URL。import ("net/http"//将应用切换到“发布模式”以提升性能//创建路由//默认使用了两个中间件Logger(),Recovery()})})//路由组1})})//路由组2})})//监听端口,默认绑定端口8080。

weixin_57023347的博客 5850

Web 框架 Gin | Gin快速入门

大家好!我是 xcbeyond, xcbeyond 就是我,大家也可以叫我超哥!本文首发于我的博客:Gin 介绍 1、Gin 概述 Gin 是一个基于 Go 语言编写的 Web 框架,与 martini 框架类似,但拥有更好的性能,借助高性能的 httprouter,速度提升了近 40 倍。如果你追求高性能和开发效率,你会爱上 Gin 框架。 1.1 功能特性 快速 基于Radox树(一种更节省空间的 Trie 树结构)的路由,内存占用更小。 没有反射。 可预测的 API 性能。 中间件支.

xcbeyond|疯狂源自梦想,技术成就辉煌 1921

Gin框架介绍及使用

Gin框架介绍及使用 Gin是一个用Go语言编写的web框架。它是一个类似于martini但拥有更好性能的API框架, 由于使用了httprouter,速度提高了近40倍。 如果你是性能和高效的追求者, 你会爱上Gin。 Gin框架介绍 Go世界里最流行的Web框架,Github上有32K+star。 基于httprouter开发的Web框架。 中文文档齐全,简单易用的轻量级框架。 Gin框架安装与使用 安装 下载并安装Gin: go get -u github.com/gin-gonic/gin 第一个

m764395448的博客 3246

Gin源码之gin.Context结构体及其方法

Gin源码之gin.Context结构体及其方法

weixin_52690231的博客 5065

Gin项目实战02- 结构搭建

gin 实战项目

chenangdaka的博客 1696

Go语言Gin框架入门到实践:一篇超详细的Web开发指南

Go 语言(Golang)凭借其简洁的语法、出色的并发性能和高效的编译速度,在微服务和云原生领域大放异彩。而在众多 Go Web 框架中,Gin 以其极致的性能和轻量级的特性脱颖而出,成为最受欢迎和使用最广泛的框架之一。本文将作为你学习 Gin 框架的向导,从环境搭建开始,一步步带你掌握 Gin 的核心功能,并通过一个完整的实战案例,让你真正理解如何使用 Gin 高效地构建 Web 应用和 API。中间件本质上就是一个。下面是一个简单的示例,用于计算请求处理时间。

dl999666的博客 1459

Go语言web框架——Gin

Gin框架基本内容

尚墨1111的博客 8700

【Golang】Gin框架:Go语言中的轻量级Web框架

Gin是一个用Go语言编写的高性能HTTP web框架,它提供了快速的HTTP路由、中间件支持以及JSON编码等功能。 Gin框架以其简洁的API和高效的性能著称,非常适合用来构建RESTful API和中小型Web应用。 - **Gin:Go 语言编写的 Web 框架,以更好的性能实现类似 Martini 框架的 API。** - Gin是一个golang的net/http库封装的web微框架,封装比较优雅,API友好,源码注释比较明确。具有快速灵活,容错方便等特点。

景天科技苑 5万+

gin context和官方context_轻量级 Web 框架 Gin 结构分析

Go 语言最流行了两个轻量级 Web 框架分别是 Gin 和 Echo,这两个框架大同小异,都是插件式轻量级框架,背后都有一个开源小生态来提供各式各样的小插件,这两个框架的性能也都非常好,裸测起来跑的飞快。本节我们只讲 Gin 的实现原理和使用方法,Gin 起步比 Echo 要早,市场占有率要高一些,生态也丰富一些。goget-ugithub.com/gin-gonic/ginHe...

weixin_39854288的博客 489

Gin入门指南:从零开始快速掌握Go Web框架Gin

中间件函数用于记录日志。路由和请求处理:Gin 提供了简单而灵活的路由功能,可以方便地定义不同URL路径的处理函数,并支持 GET、POST 等各种HTTP请求方法。函数用于返回成功结果,它接收 Gin 的上下文对象,以及三个自定义的参数,分别为状态码、提示信息和数据。一般来说,我们需要在处理请求的路由函数中使用 defer 语句将捕获的异常记录下来,所以我们可以通过中间件实现全局的异常处理。参数绑定和验证:Gin 提供了强大的参数绑定和验证功能,可以方便地将请求参数绑定到结构体中,并进行数据验证和转换。

赵士杰——成功并非一蹴而就,而是在持之以恒的努力中逐渐实现。 3085

Ansys Zemax|无焦系统设计中的角度单位优化技巧

本文详细介绍了在Ansys Zemax OpticStudio中进行无焦系统设计的核心技巧。重点阐述了从聚焦思维到角度思维的转换,并演示了如何通过开启‘无焦像空间’模式、构建以波前和光束尺寸为目标的评价函数来优化激光扩束镜等系统。文章还分享了使用IMSF操作数处理混合系统以及常见误区避坑的实用指南,旨在帮助光学设计者高效完成无焦系统的设计与分析。

weixin_29231027的博客 363

gin框架

gin简介 gin是用Go语言写的后端web框架,简洁、轻量、支持高并发, 官方网站:https://gin-gonic.github.io/gin/ Github地址:https://github.com/gin-gonic/gin 下载 go get -u github.com/gin-gonic/gin 源码结构如下 sszxr:gin sszxr$ pwd /Users/sszxr/go...

Charliewolf的博客 3797

Intouch间接变量(扩展)

间接变量只是Intouch提供给开发者的便捷方式之一,并不是所有情况都一定要使用间接变量,有时候用条件语句或者内存变量的方式也能起到类似的作用。

weixin_41571357的博客 2168

Golang gin框架

gin框架学习一、Gin 介绍二、Gin 环境搭建三、golang 程序的热加载四、Gin 框架中的路由五、Gin HTML 模板渲染六、静态文件服务七、路由详解八、Gin 中自定义控制器九、Gin 中间件十、Gin 中自定义Model十一、Gin 文件上传十二、Gin 中的Cookie十三、Gin 中的Session十四、Gin 中使用GORM 操作mysql 数据库十五、原生 SQL 和 SQL 生成器十六、Gin 中使用GORM 实现表关联查询十七、 GORM 中使用事务十八、Gin 中使用go

book_longker的专栏 2万+

.NET+Vue企业级RBAC权限平台:开箱即用的生产就绪方案

RBAC(基于角色的访问控制)是企业系统安全治理的基础模型,其核心在于将用户、角色与权限解耦,通过动态能力组合实现细粒度管控。在真实工程中,RBAC需突破静态角色限制,支持时间维度授权、前后端双重鉴权、行级数据范围控制等关键能力。本文聚焦于一套已落地金融、政务、制造等多行业的生产级实现——基于.NET Minimal API与Vue 3的前后端分离架构,提供菜单/按钮/接口/数据四层权限控制,内置滑动过期JWT、Redis权限缓存、动态路由加载与权限指令等开箱即用能力,显著降低权限配置与审计成本。

weixin_33326218的博客 277

STK传感器配置全攻略:从光学到雷达的7种视场类型详解(附避坑指南)

本文详细解析了STK软件中传感器的7种视场类型,从基础的Simple Conic、Complex Conic到高级的SAR与EOIR,提供了完整的配置步骤与实战避坑指南。重点阐述了雷达、光学等不同物理设备的建模选择逻辑,并针对常见的“No Access”等报错问题给出了系统性的排查方案,帮助工程师高效完成卫星系统设计与任务分析。

weixin_29232507的博客 230

Gin 框架介绍与快速入门

TOC]Gin是一个轻量级的Go语言Web框架,它具有高性能和简洁的设计。由于其快速的路由匹配和处理性能,Gin成为Go语言中最受欢迎的Web框架之一。

淘小欣的博客 6606

金蝶ERP元数据解析:字段属性与表结构映射实战

本文深入解析金蝶ERP元数据的核心价值与应用实践,详细介绍了字段属性与表结构映射的关键技术。通过缓存获取与工具类直连两种方案对比,结合实际案例展示元数据在数据库校验、动态表单生成等场景的应用,帮助开发者高效解决二次开发中的常见问题。

weixin_33738555的博客 435

探索Gin框架:快速构建高性能的Golang Web应用

Gin框架是一个轻量级的Web框架,基于Go语言开发,旨在提供高性能和简洁的API。作者:鼠鼠我捏,要死了捏

qq_35716689的博客 50万+

BambuStudio学习笔记:GCode 类

关键成员解析 核心配置组件 状态跟踪系统 功能模块集成 核心方法剖析 主输出管道 实现多阶段处理: 分层处理流程: 实现智能路径规划: 关键特性: 实现模板故障检测: 采用三级流水线: 支持扩展类型: 实现多通道输出: 采用: 实现: 支持: 支持模块化编译: 典型流程: 实现功能:

zzzkk2009的专栏 938

Buck的振铃实验与分析

上上期我们提到了buck电路的开关的振铃波形,本质原因是LC的阻尼振荡。文章偏理论,那BUCK到底是怎么产生尖峰振荡呢? 要想把这个问题搞清楚,也很是不容易,所以文章有点长,请直接点赞转发加收藏。 问题 本期主要分析以下这两个问题: 1、死区时间是什么?这里有个小台阶是什么情况? 2、上下尖峰振荡是如何产生的?跟哪些因素有关? 理想的BUCK的SW波形 我们由浅入深,一步一步来,先看理想的开关SW波形—没有尖峰电压的波形。 为了能更好的看buck电路各个点的电压电流情况.

weixin_42005993的博客 3万+
上一篇: 分布式系统技术难题--异地多活
下一篇: MySQL 是怎样运行的:从根儿上理解 MySQL
码洞
博客等级 码龄11年 41粉丝 137原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值