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