go進(jìn)行http請(qǐng)求偶發(fā)EOF問題分析
簡介
go使用連接池進(jìn)行http請(qǐng)求,一般都能請(qǐng)求成功,但偶然會(huì)出現(xiàn)請(qǐng)求失敗返回EOF錯(cuò)誤的情況;類似java的org.apache.http.NoHttpResponseException
分析
客戶端通過keep alive機(jī)制保障性能,簡單理解就是復(fù)用tcp五元會(huì)話,用于進(jìn)行多次http請(qǐng)求;但如果服務(wù)端的空閑?;顣r(shí)間是10s,在第一次請(qǐng)求完的10s進(jìn)行了第二次請(qǐng)求,此時(shí)客戶端認(rèn)為連接仍然有效繼續(xù)發(fā)起請(qǐng)求,但服務(wù)端發(fā)出了FIN報(bào)文不再對(duì)此連接進(jìn)行響應(yīng),從而導(dǎo)致客戶端請(qǐng)求失敗并出現(xiàn)EOF錯(cuò)誤。
偶發(fā)就是因?yàn)閮蓚€(gè)時(shí)間要恰好碰到一起才可能觸發(fā)這個(gè)問題
- 服務(wù)器發(fā)送了FIN報(bào)文,但是客戶端還沒有收到,但是客戶端已經(jīng)發(fā)送了請(qǐng)求數(shù)據(jù)包
- 如果在服務(wù)器超時(shí)前發(fā)起了請(qǐng)求,那連接此時(shí)還可用,正常
- 如果在服務(wù)器超時(shí)后發(fā)起了請(qǐng)求,那連接已經(jīng)完成FIN關(guān)閉流程,請(qǐng)求會(huì)觸發(fā)新的會(huì)話,正常
解決方式:
- 在出現(xiàn)EOF的時(shí)候,進(jìn)行重試,此時(shí)會(huì)觸發(fā)新的五元組連接進(jìn)行請(qǐng)求(推薦)
- 設(shè)置客戶端的空閑?;顣r(shí)間小于服務(wù)端的空閑?;顣r(shí)間
- IdleConnTimeout 此時(shí)客戶端會(huì)在超時(shí)時(shí)主動(dòng)向服務(wù)端發(fā)送RST進(jìn)行連接重置
代碼
package main
import (
"bytes"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"time"
)
func main() {
// 創(chuàng)建自定義的 Transport,設(shè)置連接池參數(shù)
tr := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true, // 忽略 TLS 證書驗(yàn)證
},
MaxIdleConns: 1, // 限制最大空閑連接數(shù)為1
MaxIdleConnsPerHost: 1, // 限制每個(gè)host最大空閑連接數(shù)為1
IdleConnTimeout: 20 * time.Second, // 本地空閑連接超時(shí)設(shè)置為20s
DisableKeepAlives: false, // 啟用 keep-alive
MaxConnsPerHost: 1, // 限制每個(gè)host的最大連接數(shù)為1,強(qiáng)制復(fù)用連接
ForceAttemptHTTP2: false, // 禁用 HTTP/2
}
// 創(chuàng)建 HTTP 客戶端
client := &http.Client{
Transport: tr,
Timeout: 5 * time.Second, // 設(shè)置請(qǐng)求超時(shí)時(shí)間
}
// 準(zhǔn)備請(qǐng)求參數(shù)
url := "https://192.168.24.70:2018/api/zguard/sysmng/syscfg/basecfg/sysname/651d5b9f-225b-4c8c-9f06-80bfad3fa977"
cookie := "session-id=f416b188c91bc72a06853b362d5cb7b3a6b68a43"
// 準(zhǔn)備請(qǐng)求體數(shù)據(jù)
requestBody := map[string]string{
"sys_name": "N-GUARD",
}
// 將 map 轉(zhuǎn)換為 JSON
jsonBody, err := json.Marshal(requestBody)
if err != nil {
fmt.Printf("JSON 編碼失敗: %v\n", err)
}
// 循環(huán)發(fā)送請(qǐng)求,模擬使用已關(guān)閉的連接
for i := 0; i < 5; i++ { // 只測試兩次請(qǐng)求即可
// 每次請(qǐng)求都創(chuàng)建新的 bytes.Buffer,確保 Body 可以重復(fù)讀取
bodyReader := bytes.NewBuffer(jsonBody)
req, err := http.NewRequest("PUT", url, bodyReader)
if err != nil {
fmt.Printf("創(chuàng)建請(qǐng)求失敗: %v\n", err)
continue
}
// 設(shè)置 Content-Length
req.ContentLength = int64(len(jsonBody))
// 設(shè)置請(qǐng)求頭
req.Header.Set("Cookie", cookie)
req.Header.Set("Content-Type", "application/json")
fmt.Printf("發(fā)送第 %d 個(gè)請(qǐng)求...\n", i+1)
// 發(fā)送請(qǐng)求
resp, err := client.Do(req)
if err != nil {
fmt.Printf("請(qǐng)求失敗: %v\n", err)
if errors.Is(err, io.EOF) {
fmt.Printf("連接不再可用: 重試:新的五元重新發(fā)起連接\n")
bodyReader := bytes.NewBuffer(jsonBody)
reqretry, err := http.NewRequest("PUT", url, bodyReader)
if err != nil {
fmt.Printf("創(chuàng)建請(qǐng)求失敗: %v\n", err)
continue
}
// 設(shè)置 Content-Length
reqretry.ContentLength = int64(len(jsonBody))
// 設(shè)置請(qǐng)求頭
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
}
}
// 讀取響應(yīng)
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Printf("讀取響應(yīng)失敗: %v\n", err)
}
resp.Body.Close()
fmt.Printf("請(qǐng)求 %d - 狀態(tài)碼: %d, 響應(yīng): %s\n", i+1, resp.StatusCode, string(body))
fmt.Println("等待10秒后發(fā)送第二個(gè)請(qǐng)求...")
time.Sleep(10 * time.Second) // 等待10秒,此時(shí)服務(wù)端已經(jīng)關(guān)閉連接(10s)
time.Sleep(500 * time.Millisecond)
}
}
運(yùn)行
[xiaofeng@localhost httpkeepalive]$ go run main.go
發(fā)送第 1 個(gè)請(qǐng)求...
請(qǐng)求 1 - 狀態(tài)碼: 200, 響應(yīng): {"code":0,"result":"0","message":"成功"}
等待10秒后發(fā)送第二個(gè)請(qǐng)求...
發(fā)送第 2 個(gè)請(qǐng)求...
請(qǐng)求 2 - 狀態(tài)碼: 200, 響應(yīng): {"code":0,"result":"0","message":"成功"}
等待10秒后發(fā)送第二個(gè)請(qǐng)求...
發(fā)送第 3 個(gè)請(qǐng)求...
請(qǐng)求失敗: Put "https://192.168.24.70:2018/api/zguard/sysmng/syscfg/basecfg/sysname/651d5b9f-225b-4c8c-9f06-80bfad3fa977": EOF
連接不再可用: 重試:新的五元重新發(fā)起連接
請(qǐng)求 3 - 狀態(tài)碼: 200, 響應(yīng): {"code":0,"result":"0","message":"成功"}
等待10秒后發(fā)送第二個(gè)請(qǐng)求...
發(fā)送第 4 個(gè)請(qǐng)求...
請(qǐng)求失敗: Put "https://192.168.24.70:2018/api/zguard/sysmng/syscfg/basecfg/sysname/651d5b9f-225b-4c8c-9f06-80bfad3fa977": EOF
連接不再可用: 重試:新的五元重新發(fā)起連接
請(qǐng)求 4 - 狀態(tài)碼: 200, 響應(yīng): {"code":0,"result":"0","message":"成功"}
等待10秒后發(fā)送第二個(gè)請(qǐng)求...
發(fā)送第 5 個(gè)請(qǐng)求...
請(qǐng)求 5 - 狀態(tài)碼: 200, 響應(yīng): {"code":0,"result":"0","message":"成功"}
等待10秒后發(fā)送第二個(gè)請(qǐng)求...
報(bào)文

