@@ -399,41 +399,152 @@ def normalized_headers(*headers)
399399 end
400400 end
401401
402+ # @private
403+ #
404+ # An adapter that satisfies the IO interface required by `::IO.copy_stream`
405+ class ReadIOAdapter
406+ # @private
407+ #
408+ # @param max_len [Integer, nil]
409+ #
410+ # @return [String]
411+ #
412+ private def read_enum ( max_len )
413+ case max_len
414+ in nil
415+ @stream . to_a . join
416+ in Integer
417+ @buf << @stream . next while @buf . length < max_len
418+ @buf . slice! ( ..max_len )
419+ end
420+ rescue StopIteration
421+ @stream = nil
422+ @buf . slice! ( 0 ..)
423+ end
424+
425+ # @private
426+ #
427+ # @param max_len [Integer, nil]
428+ # @param out_string [String, nil]
429+ #
430+ # @return [String, nil]
431+ #
432+ def read ( max_len = nil , out_string = nil )
433+ case @stream
434+ in nil
435+ nil
436+ in IO | StringIO
437+ @stream . read ( max_len , out_string )
438+ in Enumerator
439+ read = read_enum ( max_len )
440+ case out_string
441+ in String
442+ out_string . replace ( read )
443+ in nil
444+ read
445+ end
446+ end
447+ . tap ( &@blk )
448+ end
449+
450+ # @private
451+ #
452+ # @param stream [String, IO, StringIO, Enumerable]
453+ # @param blk [Proc]
454+ #
455+ def initialize ( stream , &blk )
456+ @stream = stream . is_a? ( String ) ? StringIO . new ( stream ) : stream
457+ @buf = String . new . b
458+ @blk = blk
459+ end
460+ end
461+
462+ class << self
463+ # @param blk [Proc]
464+ #
465+ # @return [Enumerable]
466+ #
467+ def string_io ( &blk )
468+ Enumerator . new do |y |
469+ y . define_singleton_method ( :write ) do
470+ self << _1 . clone
471+ _1 . bytesize
472+ end
473+
474+ blk . call ( y )
475+ end
476+ end
477+ end
478+
402479 class << self
403480 # @private
404481 #
405- # @param io [StringIO ]
482+ # @param y [Enumerator::Yielder ]
406483 # @param boundary [String]
407484 # @param key [Symbol, String]
408485 # @param val [Object]
409486 #
410- private def encode_multipart_formdata ( io , boundary :, key :, val :)
411- io << "--#{ boundary } \r \n "
412- io << "Content-Disposition: form-data"
487+ private def encode_multipart_formdata ( y , boundary :, key :, val :)
488+ y << "--#{ boundary } \r \n "
489+ y << "Content-Disposition: form-data"
413490 unless key . nil?
414491 name = ERB ::Util . url_encode ( key . to_s )
415- io << "; name=\" #{ name } \" "
492+ y << "; name=\" #{ name } \" "
416493 end
417494 if val . is_a? ( IO )
418495 filename = ERB ::Util . url_encode ( File . basename ( val . to_path ) )
419- io << "; filename=\" #{ filename } \" "
496+ y << "; filename=\" #{ filename } \" "
420497 end
421- io << "\r \n "
498+ y << "\r \n "
422499 case val
423- in IO | StringIO
424- io << "Content-Type: application/octet-stream\r \n \r \n "
425- IO . copy_stream ( val , io )
500+ in IO
501+ y << "Content-Type: application/octet-stream\r \n \r \n "
502+ IO . copy_stream ( val , y )
503+ in StringIO
504+ y << "Content-Type: application/octet-stream\r \n \r \n "
505+ y << val . string
426506 in String
427- io << "Content-Type: application/octet-stream\r \n \r \n "
428- io << val . to_s
507+ y << "Content-Type: application/octet-stream\r \n \r \n "
508+ y << val . to_s
429509 in true | false | Integer | Float | Symbol
430- io << "Content-Type: text/plain\r \n \r \n "
431- io << val . to_s
510+ y << "Content-Type: text/plain\r \n \r \n "
511+ y << val . to_s
432512 else
433- io << "Content-Type: application/json\r \n \r \n "
434- io << JSON . fast_generate ( val )
513+ y << "Content-Type: application/json\r \n \r \n "
514+ y << JSON . fast_generate ( val )
435515 end
436- io << "\r \n "
516+ y << "\r \n "
517+ end
518+
519+ # @private
520+ #
521+ # @param body [Object]
522+ #
523+ # @return [Array(String, Enumerable)]
524+ #
525+ private def encode_multipart_streaming ( body )
526+ boundary = SecureRandom . urlsafe_base64 ( 60 )
527+
528+ strio = string_io do |y |
529+ case body
530+ in Hash
531+ body . each do |key , val |
532+ case val
533+ in Array if val . all? { primitive? ( _1 ) }
534+ val . each do |v |
535+ encode_multipart_formdata ( y , boundary : boundary , key : key , val : v )
536+ end
537+ else
538+ encode_multipart_formdata ( y , boundary : boundary , key : key , val : val )
539+ end
540+ end
541+ else
542+ encode_multipart_formdata ( y , boundary : boundary , key : nil , val : body )
543+ end
544+ y << "--#{ boundary } --\r \n "
545+ end
546+
547+ [ boundary , strio ]
437548 end
438549
439550 # @private
@@ -449,37 +560,11 @@ def encode_content(headers, body)
449560 in [ "application/json" , Hash | Array ]
450561 [ headers , JSON . fast_generate ( body ) ]
451562 in [ %r{^multipart/form-data} , Hash | IO | StringIO ]
452- boundary = SecureRandom . urlsafe_base64 ( 60 )
453- strio = StringIO . new . tap do |io |
454- case body
455- in Hash
456- body . each do |key , val |
457- case val
458- in Array if val . all? { primitive? ( _1 ) }
459- val . each do |v |
460- encode_multipart_formdata ( io , boundary : boundary , key : key , val : v )
461- end
462- else
463- encode_multipart_formdata ( io , boundary : boundary , key : key , val : val )
464- end
465- end
466- else
467- encode_multipart_formdata ( io , boundary : boundary , key : nil , val : body )
468- end
469- io << "--#{ boundary } --\r \n "
470- io . rewind
471- end
472- headers = {
473- **headers ,
474- "content-type" => "#{ content_type } ; boundary=#{ boundary } " ,
475- "transfer-encoding" => "chunked"
476- }
563+ boundary , strio = encode_multipart_streaming ( body )
564+ headers = { **headers , "content-type" => "#{ content_type } ; boundary=#{ boundary } " }
477565 [ headers , strio ]
478566 in [ _ , StringIO ]
479567 [ headers , body . string ]
480- in [ _ , IO ]
481- headers = { **headers , "transfer-encoding" => "chunked" }
482- [ headers , body ]
483568 else
484569 [ headers , body ]
485570 end
@@ -589,8 +674,9 @@ def decode_lines(enum)
589674
590675 chain_fused ( enum ) do |y |
591676 enum . each do |row |
677+ offset = buffer . bytesize
592678 buffer << row
593- while ( match = re . match ( buffer , cr_seen . to_i ) )
679+ while ( match = re . match ( buffer , cr_seen & .to_i || offset ) )
594680 case [ match . captures . first , cr_seen ]
595681 in [ "\r " , nil ]
596682 cr_seen = match . end ( 1 )
@@ -600,6 +686,7 @@ def decode_lines(enum)
600686 else
601687 y << buffer . slice! ( ..( match . end ( 1 ) . pred ) )
602688 end
689+ offset = 0
603690 cr_seen = nil
604691 end
605692 end
@@ -637,7 +724,7 @@ def decode_sse(lines)
637724 in "event"
638725 current . merge! ( event : value )
639726 in "data"
640- ( current [ :data ] ||= String . new . b ) << value << "\n "
727+ ( current [ :data ] ||= String . new . b ) << ( value << "\n " )
641728 in "id" unless value . include? ( "\0 " )
642729 current . merge! ( id : value )
643730 in "retry" if /^\d +$/ =~ value
0 commit comments