@@ -78,7 +78,10 @@ use tonic::{
7878 body:: Body ,
7979 client:: GrpcService ,
8080 codegen:: InterceptedService ,
81- metadata:: { MetadataKey , MetadataMap , MetadataValue } ,
81+ metadata:: {
82+ AsciiMetadataKey , AsciiMetadataValue , BinaryMetadataKey , BinaryMetadataValue , MetadataMap ,
83+ MetadataValue ,
84+ } ,
8285 service:: Interceptor ,
8386 transport:: { Certificate , Channel , Endpoint , Identity } ,
8487} ;
@@ -146,9 +149,20 @@ pub struct ClientOptions {
146149 pub keep_alive : Option < ClientKeepAliveConfig > ,
147150
148151 /// HTTP headers to include on every RPC call.
152+ ///
153+ /// These must be valid gRPC metadata keys, and must not be binary metadata keys (ending in
154+ /// `-bin). To set binary headers, use [ClientOptions::binary_headers]. Invalid header keys or
155+ /// values will cause an error to be returned when connecting.
149156 #[ builder( default ) ]
150157 pub headers : Option < HashMap < String , String > > ,
151158
159+ /// HTTP headers to include on every RPC call as binary gRPC metadata (encoded as base64).
160+ ///
161+ /// These must be valid binary gRPC metadata keys (and end with a `-bin` suffix). Invalid
162+ /// header keys will cause an error to be returned when connecting.
163+ #[ builder( default ) ]
164+ pub binary_headers : Option < HashMap < String , Vec < u8 > > > ,
165+
152166 /// API key which is set as the "Authorization" header with "Bearer " prepended. This will only
153167 /// be applied if the headers don't already have an "Authorization" header.
154168 #[ builder( default ) ]
@@ -322,6 +336,9 @@ pub enum ClientInitError {
322336 /// Invalid URI. Configuration error, fatal.
323337 #[ error( "Invalid URI: {0:?}" ) ]
324338 InvalidUri ( #[ from] InvalidUri ) ,
339+ /// Invalid gRPC metadata headers. Configuration error.
340+ #[ error( "Invalid headers: {0}" ) ]
341+ InvalidHeaders ( #[ from] InvalidHeaderError ) ,
325342 /// Server connection error. Crashing and restarting the worker is likely best.
326343 #[ error( "Server connection error: {0:?}" ) ]
327344 TonicTransportError ( #[ from] tonic:: transport:: Error ) ,
@@ -331,6 +348,37 @@ pub enum ClientInitError {
331348 SystemInfoCallError ( tonic:: Status ) ,
332349}
333350
351+ /// Errors thrown when a gRPC metadata header is invalid.
352+ #[ derive( thiserror:: Error , Debug ) ]
353+ pub enum InvalidHeaderError {
354+ /// A binary header key was invalid
355+ #[ error( "Invalid binary header key '{key}': {source}" ) ]
356+ InvalidBinaryHeaderKey {
357+ /// The invalid key
358+ key : String ,
359+ /// The source error from tonic
360+ source : tonic:: metadata:: errors:: InvalidMetadataKey ,
361+ } ,
362+ /// An ASCII header key was invalid
363+ #[ error( "Invalid ASCII header key '{key}': {source}" ) ]
364+ InvalidAsciiHeaderKey {
365+ /// The invalid key
366+ key : String ,
367+ /// The source error from tonic
368+ source : tonic:: metadata:: errors:: InvalidMetadataKey ,
369+ } ,
370+ /// An ASCII header value was invalid
371+ #[ error( "Invalid ASCII header value for key '{key}': {source}" ) ]
372+ InvalidAsciiHeaderValue {
373+ /// The key
374+ key : String ,
375+ /// The invalid value
376+ value : String ,
377+ /// The source error from tonic
378+ source : tonic:: metadata:: errors:: InvalidMetadataValue ,
379+ } ,
380+ }
381+
334382/// A client with [ClientOptions] attached, which can be passed to initialize workers,
335383/// or can be used directly. Is cheap to clone.
336384#[ derive( Clone , Debug ) ]
@@ -344,9 +392,33 @@ pub struct ConfiguredClient<C> {
344392}
345393
346394impl < C > ConfiguredClient < C > {
347- /// Set HTTP request headers overwriting previous headers
348- pub fn set_headers ( & self , headers : HashMap < String , String > ) {
349- self . headers . write ( ) . user_headers = headers;
395+ /// Set HTTP request headers overwriting previous headers.
396+ ///
397+ /// This will not affect headers set via [ClientOptions::binary_headers].
398+ ///
399+ /// # Errors
400+ ///
401+ /// Will return an error if any of the provided keys or values are not valid gRPC metadata.
402+ /// If an error is returned, the previous headers will remain unchanged.
403+ pub fn set_headers ( & self , headers : HashMap < String , String > ) -> Result < ( ) , InvalidHeaderError > {
404+ self . headers . write ( ) . user_headers = parse_ascii_headers ( headers) ?;
405+ Ok ( ( ) )
406+ }
407+
408+ /// Set binary HTTP request headers overwriting previous headers.
409+ ///
410+ /// This will not affect headers set via [ClientOptions::headers].
411+ ///
412+ /// # Errors
413+ ///
414+ /// Will return an error if any of the provided keys are not valid gRPC binary metadata keys.
415+ /// If an error is returned, the previous headers will remain unchanged.
416+ pub fn set_binary_headers (
417+ & self ,
418+ binary_headers : HashMap < String , Vec < u8 > > ,
419+ ) -> Result < ( ) , InvalidHeaderError > {
420+ self . headers . write ( ) . user_binary_headers = parse_binary_headers ( binary_headers) ?;
421+ Ok ( ( ) )
350422 }
351423
352424 /// Set API key, overwriting previous
@@ -373,7 +445,8 @@ impl<C> ConfiguredClient<C> {
373445
374446#[ derive( Debug ) ]
375447struct ClientHeaders {
376- user_headers : HashMap < String , String > ,
448+ user_headers : HashMap < AsciiMetadataKey , AsciiMetadataValue > ,
449+ user_binary_headers : HashMap < BinaryMetadataKey , BinaryMetadataValue > ,
377450 api_key : Option < String > ,
378451}
379452
@@ -382,10 +455,13 @@ impl ClientHeaders {
382455 for ( key, val) in self . user_headers . iter ( ) {
383456 // Only if not already present
384457 if !metadata. contains_key ( key) {
385- // Ignore invalid keys/values
386- if let ( Ok ( key) , Ok ( val) ) = ( MetadataKey :: from_str ( key) , val. parse ( ) ) {
387- metadata. insert ( key, val) ;
388- }
458+ metadata. insert ( key, val. clone ( ) ) ;
459+ }
460+ }
461+ for ( key, val) in self . user_binary_headers . iter ( ) {
462+ // Only if not already present
463+ if !metadata. contains_key ( key) {
464+ metadata. insert_bin ( key, val. clone ( ) ) ;
389465 }
390466 }
391467 if let Some ( api_key) = & self . api_key {
@@ -491,7 +567,10 @@ impl ClientOptions {
491567 } ;
492568
493569 let headers = Arc :: new ( RwLock :: new ( ClientHeaders {
494- user_headers : self . headers . clone ( ) . unwrap_or_default ( ) ,
570+ user_headers : parse_ascii_headers ( self . headers . clone ( ) . unwrap_or_default ( ) ) ?,
571+ user_binary_headers : parse_binary_headers (
572+ self . binary_headers . clone ( ) . unwrap_or_default ( ) ,
573+ ) ?,
495574 api_key : self . api_key . clone ( ) ,
496575 } ) ) ;
497576 let interceptor = ServiceCallInterceptor {
@@ -558,6 +637,57 @@ impl ClientOptions {
558637 }
559638}
560639
640+ fn parse_ascii_headers (
641+ headers : HashMap < String , String > ,
642+ ) -> Result < HashMap < AsciiMetadataKey , AsciiMetadataValue > , InvalidHeaderError > {
643+ let mut parsed_headers = HashMap :: with_capacity ( headers. len ( ) ) ;
644+ for ( k, v) in headers. into_iter ( ) {
645+ let key = match AsciiMetadataKey :: from_str ( & k) {
646+ Ok ( key) => key,
647+ Err ( err) => {
648+ return Err ( InvalidHeaderError :: InvalidAsciiHeaderKey {
649+ key : k,
650+ source : err,
651+ } ) ;
652+ }
653+ } ;
654+ let value = match MetadataValue :: from_str ( & v) {
655+ Ok ( value) => value,
656+ Err ( err) => {
657+ return Err ( InvalidHeaderError :: InvalidAsciiHeaderValue {
658+ key : k,
659+ value : v,
660+ source : err,
661+ } ) ;
662+ }
663+ } ;
664+ parsed_headers. insert ( key, value) ;
665+ }
666+
667+ Ok ( parsed_headers)
668+ }
669+
670+ fn parse_binary_headers (
671+ headers : HashMap < String , Vec < u8 > > ,
672+ ) -> Result < HashMap < BinaryMetadataKey , BinaryMetadataValue > , InvalidHeaderError > {
673+ let mut parsed_headers = HashMap :: with_capacity ( headers. len ( ) ) ;
674+ for ( k, v) in headers. into_iter ( ) {
675+ let key = match BinaryMetadataKey :: from_str ( & k) {
676+ Ok ( key) => key,
677+ Err ( err) => {
678+ return Err ( InvalidHeaderError :: InvalidBinaryHeaderKey {
679+ key : k,
680+ source : err,
681+ } ) ;
682+ }
683+ } ;
684+ let value = BinaryMetadataValue :: from_bytes ( & v) ;
685+ parsed_headers. insert ( key, value) ;
686+ }
687+
688+ Ok ( parsed_headers)
689+ }
690+
561691/// Interceptor which attaches common metadata (like "client-name") to every outgoing call
562692#[ derive( Clone ) ]
563693pub struct ServiceCallInterceptor {
@@ -1770,13 +1900,17 @@ mod tests {
17701900 // Initial header set
17711901 let headers = Arc :: new ( RwLock :: new ( ClientHeaders {
17721902 user_headers : HashMap :: new ( ) ,
1903+ user_binary_headers : HashMap :: new ( ) ,
17731904 api_key : Some ( "my-api-key" . to_owned ( ) ) ,
17741905 } ) ) ;
1775- headers
1776- . clone ( )
1777- . write ( )
1778- . user_headers
1779- . insert ( "my-meta-key" . to_owned ( ) , "my-meta-val" . to_owned ( ) ) ;
1906+ headers. clone ( ) . write ( ) . user_headers . insert (
1907+ "my-meta-key" . parse ( ) . unwrap ( ) ,
1908+ "my-meta-val" . parse ( ) . unwrap ( ) ,
1909+ ) ;
1910+ headers. clone ( ) . write ( ) . user_binary_headers . insert (
1911+ "my-bin-meta-key-bin" . parse ( ) . unwrap ( ) ,
1912+ vec ! [ 1 , 2 , 3 ] . try_into ( ) . unwrap ( ) ,
1913+ ) ;
17801914 let mut interceptor = ServiceCallInterceptor {
17811915 opts,
17821916 headers : headers. clone ( ) ,
@@ -1789,33 +1923,44 @@ mod tests {
17891923 req. metadata( ) . get( "authorization" ) . unwrap( ) ,
17901924 "Bearer my-api-key"
17911925 ) ;
1926+ assert_eq ! (
1927+ req. metadata( ) . get_bin( "my-bin-meta-key-bin" ) . unwrap( ) ,
1928+ vec![ 1 , 2 , 3 ] . as_slice( )
1929+ ) ;
17921930
17931931 // Overwrite at request time
17941932 let mut req = tonic:: Request :: new ( ( ) ) ;
17951933 req. metadata_mut ( )
17961934 . insert ( "my-meta-key" , "my-meta-val2" . parse ( ) . unwrap ( ) ) ;
17971935 req. metadata_mut ( )
17981936 . insert ( "authorization" , "my-api-key2" . parse ( ) . unwrap ( ) ) ;
1937+ req. metadata_mut ( )
1938+ . insert_bin ( "my-bin-meta-key-bin" , vec ! [ 4 , 5 , 6 ] . try_into ( ) . unwrap ( ) ) ;
17991939 let req = interceptor. call ( req) . unwrap ( ) ;
18001940 assert_eq ! ( req. metadata( ) . get( "my-meta-key" ) . unwrap( ) , "my-meta-val2" ) ;
18011941 assert_eq ! ( req. metadata( ) . get( "authorization" ) . unwrap( ) , "my-api-key2" ) ;
1942+ assert_eq ! (
1943+ req. metadata( ) . get_bin( "my-bin-meta-key-bin" ) . unwrap( ) ,
1944+ vec![ 4 , 5 , 6 ] . as_slice( )
1945+ ) ;
18021946
18031947 // Overwrite auth on header
1804- headers
1805- . clone ( )
1806- . write ( )
1807- . user_headers
1808- . insert ( "authorization" . to_owned ( ) , "my-api-key3" . to_owned ( ) ) ;
1948+ headers. clone ( ) . write ( ) . user_headers . insert (
1949+ "authorization" . parse ( ) . unwrap ( ) ,
1950+ "my-api-key3" . parse ( ) . unwrap ( ) ,
1951+ ) ;
18091952 let req = interceptor. call ( tonic:: Request :: new ( ( ) ) ) . unwrap ( ) ;
18101953 assert_eq ! ( req. metadata( ) . get( "my-meta-key" ) . unwrap( ) , "my-meta-val" ) ;
18111954 assert_eq ! ( req. metadata( ) . get( "authorization" ) . unwrap( ) , "my-api-key3" ) ;
18121955
18131956 // Remove headers and auth and confirm gone
18141957 headers. clone ( ) . write ( ) . user_headers . clear ( ) ;
1958+ headers. clone ( ) . write ( ) . user_binary_headers . clear ( ) ;
18151959 headers. clone ( ) . write ( ) . api_key . take ( ) ;
18161960 let req = interceptor. call ( tonic:: Request :: new ( ( ) ) ) . unwrap ( ) ;
18171961 assert ! ( !req. metadata( ) . contains_key( "my-meta-key" ) ) ;
18181962 assert ! ( !req. metadata( ) . contains_key( "authorization" ) ) ;
1963+ assert ! ( !req. metadata( ) . contains_key( "my-bin-meta-key-bin" ) ) ;
18191964
18201965 // Timeout header not overriden
18211966 let mut req = tonic:: Request :: new ( ( ) ) ;
@@ -1828,6 +1973,55 @@ mod tests {
18281973 ) ;
18291974 }
18301975
1976+ #[ test]
1977+ fn invalid_ascii_header_key ( ) {
1978+ let invalid_headers = {
1979+ let mut h = HashMap :: new ( ) ;
1980+ h. insert ( "x-binary-key-bin" . to_owned ( ) , "value" . to_owned ( ) ) ;
1981+ h
1982+ } ;
1983+
1984+ let result = parse_ascii_headers ( invalid_headers) ;
1985+ assert ! ( result. is_err( ) ) ;
1986+ assert_eq ! (
1987+ result. err( ) . unwrap( ) . to_string( ) ,
1988+ "Invalid ASCII header key 'x-binary-key-bin': invalid gRPC metadata key name"
1989+ ) ;
1990+ }
1991+
1992+ #[ test]
1993+ fn invalid_ascii_header_value ( ) {
1994+ let invalid_headers = {
1995+ let mut h = HashMap :: new ( ) ;
1996+ // Nul bytes are valid UTF-8, but not valid ascii gRPC headers:
1997+ h. insert ( "x-ascii-key" . to_owned ( ) , "\x00 value" . to_owned ( ) ) ;
1998+ h
1999+ } ;
2000+
2001+ let result = parse_ascii_headers ( invalid_headers) ;
2002+ assert ! ( result. is_err( ) ) ;
2003+ assert_eq ! (
2004+ result. err( ) . unwrap( ) . to_string( ) ,
2005+ "Invalid ASCII header value for key 'x-ascii-key': failed to parse metadata value"
2006+ ) ;
2007+ }
2008+
2009+ #[ test]
2010+ fn invalid_binary_header_key ( ) {
2011+ let invalid_headers = {
2012+ let mut h = HashMap :: new ( ) ;
2013+ h. insert ( "x-ascii-key" . to_owned ( ) , vec ! [ 1 , 2 , 3 ] ) ;
2014+ h
2015+ } ;
2016+
2017+ let result = parse_binary_headers ( invalid_headers) ;
2018+ assert ! ( result. is_err( ) ) ;
2019+ assert_eq ! (
2020+ result. err( ) . unwrap( ) . to_string( ) ,
2021+ "Invalid binary header key 'x-ascii-key': invalid gRPC metadata key name"
2022+ ) ;
2023+ }
2024+
18312025 #[ test]
18322026 fn keep_alive_defaults ( ) {
18332027 let mut builder = ClientOptionsBuilder :: default ( ) ;
0 commit comments