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

这篇具有很好参考价值的文章主要介绍了【Golang】golang中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)
}

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

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

下面来看下源代码:
ListenAndServe–>Server:Server方法中有一个大的for循环,这个for循环中,针对每个请求,都会起一个协程进行处理。
【Golang】golang中http请求的context传递到异步任务的坑
serve方法处理一个连接中的请求,并在一个请求serverHandler{c.server}.ServeHTTP(w, w.req)结束后cancel掉对应的context:

// 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。此外,在请求异常时候也会主动cancel掉context(cancel目的就是为了快速失败),具体可见w.conn.r.startBackgroundRead() 其中的实现。

在日常开发中,我们知道有时候会存在客户端超时情况,和ctx相关的原因可归纳如下:

  • 服务端收到的请求的request context被cancel掉。
  • 客户端本身收到context deadline exceeded错误
  • 服务端业务业务使用了http的context,但没有用于做rpc等需要建立连接的任务,那么客户端即使收到了context canceled的错误,服务端实际上还是在继续执行业务代码。
  • 服务端业务业务使用了http的context,并用于做rpc等需要建立连接的任务,那么客户端收到context canceled错误,并且服务端也会在对应的rpc等建立连接任务处返回context cancled的错误。

最后,如果context cancel掉了,但是业务又在继续执行,有时候并不是我们想要的结果,因为这会占用资源,因此我们可以主动在业务中通过监听context Done的信号来做context canceled的处理,从而可以达到快速失败,节约资源的目的。文章来源地址https://www.toymoban.com/news/detail-472723.html

到了这里,关于【Golang】golang中http请求的context传递到异步任务的坑的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • golang 发起 http 请求,获取访问域名的 ip 地址(net, httptrace)

    今天碰到了个需求,我要知道程序对外访问的 http 请求域名的 ip 地址。 直接查看 golang 的 net/http 包,发现 Response 中并没有我想要的 ip 信息。 考虑到在 OSI 七层模型中, ip 是网络层协议,而 http 是应用层协议。去翻了下 net 包,发现了基础用法。 先提一下,域名访问服务器

    2024年02月02日
    浏览(54)
  • 【Golang 接口自动化01】使用标准库net/http发送Get请求

    目录 发送Get请求 响应信息 拓展  资料获取方法 使用Golang发送get请求很容易,我们还是使用 http://httpbin.org 作为服务端来进行演示。 更多的响应内容我们查看安装路径的net包中Response struct 的信息,里面有详细的注释,参考路径: C:Gosrcnethttpresponse.go : 用过Python的同学都知

    2024年02月14日
    浏览(40)
  • 【Golang 接口自动化02】使用标准库net/http发送Post请求

    目录 写在前面 发送Post请求 示例代码 源码分析 Post请求参数解析 响应数据解析 验证 发送Json/XMl Json请求示例代码 xml请求示例代码 总结 资料获取方法 上一篇我们介绍了使用  net/http  发送get请求,因为考虑到篇幅问题,把Post单独拎了出来,我们在这一篇一起从源码来了解一

    2024年02月14日
    浏览(62)
  • golang中context详解

    编码中遇到上下文信息传递,并发信息取消等,记录下在go语言中context包的使用。 在Go语言中,context包提供了一种在程序中传递截止日期、取消信号、请求范围数据和其他元数据的方式。context包的核心类型是Context接口,它定义了在执行上下文中传递的方法。Context接口的主要

    2024年01月21日
    浏览(32)
  • golang Context应用举例

      golang标准库里Context实际上是一个接口(即一种编程规范、 一种约定)。   通过查看源码里的注释,我们得到如下约定: Done()函数返回一个只读管道,且管道里不存放任何元素(struct{}),所以用这个管道就是为了实现阻塞 Deadline()用来记录到期时间,以及是否到期。 Err()用来

    2024年02月08日
    浏览(35)
  • Golang:浅析Context包

    在golang官方文档context package - context - Go Packages中是这样介绍context包的: 在context包中定义了context类型来在不同的Goroutine 之间传递上下文,携带截止时间、取消信号以及携带上下文的系统参数(k-v)的类型。对服务器的传入请求应该创建上下文,对服务器的传出调用应该接受上

    2024年02月06日
    浏览(42)
  • golang之context实用记录

    简言 WithCancel()函数接受一个 Context 并返回其子Context和取消函数cancel 新创建协程中传入子Context做参数,且需监控子Context的Done通道,若收到消息,则退出 需要新协程结束时,在外面调用 cancel 函数,即会往子Context的Done通道发送消息 注意:当 父Context的 Done() 关闭的时候,子

    2024年02月09日
    浏览(32)
  • Golang 如何基于现有的 context 创建新的 context?

    目录 基于现有的 context 创建新的 context 现有创建方法的问题 Go 1.21 中的 context.WithoutCancel 函数 Go 版本低于 1.21 该怎么办? 在 Golang 中,context 包提供了创建和管理上下文的功能。当需要基于现有的 context.Context 创建新的 context 时,通常是为了添加额外的控制信息或为了满足特

    2024年01月17日
    浏览(40)
  • 【golang】Context超时控制与原理

    在Go语言圈子中流行着一句话: Never start a goroutine without knowing how it will stop。 翻译:如果你不知道协程如何退出,就不要使用它。 在创建协程时,我们可能还会再创建一些别的子协程,那么这些协程的退出就成了问题。在Go1.7之后,Go官方引入了Context来实现协程的退出。不仅

    2024年01月22日
    浏览(36)
  • Golang中context包基础知识详解

    目录 什么是context.Context? 如何构造context.Context对象? 衍生Context方法 使用context包需要注意的点 context.Context是Golang标准库提供的接口(context包对此接口有多种实现),该接口提供了四个抽象法: Deadline方法,返回context.Context被取消的时间点,也就是需要完成任务的截止时间

    2024年02月02日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包