gin源码分析(2)gin启动http服务

news/2024/6/24 8:04:36/文章来源:https://blog.csdn.net/shell812/article/details/137263796

启动http服务

//使用gin.Run()启动http服务
//使用gin.RunTLS启动https服务
func (engine *Engine) Run(addr ...string) (err error) {//获取端口,默认:8080address := resolveAddress(addr)debugPrint("Listening and serving HTTP on %s\n", address)//调用go实现好的http功能包,实现http功能//传入engine.Handler(),返回一个http Handler,由engine实现了该Handler函数ServeHTTP err = http.ListenAndServe(address, engine.Handler())return
}

gin.Run()函数调用go的官方包启动了一个http服务,并实现了http服务的回调ServeHTTP函数。当请求来的时候会调用gin的ServeHTTP函数

func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {//使用对象池初始化Contextc := engine.pool.Get().(*Context)//初始化writermem//writermem继承http.ResponseWriter。用来http回复c.writermem.reset(w)c.Request = req//初始化Contextc.reset()//处理请求engine.handleHTTPRequest(c)//移出对象池engine.pool.Put(c)
}

func (engine *Engine) handleHTTPRequest(c *Context) {httpMethod := c.Request.Method//省略一些参数初始化// Find root of the tree for the given HTTP methodt := engine.treesfor i, tl := 0, len(t); i < tl; i++ {if t[i].method != httpMethod {continue}root := t[i].root// 遍历请求树,找到方法value := root.getValue(rPath, c.params, c.skippedNodes, unescape)if value.params != nil {c.Params = *value.params}// 开始调用中间件if value.handlers != nil {c.handlers = value.handlersc.fullPath = value.fullPathc.Next()c.writermem.WriteHeaderNow()return}//处理重定向if httpMethod != http.MethodConnect && rPath != "/" {if value.tsr && engine.RedirectTrailingSlash {redirectTrailingSlash(c)return}if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {return}}break}//处理404请求if engine.HandleMethodNotAllowed {for _, tree := range engine.trees {if tree.method == httpMethod {continue}if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil {c.handlers = engine.allNoMethodserveError(c, http.StatusMethodNotAllowed, default405Body)return}}}c.handlers = engine.allNoRouteserveError(c, http.StatusNotFound, default404Body)
}

中间间调用与停止

调用

请求进来后后,调用Nex()函数后,所有的中间件(这里包括请求的处理函数)都会遍历运行

func (c *Context) Next() {c.index++//index大于handlers数量后停止后面中间件的运行for c.index < int8(len(c.handlers)) {c.handlers[c.index](c)c.index++}
}

Next()调用顺序分析
//中间件1
func M1(c *gin.Context) {fmt.Println("==============M1-A")c.Next()fmt.Println("==============M1-B")
}//中间件2
func M2(c *gin.Context) {fmt.Println("==============M2-A")
}//请求的执行函数
func List(c *gin.Context) {fmt.Println("user/list")
}//请求/user/list后的打印
==============M1-A
==============M2-A
user/list
==============M1-B

分析:

NEXT()调用c.handlers[c.index](c)执行第一个中间件,在调用第一个中间件函数M1执行中途调用了Next(),进入Next(),函数下标c.index++,指向下一个中间件函数,等待下一个中间件函数M2,以及M2后面的处理函数。直到所有的函数都调用完,回到M1,执行后面的逻辑,打印M1-B

停止

停止就很简单,把该次的调用index数值设为abortIndex,abortIndex为handlers的最大数量combineHandlers函数里做了限制handler添加的最大数量

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

关于Http Server

Gin http请求并发的关键在于调用了Go的原生Http框架,这个框架有点大,我们可以慢慢地去探索一下

ListenAndServe是gin启动http服务的调用函数

