diff --git a/transport/grpc/headers.go b/transport/grpc/headers.go index ce8eae62f..86a2aef42 100644 --- a/transport/grpc/headers.go +++ b/transport/grpc/headers.go @@ -76,6 +76,10 @@ const ( // _applicationErrorDetailsHeader is the header for the the application error // meta details string. _applicationErrorDetailsHeader = "rpc-application-error-details" + // _routingRegionHeader is one of the cross-zone header for the region of the routing key. + _routingRegionHeader = "rpc-routing-region" + // _routingZoneHeader is one of the cross-zone header for the zone of the routing key. + _routingZoneHeader = "rpc-routing-zone" // ApplicationErrorHeaderValue is the value that will be set for // ApplicationErrorHeader is there was an application error. @@ -88,12 +92,24 @@ const ( contentTypeHeader = "content-type" ) +var ( + routingHeaders = map[string]bool{ + _routingZoneHeader: true, + _routingRegionHeader: true, + } +) + // TODO: there are way too many repeat calls to strings.ToLower // Note that these calls are done indirectly, primarily through // transport.CanonicalizeHeaderKey func isReserved(header string) bool { - return strings.HasPrefix(strings.ToLower(header), "rpc-") + header = transport.CanonicalizeHeaderKey(header) + // exempt routing headers from isReserved check + if routingHeaders[header] { + return false + } + return strings.HasPrefix(header, "rpc-") } // transportRequestToMetadata will populate all reserved and application headers @@ -131,6 +147,10 @@ func metadataToTransportRequest(md metadata.MD) (*transport.Request, error) { return nil, yarpcerrors.InvalidArgumentErrorf("header has more than one value: %s:%v", header, values) } header = transport.CanonicalizeHeaderKey(header) + // skip routing header + if routingHeaders[header] { + continue + } switch header { case CallerHeader: request.Caller = value diff --git a/transport/grpc/headers_test.go b/transport/grpc/headers_test.go index 30834b471..1c4e7421e 100644 --- a/transport/grpc/headers_test.go +++ b/transport/grpc/headers_test.go @@ -50,6 +50,8 @@ func TestMetadataToTransportRequest(t *testing.T) { CallerProcedureHeader, "example-caller-procedure", "foo", "bar", "baz", "bat", + _routingRegionHeader, "phx", + _routingZoneHeader, "phx98", ), TransportRequest: &transport.Request{ Caller: "example-caller", @@ -162,7 +164,7 @@ func TestTransportRequestToMetadata(t *testing.T) { }, }, { - Name: "Reserved header key in application headers", + Name: "Reserved header key in application headers - caller headers", MD: metadata.Pairs(), TransportRequest: &transport.Request{ Headers: transport.HeadersFromMap(map[string]string{ @@ -171,6 +173,16 @@ func TestTransportRequestToMetadata(t *testing.T) { }, Error: yarpcerrors.InvalidArgumentErrorf("cannot use reserved header in application headers: %s", CallerHeader), }, + { + Name: "Routing headers exempted", + MD: metadata.Pairs( + _routingZoneHeader, "example-zone"), + TransportRequest: &transport.Request{ + Headers: transport.HeadersFromMap(map[string]string{ + _routingZoneHeader: "example-zone", + }), + }, + }, } { t.Run(tt.Name, func(t *testing.T) { md, err := transportRequestToMetadata(tt.TransportRequest) @@ -203,6 +215,9 @@ func TestIsReserved(t *testing.T) { assert.True(t, isReserved(RoutingDelegateHeader)) assert.True(t, isReserved(EncodingHeader)) assert.True(t, isReserved("rpc-foo")) + for header := range routingHeaders { + assert.False(t, isReserved(header)) + } } func TestMDReadWriterDuplicateKey(t *testing.T) {