-
Notifications
You must be signed in to change notification settings - Fork 4.6k
client: ignore http status header for gRPC streams #8548
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
base: master
Are you sure you want to change the base?
Changes from 3 commits
50b5337
b1a7265
b4cce55
7087664
aceb8be
734cd4d
bebc8d3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -1465,17 +1465,13 @@ func (t *http2Client) operateHeaders(frame *http2.MetaHeadersFrame) { | |||||
contentTypeErr = "malformed header: missing HTTP content-type" | ||||||
grpcMessage string | ||||||
recvCompress string | ||||||
httpStatusCode *int | ||||||
httpStatusErr string | ||||||
rawStatusCode = codes.Unknown | ||||||
rawStatusCode = codes.Internal | ||||||
// headerError is set if an error is encountered while parsing the headers | ||||||
headerError string | ||||||
headerError string | ||||||
receivedHTTPStatus string | ||||||
|
||||||
) | ||||||
|
||||||
if initialHeader { | ||||||
httpStatusErr = "malformed header: missing HTTP status" | ||||||
} | ||||||
|
||||||
for _, hf := range frame.Fields { | ||||||
switch hf.Name { | ||||||
case "content-type": | ||||||
|
@@ -1499,32 +1495,9 @@ func (t *http2Client) operateHeaders(frame *http2.MetaHeadersFrame) { | |||||
case "grpc-message": | ||||||
grpcMessage = decodeGrpcMessage(hf.Value) | ||||||
case ":status": | ||||||
c, err := strconv.ParseInt(hf.Value, 10, 32) | ||||||
if err != nil { | ||||||
se := status.New(codes.Internal, fmt.Sprintf("transport: malformed http-status: %v", err)) | ||||||
t.closeStream(s, se.Err(), true, http2.ErrCodeProtocol, se, nil, endStream) | ||||||
return | ||||||
} | ||||||
statusCode := int(c) | ||||||
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 | ||||||
if !isGRPC { | ||||||
dfawley marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
receivedHTTPStatus = hf.Value | ||||||
} | ||||||
httpStatusCode = &statusCode | ||||||
if statusCode == 200 { | ||||||
httpStatusErr = "" | ||||||
break | ||||||
} | ||||||
|
||||||
httpStatusErr = fmt.Sprintf( | ||||||
"unexpected HTTP status code received from server: %d (%s)", | ||||||
statusCode, | ||||||
http.StatusText(statusCode), | ||||||
) | ||||||
default: | ||||||
if isReservedHeader(hf.Name) && !isWhitelistedHeader(hf.Name) { | ||||||
break | ||||||
|
@@ -1539,25 +1512,53 @@ func (t *http2Client) operateHeaders(frame *http2.MetaHeadersFrame) { | |||||
} | ||||||
} | ||||||
|
||||||
if !isGRPC || httpStatusErr != "" { | ||||||
var code = codes.Internal // when header does not include HTTP status, return INTERNAL | ||||||
//if not a gRPC response - evaluate entire http status and process close stream / response | ||||||
//for 200 -> Unknown, else decode the error | ||||||
arjan-bal marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
if !isGRPC { | ||||||
var grpcErrorCode = codes.Internal // when header does not include HTTP status, return INTERNAL | ||||||
var errs []string | ||||||
dfawley marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
|
||||||
if httpStatusCode != nil { | ||||||
switch receivedHTTPStatus { | ||||||
case "": | ||||||
httpStatusErr = "malformed header: missing HTTP status" | ||||||
case "200": | ||||||
grpcErrorCode = codes.Unknown | ||||||
|
||||||
default: | ||||||
// Any other status code (e.g., "404", "503"). We must parse it. | ||||||
|
// Any other status code (e.g., "404", "503"). We must parse it. | |
// Parse the status code (e.g. "200", 404"). |
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.
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.
Why do we cast to an int
here? It seems unnecessary.
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.
changed
dfawley marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
arjan-bal marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
arjan-bal marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2618,6 +2618,8 @@ func (s) TestClientDecodeHeaderStatusErr(t *testing.T) { | |
metaHeaderFrame *http2.MetaHeadersFrame | ||
// output | ||
wantStatus *status.Status | ||
// end stream output | ||
|
||
wantStatusEndStream *status.Status | ||
}{ | ||
{ | ||
name: "valid header", | ||
|
@@ -2629,7 +2631,8 @@ func (s) TestClientDecodeHeaderStatusErr(t *testing.T) { | |
}, | ||
}, | ||
// no error | ||
wantStatus: status.New(codes.OK, ""), | ||
wantStatus: status.New(codes.OK, ""), | ||
wantStatusEndStream: status.New(codes.OK, ""), | ||
}, | ||
{ | ||
name: "missing content-type header", | ||
|
@@ -2643,6 +2646,10 @@ func (s) TestClientDecodeHeaderStatusErr(t *testing.T) { | |
codes.Unknown, | ||
"malformed header: missing HTTP content-type", | ||
), | ||
wantStatusEndStream: status.New( | ||
codes.Unknown, | ||
"malformed header: missing HTTP content-type", | ||
), | ||
}, | ||
{ | ||
name: "invalid grpc status header field", | ||
|
@@ -2657,6 +2664,10 @@ func (s) TestClientDecodeHeaderStatusErr(t *testing.T) { | |
codes.Internal, | ||
"transport: malformed grpc-status: strconv.ParseInt: parsing \"xxxx\": invalid syntax", | ||
), | ||
wantStatusEndStream: status.New( | ||
codes.Internal, | ||
"transport: malformed grpc-status: strconv.ParseInt: parsing \"xxxx\": invalid syntax", | ||
), | ||
}, | ||
{ | ||
name: "invalid http content type", | ||
|
@@ -2669,6 +2680,10 @@ func (s) TestClientDecodeHeaderStatusErr(t *testing.T) { | |
codes.Internal, | ||
"malformed header: missing HTTP status; transport: received unexpected content-type \"application/json\"", | ||
), | ||
wantStatusEndStream: status.New( | ||
codes.Internal, | ||
"malformed header: missing HTTP status; transport: received unexpected content-type \"application/json\"", | ||
), | ||
}, | ||
{ | ||
name: "http fallback and invalid http status", | ||
|
@@ -2682,6 +2697,10 @@ func (s) TestClientDecodeHeaderStatusErr(t *testing.T) { | |
codes.Internal, | ||
"transport: malformed http-status: strconv.ParseInt: parsing \"xxxx\": invalid syntax", | ||
), | ||
wantStatusEndStream: status.New( | ||
codes.Internal, | ||
"transport: malformed http-status: strconv.ParseInt: parsing \"xxxx\": invalid syntax", | ||
), | ||
}, | ||
{ | ||
name: "http2 frame size exceeds", | ||
|
@@ -2693,32 +2712,69 @@ func (s) TestClientDecodeHeaderStatusErr(t *testing.T) { | |
codes.Internal, | ||
"peer header list size exceeded limit", | ||
), | ||
wantStatusEndStream: status.New( | ||
codes.Internal, | ||
"peer header list size exceeded limit", | ||
), | ||
}, | ||
{ | ||
name: "bad status in grpc mode", | ||
name: "ignoring bad http status in grpc mode", | ||
metaHeaderFrame: &http2.MetaHeadersFrame{ | ||
Fields: []hpack.HeaderField{ | ||
{Name: "content-type", Value: "application/grpc"}, | ||
{Name: "grpc-status", Value: "0"}, | ||
{Name: ":status", Value: "504"}, | ||
}, | ||
}, | ||
wantStatus: status.New( | ||
codes.Unavailable, | ||
"unexpected HTTP status code received from server: 504 (Gateway Timeout)", | ||
), | ||
wantStatus: status.New(codes.OK, ""), | ||
wantStatusEndStream: status.New(codes.OK, ""), | ||
}, | ||
{ | ||
name: "missing http status", | ||
name: "missing http status and grpc status", | ||
metaHeaderFrame: &http2.MetaHeadersFrame{ | ||
Fields: []hpack.HeaderField{ | ||
{Name: "content-type", Value: "application/grpc"}, | ||
}, | ||
}, | ||
wantStatus: status.New( | ||
codes.Internal, | ||
"malformed header: missing HTTP status", | ||
), | ||
wantStatus: status.New(codes.OK, ""), | ||
wantStatusEndStream: status.New(codes.Internal, ""), | ||
}, | ||
{ | ||
name: "ignore http status and fail for grpc status missing in trailer", | ||
metaHeaderFrame: &http2.MetaHeadersFrame{ | ||
Fields: []hpack.HeaderField{ | ||
{Name: "content-type", Value: "application/grpc"}, | ||
{Name: ":status", Value: "504"}, | ||
}, | ||
}, | ||
wantStatus: status.New(codes.OK, ""), | ||
wantStatusEndStream: status.New(codes.Internal, ""), | ||
}, | ||
{ | ||
name: "ignore valid http status for grpc", | ||
metaHeaderFrame: &http2.MetaHeadersFrame{ | ||
Fields: []hpack.HeaderField{ | ||
{Name: "content-type", Value: "application/grpc"}, | ||
{Name: "grpc-status", Value: "4"}, | ||
{Name: "grpc-message", Value: "Request timed out: Internal error"}, | ||
{Name: ":status", Value: "200"}, | ||
}, | ||
}, | ||
wantStatus: status.New(codes.OK, ""), | ||
wantStatusEndStream: status.New(codes.DeadlineExceeded, "Request timed out: Internal error"), | ||
}, | ||
{ | ||
name: "ignore illegal http status for grpc", | ||
metaHeaderFrame: &http2.MetaHeadersFrame{ | ||
Fields: []hpack.HeaderField{ | ||
{Name: "content-type", Value: "application/grpc"}, | ||
{Name: "grpc-status", Value: "4"}, | ||
{Name: "grpc-message", Value: "Request timed out: Internal error"}, | ||
{Name: ":status", Value: "thisIsIllegal"}, | ||
}, | ||
}, | ||
wantStatus: status.New(codes.OK, ""), | ||
wantStatusEndStream: status.New(codes.DeadlineExceeded, "Request timed out: Internal error"), | ||
}, | ||
} { | ||
|
||
|
@@ -2733,7 +2789,6 @@ func (s) TestClientDecodeHeaderStatusErr(t *testing.T) { | |
} | ||
|
||
s.operateHeaders(test.metaHeaderFrame) | ||
|
||
got := ts.status | ||
want := test.wantStatus | ||
if got.Code() != want.Code() || got.Message() != want.Message() { | ||
|
@@ -2755,6 +2810,9 @@ func (s) TestClientDecodeHeaderStatusErr(t *testing.T) { | |
|
||
got := ts.status | ||
want := test.wantStatus | ||
if test.wantStatusEndStream != nil { | ||
want = test.wantStatusEndStream | ||
} | ||
arjan-bal marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
if got.Code() != want.Code() || got.Message() != want.Message() { | ||
t.Fatalf("operateHeaders(%v); status = \ngot: %s\nwant: %s", test.metaHeaderFrame, got, want) | ||
} | ||
|
@@ -3159,17 +3217,14 @@ func (s) TestClientTransport_Handle1xxHeaders(t *testing.T) { | |
wantStatus *status.Status | ||
}{ | ||
{ | ||
name: "1xx with END_STREAM is error", | ||
name: "1xx with END_STREAM will be ignored", | ||
metaHeaderFrame: &http2.MetaHeadersFrame{ | ||
Fields: []hpack.HeaderField{ | ||
{Name: ":status", Value: "100"}, | ||
}, | ||
}, | ||
httpFlags: http2.FlagHeadersEndStream, | ||
arjan-bal marked this conversation as resolved.
Show resolved
Hide resolved
|
||
wantStatus: status.New( | ||
codes.Internal, | ||
"protocol error: informational header with status code 100 must not have END_STREAM set", | ||
), | ||
httpFlags: http2.FlagHeadersEndStream, | ||
wantStatus: nil, | ||
}, | ||
{ | ||
name: "1xx without END_STREAM is ignored", | ||
|
Uh oh!
There was an error while loading. Please reload this page.