func ListenAndServe(addr string, handler Handler) error {//初始化httpServer服务结构体 server := &Server{Addr: addr, Handler: handler}//启动服务监听return server.ListenAndServe()
}//server.ListenAndServe()
func (srv *Server) ListenAndServe() error {//此处省掉部分参数配置//启动tcp监听 ln, err := net.Listen("tcp", addr)if err != nil {return err}//传入tcp listen return srv.Serve(ln)
}//srv.Serve(ln)
func (srv *Server) Serve(l net.Listener) error {//去掉一些空判断 //初始化onceCloseListener结构体//onceCloseListener继承net.Listener,再加sync.Once//保证tcp只被关闭一次 origListener := ll = &onceCloseListener{Listener: l}defer l.Close()//初始化http2协议 if err := srv.setupHTTP2_Serve(); err != nil {return err}//把当前Listener当成key,value为空结构体存入map中,trackListener重要代码如下//s.listeners[ln] = struct{}{}  //ln为Listener为指针&l //s.listenerGroup.Add(1)if !srv.trackListener(&l, true) {return ErrServerClosed}//从map中删除listener defer srv.trackListener(&l, false)//初始化goroutine的context//context是go原生提供用于goroutine的数据共享和流程控制 baseCtx := context.Background()if srv.BaseContext != nil {baseCtx = srv.BaseContext(origListener)if baseCtx == nil {panic("BaseContext returned a nil context")}}var tempDelay time.Duration // how long to sleep on accept failure//把这个Server做为value存入context中 ctx := context.WithValue(baseCtx, ServerContextKey, srv)for {//等待请求接入 rw, err := l.Accept()//tcp出错,等待 tempDelay毫秒 后重试if err != nil {//srv是否正在关闭 if srv.shuttingDown() {return ErrServerClosed}if ne, ok := err.(net.Error); ok && ne.Temporary() {if tempDelay == 0 {tempDelay = 5 * time.Millisecond} else {tempDelay *= 2}if max := 1 * time.Second; tempDelay > max {tempDelay = max}srv.logf("http: Accept error: %v; retrying in %v", err, tempDelay)time.Sleep(tempDelay)continue}return err}connCtx := ctxif cc := srv.ConnContext; cc != nil {connCtx = cc(connCtx, rw)if connCtx == nil {panic("ConnContext returned nil")}}tempDelay = 0//初始化一个conn结构体//conn结构体,绑定了本Server和Accept()返回的net.Conn c := srv.newConn(rw)//设置状态标志//此处的c.rwc就是上面的rw c.setState(c.rwc, StateNew, runHooks) // before Serve can return//启动一个goroutine,处理http请求 go c.serve(connCtx)}
}

func (c *conn) serve(ctx context.Context) {if ra := c.rwc.RemoteAddr(); ra != nil {c.remoteAddr = ra.String()}ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())var inFlightResponse *response//处理异常和关闭TCP defer func() {if err := recover(); err != nil && err != ErrAbortHandler {const size = 64 << 10buf := make([]byte, size)buf = buf[:runtime.Stack(buf, false)]c.server.logf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf)}if inFlightResponse != nil {inFlightResponse.cancelCtx()}if !c.hijacked() {if inFlightResponse != nil {inFlightResponse.conn.r.abortPendingRead()inFlightResponse.reqBody.Close()}c.close()c.setState(c.rwc, StateClosed, runHooks)}}()//省略掉处理https请求代码// HTTP/1.x from here on.//由BaseContext创建一个cancelContext ctx, cancelCtx := context.WithCancel(ctx)c.cancelCtx = cancelCtxdefer cancelCtx()//创建一个读Buffer和写Buffer c.r = &connReader{conn: c}c.bufr = newBufioReader(c.r)c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)for {//读出一个Respon w, err := c.readRequest(ctx)if c.r.remain != c.server.initialReadLimitSize() {// If we read any bytes off the wire, we're active.c.setState(c.rwc, StateActive, runHooks)}if err != nil {//省略错误处理 }// Expect 100 Continue supportreq := w.reqif req.expectsContinue() {if req.ProtoAtLeast(1, 1) && req.ContentLength != 0 {// Wrap the Body reader with one that replies on the connectionreq.Body = &expectContinueReader{readCloser: req.Body, resp: w}w.canWriteContinue.Store(true)}} else if req.Header.get("Expect") != "" {w.sendExpectationFailed()return}c.curReq.Store(w)if requestBodyRemains(req.Body) {registerOnHitEOF(req.Body, w.conn.r.startBackgroundRead)} else {w.conn.r.startBackgroundRead()}// HTTP cannot have multiple simultaneous active requests.[*]// Until the server replies to this request, it can't read another,// so we might as well run the handler in this goroutine.// [*] Not strictly true: HTTP pipelining. We could let them all process// in parallel even if their responses need to be serialized.// But we're not going to implement HTTP pipelining because it// was never deployed in the wild and the answer is HTTP/2.inFlightResponse = w//调用ServeHTTP回调serverHandler{c.server}.ServeHTTP(w, w.req)inFlightResponse = nilw的cancelCtx与c的cancelCtx不是同一个cancelCtxw.cancelCtx()//是否被劫持if c.hijacked() {return}//把response的buffer,flush出去,并关闭一些bufferw.finishRequest()c.rwc.SetWriteDeadline(time.Time{})if !w.shouldReuseConnection() {if w.requestBodyLimitHit || w.closedRequestBodyEarly() {c.closeWriteAndWait()}return}c.setState(c.rwc, StateIdle, runHooks)c.curReq.Store(nil)if !w.conn.server.doKeepAlives() {// We're in shutdown mode. We might've replied// to the user without "Connection: close" and// they might think they can send another// request, but such is life with HTTP/1.1.return}//设置超时关闭时间if d := c.server.idleTimeout(); d != 0 {c.rwc.SetReadDeadline(time.Now().Add(d))} else {c.rwc.SetReadDeadline(time.Time{})}// Wait for the connection to become readable again before trying to// read the next request. This prevents a ReadHeaderTimeout or// ReadTimeout from starting until the first bytes of the next request// have been received.if _, err := c.bufr.Peek(4); err != nil {return}c.rwc.SetReadDeadline(time.Time{})}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_1034341.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【c++】类和对象(七)

