【Golang】golang中http请求的context传递到异步任务的坑

news2025/1/5 9:47:37

文章目录

    • 前言
    • 一、HTTP请求的Context传递到异步任务的坑

前言

在golang中,context.Context可以用来用来设置截止日期、同步信号,传递请求相关值的结构体。 与 goroutine 有比较密切的关系。

在web程序中,每个Request都需要开启一个goroutine做一些事情,这些goroutine又可能会开启其他的 goroutine去访问后端资源,比如数据库、RPC服务等,它们需要访问一些共享的资源,比如用户身份信息、认证token、请求截止时间等 这时候可以通过Context,来跟踪这些goroutine,并且通过Context来控制它们, 这就是Go语言为我们提供的Context,中文可以理解为“上下文”。

简单看一下Context结构:

type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key interface{}) interface{}
}
  • Deadline方法是获取设置的截止时间的意思,第一个返回值是截止时间,到了这个时间点,Context会自动发起取消请求; 第二个返回值ok==false时表示没有设置截止时间,如果需要取消的话,需要调用取消函数(CancleFunc)进行取消。
  • Done方法返回一个只读的chan,类型为struct{},在goroutine中,如果该方法返回的chan可以读取,则意味着parent context已经发起了取消请求, 我们通过Done方法收到这个信号后,就应该做清理操作,然后退出goroutine,释放资源。之后,Err 方法会返回一个错误,告知为什么 Context 被取消。
  • Err方法返回取消的错误原因,Context被取消的原因。
  • Value方法获取该Context上绑定的值,是一个键值对,通过一个Key才可以获取对应的值,这个值一般是线程安全的。

常用的

// 传递一个父Context作为参数,返回子Context,以及一个取消函数用来取消Context。
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
// 和WithCancel差不多,它会多传递一个截止时间参数,意味着到了这个时间点,会自动取消Context,
// 当然我们也可以不等到这个时候,可以提前通过取消函数进行取消。
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)

// WithTimeout和WithDeadline基本上一样,这个表示是超时自动取消,是多少时间后自动取消Context的意思
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)

//WithValue函数和取消Context无关,它是为了生成一个绑定了一个键值对数据的Context,
// 绑定的数据可以通过Context.Value方法访问到,这是我们实际用经常要用到的技巧,一般我们想要通过上下文来传递数据时,可以通过这个方法,
// 如我们需要tarce追踪系统调用栈的时候。
func WithValue(parent Context, key, val interface{}) Context

一、HTTP请求的Context传递到异步任务的坑

看下面例子:我们将http的context传递到goroutine 中:

package main

import (
	"context"
	"fmt"
	"net/http"
	"time"
)

func IndexHandler(resp http.ResponseWriter, req *http.Request) {
	ctx := req.Context()
	go func(ctx context.Context) {
		for {
			select {
			case <-ctx.Done():
				fmt.Println("gorountine off,the err is: ", ctx.Err())
				return
			default:
				fmt.Println(333)
			}
		}
	}(ctx)

	time.Sleep(1000)
	resp.Write([]byte{1})
}
func main() {

	http.HandleFunc("/test1", IndexHandler)
	http.ListenAndServe("127.0.0.1:8080", nil)
}

结果:
在这里插入图片描述
从上面结果来看,在http请求返回之后,传入gorountine的context被cancel掉了,如果不巧,你在gorountine中进行一些http调用或者rpc调用传入了这个context,那么对应的请求也将会被cancel掉。因此,在http请求中异步任务出去时,如果这个异步任务中需要进行一些rpc类请求,那么就不要直接使用或者继承http的context,否则将会被cancel。

纠其原因:http请求再结束后,将会cancel掉这个context,所以异步出去的请求中收到的context是被cancel掉的。

下面来看下源代码:
ListenAndServe–>Server:Server方法中有一个大的for循环,这个for循环中,针对每个请求,都会起一个协程进行处理。
在这里插入图片描述
serve方法处理一个新连接,

// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
	c.remoteAddr = c.rwc.RemoteAddr().String()
	ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
	defer func() {
		if err := recover(); err != nil && err != ErrAbortHandler {
			const size = 64 << 10
			buf := make([]byte, size)
			buf = buf[:runtime.Stack(buf, false)]
			c.server.logf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf)
		}
		if !c.hijacked() {
			c.close()
			c.setState(c.rwc, StateClosed, runHooks)
		}
	}()

	if tlsConn, ok := c.rwc.(*tls.Conn); ok {
		if d := c.server.ReadTimeout; d != 0 {
			c.rwc.SetReadDeadline(time.Now().Add(d))
		}
		if d := c.server.WriteTimeout; d != 0 {
			c.rwc.SetWriteDeadline(time.Now().Add(d))
		}
		if err := tlsConn.Handshake(); err != nil {
			// If the handshake failed due to the client not speaking
			// TLS, assume they're speaking plaintext HTTP and write a
			// 400 response on the TLS conn's underlying net.Conn.
			if re, ok := err.(tls.RecordHeaderError); ok && re.Conn != nil && tlsRecordHeaderLooksLikeHTTP(re.RecordHeader) {
				io.WriteString(re.Conn, "HTTP/1.0 400 Bad Request\r\n\r\nClient sent an HTTP request to an HTTPS server.\n")
				re.Conn.Close()
				return
			}
			c.server.logf("http: TLS handshake error from %s: %v", c.rwc.RemoteAddr(), err)
			return
		}
		c.tlsState = new(tls.ConnectionState)
		*c.tlsState = tlsConn.ConnectionState()
		if proto := c.tlsState.NegotiatedProtocol; validNextProto(proto) {
			if fn := c.server.TLSNextProto[proto]; fn != nil {
				h := initALPNRequest{ctx, tlsConn, serverHandler{c.server}}
				// Mark freshly created HTTP/2 as active and prevent any server state hooks
				// from being run on these connections. This prevents closeIdleConns from
				// closing such connections. See issue https://golang.org/issue/39776.
				c.setState(c.rwc, StateActive, skipHooks)
				fn(c.server, tlsConn, h)
			}
			return
		}
	}

	// HTTP/1.x from here on.

	ctx, cancelCtx := context.WithCancel(ctx)
	c.cancelCtx = cancelCtx
	defer cancelCtx()

	c.r = &connReader{conn: c}
	c.bufr = newBufioReader(c.r)
	c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)

	for {
		// 从连接中读取请求
		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)
		}
		.....
		.....
		// Expect 100 Continue support
		req := w.req
		if req.expectsContinue() {
			if req.ProtoAtLeast(1, 1) && req.ContentLength != 0 {
				// Wrap the Body reader with one that replies on the connection
				req.Body = &expectContinueReader{readCloser: req.Body, resp: w}
				w.canWriteContinue.setTrue()
			}
		} 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.
		serverHandler{c.server}.ServeHTTP(w, w.req)
		/**
		* 重点在这儿,处理完请求后将会调用w.cancelCtx()方法cancel掉context
		**/
		w.cancelCtx()
		if c.hijacked() {
			return
		}
		w.finishRequest()
		if !w.shouldReuseConnection() {
			if w.requestBodyLimitHit || w.closedRequestBodyEarly() {
				c.closeWriteAndWait()
			}
			return
		}
		c.setState(c.rwc, StateIdle, runHooks)
		c.curReq.Store((*response)(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))
			if _, err := c.bufr.Peek(4); err != nil {
				return
			}
		}
		c.rwc.SetReadDeadline(time.Time{})
	}
}

至此,我们知道,http请求在结束后将会主动cancel掉context。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用docker部署nginx并支持https