到此這篇關(guān)于go進(jìn)行http請(qǐng)求偶發(fā)EOF問題分析的文章就介紹到這了,更多相關(guān)go http請(qǐng)求偶發(fā)EOF內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用Go語言進(jìn)行安卓開發(fā)的詳細(xì)教程
本文將介紹如何使用Go語言進(jìn)行安卓開發(fā),我們將探討使用Go語言進(jìn)行安卓開發(fā)的優(yōu)點(diǎn)、準(zhǔn)備工作、基本概念和示例代碼,通過本文的學(xué)習(xí),你將了解如何使用Go語言構(gòu)建高效的安卓應(yīng)用程序,需要的朋友可以參考下2023-11-11
Golang使用crypto/ed25519實(shí)現(xiàn)數(shù)字簽名和驗(yàn)證
本文將深入探討如何在?Golang?中使用?crypto/ed25519?進(jìn)行數(shù)字簽名和驗(yàn)證,我們將從基本原理開始,逐步引導(dǎo)讀者了解生成密鑰對(duì)、進(jìn)行數(shù)字簽名,以及驗(yàn)證簽名的具體過程,希望對(duì)大家有所幫助2024-02-02
Go語言如何高效的進(jìn)行字符串拼接(6種方式對(duì)比分析)
本文主要介紹了Go語言如何高效的進(jìn)行字符串拼接(6種方式對(duì)比分析),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08