&#x1f525;个人主页&#xff1a;Quitecoder &#x1f525;专栏&#xff1a;c笔记仓 朋友们大家好&#xff0c;本篇文章来到类和对象的最后一部分 目录 1.static成员1.1特性 2.友元2.1引入&#xff1a;<<和>>的重载2.2友元函数2.3友元类 3.内部类4.匿名对象5.拷…

【Linux】在生产环境中,Linux系统排查常用命令

问题排查 文章目录 问题排查top命令CPU&#xff1a;vmstatprocscpu内存&#xff1a;free硬盘&#xff1a;df硬盘IO&#xff1a;iostat网络IO&#xff1a;ifstat 生产环境服务器变慢&#xff0c;诊断思路和性能评估 top命令 查看整机系统新能 使用top命令的话&#xff0c;重点…

Javascript/Node.JS中如何用多种方式避免属性为空(cannot read property of undefined ERROR)

>>>>>>问题 "cannot read property of undefined" 是一个常见的 JavaScript 错误&#xff0c;包含我在内很多人都会遇到&#xff0c;表示你试图访问一个未定义&#xff08;undefined&#xff09;对象的属性。这通常是因为你在访问一个不存在的对象…

【算法-PID】

算法-PID ■ PID■ 闭环原理■ PID 控制流程■ PID 比例环节&#xff08;Proportion&#xff09;■ PID 积分环节&#xff08;Integral&#xff09;■ PID 微分环节&#xff08;Differential&#xff09; ■ 位置式PID&#xff0c;增量式PID介绍■ 位置式 PID 公式■ 增量式 PI…

OpenCv —— cv::VideoCapture设置摄像头图像格式为“MJPEG“

背景 今天恰巧同事有台USB摄像头,她想要在Windows系统下通过OpenCV读取该摄像头宽高为1080x768、帧率为60的视频,用来做图像算法处理。但无奈通过网上OpenCV教程 读取的视频对应尺寸的帧率仅为10帧左右,根本无法满足使用要求。于是作者通过本篇文章介绍如何解决,欢迎交流指…

寒冬已逝,“量子春天”正来

最近&#xff0c;全球对量子技术领域的私人投资有所下降&#xff0c;引发了一些观点认为这个领域可能正逐渐衰退。 政治家、资助者和投资者并不总是以科学为关注焦点。然而&#xff0c;某些科技领域偶尔会成为热点&#xff0c;正如20世纪50年代核能技术的兴起&#xff0c;那时人…

如何开发创建自己的npm包并成功发布、维护至npm官方网站

npm&#xff0c;全称为Node Package Manager&#xff0c;是专为JavaScript生态系统设计的软件包管理系统&#xff0c;尤其与Node.js平台紧密关联。作为Node.js的默认包管理工具&#xff0c;npm为开发者提供了便捷的方式来安装、共享、分发和管理代码模块。 npm作为JavaScript世…

【Go】二十、反射

文章目录 1、反射2、对基本数据类型反射3、对结构体进行反射4、获取变量的类别5、通过反射修改基本类型变量的值6、通过反射操作结构体的属性和方法 1、反射 //核心包 import ("reflect")通过反射&#xff1a; 可以在运行时动态获取变量的类型、获取结构体的信息&a…

C++算法补充---STL

