Skip to content

Conversation

mbissa
Copy link

@mbissa mbissa commented Sep 1, 2025

Fixes #8486

When a gRPC response is received with content type application/grpc, we then do not expect any information in the http status and the status information needs to be conveyed by gRPC status only.

In case of missing gRPC status, we will throw an Internal error instead of Unknown in accordance with https://grpc.io/docs/guides/status-codes/

Changes :
- Ignore http status in case of content type application/grpc
- Change the default rawStatusCode to return Internal for missing grpc status

RELEASE NOTES:

  • client : Ignore http status for gRPC mode and return Internal error for missing grpc status

Fixes grpc#8486

When a gRPC response is received with content type application/grpc,
we then do not expect any information in the http status and the status information
needs to be conveyed by gRPC status only.

In case of missing gRPC status, we will throw an Internal error instead of Unknown
in accordance with https://grpc.io/docs/guides/status-codes/

	Changes :
	- Ignore http status in case of content type application/grpc
	- Change the default rawStatusCode to return Internal for missing grpc status

RELEASE NOTES:
* client : Ignore http status for gRPC mode and return Internal error for
missing grpc status
@mbissa mbissa added this to the 1.76 Release milestone Sep 1, 2025
@mbissa mbissa requested a review from arjan-bal September 1, 2025 06:27
@mbissa mbissa self-assigned this Sep 1, 2025
Copy link

codecov bot commented Sep 1, 2025

Codecov Report

❌ Patch coverage is 43.90244% with 23 lines in your changes missing coverage. Please review.
✅ Project coverage is 80.77%. Comparing base (e60a04b) to head (7087664).
⚠️ Report is 3 commits behind head on master.

Files with missing lines Patch % Lines
internal/transport/http2_client.go 43.90% 9 Missing and 14 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #8548      +/-   ##
==========================================
- Coverage   81.64%   80.77%   -0.88%     
==========================================
  Files         413      413              
  Lines       40621    40754     +133     
==========================================
- Hits        33167    32920     -247     
- Misses       5991     6202     +211     
- Partials     1463     1632     +169     
Files with missing lines Coverage Δ
internal/transport/http2_client.go 63.21% <43.90%> (-20.15%) ⬇️

... and 30 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@arjan-bal
Copy link
Contributor

In the grpc-go repo, we use the assignee to indicate who the review is pending on (author or reviewer). We ignore the reviewer status (the yellow pending re-review dot). Assigning to myself.

@arjan-bal arjan-bal assigned arjan-bal and unassigned mbissa Sep 1, 2025
@mbissa mbissa requested a review from dfawley September 1, 2025 06:34
@arjan-bal
Copy link
Contributor

arjan-bal commented Sep 1, 2025

For the PR title, we add the name of the Go package (e.g. transport), with the prefix if it's ambiguous (e.g. xds/bootstrap), followed by the change description as a complete sentence, written as though it were an order (an imperative sentence), see go/cl-descriptions#first-line.

