google.golang.org/grpc@v1.62.1/test/http_header_end2end_test.go (about) 1 /* 2 * 3 * Copyright 2022 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 package test 19 20 import ( 21 "context" 22 "fmt" 23 "net" 24 "testing" 25 26 "google.golang.org/grpc" 27 "google.golang.org/grpc/codes" 28 "google.golang.org/grpc/credentials/insecure" 29 "google.golang.org/grpc/internal/transport" 30 "google.golang.org/grpc/status" 31 32 testgrpc "google.golang.org/grpc/interop/grpc_testing" 33 ) 34 35 func (s) TestHTTPHeaderFrameErrorHandlingHTTPMode(t *testing.T) { 36 type test struct { 37 name string 38 header []string 39 errCode codes.Code 40 } 41 42 var tests []test 43 44 // Non-gRPC content-type fallback path. 45 for httpCode := range transport.HTTPStatusConvTab { 46 tests = append(tests, test{ 47 name: fmt.Sprintf("Non-gRPC content-type fallback path with httpCode: %v", httpCode), 48 header: []string{ 49 ":status", fmt.Sprintf("%d", httpCode), 50 "content-type", "text/html", // non-gRPC content type to switch to HTTP mode. 51 "grpc-status", "1", // Make up a gRPC status error 52 "grpc-status-details-bin", "???", // Make up a gRPC field parsing error 53 }, 54 errCode: transport.HTTPStatusConvTab[int(httpCode)], 55 }) 56 } 57 58 // Missing content-type fallback path. 59 for httpCode := range transport.HTTPStatusConvTab { 60 tests = append(tests, test{ 61 name: fmt.Sprintf("Missing content-type fallback path with httpCode: %v", httpCode), 62 header: []string{ 63 ":status", fmt.Sprintf("%d", httpCode), 64 // Omitting content type to switch to HTTP mode. 65 "grpc-status", "1", // Make up a gRPC status error 66 "grpc-status-details-bin", "???", // Make up a gRPC field parsing error 67 }, 68 errCode: transport.HTTPStatusConvTab[int(httpCode)], 69 }) 70 } 71 72 // Malformed HTTP status when fallback. 73 tests = append(tests, test{ 74 name: "Malformed HTTP status when fallback", 75 header: []string{ 76 ":status", "abc", 77 // Omitting content type to switch to HTTP mode. 78 "grpc-status", "1", // Make up a gRPC status error 79 "grpc-status-details-bin", "???", // Make up a gRPC field parsing error 80 }, 81 errCode: codes.Internal, 82 }) 83 84 for _, test := range tests { 85 t.Run(test.name, func(t *testing.T) { 86 serverAddr, cleanup, err := startServer(t, test.header) 87 if err != nil { 88 t.Fatal(err) 89 } 90 defer cleanup() 91 if err := doHTTPHeaderTest(serverAddr, test.errCode); err != nil { 92 t.Error(err) 93 } 94 }) 95 } 96 } 97 98 // Testing erroneous ResponseHeader or Trailers-only (delivered in the first HEADERS frame). 99 func (s) TestHTTPHeaderFrameErrorHandlingInitialHeader(t *testing.T) { 100 for _, test := range []struct { 101 name string 102 header []string 103 errCode codes.Code 104 }{ 105 { 106 name: "missing gRPC status", 107 header: []string{ 108 ":status", "403", 109 "content-type", "application/grpc", 110 }, 111 errCode: codes.PermissionDenied, 112 }, 113 { 114 name: "malformed grpc-status", 115 header: []string{ 116 ":status", "502", 117 "content-type", "application/grpc", 118 "grpc-status", "abc", 119 }, 120 errCode: codes.Internal, 121 }, 122 { 123 name: "Malformed grpc-tags-bin field", 124 header: []string{ 125 ":status", "502", 126 "content-type", "application/grpc", 127 "grpc-status", "0", 128 "grpc-tags-bin", "???", 129 }, 130 errCode: codes.Unavailable, 131 }, 132 { 133 name: "gRPC status error", 134 header: []string{ 135 ":status", "502", 136 "content-type", "application/grpc", 137 "grpc-status", "3", 138 }, 139 errCode: codes.Unavailable, 140 }, 141 } { 142 t.Run(test.name, func(t *testing.T) { 143 serverAddr, cleanup, err := startServer(t, test.header) 144 if err != nil { 145 t.Fatal(err) 146 } 147 defer cleanup() 148 if err := doHTTPHeaderTest(serverAddr, test.errCode); err != nil { 149 t.Error(err) 150 } 151 }) 152 } 153 } 154 155 // Testing non-Trailers-only Trailers (delivered in second HEADERS frame) 156 func (s) TestHTTPHeaderFrameErrorHandlingNormalTrailer(t *testing.T) { 157 tests := []struct { 158 name string 159 responseHeader []string 160 trailer []string 161 errCode codes.Code 162 }{ 163 { 164 name: "trailer missing grpc-status", 165 responseHeader: []string{ 166 ":status", "200", 167 "content-type", "application/grpc", 168 }, 169 trailer: []string{ 170 // trailer missing grpc-status 171 ":status", "502", 172 }, 173 errCode: codes.Unavailable, 174 }, 175 { 176 name: "malformed grpc-status-details-bin field with status 404", 177 responseHeader: []string{ 178 ":status", "404", 179 "content-type", "application/grpc", 180 }, 181 trailer: []string{ 182 // malformed grpc-status-details-bin field 183 "grpc-status", "0", 184 "grpc-status-details-bin", "????", 185 }, 186 errCode: codes.Unimplemented, 187 }, 188 { 189 name: "malformed grpc-status-details-bin field with status 200", 190 responseHeader: []string{ 191 ":status", "200", 192 "content-type", "application/grpc", 193 }, 194 trailer: []string{ 195 // malformed grpc-status-details-bin field 196 "grpc-status", "0", 197 "grpc-status-details-bin", "????", 198 }, 199 errCode: codes.Internal, 200 }, 201 } 202 for _, test := range tests { 203 t.Run(test.name, func(t *testing.T) { 204 serverAddr, cleanup, err := startServer(t, test.responseHeader, test.trailer) 205 if err != nil { 206 t.Fatal(err) 207 } 208 defer cleanup() 209 if err := doHTTPHeaderTest(serverAddr, test.errCode); err != nil { 210 t.Error(err) 211 } 212 }) 213 214 } 215 } 216 217 func (s) TestHTTPHeaderFrameErrorHandlingMoreThanTwoHeaders(t *testing.T) { 218 header := []string{ 219 ":status", "200", 220 "content-type", "application/grpc", 221 } 222 serverAddr, cleanup, err := startServer(t, header, header, header) 223 if err != nil { 224 t.Fatal(err) 225 } 226 defer cleanup() 227 if err := doHTTPHeaderTest(serverAddr, codes.Internal); err != nil { 228 t.Fatal(err) 229 } 230 } 231 232 func startServer(t *testing.T, headerFields ...[]string) (serverAddr string, cleanup func(), err error) { 233 t.Helper() 234 235 lis, err := net.Listen("tcp", "localhost:0") 236 if err != nil { 237 return "", nil, fmt.Errorf("listening on %q: %v", "localhost:0", err) 238 } 239 server := &httpServer{responses: []httpServerResponse{{trailers: headerFields}}} 240 server.start(t, lis) 241 return lis.Addr().String(), func() { lis.Close() }, nil 242 } 243 244 func doHTTPHeaderTest(lisAddr string, errCode codes.Code) error { 245 cc, err := grpc.Dial(lisAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) 246 if err != nil { 247 return fmt.Errorf("dial(%q): %v", lisAddr, err) 248 } 249 defer cc.Close() 250 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 251 defer cancel() 252 client := testgrpc.NewTestServiceClient(cc) 253 stream, err := client.FullDuplexCall(ctx) 254 if err != nil { 255 return fmt.Errorf("creating FullDuplex stream: %v", err) 256 } 257 if _, err := stream.Recv(); err == nil || status.Code(err) != errCode { 258 return fmt.Errorf("stream.Recv() = %v, want error code: %v", err, errCode) 259 } 260 return nil 261 }