@@ -37,6 +37,7 @@ use crate::{
3737} ;
3838use base64:: write:: EncoderWriter as Base64Encoder ;
3939use bytes:: BytesMut ;
40+ use lazy_static:: lazy_static;
4041use serde:: Serialize ;
4142use std:: {
4243 error, fmt,
@@ -97,6 +98,37 @@ impl fmt::Display for BuildError {
9798/// Default address to Elasticsearch running on `http://localhost:9200`
9899pub static DEFAULT_ADDRESS : & str = "http://localhost:9200" ;
99100
101+ lazy_static ! {
102+ /// Client metadata header: service, language, transport, followed by additional information
103+ static ref CLIENT_META : String = build_meta( ) ;
104+ }
105+
106+ fn build_meta ( ) -> String {
107+ let mut version_parts = env ! ( "CARGO_PKG_VERSION" ) . split ( & [ '.' , '-' ] [ ..] ) ;
108+ let mut version = String :: new ( ) ;
109+
110+ // major.minor.patch followed with an optional 'p' for preliminary versions
111+ version. push_str ( version_parts. next ( ) . unwrap ( ) ) ;
112+ version. push ( '.' ) ;
113+ version. push_str ( version_parts. next ( ) . unwrap ( ) ) ;
114+ version. push ( '.' ) ;
115+ version. push_str ( version_parts. next ( ) . unwrap ( ) ) ;
116+ if version_parts. next ( ) . is_some ( ) {
117+ version. push ( 'p' ) ;
118+ }
119+
120+ let rustc = env ! ( "RUSTC_VERSION" ) ;
121+ let mut meta = format ! ( "es={},rs={},t={}" , version, rustc, version) ;
122+
123+ if cfg ! ( feature = "native-tls" ) {
124+ meta. push_str ( ",tls=n" ) ;
125+ } else if cfg ! ( feature = "rustls-tls" ) {
126+ meta. push_str ( ",tls=r" ) ;
127+ }
128+
129+ meta
130+ }
131+
100132/// Builds a HTTP transport to make API calls to Elasticsearch
101133pub struct TransportBuilder {
102134 client_builder : reqwest:: ClientBuilder ,
@@ -108,6 +140,7 @@ pub struct TransportBuilder {
108140 proxy_credentials : Option < Credentials > ,
109141 disable_proxy : bool ,
110142 headers : HeaderMap ,
143+ meta_header : bool ,
111144 timeout : Option < Duration > ,
112145}
113146
@@ -128,6 +161,7 @@ impl TransportBuilder {
128161 proxy_credentials : None ,
129162 disable_proxy : false ,
130163 headers : HeaderMap :: new ( ) ,
164+ meta_header : true ,
131165 timeout : None ,
132166 }
133167 }
@@ -187,6 +221,16 @@ impl TransportBuilder {
187221 self
188222 }
189223
224+ /// Whether to send a `x-elastic-client-meta` header that describes the runtime environment.
225+ ///
226+ /// This header contains information that is similar to what could be found in `User-Agent`. Using a separate
227+ /// header allows applications to use `User-Agent` for their own needs, e.g. to identify application version
228+ /// or other environment information. Defaults to `true`.
229+ pub fn enable_meta_header ( mut self , enable : bool ) -> Self {
230+ self . meta_header = enable;
231+ self
232+ }
233+
190234 /// Sets a global request timeout for the client.
191235 ///
192236 /// The timeout is applied from when the request starts connecting until the response body has finished.
@@ -270,6 +314,7 @@ impl TransportBuilder {
270314 client,
271315 conn_pool : self . conn_pool ,
272316 credentials : self . credentials ,
317+ send_meta : self . meta_header ,
273318 } )
274319 }
275320}
@@ -309,6 +354,7 @@ pub struct Transport {
309354 client : reqwest:: Client ,
310355 credentials : Option < Credentials > ,
311356 conn_pool : Box < dyn ConnectionPool > ,
357+ send_meta : bool ,
312358}
313359
314360impl Transport {
@@ -396,13 +442,20 @@ impl Transport {
396442 }
397443
398444 // default headers first, overwrite with any provided
399- let mut request_headers = HeaderMap :: with_capacity ( 3 + headers. len ( ) ) ;
445+ let mut request_headers = HeaderMap :: with_capacity ( 4 + headers. len ( ) ) ;
400446 request_headers. insert ( CONTENT_TYPE , HeaderValue :: from_static ( DEFAULT_CONTENT_TYPE ) ) ;
401447 request_headers. insert ( ACCEPT , HeaderValue :: from_static ( DEFAULT_ACCEPT ) ) ;
402448 request_headers. insert ( USER_AGENT , HeaderValue :: from_static ( DEFAULT_USER_AGENT ) ) ;
403449 for ( name, value) in headers {
404450 request_headers. insert ( name. unwrap ( ) , value) ;
405451 }
452+ // if meta header enabled, send it last so that it's not overridden.
453+ if self . send_meta {
454+ request_headers. insert (
455+ "x-elastic-client-meta" ,
456+ HeaderValue :: from_static ( CLIENT_META . as_str ( ) ) ,
457+ ) ;
458+ }
406459
407460 request_builder = request_builder. headers ( request_headers) ;
408461
@@ -601,9 +654,10 @@ impl ConnectionPool for CloudConnectionPool {
601654
602655#[ cfg( test) ]
603656pub mod tests {
657+ use super :: * ;
604658 #[ cfg( any( feature = "native-tls" , feature = "rustls-tls" ) ) ]
605659 use crate :: auth:: ClientCertificate ;
606- use crate :: http :: transport :: { CloudId , Connection , SingleNodeConnectionPool , TransportBuilder } ;
660+ use regex :: Regex ;
607661 use url:: Url ;
608662
609663 #[ test]
@@ -742,4 +796,12 @@ pub mod tests {
742796 let conn = Connection :: new ( url) ;
743797 assert_eq ! ( conn. url. as_str( ) , "http://10.1.2.3/" ) ;
744798 }
799+
800+ #[ test]
801+ pub fn test_meta_header ( ) {
802+ let re = Regex :: new ( r"^es=[0-9]{1,2}\.[0-9]{1,2}\.[0-9]{1,3}p?,rs=[0-9]{1,2}\.[0-9]{1,2}\.[0-9]{1,3}p?,t=[0-9]{1,2}\.[0-9]{1,2}\.[0-9]{1,3}p?(,tls=[rn])?$" ) . unwrap ( ) ;
803+ let x: & str = CLIENT_META . as_str ( ) ;
804+ println ! ( "{}" , x) ;
805+ assert ! ( re. is_match( x) ) ;
806+ }
745807}
0 commit comments