The problem can be reproduced with the below simple go code snippet:
Simple go http server:
package main
import (
"fmt"
"log"
"net/http"
"time"
)
func handler(w http.ResponseWriter, r *http.Request) {
go func(done <-chan struct{}) {
<-done
fmt.Println("message", "client connection has gone away, request got cancelled")
}(r.Context().Done())
time.Sleep(30 * time.Second)
fmt.Fprintf(w, "Hi there, I love %s!
", r.URL.Path[1:])
}
func main() {
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
Start above http server, if i send one simple GET
request with curl(postman also) like:
curl -X GET http://localhost:8080/
then press Ctrl+C
to terminate the request, then i am able to see the printed message at server side:
message client connection has gone away, request got cancelled
above is the correct behaviour i expect: to simulate the situation that when client is gone the server can capture it and then cancel all the unnecessary work as early as it can.
But when i send one POST request with request body, this expected behaviour doesn't happen, the <-done
signal was captured until the request deadline meet.
curl -X POST http://localhost:8080/ -H 'Content-Type: application/json' -d '{}'
To summarize my questions:
GET
, POST
(with or without request body) request make such a difference?Read the request body to detect when the client closes the connection:
func handler(w http.ResponseWriter, r *http.Request) {
go func(done <-chan struct{}) {
<-done
fmt.Println("message", "client connection has gone away, request got cancelled")
}(r.Context().Done())
io.Copy(ioutil.Discard, r.Body) // <-- read the body
time.Sleep(30 * time.Second)
fmt.Fprintf(w, "Hi there, I love %s!
", r.URL.Path[1:])
}
The net/http server checks for closed connections by reading the connection. No reads are started until the application starts reading the request body (if any).