这里写目录标题 CSTL容器字符串函数(string容器函数)字符串转字符 算法交换函数拿到容器或者数组的第一个最大&#xff08;小&#xff09;值元素的下标或者值排序函数求字符数组的有效长度atoi函数&#xff08;将字符串类型的数字转为真正的int型数字&#xff09;string转字符 …

06 | Swoole 源码分析之 Coroutine 协程模块

首发原文链接&#xff1a;Swoole 源码分析之 Coroutine 协程模块 大家好&#xff0c;我是码农先森。 引言 协程又称轻量级线程&#xff0c;但与线程不同的是&#xff1b;协程是用户级线程&#xff0c;不需要操作系统参与。由用户显式控制&#xff0c;可以在需要的时候挂起、或…

【大模型】大模型 CPU 推理之 llama.cpp

【大模型】大模型 CPU 推理之 llama.cpp llama.cpp安装llama.cppMemory/Disk RequirementsQuantization测试推理下载模型测试 参考 llama.cpp 描述 The main goal of llama.cpp is to enable LLM inference with minimal setup and state-of-the-art performance on a wide var…

鸿蒙HarmonyOS应用开发之在非ArkTS线程中回调ArkTS接口

场景介绍 ArkTS是单线程语言&#xff0c;通过NAPI接口对ArkTS对象的所有操作都须保证在同一个ArkTS线程上进行。本示例将介绍通过napi_get_uv_event_loop和uv_queue_work实现在非ArkTS线程中通过NAPI接口回调ArkTS函数。 使用示例 接口声明、编译配置以及模块注册 接口声明 …

应急响应靶机训练-Linux2题解

前言 接上文&#xff0c;应急响应靶机训练Linux2 靶机地址&#xff1a;应急响应靶机-Linux(2) 题解 登录虚拟机&#xff1a; 修改面板密码 提交攻击者IP 答案&#xff1a;192.168.20.1 查看宝塔日志即可 用的net直接是网关 提交攻击者修改的管理员密码(明文) 答案&…

SpringBoot:自定义线程池配置类

文章目录 一、前言二、案例展示1、初始版本2、代码审核意见和优化建议3、潜在问题和风险4、优化建议5、优化后的代码 三、具体使用 一、前言 有时候我们在项目中做一些长链路的跑批任务时&#xff0c;基于Springboot项目的定时任务&#xff0c;我们可以指定一个自定义的线程配…

RVM安装Ruby笔记(Mac)

环境 硬件&#xff1a;Macbook Pro 系统&#xff1a;macOS 14.1 安装公钥 通过gpg安装公钥失败&#xff0c;报错如下&#xff1a; 换了几个公钥地址&#xff08;hkp://subkeys.pgp.net&#xff0c;hkp://keys.gnupg.net&#xff0c;hkp://pgp.mit.edu&#xff09;&#xff0c;…

2024最新华为OD机试试题库全 -【二叉树的广度搜索】- C卷

1. 🌈题目详情 1.1 ⚠️题目 有一棵二叉树,每个节点由一个大写字母标识(最多26个节点)。 现有两组字母,分别表示后序遍历(左孩子->右孩子->父节点)和中序遍历(左孩子->父节点->右孩子)的结果,请你输出层序遍历的结果。 1.2 🔣输入要求 每个输入文…

【服务端】node.js详细的配置

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;开发者-曼亿点 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 曼亿点 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a…

【面试专题】设计模式

1.说一下开发中需要遵守的设计原则&#xff1f; 设计模式中主要有六大设计原则&#xff0c;简称为SOLID &#xff0c;是由于各个原则的首字母简称合并的来(两个L算一个,solid 稳定的)&#xff0c;六大设计原则分别如下&#xff1a; 1、单一职责原则 单一职责原则的定义描述非…

C++ —— C++11新增语法

目录 一&#xff0c;列表初始化 1.1 这是什么&#xff1f; 1.2 initializer_list 1.3 在容器的运用 1.4 STL中的变化 二&#xff0c;右值引用和左值引用 2.1 是什么&#xff1f; 2.2 这两个东西有啥关系&#xff1f; 2.3 有啥用&#xff1f; 三&#xff0c;*移动构…

二维码门楼牌管理应用平台建设:实现数据高效整理与入库

文章目录 前言一、数据整理入库的重要性二、数据整理入库的流程三、数据整理入库的技术支持四、数据整理入库的挑战与对策 前言 随着城市管理的数字化和智能化发展&#xff0c;二维码门楼牌管理应用平台成为了城市治理的新宠。本文将探讨如何通过数据整理入库这一关键环节&…