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  }