2013-07-18 20 views
14

私は単純なRESTライブラリI wroteで受信している非常に珍しいエラーをデバッグしようとしています。複数のリクエストを連続して実行すると、Golang httpリクエストでEOFエラーが発生する

Get、Post、Put、Deleteのリクエストに標準の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 

2つ以上のリクエストを行った場合、エラーが発生します。 PUTリクエスト以外のすべてをコメントアウトすると、テストは一貫して失敗します。 GETなどの2回目のテストを組み込むと、どちらか一方が失敗することがあります(時には両方が失敗する)。

ありがとうございました。ありがとうございます!ソースへ

リンク:http://github.com/chourobin/go.firebase

+0

全体のコードを表示してください

は、このような状況に関連したチケットがあります。 – Volker

+0

'Error:Post https://go-firebase-test.firebaseio.com/.json:EOF'行の' .json'の前にファイル名があるように見えます。 '.json'がweb-root内の有効なファイル名でない場合、すぐに' EOF'を返します。 URL文字列を作成する関数を確認してください。私はそれが問題になると思う。 – Intermernet

+0

コメントありがとう、私はそのショットを与えるだろう。 – chourobin

答えて

12

私はあなたのコードに問題がない推測するつもりです。問題の原因としては、サーバーが接続を終了している可能性が考えられます。レートリミットはこれの1つの理由です。

あなたのテストは非常に脆く、気密ではない外部サービスに頼るべきではありません。代わりに、テストサーバーをローカルにスピンアップすることについて考える必要があります。

+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]は、このリクエストに返信した後(サーバ用)または**リクエストを送信した後に(クライアント用に)接続を閉じるかどうかを示します* *。 – jsherer

+0

これはまさに私が必要としていたものでした。ありがとう!私のユースケースは、OTP認証コードで2番目のリクエストを発行する必要があるCLIアプリケーションを介してGitHub Oauthトークンを作成することです。 'req.Close'がなければ、私は" http:接続が壊れたときにHTTPリクエストを書くことができません "というエラーを出していました。 –

+0

この不具合はGo 1.6で修正する必要があります。https://go-review.googlesource.com/#/c/3210/ – petrkotek

18

私はなぜちょうどビルトインhttp.Serverを使用してテストしたいコンテンツを提供していませ、あなたのユニットテストで外部のサーバを打つべきではない主張に同意します。サイトマップをクロールしようとしているときに、私は最近、これと同じ問題に遭遇した

(あり、実際にこれを支援するためのhttptestパッケージです)、そしてこれは、私がこれまでに発見したものです:デフォルトで

Goがでリクエストを送信しますヘッダConnection: Keep-Aliveおよび再使用のための接続を維持します。私が遭遇した問題は、サーバーが応答ヘッダーのConnection: Keep-Aliveで応答していて、ただちに接続を閉じることです。

この場合、どのように接続を実装するのかに関する少しの背景があります(net/http/transport.goの完全なコードを参照してください)。 2つのゴルーチンがあり、1つは書き込みを担当し、もう1つは読み取りを行います(readLoopwriteLoop)。ほとんどの場合、readLoopはソケット上でクローズを検出し、接続を閉じます。ここでの問題は、readLoopが実際にクローズを検出する前に別の要求を開始し、読み取られたEOFが、要求の前に発生したクローズではなく新しい要求のエラーとして解釈される場合に発生します。

この場合、リクエスト間でスリープ状態になるのは、新しいリクエストの前に接続のクローズを検出してシャットダウンするreadLoop時間を与え、新しいリクエストが新しい接続を開始するためです。(そして断続的に失敗する理由は、あなたのリクエストの間にいくつかの金額コードがあり、ゴルーチンのスケジューリングに応じて、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

それは理にかなっています。私が作った2回目以降のリクエストごとにEOFエラーが発生しました。私はPython CLIで同じ一連のリクエストを実行するとすべてがうまくいっていたので、Go固有のものであることが分かったからです。私はちょうど目標サーバが毎回Goの 'http'実装を怒らせる毎秒のリクエストで何かをやっているという考えを恐れていました。 – jeteon