@@ -399,41 +399,152 @@ def normalized_headers(*headers)
399
399
end
400
400
end
401
401
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
+
402
479
class << self
403
480
# @private
404
481
#
405
- # @param io [StringIO ]
482
+ # @param y [Enumerator::Yielder ]
406
483
# @param boundary [String]
407
484
# @param key [Symbol, String]
408
485
# @param val [Object]
409
486
#
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"
413
490
unless key . nil?
414
491
name = ERB ::Util . url_encode ( key . to_s )
415
- io << "; name=\" #{ name } \" "
492
+ y << "; name=\" #{ name } \" "
416
493
end
417
494
if val . is_a? ( IO )
418
495
filename = ERB ::Util . url_encode ( File . basename ( val . to_path ) )
419
- io << "; filename=\" #{ filename } \" "
496
+ y << "; filename=\" #{ filename } \" "
420
497
end
421
- io << "\r \n "
498
+ y << "\r \n "
422
499
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
426
506
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
429
509
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
432
512
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 )
435
515
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 ]
437
548
end
438
549
439
550
# @private
@@ -449,37 +560,11 @@ def encode_content(headers, body)
449
560
in [ "application/json" , Hash | Array ]
450
561
[ headers , JSON . fast_generate ( body ) ]
451
562
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 } " }
477
565
[ headers , strio ]
478
566
in [ _ , StringIO ]
479
567
[ headers , body . string ]
480
- in [ _ , IO ]
481
- headers = { **headers , "transfer-encoding" => "chunked" }
482
- [ headers , body ]
483
568
else
484
569
[ headers , body ]
485
570
end
@@ -589,8 +674,9 @@ def decode_lines(enum)
589
674
590
675
chain_fused ( enum ) do |y |
591
676
enum . each do |row |
677
+ offset = buffer . bytesize
592
678
buffer << row
593
- while ( match = re . match ( buffer , cr_seen . to_i ) )
679
+ while ( match = re . match ( buffer , cr_seen & .to_i || offset ) )
594
680
case [ match . captures . first , cr_seen ]
595
681
in [ "\r " , nil ]
596
682
cr_seen = match . end ( 1 )
@@ -600,6 +686,7 @@ def decode_lines(enum)
600
686
else
601
687
y << buffer . slice! ( ..( match . end ( 1 ) . pred ) )
602
688
end
689
+ offset = 0
603
690
cr_seen = nil
604
691
end
605
692
end
@@ -637,7 +724,7 @@ def decode_sse(lines)
637
724
in "event"
638
725
current . merge! ( event : value )
639
726
in "data"
640
- ( current [ :data ] ||= String . new . b ) << value << "\n "
727
+ ( current [ :data ] ||= String . new . b ) << ( value << "\n " )
641
728
in "id" unless value . include? ( "\0 " )
642
729
current . merge! ( id : value )
643
730
in "retry" if /^\d +$/ =~ value
0 commit comments