Skip to content

Commit 28efdf5

Browse files
committed
stream: swallow Header errors as we used to; RecvMsg can still return it (grpc#6586)
1 parent 4c9777c commit 28efdf5

File tree

2 files changed

+72
-2
lines changed

2 files changed

+72
-2
lines changed

stream.go

+5-2
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,9 @@ type Stream interface {
9191
// status package.
9292
type ClientStream interface {
9393
// Header returns the header metadata received from the server if there
94-
// is any. It blocks if the metadata is not ready to read.
94+
// is any. It blocks if the metadata is not ready to read. If the metadata
95+
// is nil and the error is also nil, then the stream was terminated without
96+
// headers, and the status can be discovered by calling RecvMsg.
9597
Header() (metadata.MD, error)
9698
// Trailer returns the trailer metadata from the server, if there is any.
9799
// It must only be called after stream.CloseAndRecv has returned, or
@@ -802,7 +804,8 @@ func (cs *clientStream) Header() (metadata.MD, error) {
802804

803805
if err != nil {
804806
cs.finish(err)
805-
return nil, err
807+
// Do not return the error. The user should get it by calling Recv().
808+
return nil, nil
806809
}
807810

808811
if len(cs.binlogs) != 0 && !cs.serverHeaderBinlogged && m != nil {

stream_test.go

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
*
3+
* Copyright 2023 gRPC authors.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*/
18+
19+
package grpc_test
20+
21+
import (
22+
"context"
23+
"testing"
24+
"time"
25+
26+
"google.golang.org/grpc/codes"
27+
"google.golang.org/grpc/internal/grpctest"
28+
"google.golang.org/grpc/internal/stubserver"
29+
"google.golang.org/grpc/interop/grpc_testing"
30+
"google.golang.org/grpc/status"
31+
)
32+
33+
const defaultTestTimeout = 10 * time.Second
34+
35+
type s struct {
36+
grpctest.Tester
37+
}
38+
39+
func Test(t *testing.T) {
40+
grpctest.RunSubTests(t, s{})
41+
}
42+
43+
func (s) TestStream_Header_TrailersOnly(t *testing.T) {
44+
ss := stubserver.StubServer{
45+
FullDuplexCallF: func(stream grpc_testing.TestService_FullDuplexCallServer) error {
46+
return status.Errorf(codes.NotFound, "a test error")
47+
},
48+
}
49+
if err := ss.Start(nil); err != nil {
50+
t.Fatal("Error starting server:", err)
51+
}
52+
defer ss.Stop()
53+
54+
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
55+
defer cancel()
56+
57+
s, err := ss.Client.FullDuplexCall(ctx)
58+
if err != nil {
59+
t.Fatal("Error staring call", err)
60+
}
61+
if md, err := s.Header(); md != nil || err != nil {
62+
t.Fatalf("s.Header() = %v, %v; want nil, nil", md, err)
63+
}
64+
if _, err := s.Recv(); status.Code(err) != codes.NotFound {
65+
t.Fatalf("s.Recv() = _, %v; want _, err.Code()=codes.NotFound", err)
66+
}
67+
}

0 commit comments

Comments
 (0)