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

client: handle HTTP header parsing error correctly #2599

Merged
merged 7 commits into from
Mar 6, 2019

Conversation

lyuxuan
Copy link
Contributor

@lyuxuan lyuxuan commented Jan 28, 2019

@lyuxuan lyuxuan requested a review from dfawley January 28, 2019 23:15
Copy link
Member

@dfawley dfawley left a 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!

@@ -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}
Copy link
Member

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.

@lyuxuan lyuxuan force-pushed the fixhttp2err branch 2 times, most recently from 526bea6 to c0e5a41 Compare January 30, 2019 01:01
@lyuxuan lyuxuan assigned dfawley and unassigned lyuxuan Jan 30, 2019
@@ -120,8 +118,32 @@ type decodeState struct {
statsTags []byte
statsTrace []byte
contentSubtype string
}

func newDecodeState(serverSide bool, ignoreContentType bool) *decodeState {
Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

}
}

// Records the states during HPACK decoding. Must be reset once the
Copy link
Member

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved comments.

}

// Records the states during HPACK decoding. Must be reset once the
// decoding of the entire headers are finished.
Copy link
Member

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.

Copy link
Contributor Author

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.

// 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).
Copy link
Member

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?

Copy link
Contributor Author

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.

// 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
Copy link
Member

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.)

Copy link
Contributor Author

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 Show resolved Hide resolved
internal/transport/http_util.go Outdated Show resolved Hide resolved
internal/transport/http_util.go Outdated Show resolved Hide resolved
@@ -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.
Copy link
Member

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?

@menghanl menghanl assigned lyuxuan and unassigned dfawley Feb 15, 2019
@lyuxuan lyuxuan force-pushed the fixhttp2err branch 2 times, most recently from a54f089 to 3796c19 Compare February 20, 2019 00:49
@menghanl menghanl assigned dfawley and unassigned lyuxuan Feb 21, 2019
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 {
Copy link
Member

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).

Copy link
Contributor Author

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
Copy link
Member

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?

Copy link
Contributor Author

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.

@lyuxuan lyuxuan merged commit 79c9bc6 into grpc:master Mar 6, 2019
@dfawley dfawley added Type: Dependencies Updating/adding/removing dependencies Type: Bug and removed Type: Dependencies Updating/adding/removing dependencies labels Mar 7, 2019
@dfawley dfawley added this to the 1.20 Release milestone Mar 7, 2019
@lock lock bot locked as resolved and limited conversation to collaborators Dec 2, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants