context.go (9325B)
1 package runtime 2 3 import ( 4 "context" 5 "encoding/base64" 6 "fmt" 7 "net" 8 "net/http" 9 "net/textproto" 10 "strconv" 11 "strings" 12 "sync" 13 "time" 14 15 "google.golang.org/grpc/codes" 16 "google.golang.org/grpc/metadata" 17 "google.golang.org/grpc/status" 18 ) 19 20 // MetadataHeaderPrefix is the http prefix that represents custom metadata 21 // parameters to or from a gRPC call. 22 const MetadataHeaderPrefix = "Grpc-Metadata-" 23 24 // MetadataPrefix is prepended to permanent HTTP header keys (as specified 25 // by the IANA) when added to the gRPC context. 26 const MetadataPrefix = "grpcgateway-" 27 28 // MetadataTrailerPrefix is prepended to gRPC metadata as it is converted to 29 // HTTP headers in a response handled by grpc-gateway 30 const MetadataTrailerPrefix = "Grpc-Trailer-" 31 32 const metadataGrpcTimeout = "Grpc-Timeout" 33 const metadataHeaderBinarySuffix = "-Bin" 34 35 const xForwardedFor = "X-Forwarded-For" 36 const xForwardedHost = "X-Forwarded-Host" 37 38 var ( 39 // DefaultContextTimeout is used for gRPC call context.WithTimeout whenever a Grpc-Timeout inbound 40 // header isn't present. If the value is 0 the sent `context` will not have a timeout. 41 DefaultContextTimeout = 0 * time.Second 42 ) 43 44 type ( 45 rpcMethodKey struct{} 46 httpPathPatternKey struct{} 47 48 AnnotateContextOption func(ctx context.Context) context.Context 49 ) 50 51 func WithHTTPPathPattern(pattern string) AnnotateContextOption { 52 return func(ctx context.Context) context.Context { 53 return withHTTPPathPattern(ctx, pattern) 54 } 55 } 56 57 func decodeBinHeader(v string) ([]byte, error) { 58 if len(v)%4 == 0 { 59 // Input was padded, or padding was not necessary. 60 return base64.StdEncoding.DecodeString(v) 61 } 62 return base64.RawStdEncoding.DecodeString(v) 63 } 64 65 /* 66 AnnotateContext adds context information such as metadata from the request. 67 68 At a minimum, the RemoteAddr is included in the fashion of "X-Forwarded-For", 69 except that the forwarded destination is not another HTTP service but rather 70 a gRPC service. 71 */ 72 func AnnotateContext(ctx context.Context, mux *ServeMux, req *http.Request, rpcMethodName string, options ...AnnotateContextOption) (context.Context, error) { 73 ctx, md, err := annotateContext(ctx, mux, req, rpcMethodName, options...) 74 if err != nil { 75 return nil, err 76 } 77 if md == nil { 78 return ctx, nil 79 } 80 81 return metadata.NewOutgoingContext(ctx, md), nil 82 } 83 84 // AnnotateIncomingContext adds context information such as metadata from the request. 85 // Attach metadata as incoming context. 86 func AnnotateIncomingContext(ctx context.Context, mux *ServeMux, req *http.Request, rpcMethodName string, options ...AnnotateContextOption) (context.Context, error) { 87 ctx, md, err := annotateContext(ctx, mux, req, rpcMethodName, options...) 88 if err != nil { 89 return nil, err 90 } 91 if md == nil { 92 return ctx, nil 93 } 94 95 return metadata.NewIncomingContext(ctx, md), nil 96 } 97 98 func annotateContext(ctx context.Context, mux *ServeMux, req *http.Request, rpcMethodName string, options ...AnnotateContextOption) (context.Context, metadata.MD, error) { 99 ctx = withRPCMethod(ctx, rpcMethodName) 100 for _, o := range options { 101 ctx = o(ctx) 102 } 103 var pairs []string 104 timeout := DefaultContextTimeout 105 if tm := req.Header.Get(metadataGrpcTimeout); tm != "" { 106 var err error 107 timeout, err = timeoutDecode(tm) 108 if err != nil { 109 return nil, nil, status.Errorf(codes.InvalidArgument, "invalid grpc-timeout: %s", tm) 110 } 111 } 112 113 for key, vals := range req.Header { 114 key = textproto.CanonicalMIMEHeaderKey(key) 115 for _, val := range vals { 116 // For backwards-compatibility, pass through 'authorization' header with no prefix. 117 if key == "Authorization" { 118 pairs = append(pairs, "authorization", val) 119 } 120 if h, ok := mux.incomingHeaderMatcher(key); ok { 121 // Handles "-bin" metadata in grpc, since grpc will do another base64 122 // encode before sending to server, we need to decode it first. 123 if strings.HasSuffix(key, metadataHeaderBinarySuffix) { 124 b, err := decodeBinHeader(val) 125 if err != nil { 126 return nil, nil, status.Errorf(codes.InvalidArgument, "invalid binary header %s: %s", key, err) 127 } 128 129 val = string(b) 130 } 131 pairs = append(pairs, h, val) 132 } 133 } 134 } 135 if host := req.Header.Get(xForwardedHost); host != "" { 136 pairs = append(pairs, strings.ToLower(xForwardedHost), host) 137 } else if req.Host != "" { 138 pairs = append(pairs, strings.ToLower(xForwardedHost), req.Host) 139 } 140 141 if addr := req.RemoteAddr; addr != "" { 142 if remoteIP, _, err := net.SplitHostPort(addr); err == nil { 143 if fwd := req.Header.Get(xForwardedFor); fwd == "" { 144 pairs = append(pairs, strings.ToLower(xForwardedFor), remoteIP) 145 } else { 146 pairs = append(pairs, strings.ToLower(xForwardedFor), fmt.Sprintf("%s, %s", fwd, remoteIP)) 147 } 148 } 149 } 150 151 if timeout != 0 { 152 //nolint:govet // The context outlives this function 153 ctx, _ = context.WithTimeout(ctx, timeout) 154 } 155 if len(pairs) == 0 { 156 return ctx, nil, nil 157 } 158 md := metadata.Pairs(pairs...) 159 for _, mda := range mux.metadataAnnotators { 160 md = metadata.Join(md, mda(ctx, req)) 161 } 162 return ctx, md, nil 163 } 164 165 // ServerMetadata consists of metadata sent from gRPC server. 166 type ServerMetadata struct { 167 HeaderMD metadata.MD 168 TrailerMD metadata.MD 169 } 170 171 type serverMetadataKey struct{} 172 173 // NewServerMetadataContext creates a new context with ServerMetadata 174 func NewServerMetadataContext(ctx context.Context, md ServerMetadata) context.Context { 175 return context.WithValue(ctx, serverMetadataKey{}, md) 176 } 177 178 // ServerMetadataFromContext returns the ServerMetadata in ctx 179 func ServerMetadataFromContext(ctx context.Context) (md ServerMetadata, ok bool) { 180 md, ok = ctx.Value(serverMetadataKey{}).(ServerMetadata) 181 return 182 } 183 184 // ServerTransportStream implements grpc.ServerTransportStream. 185 // It should only be used by the generated files to support grpc.SendHeader 186 // outside of gRPC server use. 187 type ServerTransportStream struct { 188 mu sync.Mutex 189 header metadata.MD 190 trailer metadata.MD 191 } 192 193 // Method returns the method for the stream. 194 func (s *ServerTransportStream) Method() string { 195 return "" 196 } 197 198 // Header returns the header metadata of the stream. 199 func (s *ServerTransportStream) Header() metadata.MD { 200 s.mu.Lock() 201 defer s.mu.Unlock() 202 return s.header.Copy() 203 } 204 205 // SetHeader sets the header metadata. 206 func (s *ServerTransportStream) SetHeader(md metadata.MD) error { 207 if md.Len() == 0 { 208 return nil 209 } 210 211 s.mu.Lock() 212 s.header = metadata.Join(s.header, md) 213 s.mu.Unlock() 214 return nil 215 } 216 217 // SendHeader sets the header metadata. 218 func (s *ServerTransportStream) SendHeader(md metadata.MD) error { 219 return s.SetHeader(md) 220 } 221 222 // Trailer returns the cached trailer metadata. 223 func (s *ServerTransportStream) Trailer() metadata.MD { 224 s.mu.Lock() 225 defer s.mu.Unlock() 226 return s.trailer.Copy() 227 } 228 229 // SetTrailer sets the trailer metadata. 230 func (s *ServerTransportStream) SetTrailer(md metadata.MD) error { 231 if md.Len() == 0 { 232 return nil 233 } 234 235 s.mu.Lock() 236 s.trailer = metadata.Join(s.trailer, md) 237 s.mu.Unlock() 238 return nil 239 } 240 241 func timeoutDecode(s string) (time.Duration, error) { 242 size := len(s) 243 if size < 2 { 244 return 0, fmt.Errorf("timeout string is too short: %q", s) 245 } 246 d, ok := timeoutUnitToDuration(s[size-1]) 247 if !ok { 248 return 0, fmt.Errorf("timeout unit is not recognized: %q", s) 249 } 250 t, err := strconv.ParseInt(s[:size-1], 10, 64) 251 if err != nil { 252 return 0, err 253 } 254 return d * time.Duration(t), nil 255 } 256 257 func timeoutUnitToDuration(u uint8) (d time.Duration, ok bool) { 258 switch u { 259 case 'H': 260 return time.Hour, true 261 case 'M': 262 return time.Minute, true 263 case 'S': 264 return time.Second, true 265 case 'm': 266 return time.Millisecond, true 267 case 'u': 268 return time.Microsecond, true 269 case 'n': 270 return time.Nanosecond, true 271 default: 272 } 273 return 274 } 275 276 // isPermanentHTTPHeader checks whether hdr belongs to the list of 277 // permanent request headers maintained by IANA. 278 // http://www.iana.org/assignments/message-headers/message-headers.xml 279 func isPermanentHTTPHeader(hdr string) bool { 280 switch hdr { 281 case 282 "Accept", 283 "Accept-Charset", 284 "Accept-Language", 285 "Accept-Ranges", 286 "Authorization", 287 "Cache-Control", 288 "Content-Type", 289 "Cookie", 290 "Date", 291 "Expect", 292 "From", 293 "Host", 294 "If-Match", 295 "If-Modified-Since", 296 "If-None-Match", 297 "If-Schedule-Tag-Match", 298 "If-Unmodified-Since", 299 "Max-Forwards", 300 "Origin", 301 "Pragma", 302 "Referer", 303 "User-Agent", 304 "Via", 305 "Warning": 306 return true 307 } 308 return false 309 } 310 311 // RPCMethod returns the method string for the server context. The returned 312 // string is in the format of "/package.service/method". 313 func RPCMethod(ctx context.Context) (string, bool) { 314 m := ctx.Value(rpcMethodKey{}) 315 if m == nil { 316 return "", false 317 } 318 ms, ok := m.(string) 319 if !ok { 320 return "", false 321 } 322 return ms, true 323 } 324 325 func withRPCMethod(ctx context.Context, rpcMethodName string) context.Context { 326 return context.WithValue(ctx, rpcMethodKey{}, rpcMethodName) 327 } 328 329 // HTTPPathPattern returns the HTTP path pattern string relating to the HTTP handler, if one exists. 330 // The format of the returned string is defined by the google.api.http path template type. 331 func HTTPPathPattern(ctx context.Context) (string, bool) { 332 m := ctx.Value(httpPathPatternKey{}) 333 if m == nil { 334 return "", false 335 } 336 ms, ok := m.(string) 337 if !ok { 338 return "", false 339 } 340 return ms, true 341 } 342 343 func withHTTPPathPattern(ctx context.Context, httpPathPattern string) context.Context { 344 return context.WithValue(ctx, httpPathPatternKey{}, httpPathPattern) 345 }