Skip to content
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

net/http: support concurrent Request.Body reads & ResponseWriter.Write calls in HTTP/1.x server #15527

Closed
exel5 opened this issue May 3, 2016 · 23 comments
Assignees
Labels
early-in-cycle A change that should be done early in the 3 month dev cycle. FrozenDueToAge NeedsFix The path to resolution is known, but the work has not been done.
Milestone

Comments

@exel5
Copy link

exel5 commented May 3, 2016

Please answer these questions before submitting your issue. Thanks!

  1. What version of Go are you using (go version)?
    Go 1.5.2
  2. What operating system and processor architecture are you using (go env)?
    windows_amd64
  3. What did you do?
    If possible, provide a recipe for reproducing the error.
    A complete runnable program is good.
    A link on play.golang.org is best.

https://play.golang.org/p/DaWZXCQNfV
this example ends up with error "http: invalid Read on closed Body"

https://play.golang.org/p/WYCsIQzx_F
but changing 100 to 10 in strings.Repeat goes without any errors

also this example https://play.golang.org/p/YxjKnmgfGP
$ curl -d 'qweqweq weq weqwe qwe qew qwe' http://localhost:6060/
<qweqweq weq weq><we qwe qew qwe >

shows that reading and writing goes simultaneously and without errors

  1. What did you expect to see?
    i expect to see the same error in both cases
  2. What did you see instead?
    i see different behaviour
@bradfitz bradfitz changed the title Writing to http.ResponseWriter closes request.Body, but seems not always net/http: writing to http.ResponseWriter closes request.Body, but seems not always May 4, 2016
@bradfitz bradfitz added this to the Go1.7 milestone May 4, 2016
@bradfitz bradfitz self-assigned this May 4, 2016
@bradfitz bradfitz modified the milestones: Go1.8Maybe, Go1.7 May 10, 2016
@bradfitz
Copy link
Contributor

I've sent https://golang.org/cl/23011 to doucment the status quo.

We can keep this bug open to track making the HTTP/1.x server support concurrent reads & writes like HTTP/2.

@bradfitz bradfitz changed the title net/http: writing to http.ResponseWriter closes request.Body, but seems not always net/http: support concurrent Request.Body reads & ResponseWriter.Write calls in HTTP/1.x server May 10, 2016
@gopherbot
Copy link
Contributor

CL https://golang.org/cl/23011 mentions this issue.

gopherbot pushed a commit that referenced this issue May 11, 2016
Summary: Go's HTTP/1.x server closes the request body once writes are
flushed. Go's HTTP/2 server supports concurrent read & write.

Added a TODO to make the HTTP/1.x server also support concurrent
read+write. But for now, document it.

Updates #15527

Change-Id: I81f7354923d37bfc1632629679c75c06a62bb584
Reviewed-on: https://go-review.googlesource.com/23011
Reviewed-by: Andrew Gerrand <[email protected]>
@bradfitz
Copy link
Contributor

@dpiddy points out the closest thing in RFC 2616 about this topic:

        If an origin server receives a request that does not include an
        Expect request-header field with the "100-continue" expectation,
        the request includes a request body, and the server responds
        with a final status code before reading the entire request body
        from the transport connection, then the server SHOULD NOT close
        the transport connection until it has read the entire request,
        or until the client closes the connection. Otherwise, the client
        might not reliably receive the response message. However, this
        requirement is not be construed as preventing a server from
        defending itself against denial-of-service attacks, or from
        badly broken client implementations.

@quentinmit quentinmit added the NeedsFix The path to resolution is known, but the work has not been done. label Oct 10, 2016
@rsc rsc modified the milestones: Go1.9, Go1.8Maybe Oct 20, 2016
@bradfitz bradfitz modified the milestones: Go1.10Early, Go1.9 May 24, 2017
@bradfitz bradfitz added early-in-cycle A change that should be done early in the 3 month dev cycle. and removed early-in-cycle A change that should be done early in the 3 month dev cycle. labels Jun 14, 2017
@bradfitz bradfitz modified the milestones: Go1.10Early, Go1.10 Jun 14, 2017
@rsc rsc modified the milestones: Go1.10, Go1.11 Nov 22, 2017
@herrberk
Copy link

herrberk commented May 4, 2018

I believe this is a critical issue affecting many users out there and should be addressed as quickly as possible. I was able to reproduce this issue using Transfer-Encoding: chunked as I explained in this related issue:

This sample server-client pair reproduces this issue:
https://play.golang.org/p/G8F0X4DQCDn

Please let me know if any assistance is needed to fix this issue for the next release. Thanks!

@Stebalien
Copy link

Relevant HTTP WG email thread: https://lists.w3.org/Archives/Public/ietf-http-wg/2004JanMar/0041.html

TL;DR: Yes, you can respond before reading the entire request.


        If an origin server receives a request that does not include an
        Expect request-header field with the "100-continue" expectation,
        the request includes a request body, and the server responds
        with a final status code before reading the entire request body
        from the transport connection, then the server SHOULD NOT close
        the transport connection until it has read the entire request,
        or until the client closes the connection. Otherwise, the client
        might not reliably receive the response message. However, this
        requirement is not be construed as preventing a server from
        defending itself against denial-of-service attacks, or from
        badly broken client implementations.

This section should be interpreted exactly as written. The server can't close the connection before reading the entire body of the request for the reason stated in that section. That doesn't mean the server can't respond concurrently.

@bradfitz
Copy link
Contributor

@Stebalien, thanks for finding that.

FWIW, Go's HTTP/1.x server originally permitted this but we encountered enough confused implementations in the wild that would end up deadlocking if they got a response before they were expecting it. (e.g. somebody sent us a POST + body but the Go server sent an Unauthorized response before reading the body). The peer would then deadlock, never reading the response because it wasn't finished writing its body, and we weren't reading their body because our Handler was done running. We've changed behavior a few times over the years (read to EOF, read some, close immediately) and I can't even remember what we do now.

Still worth revisiting, but I suspect we might need to make this behavior opt-in for HTTP/1.x on a per-Handler basis somehow.

@herrberk
Copy link

@Stebalien @bradfitz I understand, permitting this can cause issues like you mentioned but it's already there for HTTP/2, so IMHO it should be added to HTTP/1.x at least as an opt-in feature.

@bradfitz bradfitz removed this from the Go1.11 milestone Jun 28, 2018
@guonaihong
Copy link

guonaihong commented Aug 22, 2021

I also encountered this problem

  • server
func myecho(w http.ResponseWriter, r *http.Request) {
        n, err := io.Copy(w, r.Body)
        if err != nil {
                fmt.Printf("err = %v\n", err)
                //panic(err)
        }

        fmt.Printf("n = %d\n", n)
}

func netHTTPBug() {
        http.HandleFunc("/post", myecho)
        http.ListenAndServe(":8080", nil)
}
  • client
seq 600 &>need.data
wc -c need.data 
2292 need.data

curl -X POST -d @./test.data 127.0.0.1:8080/post &>got.data

wc -c got.data 
829 got.data

I looked at the net/http code yesterday, and it seemed that it was enough to modify a bool variable. Is there any troublesome reason not to modify it?

@silverbp
Copy link

@guonaihong which bool variable are you referring to? I am running into the issue and looking for a solution to resolve it.

@guonaihong
Copy link

guonaihong commented Sep 30, 2021

image

Here you can see the code in my screenshot. I remember setting discard to true to run my demo above.

@silverbp

bigbes added a commit to soverenio/go-echo that referenced this issue Jul 27, 2022
@geofffranks
Copy link

This bug has been open for over six years, and is causing a great deal of pain for our customers. Are there any golang maintainers we can work with on trying to get this problem solved finally? Any chance you might be the right person for this @neild, or at least know someone who can help?

@gopherbot
Copy link
Contributor

Change https://go.dev/cl/472636 mentions this issue: net/http: support full-duplex HTTP/1 responses

@neild neild self-assigned this Mar 1, 2023
gopherbot pushed a commit that referenced this issue Mar 7, 2023
Add support for concurrently reading from an HTTP/1 request body
while writing the response.

