-
Notifications
You must be signed in to change notification settings - Fork 4.4k
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
client: handle HTTP header parsing error correctly #2599
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One comment from when I started reviewing this yesterday. With the most recent commit, travis is failing - please make sure travis is green and then reassign to me when you are ready. Thanks!
internal/transport/http2_client.go
Outdated
@@ -1139,9 +1139,9 @@ func (t *http2Client) operateHeaders(frame *http2.MetaHeadersFrame) { | |||
return | |||
} | |||
atomic.StoreUint32(&s.bytesReceived, 1) | |||
var state decodeState | |||
state := decodeState{isTrailer: atomic.LoadUint32(&s.headerDone) == 1} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This (gRPC trailer = second HTTP/2 HEADERS frame received) is not right, or the name isTrailer
is misleading if this is how you have intended it to behave. Just below we use the end stream bit in the frame to determine whether this is trailers. That will result in true
for trailers-only responses, whereas the check added here would be false
.
526bea6
to
c0e5a41
Compare
internal/transport/http_util.go
Outdated
@@ -120,8 +118,32 @@ type decodeState struct { | |||
statsTags []byte | |||
statsTrace []byte | |||
contentSubtype string | |||
} | |||
|
|||
func newDecodeState(serverSide bool, ignoreContentType bool) *decodeState { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is actual preferable to not have a constructor here. Consider:
state := newDecodeState(true, false)
and
state := &decodeState{serverSide: true, ignoreContentType: false}
The latter is more readable. Unless there's state that needs to always be initialized for correctness, a constructor is not necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
internal/transport/http_util.go
Outdated
} | ||
} | ||
|
||
// Records the states during HPACK decoding. Must be reset once the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment applies more for parsedHeaderData than decodeState, doesn't it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
moved comments.
internal/transport/http_util.go
Outdated
} | ||
|
||
// Records the states during HPACK decoding. Must be reset once the | ||
// decoding of the entire headers are finished. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this "reset" comment correct? Do we ever reset it? I thought we just make new ones every time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Deleted the reset. I don't think reset is done, we just create a new struct every time. This is old comments from before.
internal/transport/http_util.go
Outdated
// content-type is grpc or not. | ||
// | ||
// If we've already received a HEADERS frame which indicates gRPC peer, then we can ignore | ||
// content-type for the following HEADERS frame (i.e. Trailers). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trailers (after headers) shouldn't even have a content-type - or if it does, we should always ignore it, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rephrased. Mentioned Trailers (after headers) shouldn't even have a content-type, and we ignore checking it.
internal/transport/http_util.go
Outdated
// we got a valid content-type that starts with "applicatin/grpc", so we are operating in grpc | ||
// mode. | ||
if hf.Name == "content-type" { | ||
isGRPC = true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It may be cleaner to put "isGRPC" into parsedHeaderData to avoid setting it here based on knowledge of how processHeaderField works (i.e. no error and field name = "content-type" implies GRPC).
Maybe the above logic can all be moved there too? (The switch and the knowledge of skipping certain fields.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
test/end2end_test.go
Outdated
@@ -7127,3 +7129,241 @@ func (s) TestRPCWaitsForResolver(t *testing.T) { | |||
t.Fatalf("TestService/UnaryCall(_, _) = _, %v, want _, nil", err) | |||
} | |||
} | |||
|
|||
// A copy from http_util.go for testing. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Export from transport instead of copying?
a54f089
to
3796c19
Compare
internal/transport/http_util.go
Outdated
for _, hf := range frame.Fields { | ||
if hf.Name != "content-type" && hf.Name != ":status" && grpcErr != nil { | ||
if hf.Name != "content-type" && hf.Name != ":status" && d.data.grpcErr != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we still need this? Is it not safe to parse everything? (If things must be skipped, can we move this logic to processHeaderField
?)
If we're just skipping parsing some header fields in the name of efficiency, I don't think extra code complexity is worth optimizing error cases (unless it's a substantial difference).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
} | ||
d.httpStatus = &code | ||
d.data.httpStatus = &code |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to eliminate httpStatus
from data
and set httpErr
here instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's possible, but requires tricky ways, i.e compare error string is empty or not, and decide the final status error message. Decided not to do that from offline discussion.
https://github.com/grpc/grpc/blob/master/doc/http-grpc-status-mapping.md