Comment on lines 2780 to 2782
if test.wantStatusEndStream != nil {
want = test.wantStatusEndStream
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should explicitly wantStatusEndStream for all the tests which have non nil values. In table-driven tests, it's fine to duplicate parameters across test cases, even if most share the same value. This practice keeps the tests declarative and simplifies the logic, often to just an equality or diff comparison.

Copy link
Author

Choose a reason for hiding this comment

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

Done, added duplicate parameters

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks, we should be able to remove this if block now, right?

@arjan-bal arjan-bal assigned dfawley and unassigned arjan-bal Sep 1, 2025
@dfawley dfawley removed their assignment Sep 3, 2025
@mbissa mbissa force-pushed the ignore-status-for-grpc-content-type-8486 branch 2 times, most recently from e1f6dbc to f123124 Compare September 9, 2025 04:31
@mbissa mbissa force-pushed the ignore-status-for-grpc-content-type-8486 branch from f123124 to b4cce55 Compare September 9, 2025 04:34
@arjan-bal arjan-bal changed the title ignoring http status when content type is application/grpc grpc#8486 client : Ignore http status for gRPC mode and return Internal error for missing grpc status Sep 10, 2025
@arjan-bal arjan-bal assigned arjan-bal and dfawley and unassigned mbissa Sep 10, 2025
@mbissa mbissa changed the title client : Ignore http status for gRPC mode and return Internal error for missing grpc status client : Ignore http status for gRPC mode Sep 10, 2025
@mbissa mbissa changed the title client : Ignore http status for gRPC mode client transport : ignore http status for gRPC mode Sep 10, 2025
@mbissa mbissa removed the request for review from dfawley September 10, 2025 17:59
Comment on lines 1515 to 1516
//if not a gRPC response - evaluate entire http status and process close stream / response
//for 200 -> Unknown, else decode the error
Copy link
Contributor

Choose a reason for hiding this comment

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

style: Please use complete sentences for comments. Also leave a after //. See https://google.github.io/styleguide/go/decisions#comment-sentences

// headerError is set if an error is encountered while parsing the headers
headerError string
headerError string
receivedHTTPStatus string
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: We can omit the received prefix int the variable name, shortening it to httpStatus since it doesn't effect readability.

statusCode := int(c)
if statusCode >= 100 && statusCode < 200 {
//In case of informational headers return
//For trailers, since we are already in gRPC mode, we will ignore all http statuses and not enter this block
Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like the check for the endStream flag was removed.

if statusCode >= 100 && statusCode < 200 {
if endStream {
se := status.New(codes.Internal, fmt.Sprintf(
"protocol error: informational header with status code %d must not have END_STREAM set", statusCode))
t.closeStream(s, se.Err(), true, http2.ErrCodeProtocol, se, nil, endStream)
}
return
}

Copy link
Author

@mbissa mbissa Sep 12, 2025

Choose a reason for hiding this comment

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

I was confused if endstream can come when it is not a gRPC response - @dfawley confirmed that sometimes only one header comes in the response which is set with end stream and it is possible that it may not be gRPC in which case we still need to throw an error if it is informational header. I have added this code back.

Comment on lines +1524 to +1525
case "200":
grpcErrorCode = codes.Unknown
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this explicit check required? Since 200 isn't present in the HTTPStatusConvTab map, I think it may be omitted.

Copy link
Author

Choose a reason for hiding this comment

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

The idea is when no error information can be derived from http status since it is 200, we can simply return Unknown code.

if httpStatusErr != "" {
errs = append(errs, httpStatusErr)
}

if contentTypeErr != "" {
errs = append(errs, contentTypeErr)
}
// Verify the HTTP response is a 200.
Copy link
Contributor

Choose a reason for hiding this comment

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

This comment doesn't seem to be accurate. Should we remove it now?

Copy link
Author

Choose a reason for hiding this comment

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

removed it. Not sure why it was present.

Comment on lines 2780 to 2782
if test.wantStatusEndStream != nil {
want = test.wantStatusEndStream
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks, we should be able to remove this if block now, right?

metaHeaderFrame: &http2.MetaHeadersFrame{
Fields: []hpack.HeaderField{
{Name: ":status", Value: "100"},
},
},
httpFlags: http2.FlagHeadersEndStream,
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the test was correct before these changes. This test was recently added to fix a bug: #8485

Copy link
Author

Choose a reason for hiding this comment

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

Agreed. Reverted it back.

@@ -103,10 +103,9 @@ func (s) TestHTTPHeaderFrameErrorHandlingInitialHeader(t *testing.T) {
errCode codes.Code
}{
{
name: "missing gRPC status",
name: "missing gRPC content type",
Copy link
Contributor

Choose a reason for hiding this comment

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

In my opinion, a better fix for this test would be to change the assertion to Ok instead, keeping the test params the same. codes.Ok has the zero value for the integer type, so it can be omitted, see https://google.github.io/styleguide/go/decisions#zero-value-fields

Copy link
Author

Choose a reason for hiding this comment

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

there is a test for missing gRPC status already. The expected outcome will not be Ok, it would be an Internal error since gRPC content type is mandated to have the status.

Comment on lines -189 to +188
name: "malformed grpc-status-details-bin field with status 200",
name: "malformed grpc-status-details-bin field with status 404 and no content type",
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should keep this test for malformed grpc-status-details-bin and add a new test for the 404 and no content type case if necessary. This would ensure we have coverage for the existing scenario.

Copy link
Author

@mbissa mbissa Sep 12, 2025

Choose a reason for hiding this comment

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

The status 200 or 404 is immaterial in case of content type being application/grpc, the previous test already caters to that and we are finally checking the malformed grpc-status-details-bin field which we were not doing earlier. Hence, expected outcome code in the previous test is no longer Unimplemented (due to 404), the malformed grpc-status-details-bin now results in an Internal error code.

@arjan-bal arjan-bal assigned mbissa and unassigned arjan-bal Sep 11, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

client transport: ignore HTTP/2 status if content-type is application/grpc
3 participants