Normally, the HTTP/1 server automatically consumes any remaining
request body before starting to write a response, to avoid deadlocking
clients which attempt to write a complete request before reading the
response.

Add a ResponseController.EnableFullDuplex method which disables this
behavior.

For #15527
For #57786

Change-Id: Ie7ee8267d8333e9b32b82b9b84d4ad28ab8edf01
Reviewed-on: https://go-review.googlesource.com/c/go/+/472636
TryBot-Result: Gopher Robot <[email protected]>
Run-TryBot: Damien Neil <[email protected]>
Reviewed-by: Roland Shoemaker <[email protected]>
@gopherbot
Copy link
Contributor

Change https://go.dev/cl/501300 mentions this issue: go1.21: document net/http.ResponseController.EnableFullDuplex

gopherbot pushed a commit that referenced this issue Jun 6, 2023
For #15527
For #57786

Change-Id: I75ed0b4bac8e31fac2afef17dad708dc9a3d74e1
Reviewed-on: https://go-review.googlesource.com/c/go/+/501300
Run-TryBot: Damien Neil <[email protected]>
Auto-Submit: Damien Neil <[email protected]>
TryBot-Result: Gopher Robot <[email protected]>
Reviewed-by: Ian Lance Taylor <[email protected]>
@oliverpool
Copy link

This has been implemented, documented and released in go1.21, so I guess this issue can be closed.

@dmitshur dmitshur modified the milestones: Unplanned, Go1.21 Aug 18, 2023
mariash added a commit to cloudfoundry/gorouter that referenced this issue Feb 28, 2024
By default Go HTTP server consumes any unread request portion before
writing the response for HTTP/1. This prevents handlers from reading
request and writing response concurrently.

This is set to false by default since it might be an unexpected behavior
and cause deadlock for some handlers. See
golang/go#15527 (comment)
mariash added a commit to cloudfoundry/routing-release that referenced this issue Feb 28, 2024
By default Go HTTP server consumes any unread request portion before
writing the response for HTTP/1. This prevents handlers from reading
request and writing response concurrently.

This is set to false by default since it might be an unexpected behavior
and cause deadlock for some handlers. See
golang/go#15527 (comment)
geofffranks pushed a commit to cloudfoundry/gorouter that referenced this issue Feb 28, 2024
By default Go HTTP server consumes any unread request portion before
writing the response for HTTP/1. This prevents handlers from reading
request and writing response concurrently.

This is set to false by default since it might be an unexpected behavior
and cause deadlock for some handlers. See
golang/go#15527 (comment)
mariash added a commit to cloudfoundry/routing-release that referenced this issue Feb 28, 2024
By default Go HTTP server consumes any unread request portion before
writing the response for HTTP/1. This prevents handlers from reading
request and writing response concurrently.

This is set to false by default since it might be an unexpected behavior
and cause deadlock for some handlers. See
golang/go#15527 (comment)
geofffranks pushed a commit to cloudfoundry/routing-release that referenced this issue Feb 28, 2024
By default Go HTTP server consumes any unread request portion before
writing the response for HTTP/1. This prevents handlers from reading
request and writing response concurrently.

This is set to false by default since it might be an unexpected behavior
and cause deadlock for some handlers. See
golang/go#15527 (comment)
withinboredom added a commit to dunglas/frankenphp that referenced this issue Mar 24, 2024
Now that golang/go#15527 is supposedly fixed, this condition should be no-longer needed. Further, if php didn't request enough bytes, this condition would be hit. It appears PHP requests chunks ~2mb in size at a time.
withinboredom added a commit to dunglas/frankenphp that referenced this issue Mar 24, 2024
Now that golang/go#15527 is supposedly fixed, this condition should be no-longer needed. Further, if php didn't request enough bytes, this condition would be hit. It appears PHP requests chunks ~2mb in size at a time.
@golang golang locked and limited conversation to collaborators Aug 17, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
early-in-cycle A change that should be done early in the 3 month dev cycle. FrozenDueToAge NeedsFix The path to resolution is known, but the work has not been done.
Projects
None yet
Development

No branches or pull requests