2013-07-18 452 views
14

我正在嘗試調試一個非常不尋常的錯誤,我正在爲一個簡單的REST庫I wrote收到。當連續發出多個請求時,Golang http請求會導致EOF錯誤

我使用標準的net/http軟件包來獲取,發佈,放置,刪除請求,但是當我連續發出多個請求時,我的測試偶爾會失敗。我的測試是這樣的:

func TestGetObject(t *testing.T) { 
    firebaseRoot := New(firebase_url) 
    body, err := firebaseRoot.Get("1") 
    if err != nil { 
     t.Errorf("Error: %s", err) 
    } 
    t.Logf("%q", body) 
} 

func TestPushObject(t *testing.T) { 
    firebaseRoot := New(firebase_url) 
    msg := Message{"testing", "1..2..3"} 
    body, err := firebaseRoot.Push("/", msg) 
    if err != nil { 
     t.Errorf("Error: %s", err) 
    } 
    t.Logf("%q", body) 
} 

而且我提出這樣的要求:

// Send HTTP Request, return data 
func (f *firebaseRoot) SendRequest(method string, path string, body io.Reader) ([]byte, error) { 
url := f.BuildURL(path) 

// create a request 
req, err := http.NewRequest(method, url, body) 
if err != nil { 
    return nil, err 
} 

// send JSON to firebase 
resp, err := http.DefaultClient.Do(req) 
if err != nil { 
    return nil, err 
} 

if resp.StatusCode != http.StatusOK { 
    return nil, fmt.Errorf("Bad HTTP Response: %v", resp.Status) 
} 

defer resp.Body.Close() 
b, err := ioutil.ReadAll(resp.Body) 
if err != nil { 
    return nil, err 
} 

return b, nil 
} 

有時它的工作原理,但大部分時間我得到1個或2個故障:

--- FAIL: TestGetObject (0.00 seconds) 
firebase_test.go:53: Error: Get https://go-firebase-test.firebaseio.com/1.json: EOF 
firebase_test.go:55: "" 

--- FAIL: TestPushObject (0.00 seconds) 
firebase_test.go:63: Error: Post https://go-firebase-test.firebaseio.com/.json: EOF 
firebase_test.go:65: "" 
FAIL 
exit status 1 
FAIL github.com/chourobin/go.firebase 3.422s 

當我發出一個以上的請求時發生故障。如果我註釋掉除PUT請求以外的所有內容,則測試一致通過。一旦我包括第二個測試,例如GET,其中一個或另一個失敗(有時兩個都通過)。

任何幫助表示讚賞,謝謝!

鏈接到源:http://github.com/chourobin/go.firebase

+0

請出示完整的代碼。 – Volker

+0

從'錯誤:發佈https://go-firebase-test.firebaseio.com/.json:EOF'這一行看起來應該有'.json'之前的文件名。如果'.json'不是web-root中的有效文件名,它將立即返回一個'EOF'。檢查創建URL字符串的函數。我認爲這將是問題 – Intermernet

+0

感謝您的評論,我會給你一個鏡頭。 – chourobin

回答

12

我會想有你的代碼沒有問題。您的問題最可能的原因是因爲服務器正在關閉連接。限速是造成這種情況的一個可能的原因。

你的測試不應該依賴於非常脆弱而不是密封的外部服務。相反,您應該考慮在本地啓動測試服務器。

+0

謝謝,我跑了測試之間的睡眠之間,它看起來像它的工作再次。限速是可能的。 – chourobin

+1

這個答案是錯誤的(並且由於錯誤的原因而宣揚)。 @Alex Davies'的答案是正確的 – mwag

37

我經歷過這個可靠的。您需要將Req.Close設置爲true(對示例中使用的resp.Body.Close()語法的推遲是不夠的)。就像這樣:

client := &http.Client{} 
req, err := http.NewRequest(method, url, httpBody) 

// NOTE this !! 
req.Close = true 

req.Header.Set("Content-Type", "application/json") 
req.SetBasicAuth("user", "pass") 
resp, err := client.Do(req) 
if err != nil { 
    // whatever 
} 
defer resp.Body.Close() 

response, err = ioutil.ReadAll(resp.Body) 
if err != nil { 
    // Whatever 
} 
+1

只是來自Go docs:request.Close [a bool]的一個FYI指示在發送請求(針對客戶端)後,在回覆此請求(針對服務器)還是**之後是否關閉連接* *。 – jsherer

+0

這正是我所需要的。謝謝!我的用例是通過CLI應用程序創建GitHub Oauth令牌,其中第二個請求必須與OTP授權碼一起發佈。如果沒有'req.Close',我得到一個「http:無法在斷開的連接上寫HTTP請求」的錯誤,並且它的一切都按預期工作。 –

+0

這個bug應該在Go 1.6中修復,請參閱https://go-review.googlesource.com/#/c/3210/ – petrkotek

18

我與你不應該打在你的單元測試之外的服務器斷言同意,爲什麼不直接使用內置的http.Server和服務於你要測試的內容。 (實際上還有就是httptest包,以幫助這個)

我最近遇到了同樣的問題,而試圖抓取網站地圖,這是我迄今爲止發現:

轉到默認會發送請求與標頭Connection: Keep-Alive和堅持連接重新使用。我碰到的問題是服務器在響應頭中響應Connection: Keep-Alive,然後立即關閉連接。

作爲在這種情況下如何實現連接的小背景(您可以查看net/http/transport.go中的完整代碼)。有兩個goroutines,一個負責寫入,另一個負責讀取(readLoopwriteLoop)在大多數情況下,readLoop將檢測到套接字關閉,並關閉連接。當在readLoop實際檢測到關閉之前啓動另一個請求時,會發生此問題,並且它讀取的EOF被解釋爲該新請求的錯誤,而不是在請求之前發生的關閉。

鑑於這種情況,在請求之間休眠的原因是,它使readLoop時間在新請求之前檢測到連接關閉並關閉它,以便您的新請求將啓動新連接。(之所以會間歇性失敗的原因是因爲在你的請求之間運行了一些代碼並取決於調度goroutines,有時EOF在你的下一個請求之前會被正確處理,有時候不會)。而解決方案req.Close = true的工作原理是防止連接被重新使用。

有與此相關的情況門票:https://code.google.com/p/go/issues/detail?id=4677(和我創建的讓我可靠地再現這樣的欺騙票:https://code.google.com/p/go/issues/detail?id=8122

+0

我正在使用httptest軟件包,但仍然在測試中遇到此問題。然後,我滾動了足夠遠的輸出,發現運行時恐慌導致我的測試服務器無法關閉連接。 – Omn

+0

這很有道理。每當我連續發出第二個請求時,我都會收到EOF錯誤。它讓我瘋狂,因爲如果我在Python CLI中執行了相同的一系列請求,一切都已經完成,所以我知道這是Go的具體情況。我只是在想,它是目標服務器對每一個第二次請求都做了什麼事情,這個請求專門打亂了Go的'http'實現。 – jeteon