@@ -19,7 +19,7 @@ use std::fs::File;
1919use std:: io:: prelude:: * ;
2020use std:: mem;
2121use std:: path:: { Path , PathBuf } ;
22- use std:: sync:: Arc ;
22+ use std:: sync:: { Arc , OnceLock } ;
2323
2424use crate :: s3:: creds:: Provider ;
2525use crate :: s3:: error:: { Error , ErrorResponse } ;
@@ -28,12 +28,15 @@ use crate::s3::response::*;
2828use crate :: s3:: signer:: sign_v4_s3;
2929use crate :: s3:: utils:: { EMPTY_SHA256 , Multimap , sha256_hash_sb, to_amz_date, utc_now} ;
3030
31- use crate :: s3:: builders:: ComposeSource ;
31+ use crate :: s3:: builders:: { BucketExists , ComposeSource } ;
3232use crate :: s3:: segmented_bytes:: SegmentedBytes ;
3333use bytes:: Bytes ;
3434use dashmap:: DashMap ;
3535use hyper:: http:: Method ;
36+ use rand:: Rng ;
37+ use rand:: distributions:: Alphanumeric ;
3638use reqwest:: Body ;
39+ use tokio:: task;
3740
3841mod append_object;
3942mod bucket_exists;
@@ -186,7 +189,7 @@ impl ClientBuilder {
186189 client : builder. build ( ) ?,
187190 base_url : self . base_url ,
188191 provider : self . provider ,
189- region_map : Arc :: default ( ) ,
192+ .. Default :: default ( )
190193 } )
191194 }
192195}
@@ -200,7 +203,8 @@ pub struct Client {
200203 client : reqwest:: Client ,
201204 pub ( crate ) base_url : BaseUrl ,
202205 pub ( crate ) provider : Option < Arc < Box < ( dyn Provider + Send + Sync + ' static ) > > > ,
203- pub ( crate ) region_map : Arc < DashMap < String , String > > ,
206+ pub ( crate ) region_map : DashMap < String , String > ,
207+ express : OnceLock < bool > ,
204208}
205209
206210impl Client {
@@ -234,14 +238,53 @@ impl Client {
234238 . build ( )
235239 }
236240
241+ /// Returns whether is client uses an AWS host.
237242 pub fn is_aws_host ( & self ) -> bool {
238243 self . base_url . is_aws_host ( )
239244 }
240245
246+ /// Returns whether this client is configured to use HTTPS.
241247 pub fn is_secure ( & self ) -> bool {
242248 self . base_url . https
243249 }
244250
251+ /// Returns whether this client is configured to use the express endpoint and is minio enterprise.
252+ pub fn is_minio_express ( self : & Arc < Self > ) -> bool {
253+ if self . express . get ( ) . is_some ( ) {
254+ self . express . get ( ) . unwrap ( ) . clone ( )
255+ } else {
256+ task:: block_in_place ( || match tokio:: runtime:: Runtime :: new ( ) {
257+ Ok ( rt) => {
258+ let bucket_name: String = rand:: thread_rng ( )
259+ . sample_iter ( & Alphanumeric )
260+ . take ( 20 )
261+ . map ( char:: from)
262+ . collect :: < String > ( )
263+ . to_lowercase ( ) ;
264+
265+ let express = rt. block_on ( async {
266+ match BucketExists :: new ( self , bucket_name) . send ( ) . await {
267+ Ok ( v) => {
268+ if let Some ( server) = v. headers . get ( "server" ) {
269+ if let Ok ( s) = server. to_str ( ) {
270+ return s == "MinIO Enterprise/S3express" ;
271+ }
272+ }
273+ }
274+ Err ( e) => {
275+ println ! ( "is_express_internal: error: {e}\n assume false" ) ;
276+ }
277+ }
278+ false
279+ } ) ;
280+ self . express . set ( express) . unwrap_or_default ( ) ;
281+ express
282+ }
283+ Err ( _) => false ,
284+ } )
285+ }
286+ }
287+
245288 fn handle_redirect_response (
246289 & self ,
247290 status_code : u16 ,
@@ -297,7 +340,7 @@ impl Client {
297340 return match headers. get ( "Content-Type" ) {
298341 Some ( v) => match v. to_str ( ) {
299342 Ok ( s) => match s. to_lowercase ( ) . contains ( "application/xml" ) {
300- true => match ErrorResponse :: parse ( body) {
343+ true => match ErrorResponse :: parse ( body, headers ) {
301344 Ok ( v) => Error :: S3Error ( v) ,
302345 Err ( e) => e,
303346 } ,
@@ -349,15 +392,15 @@ impl Client {
349392 _ => return Error :: ServerError ( status_code) ,
350393 } ;
351394
352- let request_id = match headers. get ( "x-amz-request-id" ) {
395+ let request_id: String = match headers. get ( "x-amz-request-id" ) {
353396 Some ( v) => match v. to_str ( ) {
354397 Ok ( s) => s. to_string ( ) ,
355398 Err ( e) => return Error :: StrError ( e) ,
356399 } ,
357400 _ => String :: new ( ) ,
358401 } ;
359402
360- let host_id = match headers. get ( "x-amz-id-2" ) {
403+ let host_id: String = match headers. get ( "x-amz-id-2" ) {
361404 Some ( v) => match v. to_str ( ) {
362405 Ok ( s) => s. to_string ( ) ,
363406 Err ( e) => return Error :: StrError ( e) ,
@@ -366,6 +409,7 @@ impl Client {
366409 } ;
367410
368411 Error :: S3Error ( ErrorResponse {
412+ headers,
369413 code,
370414 message,
371415 resource : resource. to_string ( ) ,
@@ -376,7 +420,7 @@ impl Client {
376420 } )
377421 }
378422
379- pub async fn do_execute (
423+ async fn execute_internal (
380424 & self ,
381425 method : & Method ,
382426 region : & str ,
@@ -447,6 +491,31 @@ impl Client {
447491 }
448492 }
449493
494+ if false {
495+ let mut header_strings: Vec < String > = headers
496+ . iter_all ( )
497+ . map ( |( k, v) | format ! ( "{}: {}" , k, v. join( "," ) ) )
498+ . collect ( ) ;
499+
500+ // Sort headers alphabetically by name
501+ header_strings. sort ( ) ;
502+
503+ let body_str: String = String :: from_utf8 (
504+ body. clone ( )
505+ . unwrap_or ( & SegmentedBytes :: new ( ) )
506+ . to_bytes ( )
507+ . to_vec ( ) ,
508+ ) ?;
509+
510+ println ! (
511+ "S3 request: {} url={:?}; headers={:?}; body={}\n " ,
512+ method,
513+ url. path,
514+ header_strings. join( "; " ) ,
515+ body_str
516+ ) ;
517+ }
518+
450519 if * method == Method :: PUT || * method == Method :: POST {
451520 let mut bytes_vec = vec ! [ ] ;
452521 if let Some ( body) = body {
@@ -461,15 +530,17 @@ impl Client {
461530 }
462531
463532 let resp = req. send ( ) . await ?;
533+
464534 if resp. status ( ) . is_success ( ) {
465535 return Ok ( resp) ;
466536 }
467537
468538 let mut resp = resp;
469539 let status_code = resp. status ( ) . as_u16 ( ) ;
470540 let headers: reqwest:: header:: HeaderMap = mem:: take ( resp. headers_mut ( ) ) ;
541+
471542 let body: Bytes = resp. bytes ( ) . await ?;
472- let e = self . get_error_response (
543+ let e: Error = self . get_error_response (
473544 body,
474545 status_code,
475546 headers,
@@ -504,8 +575,8 @@ impl Client {
504575 object_name : & Option < & str > ,
505576 data : Option < & SegmentedBytes > ,
506577 ) -> Result < reqwest:: Response , Error > {
507- let res = self
508- . do_execute (
578+ let resp : Result < reqwest :: Response , Error > = self
579+ . execute_internal (
509580 & method,
510581 region,
511582 headers,
@@ -516,7 +587,7 @@ impl Client {
516587 true ,
517588 )
518589 . await ;
519- match res {
590+ match resp {
520591 Ok ( r) => return Ok ( r) ,
521592 Err ( e) => match e {
522593 Error :: S3Error ( ref er) => {
@@ -529,7 +600,7 @@ impl Client {
529600 } ;
530601
531602 // Retry only once on RetryHead error.
532- self . do_execute (
603+ self . execute_internal (
533604 & method,
534605 region,
535606 headers,
0 commit comments