59

I am trying to debug a very unusual error I am receiving for a simple REST library I wrote.

I am using the standard net/http package to make Get, Post, Put, Delete requests but my tests occasionally fail when I make multiple requests successively. My test looks like this:

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)
}

And I am making the request like this:

// 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
} 

Sometimes it works, but most of the time I get 1 or 2 failures:

--- 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

The failures happen when I make more than 1 request. If I comment out everything except for the PUT request, the tests consistently pass. Once I include a second test, such as GET, one or the other fails (sometimes both pass).

Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156
chourobin
  • 4,004
  • 4
  • 35
  • 48
  • Please show whole code. – Volker Jul 18 '13 at 07:12
  • From the line `Error: Post https://go-firebase-test.firebaseio.com/.json: EOF` it looks like there should be a file-name in there before `.json`. If `.json` isn't a valid file-name in the web-root it will return an `EOF` immediately. Check the function that creates the URL string. I think that will be the problem – Intermernet Jul 18 '13 at 09:16
  • I don't think the .json extension is the entire issue. If I comment out everything except for the POST request, the tests consistently pass. Once I include a second test, such as GET, one or the other fails (sometimes both pass). – chourobin Jul 18 '13 at 13:10
  • 1
    What does the server you are hitting say? Maybe it cuts the connection due to e.g. rate limiting? The most likely reason you get an EOF error is because the server closed the connection. In fact why are you hitting an external server in a unittest? – Jeremy Wall Jul 18 '13 at 22:54
  • @JeremyWall He didn't mention _unit_ tests. It might be appropriate for his tests to send requests to a server. – Alexei Sholik Jul 18 '13 at 23:03
  • 1
    _test.go files in a go project will 90+% of the time be unittest and in fact the go test library which his code uses assumes unittests for the most part. If they are integration tests then they probably shouldn't be in that location. – Jeremy Wall Jul 18 '13 at 23:05
  • @JeremyWall hitting external servers in tests is a bit unreliable (too many links in the chain so to speak) but it's not uncommon. Some of the `net/http` package tests hit external URLs as tests. – Intermernet Jul 18 '13 at 23:11
  • Thanks for the comments. You guys are right I should be mocking the response in my unit tests and I was doing it before. I was just wondering why it doesn't pass when I actually point it to an external service. It probably is due to rate limiting.. – chourobin Jul 19 '13 at 02:24

7 Answers7

85

I experienced this reliably. You need to set Req.Close to true (the defer on resp.Body.Close() syntax used in the examples is not enough). Like this:

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
}
Alex Davies
  • 851
  • 1
  • 6
  • 2
  • 7
    Just an FYI from the Go docs: request.Close [a bool] indicates whether to close the connection after replying to this request (for servers) or **after sending the request (for clients)**. – jsherer Oct 29 '14 at 15:16
  • This was exactly what I needed. Thanks! My use case is creating a GitHub Oauth token via a CLI app, where a second request has to be issued with the OTP auth code. Without `req.Close` I was getting a "http: can't write HTTP request on broken connection" error, with it everything works as expected. – Chris Pliakas Oct 31 '14 at 15:48
  • This bug should be fixed in Go 1.6, see https://go-review.googlesource.com/#/c/3210/ – petrkotek Sep 29 '15 at 04:28
  • I can confirm this. My app was making a lot of successive requests as well and would occasionally encounter this error. Upgrading to Go 1.6 solved the problem for me. – Nathan Osman May 01 '16 at 07:09
  • 19
    I am still getting this error even after set req.Close to true. Any thoughts? This is my Go Version: go version go1.8 darwin/amd64 – Patrick Mar 06 '17 at 21:03
  • @Patrick For me it was something unrelated to this, since never versions of Go have patched the issue before 1.5. In my case, I was encountering a panic elsewhere in the processing of the request which wasn't being logged – asgaines Apr 04 '18 at 15:00
  • @Patrick One workaround is to create a new req for every request – El Mostafa IDRASSI Aug 18 '20 at 18:24
  • I was facing similar issue in Bleve indexing library and closing the index fixed the issue for me. – Abhay Maniyar Jul 22 '21 at 07:55
  • @Patrick did you ever resolve your issue? The above solution didn't work for me on go 1.16 for a POST request – thedarklord47 Mar 04 '22 at 21:17
41

I agree with the assertion that you shouldn't be hitting outside servers in your unit tests, why not just use the built-in http.Server and serve up the content that you want to test. (There is actually the httptest package to help with this)

I recently ran into this same problem while trying to crawl sitemaps, and this is what I have found so far:

Go by default will send requests with the header Connection: Keep-Alive and persist connections for re-use. The problem that I ran into is that the server is responding with Connection: Keep-Alive in the response header and then immediately closing the connection.

As a little background as to how go implements connections in this case (you can look at the full code in net/http/transport.go). There are two goroutines, one responsible for writing and one responsible for reading (readLoop and writeLoop) In most circumstances readLoop will detect a close on the socket, and close down the connection. The problem here occurs when you initiate another request before the readLoop actually detects the close, and the EOF that it reads get interpreted as an error for that new request rather than a close that occurred prior to the request.

Given that this is the case the reason why sleeping in between requests works is that it gives readLoop time to detect the close on the connection before your new request and shut it down, so that your new request will initiate a new connection. (And the reason why it would intermittently fail is because there is some amount code running between your requests and depending of scheduling of goroutines, sometimes the EOF will be properly handled before your next request, sometimes not). And the req.Close = true, solution works because it prevents the connection from being re-used.

There is a ticket related to this situation: https://code.google.com/p/go/issues/detail?id=4677 (and a dupe ticket that I created that allowed me to reliably reproduce this: https://code.google.com/p/go/issues/detail?id=8122)

cgilling
  • 511
  • 4
  • 5
  • I was using the httptest package and still getting this issue with my tests. Then I scrolled far enough up my output and found the runtime panic that was causing my test server to fail to close the connection. – Omn Aug 27 '15 at 18:42
  • 1
    That makes sense. I was getting the EOF error on every second consecutive request I made. It drove me crazy because if I executed the same series of requests in a Python CLI everything worked out so I knew it was Go specific. I was just dreading the idea that it was the target server doing something on every second request that upset Go's `http` implementation specifically. – jeteon Dec 25 '15 at 02:14
  • @cgilling hi, run the code at https://play.golang.org/p/hxuIOC49ac, instead of "a quarter to a half of the requests have errors", all returned success, any idea why? – DiveInto Feb 28 '19 at 12:47
  • @DiveInto wow, I just saw this comment, sorry for the super late reply. I didn't actually go and investigate further but I can think of one of two options ordered by likelihood: (1) adobe.com supports keep alive connections and isn't closing the connection without first informing you (2) the go http code has change (I think (1) is the most likely answer by far) <- Hah reading some of the comments below it looks like I was wrong in my ordering of likelihood and the bug was fixed in go 1.6 :) – cgilling May 21 '22 at 00:21
24

I'm going to guess there is no problem with your code. The most likely cause of your problem is because the server is closing the connection. Rate limiting is one possible reason for this.

Your test shouldn't be relying on an external service that's very brittle and not hermetic. Instead you should think about spinning up a test server locally.

jacobsa
  • 5,719
  • 1
  • 28
  • 60
Jeremy Wall
  • 23,907
  • 5
  • 55
  • 73
  • 1
    Thanks, I ran the tests with a sleep in between and it looks like its working again. Rate limiting is probably it. – chourobin Jul 19 '13 at 02:43
  • 18
    this answer is wrong (and preaches for the wrong reasons). @Alex Davies' answer is correct – mwag Oct 17 '16 at 03:54
2

My experience with this error was when I entered absolutely empty input for my JSON API!

I should send {} as empty JSON, but I sent so this error happened

Amin Shojaei
  • 5,451
  • 2
  • 38
  • 46
1

I was developing an image download app when this problem occurs.
Tried request.Close=true but not work.
60% requests resulted in a EOF error.

I thought maybe it is an image server problem, not my code.
But php code works fine.

Then I use

var client = &http.Client{
    Transport: &http.Transport{},
}
client.Do(request)

to make request, instead of

http.DefaultClient.Do(request)

problem gone. Not sure why,I guess something with RoundTripper

Evol Rof
  • 2,528
  • 2
  • 22
  • 37
0

I encountered this issue while sending an invalid body to a GET request

I could reproduce by making a request similar to below:

var requestBody interface{}
requestData, _ := json.Marshal(requestBody)
payload = strings.NewReader(string(requestData))

req, err := http.NewRequest("GET", url, payload)
...
DevD'Silva
  • 101
  • 1
  • 1
  • 4
0

I faced the same issue, and after investigating for many hours, it turned out that the CDN closed the connection and caused an EOF error on our side. And the reason of the unwanted close was the User-Agent what I set to the request. I used a randomly picked browser User-Agent, which described an 8-years old version of the Chrome. Probably the request was treated as an untrusted one. After updating the User-Agent the issue has been solved.