配置nginx支持https&#xff0c;其实也简单&#xff0c;搞个证书&#xff0c;然后修改下配置文件就好了。我以前一篇文章&#xff08;使用docker部署多个nginx站点并配置负载均衡&#xff09;为例&#xff0c;做个记录。 如前所述&#xff0c;我使用docker&#xff0c;部署了3…

一文带你看懂软件测试(功能、接口、性能、自动化)详解

全文2000字&#xff0c;预计阅读时间10分钟&#xff0c;建议先点赞收藏慢慢看 一、软件测试功能测试 测试用例编写是软件测试的基本技能&#xff1b;也有很多人认为测试用例是软件测试的核心&#xff1b;软件测试中最重要的是设计和生成有效的测试用例&#xff1b;测试用例是测…

面了个京东拿30k出来的,牛逼到家了。。。

今天上班开早会就是新人见面仪式&#xff0c;听说来了个很厉害的大佬&#xff0c;年纪还不大&#xff0c;是上家公司离职过来的&#xff0c;薪资已经达到中高等水平&#xff0c;很多人都好奇不已&#xff0c;能拿到这个薪资应该人不简单&#xff0c;果然&#xff0c;自我介绍的…

RK平台如何配置USB功能

简介 RK平台基本能够通过dts配置就能实现USB功能。为了方便理解&#xff0c;我这里分三部分来介绍&#xff0c;包括&#xff1a;usb-phy&#xff0c;usb控制器&#xff0c;usb供电。 usb-phy usb-phy负责最底层的信号转换&#xff0c;主要是硬件的差分信号转换成数字信号传给…

十条ChatGPT常用的Prompt

Prompt 本文数据来源&#xff1a;Will 3.6-6.16 硅谷&#xff0c;原作者&#xff1a;rowancheung 一&#xff0c;简化复杂的信息 Prompt&#xff1a; 将&#xff08;主题&#xff09;分解成更小、更容易理解的部分。使用类比和现实生活中的例子来简化概念并使其更相关 Brea…

Python之并发多线程操作

一、threading模块介绍 multiprocess模块的完全模仿了threading模块的接口&#xff0c;二者在使用层面&#xff0c;有很大的相似性 二、开启线程的两种方式 方式一 #方式一 from threading import Thread import time def sayhi(name):time.sleep(2)print(%s say hello %na…

最大公约数(GCD) 与 最小公倍数(LCM)的 定义、关系、求法

最大公约数 与 最小公倍数 约数 和 倍数最大公约数最小公倍数 最大公约数与最小公倍数的关系求最大公约数、最小公倍数例一例二 约数 和 倍数 如果数 a a a能被数 b b b整除&#xff0c; a a a就叫做 b b b的倍数&#xff0c; b b b就叫做 a a a的约数。 约数和倍数都表示一个…

从0-1实战react项目

文章目录 1. 安装2. 完成一个组件开发3. 添加路由3. 引入element-react1. 运行发现报错./node_modules/element-react/dist/npm/es5/src/locale/format.js2. 接着又报错The <Router /> component appears to be a function component that returns a class instance. Cha…

[SpringBoot]关于Profile配置文件关于Slf4j日志

关于Profile配置文件 在Spring系列框架中&#xff0c;关于配置文件&#xff0c;允许同时存在多个配置文件&#xff08;例如同时存在a.yml、b.yml等&#xff09;&#xff0c;并且&#xff0c;你可以按需切换某个配置文件&#xff0c;这些默认不生效、需要被激活才生效的配置&am…

【ProtoBuf】protobuf序列化协议

Protobuf介绍 Protobuf (Protocol Buffers) 是谷歌开发的一款无关平台&#xff0c;无关语言&#xff0c;可扩展&#xff0c;轻量级高效的序列化结构的数据格式&#xff0c;用于将自定义数据结构序列化成字节流&#xff0c;和将字节流反序列化为数据结构。所以很适合做数据存储…

容器底层实现技术

