Search code examples
golong-polling

how to close/abort a Golang http.Client POST prematurely


I'm using http.Client for the client-side implementation of a long-poll:

resp, err := client.Post(url, "application/json", bytes.NewBuffer(jsonPostBytes))
if err != nil {
    panic(err)
}
defer resp.Body.Close()

var results []*ResponseMessage
err = json.NewDecoder(resp.Body).Decode(&results)  // code blocks here on long-poll

Is there a standard way to pre-empt/cancel the request from the client-side?

I imagine that calling resp.Body.Close() would do it, but I'd have to call that from another goroutine, as the client is normally already blocked in reading the response of the long-poll.

I know that there is a way to set a timeout via http.Transport, but my app logic need to do the cancellation based on a user action, not just a timeout.


Solution

  • Nope, client.Post is a handy wrapper for 90% of use-cases where request cancellation is not needed.

    Probably it will be enough simply to reimplement your client to get access to underlying Transport object, which has CancelRequest() function.

    Just a quick example:

    package main
    
    import (
        "log"
        "net/http"
        "time"
    )
    
    func main() {
        req, _ := http.NewRequest("GET", "http://google.com", nil)
        tr := &http.Transport{} // TODO: copy defaults from http.DefaultTransport
        client := &http.Client{Transport: tr}
        c := make(chan error, 1)
        go func() {
            resp, err := client.Do(req)
            // handle response ...
            _ = resp
            c <- err
        }()
    
        // Simulating user cancel request channel
        user := make(chan struct{}, 0)
        go func() {
            time.Sleep(100 * time.Millisecond)
            user <- struct{}{}
        }()
    
        for {
            select {
            case <-user:
                log.Println("Cancelling request")
                tr.CancelRequest(req)
            case err := <-c:
                log.Println("Client finished:", err)
                return
            }
        }
    }