go.uber.org/yarpc@v1.72.1/transport/grpc/headers.go (about) 1 // Copyright (c) 2022 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package grpc 22 23 import ( 24 "strings" 25 26 "go.uber.org/multierr" 27 "go.uber.org/yarpc/api/transport" 28 "go.uber.org/yarpc/yarpcerrors" 29 "google.golang.org/grpc/metadata" 30 ) 31 32 const ( 33 // CallerHeader is the header key for the name of the service sending the 34 // request. This corresponds to the Request.Caller attribute. 35 // This header is required. 36 CallerHeader = "rpc-caller" 37 // CallerProcedureHeader is the header key for the name of the rpc procedure from the service sending the 38 // request. This corresponds to the Request.CallerProcedure attribute. 39 // This header is optional. 40 CallerProcedureHeader = "rpc-caller-procedure" 41 // ServiceHeader is the header key for the name of the service to which 42 // the request is being sent. This corresponds to the Request.Service attribute. 43 // This header is also used in responses to ensure requests are processed by the 44 // correct service. 45 // This header is required. 46 ServiceHeader = "rpc-service" 47 // ShardKeyHeader is the header key for the shard key used by the destined service 48 // to shard the request. This corresponds to the Request.ShardKey attribute. 49 // This header is optional. 50 ShardKeyHeader = "rpc-shard-key" 51 // RoutingKeyHeader is the header key for the traffic group responsible for 52 // handling the request. This corresponds to the Request.RoutingKey attribute. 53 // This header is optional. 54 RoutingKeyHeader = "rpc-routing-key" 55 // RoutingDelegateHeader is the header key for a service that can proxy the 56 // destined service. This corresponds to the Request.RoutingDelegate attribute. 57 // This header is optional. 58 RoutingDelegateHeader = "rpc-routing-delegate" 59 // EncodingHeader is the header key for the encoding used for the request body. 60 // This corresponds to the Request.Encoding attribute. 61 // If this is not set, content-type will attempt to be read for the encoding per 62 // the gRPC wire format http://www.grpc.io/docs/guides/wire.html 63 // For example, a content-type of "application/grpc+proto" will be intepreted 64 // as the proto encoding. 65 // This header is required unless content-type is set properly. 66 EncodingHeader = "rpc-encoding" 67 // ErrorNameHeader is the header key for the error name. 68 ErrorNameHeader = "rpc-error-name" 69 // ApplicationErrorHeader is the header key that will contain a non-empty value 70 // if there was an application error. 71 ApplicationErrorHeader = "rpc-application-error" 72 73 // _applicationErrorNameHeader is the header for the name of the application 74 // error. 75 _applicationErrorNameHeader = "rpc-application-error-name" 76 // _applicationErrorDetailsHeader is the header for the the application error 77 // meta details string. 78 _applicationErrorDetailsHeader = "rpc-application-error-details" 79 80 // ApplicationErrorHeaderValue is the value that will be set for 81 // ApplicationErrorHeader is there was an application error. 82 // 83 // The definition says any non-empty value is valid, however this is 84 // the specific value that will be used for now. 85 ApplicationErrorHeaderValue = "error" 86 87 baseContentType = "application/grpc" 88 contentTypeHeader = "content-type" 89 ) 90 91 // TODO: there are way too many repeat calls to strings.ToLower 92 // Note that these calls are done indirectly, primarily through 93 // transport.CanonicalizeHeaderKey 94 95 func isReserved(header string) bool { 96 return strings.HasPrefix(strings.ToLower(header), "rpc-") 97 } 98 99 // transportRequestToMetadata will populate all reserved and application headers 100 // from the Request into a new MD. 101 func transportRequestToMetadata(request *transport.Request) (metadata.MD, error) { 102 md := metadata.New(nil) 103 if err := multierr.Combine( 104 addToMetadata(md, CallerHeader, request.Caller), 105 addToMetadata(md, ServiceHeader, request.Service), 106 addToMetadata(md, ShardKeyHeader, request.ShardKey), 107 addToMetadata(md, RoutingKeyHeader, request.RoutingKey), 108 addToMetadata(md, RoutingDelegateHeader, request.RoutingDelegate), 109 addToMetadata(md, EncodingHeader, string(request.Encoding)), 110 addToMetadata(md, CallerProcedureHeader, request.CallerProcedure), 111 ); err != nil { 112 return md, err 113 } 114 return md, addApplicationHeaders(md, request.Headers) 115 } 116 117 // metadataToTransportRequest will populate the Request with all reserved and application 118 // headers into a new Request, only not setting the Body field. 119 func metadataToTransportRequest(md metadata.MD) (*transport.Request, error) { 120 request := &transport.Request{ 121 Headers: transport.NewHeadersWithCapacity(md.Len()), 122 } 123 for header, values := range md { 124 var value string 125 switch len(values) { 126 case 0: 127 continue 128 case 1: 129 value = values[0] 130 default: 131 return nil, yarpcerrors.InvalidArgumentErrorf("header has more than one value: %s:%v", header, values) 132 } 133 header = transport.CanonicalizeHeaderKey(header) 134 switch header { 135 case CallerHeader: 136 request.Caller = value 137 case ServiceHeader: 138 request.Service = value 139 case ShardKeyHeader: 140 request.ShardKey = value 141 case RoutingKeyHeader: 142 request.RoutingKey = value 143 case RoutingDelegateHeader: 144 request.RoutingDelegate = value 145 case EncodingHeader: 146 request.Encoding = transport.Encoding(value) 147 case CallerProcedureHeader: 148 request.CallerProcedure = value 149 case contentTypeHeader: 150 // if request.Encoding was set, do not parse content-type 151 // this results in EncodingHeader overriding content-type 152 if request.Encoding == "" { 153 request.Encoding = transport.Encoding(getContentSubtype(value)) 154 } 155 default: 156 request.Headers = request.Headers.With(header, value) 157 } 158 } 159 return request, nil 160 } 161 162 func metadataToApplicationErrorMeta(responseMD metadata.MD) *transport.ApplicationErrorMeta { 163 if responseMD == nil { 164 return nil 165 } 166 167 var details, name string 168 if header := responseMD[_applicationErrorDetailsHeader]; len(header) == 1 { 169 details = header[0] 170 } 171 if header := responseMD[_applicationErrorNameHeader]; len(header) == 1 { 172 name = header[0] 173 } 174 175 return &transport.ApplicationErrorMeta{ 176 Details: details, 177 Name: name, 178 // ignore Code, this should be derived from the error since codes are 179 // natively supported in gRPC and YARPC 180 Code: nil, 181 } 182 } 183 184 // addApplicationHeaders adds the headers to md. 185 func addApplicationHeaders(md metadata.MD, headers transport.Headers) error { 186 for header, value := range headers.Items() { 187 header = transport.CanonicalizeHeaderKey(header) 188 if isReserved(header) { 189 return yarpcerrors.InvalidArgumentErrorf("cannot use reserved header in application headers: %s", header) 190 } 191 if err := addToMetadata(md, header, value); err != nil { 192 return err 193 } 194 } 195 return nil 196 } 197 198 // getApplicationHeaders returns the headers from md without any reserved headers. 199 func getApplicationHeaders(md metadata.MD) (transport.Headers, error) { 200 if len(md) == 0 { 201 return transport.Headers{}, nil 202 } 203 headers := transport.NewHeadersWithCapacity(md.Len()) 204 for header, values := range md { 205 header = transport.CanonicalizeHeaderKey(header) 206 if isReserved(header) { 207 continue 208 } 209 var value string 210 switch len(values) { 211 case 0: 212 continue 213 case 1: 214 value = values[0] 215 default: 216 return headers, yarpcerrors.InvalidArgumentErrorf("header has more than one value: %s:%v", header, values) 217 } 218 headers = headers.With(header, value) 219 } 220 return headers, nil 221 } 222 223 // add to md 224 // return error if key already in md 225 func addToMetadata(md metadata.MD, key string, value string) error { 226 if value == "" { 227 return nil 228 } 229 if _, ok := md[key]; ok { 230 return yarpcerrors.InvalidArgumentErrorf("duplicate key: %s", key) 231 } 232 md[key] = []string{value} 233 return nil 234 } 235 236 // getContentSubtype attempts to get the content subtype. 237 // returns "" if no content subtype can be parsed. 238 func getContentSubtype(contentType string) string { 239 if !strings.HasPrefix(contentType, baseContentType) || len(contentType) == len(baseContentType) { 240 return "" 241 } 242 switch contentType[len(baseContentType)] { 243 case '+', ';': 244 return contentType[len(baseContentType)+1:] 245 default: 246 return "" 247 } 248 } 249 250 type mdReadWriter metadata.MD 251 252 // ForeachKey implements opentracing.TextMapReader. 253 func (md mdReadWriter) ForeachKey(handler func(string, string) error) error { 254 for key, values := range md { 255 for _, value := range values { 256 if err := handler(key, value); err != nil { 257 return err 258 } 259 } 260 } 261 return nil 262 } 263 264 // Set implements opentracing.TextMapWriter. 265 func (md mdReadWriter) Set(key string, value string) { 266 key = strings.ToLower(key) 267 md[key] = []string{value} 268 }