一、Namespace 和 Cgroup 1、容器技术发展历史 2、Docker 容器实现原理 1. Docker 容器在实现上通过 namespace 技术实现进程隔离&#xff0c; 通过Cgroup 技术实现容器进程可用资源的限制 3、Namespace Namespace &#xff1a;命名空间 1. 作用&#xff1a;资源隔离 2. 原理&…

web前端课程作业设计:个人简历

一.说明 今天博主的web前端选修课结课了&#xff0c;期末大作业也提交了&#xff0c;今天写一篇博客把我的大作业分享给大家。 二.题目 1. 大作业题目 个人简历主页设计 2. 内容要求 应尽量包含以下内容&#xff1a; 包含个人基本信息、教育背景、个人风采、与我联系四块…

企业四要素核验-企业四要素核验接口-api接口

接口地址&#xff1a; https://登录后显示/pyi/184/358(支持:http/https)) 在线查询&#xff1a;https://www.wapi.cn/api_detail/184/358.html 网站地址&#xff1a;https://www.wapi.cn 返回格式&#xff1a;json,xml 请求方式&#xff1a;GET,POST 请求说明&#xff1a; …

【实用篇】Elasticsearch01

分布式搜索引擎01 – elasticsearch基础 1.初识elasticsearch 1.1.了解ES 1.1.1.elasticsearch的作用 elasticsearch是一款非常强大的开源搜索引擎&#xff0c;具备非常多强大功能&#xff0c;可以帮助我们从海量数据中快速找到需要的内容 例如&#xff1a; 在GitHub搜索…

智慧公厕系统如何通过物联网技术提高公厕的管理效率

智慧公厕系统可以通过物联网技术&#xff0c;实现公共卫生间的智能化管理和服务&#xff0c;提高管理效率。本文将详细介绍智慧公厕系统如何通过物联网技术提高公共卫生间的管理效率&#xff0c;从硬件、软件、系统等方面逐一分析。 XP-智慧厕所方案-HYF20230328&#xff08;16…

工业企业为什么要用边缘计算网关?

在我们进入智能制造和工业4.0的新时代&#xff0c;工业企业的数据需求正急速增长。传感器&#xff0c;机器和设备每分钟都在产生大量数据&#xff0c;它们对实时处理和分析的需求比以往任何时候都要强烈。这就是为什么工业企业需要边缘计算网关。 边缘计算网关在物联网架构中担…

大数据:Apache hive分布式sql计算平台,hive架构,hive部署,hive初体验

大数据&#xff1a;Apache hive分布式sql计算平台 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c;可能很多算法学生都得去找开发&#xff0c;测开 测开的话&#xff0c;你就得学数据库&#xff0c;sql&#xff0c;oracle&a…

Java002——JDK的安装以及配置环境变量

为什么要安装jdk 1、JDK 全称 Java Development Kit&#xff0c;意为 Java 开发工具。&#xff0c;要想开发java程序就必须安装JDK。没有JDK的话&#xff0c;无法编译运行Java程序。 2、JDK包含的基本组件包括以下文件&#xff1a;   javac.exe,用于编译java文件&#xff0c…

不经意传输(OT)了解

概述 OT&#xff0c;不经意传输&#xff0c;常被大量用于安全多方计算中&#xff0c;能够很大程度决定一个SMPC协议的效率。它的核心概念是接收方可以从发送方&#xff08;持有秘密信息&#xff09;手中选择性接收自己想要的信息而接收方对所选择的信息内容一无所知。目前有2-…

乐谱文件转换,支持批量mscz、mxl、musicxml转mp3等格式

我是一个喜欢听音乐的人&#xff0c;每天都会在路上听着歌放松自己。但是有时候想要听的歌并没有下载下来&#xff0c;或者格式不兼容。 最近我发现了一个神奇的软件——mscz转mp3&#xff0c;可以把乐谱文件转成mp3格式&#xff01; 软件界面简洁明了&#xff0c;使用也非常…