-
Notifications
You must be signed in to change notification settings - Fork 18k
net/http: write error from swiftly dropped connection #3595
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Labels
Comments
I think part of the problem is the call to http.Error causes closeAfterReply to be set so the connection to the client is severed after the first 404 reply is received. However it does not appear that the server is indicating this with a 'Connection: close' header in the first reply body. RFC 2616 8.1.2.1 suggests this is not the correct behaviour, http://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html#sec8.1.2 |
I don't think that's the problem - the client never gets as far as reading the reply after it gets an error writing the body, so the reply headers shouldn't make any difference. For the time being I'm working around this problem by commenting out the error check after req.Request.write in persistConn.roundTrip. |
There'a also a race in this program (the go ListenAndServe may not start before the post call). But ignoring that, I'm curious if patching in pending CL http://golang.org/cl/6213064/ changes anything. |
The server closes the connection before reading the entire request body. See http://golang.org/src/pkg/net/http/server.go#L315. The client fails to write the request and returns an error. This seems correct to me. |
Oh, thanks Gary, I missed the "4e6" in there. Maybe this behavior should be louder somehow. Print something in the server logs? Or maybe we should try harder to get our response to the client. Maybe we instead just stop reading the large body (but don't close the TCP connection) and instead write our response + a TCPConn.CloseWrite + actually close the connection after some small-ish time threshold. |
CL 6213064 doesn't help. The problem isn't in the server AFAICS. It's in the client - the client gets an error writing the body and it stops right there. I think it should at least try to read the response that's waiting to be read. If there's no response we then have to decide which of the two errors to return. If we decide that the response-reading error is the one, then the fix is very simple: ignore any error when writing the body. BTW here's an actual piece of working code. Sorry about my dodgy copy and paste above. package main import ( "bytes" "fmt" "io" "io/ioutil" "log" "net" "net/http" "strings" ) func main() { http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) { http.Error(w, "the bar is closed", 404) }) l, err := net.Listen("tcp", ":8080") if err != nil { log.Fatalf("listen: %v", err) } go http.Serve(l, nil) post(strings.NewReader("hello")) post(bytes.NewBuffer(make([]byte, 4e6))) } func post(r io.Reader) { resp, err := http.Post("http://localhost:8080/bar", "text/utf8", r) fmt.Printf("got err: %v\n", err) if resp != nil { data, err := ioutil.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Printf("got body: %q\n", data) } } |
So you think the http server should be open to DoS attacks by default? I don't. I do think we could do what I said in comment 5, though, not closing the connection, just doing a TCP write shutdown and a full close after some timeout. We'll let the kernel's TCP read buffer fill up and stop acking new packets and hopefully the client has read our HTTP response by the time we RST on them. |
Yes, the client could be fixed too, but there are really two problems here, and in the Go->Go case, fixing the client first might not actually work: if the RST comes in too quickly, we the client might not even be able to read the HTTP response. Related bug to add 100-continue support to the client is https://golang.org/issue/3665 but that's in addition to making the client return a response if it reads one. For a second I thought that returning err == nil when the Request.Body failed to send felt wrong, but I realize that's exactly the behavior that 100-continue does anyway, so I would be fine with it. |
Here's the client part: http://golang.org/cl/6238043 Note that it doesn't work yet (the test merely logs the failure, but doesn't treat it as a failure). The proper fix will require the server not closing so soon I think. (Feel free to prove me wrong with code or network traces) The next thing I'll try to add is having the server send the response and CloseWrite (so the client can see it), but not do a full Close for 100 ms or so. (the TCP connection is considered dead and unavailable for keep-alive both before and after this CL... no changes there) |
Okay, the CL works now. (The server has been updated too) Please test/review http://golang.org/cl/6238043 and let me know what you find. I'm especially curious about how this interacts with other non-Go tools, especially non-Go clients. Roger? Owner changed to @bradfitz. Status changed to Started. |
This issue was closed by revision a5aa91b. Status changed to Fixed. |
This issue was closed.
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
The text was updated successfully, but these errors were encountered: