@@ -428,19 +428,12 @@ def _crt_request_from_aws_request(self, aws_request):
428
428
headers_list .append ((name , str (value , 'utf-8' )))
429
429
430
430
crt_headers = awscrt .http .HttpHeaders (headers_list )
431
- # CRT requires body (if it exists) to be an I/O stream.
432
- crt_body_stream = None
433
- if aws_request .body :
434
- if hasattr (aws_request .body , 'seek' ):
435
- crt_body_stream = aws_request .body
436
- else :
437
- crt_body_stream = BytesIO (aws_request .body )
438
431
439
432
crt_request = awscrt .http .HttpRequest (
440
433
method = aws_request .method ,
441
434
path = crt_path ,
442
435
headers = crt_headers ,
443
- body_stream = crt_body_stream ,
436
+ body_stream = aws_request . body ,
444
437
)
445
438
return crt_request
446
439
@@ -453,6 +446,25 @@ def _convert_to_crt_http_request(self, botocore_http_request):
453
446
crt_request .headers .set ("host" , url_parts .netloc )
454
447
if crt_request .headers .get ('Content-MD5' ) is not None :
455
448
crt_request .headers .remove ("Content-MD5" )
449
+
450
+ # In general, the CRT S3 client expects a content length header. It
451
+ # only expects a missing content length header if the body is not
452
+ # seekable. However, botocore does not set the content length header
453
+ # for GetObject API requests and so we set the content length to zero
454
+ # to meet the CRT S3 client's expectation that the content length
455
+ # header is set even if there is no body.
456
+ if crt_request .headers .get ('Content-Length' ) is None :
457
+ if botocore_http_request .body is None :
458
+ crt_request .headers .add ('Content-Length' , "0" )
459
+
460
+ # Botocore sets the Transfer-Encoding header when it cannot determine
461
+ # the content length of the request body (e.g. it's not seekable).
462
+ # However, CRT does not support this header, but it supports
463
+ # non-seekable bodies. So we remove this header to not cause issues
464
+ # in the downstream CRT S3 request.
465
+ if crt_request .headers .get ('Transfer-Encoding' ) is not None :
466
+ crt_request .headers .remove ('Transfer-Encoding' )
467
+
456
468
return crt_request
457
469
458
470
def _capture_http_request (self , request , ** kwargs ):
@@ -555,39 +567,20 @@ def __init__(self, crt_request_serializer, os_utils):
555
567
def get_make_request_args (
556
568
self , request_type , call_args , coordinator , future , on_done_after_calls
557
569
):
558
- recv_filepath = None
559
- send_filepath = None
560
- s3_meta_request_type = getattr (
561
- S3RequestType , request_type . upper (), S3RequestType . DEFAULT
570
+ request_args_handler = getattr (
571
+ self ,
572
+ f'_get_make_request_args_ { request_type } ' ,
573
+ self . _default_get_make_request_args ,
562
574
)
563
- on_done_before_calls = []
564
- if s3_meta_request_type == S3RequestType .GET_OBJECT :
565
- final_filepath = call_args .fileobj
566
- recv_filepath = self ._os_utils .get_temp_filename (final_filepath )
567
- file_ondone_call = RenameTempFileHandler (
568
- coordinator , final_filepath , recv_filepath , self ._os_utils
569
- )
570
- on_done_before_calls .append (file_ondone_call )
571
- elif s3_meta_request_type == S3RequestType .PUT_OBJECT :
572
- send_filepath = call_args .fileobj
573
- data_len = self ._os_utils .get_file_size (send_filepath )
574
- call_args .extra_args ["ContentLength" ] = data_len
575
-
576
- crt_request = self ._request_serializer .serialize_http_request (
577
- request_type , future
575
+ return request_args_handler (
576
+ request_type = request_type ,
577
+ call_args = call_args ,
578
+ coordinator = coordinator ,
579
+ future = future ,
580
+ on_done_before_calls = [],
581
+ on_done_after_calls = on_done_after_calls ,
578
582
)
579
583
580
- return {
581
- 'request' : crt_request ,
582
- 'type' : s3_meta_request_type ,
583
- 'recv_filepath' : recv_filepath ,
584
- 'send_filepath' : send_filepath ,
585
- 'on_done' : self .get_crt_callback (
586
- future , 'done' , on_done_before_calls , on_done_after_calls
587
- ),
588
- 'on_progress' : self .get_crt_callback (future , 'progress' ),
589
- }
590
-
591
584
def get_crt_callback (
592
585
self ,
593
586
future ,
@@ -613,6 +606,97 @@ def invoke_all_callbacks(*args, **kwargs):
613
606
614
607
return invoke_all_callbacks
615
608
609
+ def _get_make_request_args_put_object (
610
+ self ,
611
+ request_type ,
612
+ call_args ,
613
+ coordinator ,
614
+ future ,
615
+ on_done_before_calls ,
616
+ on_done_after_calls ,
617
+ ):
618
+ send_filepath = None
619
+ if isinstance (call_args .fileobj , str ):
620
+ send_filepath = call_args .fileobj
621
+ data_len = self ._os_utils .get_file_size (send_filepath )
622
+ call_args .extra_args ["ContentLength" ] = data_len
623
+ else :
624
+ call_args .extra_args ["Body" ] = call_args .fileobj
625
+
626
+ # Suppress botocore's automatic MD5 calculation by setting an override
627
+ # value that will get deleted in the BotocoreCRTRequestSerializer.
628
+ # The CRT S3 client is able automatically compute checksums as part of
629
+ # requests it makes, and the intention is to configure automatic
630
+ # checksums in a future update.
631
+ call_args .extra_args ["ContentMD5" ] = "override-to-be-removed"
632
+
633
+ make_request_args = self ._default_get_make_request_args (
634
+ request_type = request_type ,
635
+ call_args = call_args ,
636
+ coordinator = coordinator ,
637
+ future = future ,
638
+ on_done_before_calls = on_done_before_calls ,
639
+ on_done_after_calls = on_done_after_calls ,
640
+ )
641
+ make_request_args ['send_filepath' ] = send_filepath
642
+ return make_request_args
643
+
644
+ def _get_make_request_args_get_object (
645
+ self ,
646
+ request_type ,
647
+ call_args ,
648
+ coordinator ,
649
+ future ,
650
+ on_done_before_calls ,
651
+ on_done_after_calls ,
652
+ ):
653
+ recv_filepath = None
654
+ on_body = None
655
+ if isinstance (call_args .fileobj , str ):
656
+ final_filepath = call_args .fileobj
657
+ recv_filepath = self ._os_utils .get_temp_filename (final_filepath )
658
+ on_done_before_calls .append (
659
+ RenameTempFileHandler (
660
+ coordinator , final_filepath , recv_filepath , self ._os_utils
661
+ )
662
+ )
663
+ else :
664
+ on_body = OnBodyFileObjWriter (call_args .fileobj )
665
+
666
+ make_request_args = self ._default_get_make_request_args (
667
+ request_type = request_type ,
668
+ call_args = call_args ,
669
+ coordinator = coordinator ,
670
+ future = future ,
671
+ on_done_before_calls = on_done_before_calls ,
672
+ on_done_after_calls = on_done_after_calls ,
673
+ )
674
+ make_request_args ['recv_filepath' ] = recv_filepath
675
+ make_request_args ['on_body' ] = on_body
676
+ return make_request_args
677
+
678
+ def _default_get_make_request_args (
679
+ self ,
680
+ request_type ,
681
+ call_args ,
682
+ coordinator ,
683
+ future ,
684
+ on_done_before_calls ,
685
+ on_done_after_calls ,
686
+ ):
687
+ return {
688
+ 'request' : self ._request_serializer .serialize_http_request (
689
+ request_type , future
690
+ ),
691
+ 'type' : getattr (
692
+ S3RequestType , request_type .upper (), S3RequestType .DEFAULT
693
+ ),
694
+ 'on_done' : self .get_crt_callback (
695
+ future , 'done' , on_done_before_calls , on_done_after_calls
696
+ ),
697
+ 'on_progress' : self .get_crt_callback (future , 'progress' ),
698
+ }
699
+
616
700
617
701
class RenameTempFileHandler :
618
702
def __init__ (self , coordinator , final_filename , temp_filename , osutil ):
@@ -642,3 +726,11 @@ def __init__(self, coordinator):
642
726
643
727
def __call__ (self , ** kwargs ):
644
728
self ._coordinator .set_done_callbacks_complete ()
729
+
730
+
731
+ class OnBodyFileObjWriter :
732
+ def __init__ (self , fileobj ):
733
+ self ._fileobj = fileobj
734
+
735
+ def __call__ (self , chunk , ** kwargs ):
736
+ self ._fileobj .write (chunk )
0 commit comments