github.com/cloudwego/kitex@v0.9.0/pkg/remote/trans/nphttp2/grpc/http_util.go (about) 1 /* 2 * 3 * Copyright 2014 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 * This file may have been modified by CloudWeGo authors. All CloudWeGo 18 * Modifications are Copyright 2021 CloudWeGo Authors. 19 */ 20 21 package grpc 22 23 import ( 24 "bytes" 25 "encoding/base64" 26 "fmt" 27 "math" 28 "net/http" 29 "strconv" 30 "strings" 31 "time" 32 "unicode/utf8" 33 34 "golang.org/x/net/http2" 35 "golang.org/x/net/http2/hpack" 36 spb "google.golang.org/genproto/googleapis/rpc/status" 37 "google.golang.org/protobuf/proto" 38 39 "github.com/cloudwego/kitex/pkg/kerrors" 40 "github.com/cloudwego/kitex/pkg/klog" 41 "github.com/cloudwego/kitex/pkg/remote/trans/nphttp2/codes" 42 "github.com/cloudwego/kitex/pkg/remote/trans/nphttp2/grpc/grpcframe" 43 "github.com/cloudwego/kitex/pkg/remote/trans/nphttp2/status" 44 "github.com/cloudwego/kitex/pkg/utils" 45 ) 46 47 const ( 48 // http2MaxFrameLen specifies the max length of a HTTP2 frame. 49 http2MaxFrameLen = 16384 // 16KB frame 50 // http://http2.github.io/http2-spec/#SettingValues 51 http2InitHeaderTableSize = 4096 52 // baseContentType is the base content-type for gRPC. This is a valid 53 // content-type on it's own, but can also include a content-subtype such as 54 // "proto" as a suffix after "+" or ";". See 55 // https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests 56 // for more details. 57 baseContentType = "application/grpc" 58 ) 59 60 var ( 61 // ClientPreface http2 preface message 62 ClientPreface = []byte(http2.ClientPreface) 63 // ClientPrefaceLen preface length 64 ClientPrefaceLen = len(ClientPreface) 65 http2ErrConvTab = map[http2.ErrCode]codes.Code{ 66 http2.ErrCodeNo: codes.Internal, 67 http2.ErrCodeProtocol: codes.Internal, 68 http2.ErrCodeInternal: codes.Internal, 69 http2.ErrCodeFlowControl: codes.ResourceExhausted, 70 http2.ErrCodeSettingsTimeout: codes.Internal, 71 http2.ErrCodeStreamClosed: codes.Internal, 72 http2.ErrCodeFrameSize: codes.Internal, 73 http2.ErrCodeRefusedStream: codes.Unavailable, 74 http2.ErrCodeCancel: codes.Canceled, 75 http2.ErrCodeCompression: codes.Internal, 76 http2.ErrCodeConnect: codes.Internal, 77 http2.ErrCodeEnhanceYourCalm: codes.ResourceExhausted, 78 http2.ErrCodeInadequateSecurity: codes.PermissionDenied, 79 http2.ErrCodeHTTP11Required: codes.Internal, 80 } 81 statusCodeConvTab = map[codes.Code]http2.ErrCode{ 82 codes.Internal: http2.ErrCodeInternal, 83 codes.Canceled: http2.ErrCodeCancel, 84 codes.Unavailable: http2.ErrCodeRefusedStream, 85 codes.ResourceExhausted: http2.ErrCodeEnhanceYourCalm, 86 codes.PermissionDenied: http2.ErrCodeInadequateSecurity, 87 } 88 // HTTPStatusConvTab is the HTTP status code to gRPC error code conversion table. 89 HTTPStatusConvTab = map[int]codes.Code{ 90 // 400 Bad Request - INTERNAL. 91 http.StatusBadRequest: codes.Internal, 92 // 401 Unauthorized - UNAUTHENTICATED. 93 http.StatusUnauthorized: codes.Unauthenticated, 94 // 403 Forbidden - PERMISSION_DENIED. 95 http.StatusForbidden: codes.PermissionDenied, 96 // 404 Not Found - UNIMPLEMENTED. 97 http.StatusNotFound: codes.Unimplemented, 98 // 429 Too Many Requests - UNAVAILABLE. 99 http.StatusTooManyRequests: codes.Unavailable, 100 // 502 Bad Gateway - UNAVAILABLE. 101 http.StatusBadGateway: codes.Unavailable, 102 // 503 Service Unavailable - UNAVAILABLE. 103 http.StatusServiceUnavailable: codes.Unavailable, 104 // 504 Gateway timeout - UNAVAILABLE. 105 http.StatusGatewayTimeout: codes.Unavailable, 106 } 107 ) 108 109 type parsedHeaderData struct { 110 encoding string 111 acceptEncoding string 112 // statusGen caches the stream status received from the trailer the server 113 // sent. Client side only. Do not access directly. After all trailers are 114 // parsed, use the status method to retrieve the status. 115 statusGen *status.Status 116 bizStatusErr kerrors.BizStatusErrorIface 117 // rawStatusCode and rawStatusMsg are set from the raw trailer fields and are not 118 // intended for direct access outside of parsing. 119 rawStatusCode *int 120 rawStatusMsg string 121 bizStatusCode *int 122 bizStatusExtra map[string]string 123 httpStatus *int 124 // Server side only fields. 125 timeoutSet bool 126 timeout time.Duration 127 method string 128 // key-value metadata map from the peer. 129 mdata map[string][]string 130 statsTags []byte 131 statsTrace []byte 132 contentSubtype string 133 134 // isGRPC field indicates whether the peer is speaking gRPC (otherwise HTTP). 135 // 136 // We are in gRPC mode (peer speaking gRPC) if: 137 // * We are client side and have already received a HEADER frame that indicates gRPC peer. 138 // * The header contains valid a content-type, i.e. a string starts with "application/grpc" 139 // And we should handle error specific to gRPC. 140 // 141 // Otherwise (i.e. a content-type string starts without "application/grpc", or does not exist), we 142 // are in HTTP fallback mode, and should handle error specific to HTTP. 143 isGRPC bool 144 grpcErr error 145 httpErr error 146 contentTypeErr string 147 } 148 149 // decodeState configures decoding criteria and records the decoded data. 150 type decodeState struct { 151 // whether decoding on server side or not 152 serverSide bool 153 154 // Records the states during HPACK decoding. It will be filled with info parsed from HTTP HEADERS 155 // frame once decodeHeader function has been invoked and returned. 156 data parsedHeaderData 157 } 158 159 // isReservedHeader checks whether hdr belongs to HTTP2 headers 160 // reserved by gRPC protocol. Any other headers are classified as the 161 // user-specified metadata. 162 func isReservedHeader(hdr string) bool { 163 if hdr != "" && hdr[0] == ':' { 164 return true 165 } 166 switch hdr { 167 case "content-type", 168 "user-agent", 169 "grpc-message-type", 170 "grpc-encoding", 171 "grpc-message", 172 "grpc-status", 173 "grpc-timeout", 174 "grpc-status-details-bin", 175 // Intentionally exclude grpc-previous-rpc-attempts and 176 // grpc-retry-pushback-ms, which are "reserved", but their API 177 // intentionally works via metadata. 178 "te": 179 return true 180 default: 181 return false 182 } 183 } 184 185 // isWhitelistedHeader checks whether hdr should be propagated into metadata 186 // visible to users, even though it is classified as "reserved", above. 187 func isWhitelistedHeader(hdr string) bool { 188 switch hdr { 189 case ":authority", "user-agent": 190 return true 191 default: 192 return false 193 } 194 } 195 196 // contentSubtype returns the content-subtype for the given content-type. The 197 // given content-type must be a valid content-type that starts with 198 // "application/grpc". A content-subtype will follow "application/grpc" after a 199 // "+" or ";". See 200 // https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests for 201 // more details. 202 // 203 // If contentType is not a valid content-type for gRPC, the boolean 204 // will be false, otherwise true. If content-type == "application/grpc", 205 // "application/grpc+", or "application/grpc;", the boolean will be true, 206 // but no content-subtype will be returned. 207 // 208 // contentType is assumed to be lowercase already. 209 func contentSubtype(contentType string) (string, bool) { 210 if contentType == baseContentType { 211 return "", true 212 } 213 if !strings.HasPrefix(contentType, baseContentType) { 214 return "", false 215 } 216 // guaranteed since != baseContentType and has baseContentType prefix 217 switch contentType[len(baseContentType)] { 218 case '+', ';': 219 // this will return true for "application/grpc+" or "application/grpc;" 220 // which the previous validContentType function tested to be valid, so we 221 // just say that no content-subtype is specified in this case 222 return contentType[len(baseContentType)+1:], true 223 default: 224 return "", false 225 } 226 } 227 228 // contentSubtype is assumed to be lowercase 229 func contentType(contentSubtype string) string { 230 if contentSubtype == "" { 231 return baseContentType 232 } 233 return baseContentType + "+" + contentSubtype 234 } 235 236 func (d *decodeState) status() *status.Status { 237 if d.data.statusGen == nil { 238 // No status-details were provided; generate status using code/msg. 239 d.data.statusGen = status.New(codes.Code(safeCastInt32(*(d.data.rawStatusCode))), d.data.rawStatusMsg) 240 } 241 return d.data.statusGen 242 } 243 244 func (d *decodeState) bizStatusErr() kerrors.BizStatusErrorIface { 245 if d.data.bizStatusErr == nil && d.data.bizStatusCode != nil { 246 d.data.bizStatusErr = kerrors.NewGRPCBizStatusErrorWithExtra( 247 safeCastInt32(*(d.data.bizStatusCode)), d.data.rawStatusMsg, d.data.bizStatusExtra) 248 if st, ok := d.data.bizStatusErr.(kerrors.GRPCStatusIface); ok { 249 st.SetGRPCStatus(d.status()) 250 } 251 } 252 return d.data.bizStatusErr 253 } 254 255 // safeCastInt32 casts the number from int to int32 in safety. 256 func safeCastInt32(n int) int32 { 257 if n > math.MaxInt32 || n < math.MinInt32 { 258 panic(fmt.Sprintf("Cast int to int32 failed, due to overflow, n=%d", n)) 259 } 260 return int32(n) 261 } 262 263 const binHdrSuffix = "-bin" 264 265 func encodeBinHeader(v []byte) string { 266 return base64.RawStdEncoding.EncodeToString(v) 267 } 268 269 func decodeBinHeader(v string) ([]byte, error) { 270 if len(v)%4 == 0 { 271 // Input was padded, or padding was not necessary. 272 return base64.StdEncoding.DecodeString(v) 273 } 274 return base64.RawStdEncoding.DecodeString(v) 275 } 276 277 func encodeMetadataHeader(k, v string) string { 278 if strings.HasSuffix(k, binHdrSuffix) { 279 return encodeBinHeader(([]byte)(v)) 280 } 281 return v 282 } 283 284 func decodeMetadataHeader(k, v string) (string, error) { 285 if strings.HasSuffix(k, binHdrSuffix) { 286 b, err := decodeBinHeader(v) 287 return string(b), err 288 } 289 return v, nil 290 } 291 292 func (d *decodeState) decodeHeader(frame *grpcframe.MetaHeadersFrame) error { 293 // frame.Truncated is set to true when framer detects that the current header 294 // list size hits MaxHeaderListSize limit. 295 if frame.Truncated { 296 return status.New(codes.Internal, "peer header list size exceeded limit").Err() 297 } 298 299 for _, hf := range frame.Fields { 300 d.processHeaderField(hf) 301 } 302 303 if d.data.isGRPC { 304 if d.data.grpcErr != nil { 305 return d.data.grpcErr 306 } 307 if d.serverSide { 308 return nil 309 } 310 if d.data.rawStatusCode == nil && d.data.statusGen == nil { 311 // gRPC status doesn't exist. 312 // Set rawStatusCode to be unknown and return nil error. 313 // So that, if the stream has ended this Unknown status 314 // will be propagated to the user. 315 // Otherwise, it will be ignored. In which case, status from 316 // a later trailer, that has StreamEnded flag set, is propagated. 317 code := int(codes.Unknown) 318 d.data.rawStatusCode = &code 319 } 320 return nil 321 } 322 323 // HTTP fallback mode 324 if d.data.httpErr != nil { 325 return d.data.httpErr 326 } 327 328 var ( 329 code = codes.Internal // when header does not include HTTP status, return INTERNAL 330 ok bool 331 ) 332 333 if d.data.httpStatus != nil { 334 code, ok = HTTPStatusConvTab[*(d.data.httpStatus)] 335 if !ok { 336 code = codes.Unknown 337 } 338 } 339 340 return status.New(code, d.constructHTTPErrMsg()).Err() 341 } 342 343 // constructErrMsg constructs error message to be returned in HTTP fallback mode. 344 // Format: HTTP status code and its corresponding message + content-type error message. 345 func (d *decodeState) constructHTTPErrMsg() string { 346 var errMsgs []string 347 348 if d.data.httpStatus == nil { 349 errMsgs = append(errMsgs, "malformed header: missing HTTP status") 350 } else { 351 errMsgs = append(errMsgs, fmt.Sprintf("%s: HTTP status code %d", http.StatusText(*(d.data.httpStatus)), *d.data.httpStatus)) 352 } 353 354 if d.data.contentTypeErr == "" { 355 errMsgs = append(errMsgs, "transport: missing content-type field") 356 } else { 357 errMsgs = append(errMsgs, d.data.contentTypeErr) 358 } 359 360 return strings.Join(errMsgs, "; ") 361 } 362 363 func (d *decodeState) addMetadata(k, v string) { 364 if d.data.mdata == nil { 365 d.data.mdata = make(map[string][]string) 366 } 367 d.data.mdata[k] = append(d.data.mdata[k], v) 368 } 369 370 func (d *decodeState) processHeaderField(f hpack.HeaderField) { 371 switch f.Name { 372 case "content-type": 373 contentSubtype, validContentType := contentSubtype(f.Value) 374 if !validContentType { 375 d.data.contentTypeErr = fmt.Sprintf("transport: received the unexpected content-type %q", f.Value) 376 return 377 } 378 d.data.contentSubtype = contentSubtype 379 // TODO: do we want to propagate the whole content-type in the metadata, 380 // or come up with a way to just propagate the content-subtype if it was set? 381 // ie {"content-type": "application/grpc+proto"} or {"content-subtype": "proto"} 382 // in the metadata? 383 d.addMetadata(f.Name, f.Value) 384 d.data.isGRPC = true 385 case "grpc-encoding": 386 d.data.encoding = f.Value 387 case "grpc-accept-encoding": 388 d.data.acceptEncoding = f.Value 389 case "grpc-status": 390 code, err := strconv.Atoi(f.Value) 391 if err != nil { 392 d.data.grpcErr = status.Errorf(codes.Internal, "transport: malformed grpc-status: %v", err) 393 return 394 } 395 d.data.rawStatusCode = &code 396 case "grpc-message": 397 d.data.rawStatusMsg = decodeGrpcMessage(f.Value) 398 case "biz-status": 399 code, err := strconv.Atoi(f.Value) 400 if err != nil { 401 d.data.grpcErr = status.Errorf(codes.Internal, "transport: malformed biz-status: %v", err) 402 return 403 } 404 d.data.bizStatusCode = &code 405 case "biz-extra": 406 extra, err := utils.JSONStr2Map(f.Value) 407 if err != nil { 408 d.data.grpcErr = status.Errorf(codes.Internal, "transport: malformed biz-extra: %v", err) 409 return 410 } 411 d.data.bizStatusExtra = extra 412 case "grpc-status-details-bin": 413 v, err := decodeBinHeader(f.Value) 414 if err != nil { 415 d.data.grpcErr = status.Errorf(codes.Internal, "transport: malformed grpc-status-details-bin: %v", err) 416 return 417 } 418 s := &spb.Status{} 419 if err := proto.Unmarshal(v, s); err != nil { 420 d.data.grpcErr = status.Errorf(codes.Internal, "transport: malformed grpc-status-details-bin: %v", err) 421 return 422 } 423 d.data.statusGen = status.FromProto(s) 424 case "grpc-timeout": 425 d.data.timeoutSet = true 426 var err error 427 if d.data.timeout, err = decodeTimeout(f.Value); err != nil { 428 d.data.grpcErr = status.Errorf(codes.Internal, "transport: malformed time-out: %v", err) 429 } 430 case ":path": 431 d.data.method = f.Value 432 case ":status": 433 code, err := strconv.Atoi(f.Value) 434 if err != nil { 435 d.data.httpErr = status.Errorf(codes.Internal, "transport: malformed http-status: %v", err) 436 return 437 } 438 d.data.httpStatus = &code 439 case "grpc-tags-bin": 440 v, err := decodeBinHeader(f.Value) 441 if err != nil { 442 d.data.grpcErr = status.Errorf(codes.Internal, "transport: malformed grpc-tags-bin: %v", err) 443 return 444 } 445 d.data.statsTags = v 446 d.addMetadata(f.Name, string(v)) 447 case "grpc-trace-bin": 448 v, err := decodeBinHeader(f.Value) 449 if err != nil { 450 d.data.grpcErr = status.Errorf(codes.Internal, "transport: malformed grpc-trace-bin: %v", err) 451 return 452 } 453 d.data.statsTrace = v 454 d.addMetadata(f.Name, string(v)) 455 default: 456 if isReservedHeader(f.Name) && !isWhitelistedHeader(f.Name) { 457 break 458 } 459 v, err := decodeMetadataHeader(f.Name, f.Value) 460 if err != nil { 461 klog.Errorf("Failed to decode metadata header (%q, %q): %v", f.Name, f.Value, err) 462 return 463 } 464 d.addMetadata(f.Name, v) 465 } 466 } 467 468 type timeoutUnit uint8 469 470 const ( 471 hour timeoutUnit = 'H' 472 minute timeoutUnit = 'M' 473 second timeoutUnit = 'S' 474 millisecond timeoutUnit = 'm' 475 microsecond timeoutUnit = 'u' 476 nanosecond timeoutUnit = 'n' 477 ) 478 479 func timeoutUnitToDuration(u timeoutUnit) (d time.Duration, ok bool) { 480 switch u { 481 case hour: 482 return time.Hour, true 483 case minute: 484 return time.Minute, true 485 case second: 486 return time.Second, true 487 case millisecond: 488 return time.Millisecond, true 489 case microsecond: 490 return time.Microsecond, true 491 case nanosecond: 492 return time.Nanosecond, true 493 default: 494 } 495 return 496 } 497 498 const maxTimeoutValue int64 = 100000000 - 1 499 500 // div does integer division and round-up the result. Note that this is 501 // equivalent to (d+r-1)/r but has less chance to overflow. 502 func div(d, r time.Duration) int64 { 503 if m := d % r; m > 0 { 504 return int64(d/r + 1) 505 } 506 return int64(d / r) 507 } 508 509 // TODO(zhaoq): It is the simplistic and not bandwidth efficient. Improve it. 510 func encodeTimeout(t time.Duration) string { 511 if t <= 0 { 512 return "0n" 513 } 514 if d := div(t, time.Nanosecond); d <= maxTimeoutValue { 515 return strconv.FormatInt(d, 10) + "n" 516 } 517 if d := div(t, time.Microsecond); d <= maxTimeoutValue { 518 return strconv.FormatInt(d, 10) + "u" 519 } 520 if d := div(t, time.Millisecond); d <= maxTimeoutValue { 521 return strconv.FormatInt(d, 10) + "m" 522 } 523 if d := div(t, time.Second); d <= maxTimeoutValue { 524 return strconv.FormatInt(d, 10) + "S" 525 } 526 if d := div(t, time.Minute); d <= maxTimeoutValue { 527 return strconv.FormatInt(d, 10) + "M" 528 } 529 // Note that maxTimeoutValue * time.Hour > MaxInt64. 530 return strconv.FormatInt(div(t, time.Hour), 10) + "H" 531 } 532 533 func decodeTimeout(s string) (time.Duration, error) { 534 size := len(s) 535 if size < 2 { 536 return 0, fmt.Errorf("transport: timeout string is too short: %q", s) 537 } 538 if size > 9 { 539 // Spec allows for 8 digits plus the unit. 540 return 0, fmt.Errorf("transport: timeout string is too long: %q", s) 541 } 542 unit := timeoutUnit(s[size-1]) 543 d, ok := timeoutUnitToDuration(unit) 544 if !ok { 545 return 0, fmt.Errorf("transport: timeout unit is not recognized: %q", s) 546 } 547 t, err := strconv.ParseInt(s[:size-1], 10, 64) 548 if err != nil { 549 return 0, err 550 } 551 const maxHours = math.MaxInt64 / int64(time.Hour) 552 if d == time.Hour && t > maxHours { 553 // This timeout would overflow math.MaxInt64; clamp it. 554 return time.Duration(math.MaxInt64), nil 555 } 556 return d * time.Duration(t), nil 557 } 558 559 const ( 560 spaceByte = ' ' 561 tildeByte = '~' 562 percentByte = '%' 563 ) 564 565 // encodeGrpcMessage is used to encode status code in header field 566 // "grpc-message". It does percent encoding and also replaces invalid utf-8 567 // characters with Unicode replacement character. 568 // 569 // It checks to see if each individual byte in msg is an allowable byte, and 570 // then either percent encoding or passing it through. When percent encoding, 571 // the byte is converted into hexadecimal notation with a '%' prepended. 572 func encodeGrpcMessage(msg string) string { 573 if msg == "" { 574 return "" 575 } 576 lenMsg := len(msg) 577 for i := 0; i < lenMsg; i++ { 578 c := msg[i] 579 if !(c >= spaceByte && c <= tildeByte && c != percentByte) { 580 return encodeGrpcMessageUnchecked(msg) 581 } 582 } 583 return msg 584 } 585 586 func encodeGrpcMessageUnchecked(msg string) string { 587 var buf bytes.Buffer 588 for len(msg) > 0 { 589 r, size := utf8.DecodeRuneInString(msg) 590 for _, b := range []byte(string(r)) { 591 if size > 1 { 592 // If size > 1, r is not ascii. Always do percent encoding. 593 buf.WriteString(fmt.Sprintf("%%%02X", b)) 594 continue 595 } 596 597 // The for loop is necessary even if size == 1. r could be 598 // utf8.RuneError. 599 // 600 // fmt.Sprintf("%%%02X", utf8.RuneError) gives "%FFFD". 601 if b >= spaceByte && b <= tildeByte && b != percentByte { 602 buf.WriteByte(b) 603 } else { 604 buf.WriteString(fmt.Sprintf("%%%02X", b)) 605 } 606 } 607 msg = msg[size:] 608 } 609 return buf.String() 610 } 611 612 // decodeGrpcMessage decodes the msg encoded by encodeGrpcMessage. 613 func decodeGrpcMessage(msg string) string { 614 if msg == "" { 615 return "" 616 } 617 lenMsg := len(msg) 618 for i := 0; i < lenMsg; i++ { 619 if msg[i] == percentByte && i+2 < lenMsg { 620 return decodeGrpcMessageUnchecked(msg) 621 } 622 } 623 return msg 624 } 625 626 func decodeGrpcMessageUnchecked(msg string) string { 627 var buf bytes.Buffer 628 lenMsg := len(msg) 629 for i := 0; i < lenMsg; i++ { 630 c := msg[i] 631 if c == percentByte && i+2 < lenMsg { 632 parsed, err := strconv.ParseUint(msg[i+1:i+3], 16, 8) 633 if err != nil { 634 buf.WriteByte(c) 635 } else { 636 buf.WriteByte(byte(parsed)) 637 i += 2 638 } 639 } else { 640 buf.WriteByte(c) 641 } 642 } 643 return buf.String() 644 }