0

I'm doing X parallel http requests and when one of them does not respond in X ms (imagine is 100ms) or less I want to cut this connection. The code I wrote does not seem to work so, how can I cut the connection and get the response as nil?

This is my sample code:

cx, cancel := context.WithCancel(context.Background())
ch := make(chan *HttpResponse)
var responses []*HttpResponse

timeout := 1.000 //1ms for testing purposes
var client = &http.Client{
    Timeout: 1 * time.Second,
}

startTime := time.Now()
for _, url := range urls {
    go func(url string) {
        fmt.Printf("Fetching %s \n", url)
        req, _ := http.NewRequest("POST", url, bytes.NewReader(request)) //request is json string
        req.WithContext(cx)
        resp, err := client.Do(req)
        ch <- &HttpResponse{url, resp, err}
        var timeElapsed = time.Since(startTime)
        msec := timeElapsed.Seconds() * float64(time.Second/time.Millisecond)
        if msec >= timeout {
            cancel()
        }
        if err != nil && resp != nil && resp.StatusCode == http.StatusOK {
            resp.Body.Close()
        }
    }(url)
}

for {
    select {
    case r := <-ch:
        fmt.Printf("%s was fetched\n", r.Url)
        if r.Err != nil {
            fmt.Println("with an error", r.Err)
        }
        responses = append(responses, r)
        if len(responses) == len(*feeds) {
            return responses
        }
    case <-time.After(100):
        //Do something
    }
}

1 Answer 1

1

Your code waits until a requests finishes (and get a resposne or an error), and then calculate the time passed, and if it was longer than the time expect, your code would cancel all the requests.

    req, _ := http.NewRequest("POST", url, bytes.NewReader(request)) //request is json string
    req.WithContext(cx) //Here you use a common cx, which all requests share.
    resp, err := client.Do(req) //Here the request is being sent and you wait it until done.
    ch <- &HttpResponse{url, resp, err}
    var timeElapsed = time.Since(startTime)
    msec := timeElapsed.Seconds() * float64(time.Second/time.Millisecond)
    if msec >= timeout {
        cancel() //here you cancel all the requests.
    }

The fix is to utilize the context package right.

    req, _ := http.NewRequest("POST", url, bytes.NewReader(request)) //request is json string
    ctx,cancel := context.WithTimeout(request.Context(),time.Duration(timeout)*time.Millisecond)
    resp,err:=client.Do(req.WithContext(ctx))
    defer cancel()

With that, you will get a nil resp (and an error) and get the connection cut when time out.

Sign up to request clarification or add additional context in comments.

7 Comments

you forgot defer cancel(), though go vet should catch that.
Thanks @leafbebop, it seems to work, now in the console I see context deadline exceeded but it does not continue with the code (it is a web server app). It keeps loading...
@xmarston I need other parts of code to know what's the problem.
@leafbebop nothing, I wasn't checking for a nil response so the code was silently failing. I corrected this behavior and now works like a charm. Thanks for the solution!!
@xmarston: you should never have to check for a nil response if you’re properly handling the error.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.