go进行http请求偶发EOF问题分析
目录
- 简介
- 分析
- 代码
- 运行
- 报文
简介
go使用连接池进行http请求,一般都能请求成功,但偶然会出现请求失败返回EOF错误的情况;类似Java的org.apache.http.NoHttpResponseException
分析
客户端通过keep alive机制保障性能,简单理解就是复用tcp五元会话,用于进行多次http请求;但如果服务端的空闲保活时间是10s,在第一次请求完的10s进行了第二次请求,此时客户端认为连接仍然有效继续发起请求,但服务端发出了FIN报文不再对此连接进行响应,从而导致客户端请求失败并出现EOF错误。
偶发就是因为两个时间要恰好碰到一起才可能触发这个问题
- 服务器发送了FIN报文,但是客户端还没有收到,但是客户端已经发送了请求数据包
- 如果在服务器超时前发起了请求,那连接此时还可用,正常
- 如果在服务器超时后发起了请求,那连接已经完成FIN关闭流程,请求会触发新的会话,正常
解决方式:
- 在出现EOF的时候,进行重试,此时会触发新的五元组连接进行请求(推荐)
- 设置客户端的空闲保活时间小于服务端的空闲保活时间
- IdleConnTimeout 此时客户端会在超时时主动向服务端发送RST进行连接重置php
代码
package main import ( "bytes" "crypto/tls" "encoding/json" "errors" "fmjst" "io" "io/ioutil" "net/http" "time" ) func main() { // 创建自定义的 Transport,设置连接池参数 tr := &http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: true, // 忽略 TLS 证书验证 }, MaxIdleConns: 1, // 限制最大空闲连接数为1 MaxIdleConnsPerHost: 1, // 限制每个host最大空闲连接数为1 IdleConnTimeout: 20 * time.Second, // 本地空闲连接超时设置为20s DisableKeepAlives: false, // 启用 keep-alive MaxConnsPerHost: 1, // 限制每个host的最大连接数为1,强制复用连接 ForceAttemptHTTP2: false, // 禁用 HTTP/2 } // 创建 HTTP 客户端 client := &http.Client{ Transport: tr, Timeout: 5 * time.Second, // 设置请求超时时间 } // 准备请求参数 url := "https://192.168.24.70:2018/api/zguard/sysmng/syscfg/basecfg/sysname/651d5b9f-225b-4c8c-9f06-80bfad3fa977" cookie := "session-id=f416b188c91bc72a06853b362d5cb7b3a6b68a43" // 准备请求体数据 requestBodhttp://www.devze.comy := map[string]string{ "sys_name": "N-GUARD", } // 将 map 转换为 JSON jsonBody, err := json.Marshal(requestBody) if err != nil { fmt.Printf("JSON 编码失败: %v\n", err) } // 循环发送请求,模拟使用已关闭的连接 for i := 0; i < 5; i++ { // 只测试两次请求即可 // 每次请求都创建新的 bytes.Buffer,确保 Body 可以重复读取 bodyReader := bytes.NewBuffer(jsonBody) req, err := http.NewRequest("PUT", url, bodyReadewww.devze.comr) if err != nil { fmt.Printf("创建请求失败: %v\n", err) continue } // 设置 Content-Length req.ContentLength = int64(len(jsonBody)) // 设置请求头 req.Header.Set("Cookie", cookie) req.Header.Set("Content-Type", "application/json") fmt.Printf("发送第 %d 个请求...\n", i+1) // 发送请求 resp, err := client.Do(req) if err != nil { fmt.Printf("请求失败: %v\n", err) if errors.Is(err, io.EOF) { fmt.Printf("连接不再可用: 重试:新的五元重新发起连接\n") bodyReader := bytes.NewBuffer(jsonBody) reqretry, err := http.NewRequest("PUT", url, bodyReader) if err != nil { fmt.Printf("创建请求失败: %v\n", err) continue } // 设置 Content-Length reqretry.ContentLength = int64(len(jsonBody)) // 设置请求头 reqretry.Header.Set("Cookie", cookie) reqretry.Header.Set("Content-Type", "application/json") resp, err = client.Do(reqretry) if err != nil { fmt.Printf("err:\n", err) continue } } else { continue } } // 读取响应 body, err := ioutil.ReadAll(resp.Body) if err != nil { fmt.Printf("读取响应失败: %v\n", err) } resp.Body.Close() fmt.Printf("请求 %d - 状态码: %d, 响应: %s\n", i+1, resp.StatusCode, string(body)) fmt.Println("等待10秒后发送第二个请求...") time.Sleep(10 * time.Second) // 等待10秒,此时服务端已经关闭连接(10s) time.Sleep(500 * time.Millisecond) } }
运行
[xiaofeng@localhost httpkeepalive]$ go run main.go
发送第 1 个请求...请求 1 - 状态码: 200, 响应: {"code":0,"result":"0","message":"成功"}等待10秒后发送第二个请求...发送第 2 个请求...请求 2 - 状态码: 200, 响应: {"code":0,"result":"0",编程"message":"成功"}等待10秒后发送第二个请求...发送第 3 个请求...请求失败: Put "https://192.168.24.70:2018/api/zguard/sysmng/syscfg/basecfg/sysname/651d5b9f-225b-4c8c-9f06-80bfad3fa977": EOF连接不再可用: 重试:新的五元重新发起连接请求 3 - 状态码: 200, 响应: {"code":0,"result":"0","message":"成功"}等待10秒后发送第二个请求...发送第 4 个请求...请求失败: Put "https://192.168.24.70:2018/api/zguard/sysmng/syscfg/basecfg/sysname/651d5b9f-225b-4c8c-9f06-80bfad3fa977": EOF连接不再可用: 重试:新的五元重新发起连接请求 4 - 状态码: 200, 响应: {"code":0,"result":"0","message":"成功"}等待10秒后发送第二个请求...发送第 5 个请求...请求 5 - 状态码: 200, 响应: {"code":0,"result":"0","message":"成功"}等待10秒后发送第二个请求...
报文
到此这篇关于go进行http请求偶发EOF问题分析的文章就介绍到这了,更多相关go http请求偶发EOF内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!
精彩评论