-
Notifications
You must be signed in to change notification settings - Fork 18k
net/http/httputil: DumpRequestOut has wrong headers on chunked body w/ ContentLength 0 (unknown) #34504
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
Comments
I don't see any bug. Everything seems to be working as intended and documented at https://golang.org/pkg/net/http/#Request.ContentLength If you want to send a "Content-Length: 0" on the wire, set |
@bradfitz the issue is here because of r.ContentLength is not honored instead the server sees ContentLength as -1 but the intention was to send a 0-byte body.
The reason is why do we need to rely on the fact that we have to set http.NoBody? - this style of communication adds additional code and complications. There is no good reason to have code like this.
My suggestion is to avoid this altogether and simply return what was set by the caller.. There are other issues here too which is basically the fact that if we need to trace the request seen by the server v/s what was traced as request set by the client are a different for example here
This is odd from a debugging point of view for us any Go client with request dumping will give one value and server sees some other value. Either we should fix httputil to dump the exact request the server will see or fix it appropriately. |
I see now why it is done intentionally and it's not easy to fix in any meaningful way. What I would like is to fix the httputil.RequestOut()'s output such that it tells us exactly what the underlying transport is going to send to the server. Keeping in line with the req.ContentLength documentation and behavior. Let me update my PR to reflect that. |
I think you're confused about zero values in Go. There's no difference between: r := new(http.Request)
r.Body = &yourReader{} And: r := new(http.Request)
r.Body = &yourReader{}
r.ContentLength = 0 So we can't assume that 0 means it's actually zero. It might just be implicitly zero. Also, httputil.DumpRequestOut does use the real http.Transport to generate its output. Again, I see no bug here. This is all working as intended, documented, and tested. If you think the docs could be more clear somewhere, do let me know where they fell short or were confusing. |
Not really, I didn't clearly understand why it might have been needed internally in the way its being assumed here.
This is what I understood after reading the code. Thank you for clarifying that. Now coming back to the problem is this - We have a code which traces the incoming request and response from the server point of view and also a client-side code to trace the outgoing client requests. But I see differences here in terms of what is being shown in the Trace output debugging which led me to this issue. For example server sees the request like this form
Whereas the client only sees this
This was the confusion on why even if the trace output says that
is set and no Transfer-Encoding is set why does server see
This AFAICS happens because newTransferWriter() is not yet called or initialized until httputil.DumpRequestOut() would have been used by the caller, so this leads to incorrect HTTP headers as seen from client v/s seen by the server. @bradfitz don't you think this is an issue? The code used is here package main
import (
"fmt"
"io"
"log"
"net/http"
"net/http/httputil"
"os"
)
type eofReader struct {
}
func (n eofReader) Close() error {
return nil
}
func (n eofReader) Read([]byte) (int, error) {
return 0, io.EOF
}
func testCase1(clnt *http.Client, debug bool) {
req, err := http.NewRequest(http.MethodPut, "http://localhost:9001/testbucket/0byte", nil)
if err != nil {
log.Fatal(err)
}
req.Body = &eofReader{}
req.ContentLength = 0
if debug {
reqBytes, err := httputil.DumpRequestOut(req, false)
if err != nil {
log.Fatal(err)
}
fmt.Println("############ REQUEST TestCase1")
fmt.Println(string(reqBytes))
}
resp, err := clnt.Do(req)
if err != nil {
log.Fatal(err)
}
io.Copy(os.Stdout, resp.Body)
resp.Body.Close()
}
func main() {
debug := os.Getenv("DEBUG") == "1"
clnt := &http.Client{}
testCase1(clnt, debug)
} |
But an http.Handler can see *http.Request.ContentLength == -1, which means the length is unknown. That's how Go's net/http package maps a chunked encoding to a length. The documentation of Request.ContentLength makes clear that the fields mean different things in client vs server context. |
No, I am not saying that it did what I am specifically saying is client httputil.DumpRequestOut() didn't say that it's going to set "Transfer-Encoding: chunked" instead of server seeing that.
Here its
But server actually saw the request without
From what I understood httputil.DumpRequestOut() should ideally print what it's going to write on the wire. This confusion led me to spend quite a bit of time understanding what is going on. Because the traces on client are not showing the same info as seen by the server. |
Right now I am trying to fix httputil.DumpRequestOut() to print NOTE: This inconsistency only happens when the body is of 0 bytes. |
Okay, retitled the bug then. |
Change https://golang.org/cl/197898 mentions this issue: |
What version of Go are you using (
go version
)?Does this issue reproduce with the latest release?
Yes
What operating system and processor architecture are you using (
go env
)?go env
OutputWhat did you do?
Based on this suggestion that req.ContentLength apparently should be honored but which isn't
#11493 (comment)
What did you expect to see?
What did you see instead?
A relevant PR was also sent https://go-review.googlesource.com/c/go/+/196965 which should fix this issue
The text was updated successfully, but these errors were encountered: