深度解析golang中Context在HTTP服务中的角色

本文深入剖析了Go语言HTTP服务中如何利用Context来取消请求,并通过一个例子展示了错误追踪的过程。当客户端使用带Cancel的Context发起请求,服务端发送大量数据时,客户端可以通过调用Cancel函数中断请求。错误追踪从客户端的Context出发,经过http.Client.Do方法,进入Transport的RoundTrip过程,最终在持久连接的readLoop中感知到Context的取消,关闭连接,导致数据读取时出现网络错误。整个过程涉及到了Context、Transport、持久连接和错误处理机制的交互,揭示了Go HTTP库的精细设计和错误处理逻辑。

问题背景

在go语言的http服务中,我们常常会使用到Context来取消一个请求,或者取消数据的读取。偶然的一次尝试,让我对Context有了一定的兴趣。接下来本文围绕下面的例子,分析http如何利用Context来控制请求的取消和影响数据读取。

例子

我们开启一个http服务,发送大量数据给每个请求,代码如下:
srv.go:http服务

package main

import (
	"fmt"
	"net/http"
)

func hello(w http.ResponseWriter, r *http.Request) {
   
   
	for i := 0; i < 100*10000; i++ {
   
   
		w.Write([]byte("hello world"))
	}
}

func main() {
   
   
	fmt.Println("listening 8888:")
	http.HandleFunc("/hello", hello)
	_ = http.ListenAndServe(":8888", nil)
}

client.go: 发送请求的客户端

package main

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

func main() {
   
   

	client := http.Client{
   
   }
	request, err := http.NewRequest(http.MethodPost, "http://127.0.0.1:8888/hello", nil)
	ctx, cancelFunc := context.WithCancel(request.Context())
	request = request.WithContext(ctx)
	if err != nil {
   
   
		return
	}
	response, err := client.Do(request)
	if err != nil {
   
   
		log.Fatal(err)
	}
	cache := make([]byte, 128)
	timer := time.NewTimer(time.Millisecond)
	go func() {
   
   
		select {
   
   
		case <-timer.C:
			cancelFunc()
		}
	}()
	for {
   
   
		read, err := response.Body.Read(cache)
		if err == nil {
   
   
			fmt.Println(string(cache[:read]))
			continue
		}
		if err == io.EOF {
   
   
			fmt.Println(string(cache[:read]))
			break
		}
		log.Fatal(err)
	}

}

代码很简单,就不做注释啦。分别启动服务和client,我们将得到如下结果:
在这里插入图片描述
我们看到这句话Process finished with the exit code 1,程序非正常退出,那么首先是追踪这个错误,下面我们追踪这个错误。

错误追踪

首先清楚这个“context canceled” 是客户端打印出来的:

log.Fatal(err)
// 这个错误来源于读取Response中的数据时得到错误,而且这个错误非io.EOF错误

断点入口:

read, err := response.Body.Read(cache)

我们会进入transport.go文件中:

func (es *bodyEOFSignal) Read(p []byte) (n int, err error) {
   
    // 这里表明我们读取的body是bodyEOFSignal类型
	es.mu.Lock()
	closed, rerr := es.closed, es.rerr
	es.mu.Unlock()
	if closed {
   
   
		return 0, errReadOnClosedResBody
	}
	if rerr != nil {
   
   
		return 0, rerr
	}

	n, err = es.body.Read(p)// 我们在这里读到了错误,这里是什么错误,在后面将会介绍
	if err != nil {
   
   
		es.mu.Lock()
		defer es.mu.Unlock()
		if es.rerr == nil {
   
   
			es.rerr = err
		}
		err 
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值