httpclient 2.1.5.2 → 2.1.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -64,7 +64,11 @@ class HTTPClient
64
64
  # Retrieves a HTTP::Message instance of HTTP response. Do not invoke this
65
65
  # method twice for now. The second invocation will be blocked.
66
66
  def pop
67
- @queue.pop
67
+ response_or_exception = @queue.pop
68
+ if response_or_exception.is_a? Exception
69
+ raise response_or_exception
70
+ end
71
+ response_or_exception
68
72
  end
69
73
 
70
74
  def push(result) # :nodoc:
@@ -337,7 +337,6 @@ class WebAgent
337
337
 
338
338
  domainname = url.host
339
339
  domain_orig, path_orig = domain, path
340
- use_security = override
341
340
 
342
341
  if domain
343
342
 
@@ -372,7 +371,7 @@ class WebAgent
372
371
  # and IE does not check, too.
373
372
  end
374
373
 
375
- path ||= url.path.sub(%r|/[^/]*|, '')
374
+ path ||= url.path.sub(%r|/[^/]*\z|, '')
376
375
  domain ||= domainname
377
376
  @cookies.synchronize do
378
377
  cookie = find_cookie_info(domain, path, name)
@@ -95,7 +95,7 @@ module HTTP
95
95
 
96
96
  # Represents HTTP message header.
97
97
  class Headers
98
- # HTTP version in a HTTP header. Float.
98
+ # HTTP version in a HTTP header. String.
99
99
  attr_accessor :http_version
100
100
  # Size of body. nil when size is unknown (e.g. chunked response).
101
101
  attr_reader :body_size
@@ -109,7 +109,7 @@ module HTTP
109
109
  # Request only. Requested query.
110
110
  attr_accessor :request_query
111
111
  # Request only. Requested via proxy or not.
112
- attr_accessor :request_via_proxy
112
+ attr_accessor :request_absolute_uri
113
113
 
114
114
  # Response only. HTTP status
115
115
  attr_reader :status_code
@@ -151,14 +151,14 @@ module HTTP
151
151
  # Creates a Message::Headers. Use init_request, init_response, or
152
152
  # init_connect_request for acutual initialize.
153
153
  def initialize
154
- @http_version = 1.1
154
+ @http_version = '1.1'
155
155
  @body_size = nil
156
156
  @chunked = false
157
157
 
158
158
  @request_method = nil
159
159
  @request_uri = nil
160
160
  @request_query = nil
161
- @request_via_proxy = nil
161
+ @request_absolute_uri = nil
162
162
 
163
163
  @status_code = nil
164
164
  @reason_phrase = nil
@@ -178,7 +178,7 @@ module HTTP
178
178
  @request_method = 'CONNECT'
179
179
  @request_uri = uri
180
180
  @request_query = nil
181
- @http_version = 1.0
181
+ @http_version = '1.0'
182
182
  end
183
183
 
184
184
  # Placeholder URI object for nil uri.
@@ -189,7 +189,7 @@ module HTTP
189
189
  @request_method = method
190
190
  @request_uri = uri || NIL_URI
191
191
  @request_query = query
192
- @request_via_proxy = false
192
+ @request_absolute_uri = false
193
193
  end
194
194
 
195
195
  # Initialize this instance as a response.
@@ -285,11 +285,38 @@ module HTTP
285
285
  get(key).collect { |item| item[1] }
286
286
  end
287
287
 
288
+ def create_query_uri()
289
+ if @request_method == 'CONNECT'
290
+ return "#{@request_uri.host}:#{@request_uri.port}"
291
+ end
292
+ path = @request_uri.path
293
+ path = '/' if path.nil? or path.empty?
294
+ if query_str = create_query_part()
295
+ path += "?#{query_str}"
296
+ end
297
+ path
298
+ end
299
+
300
+ def create_query_part()
301
+ query_str = nil
302
+ if @request_uri.query
303
+ query_str = @request_uri.query
304
+ end
305
+ if @request_query
306
+ if query_str
307
+ query_str += "&#{Message.create_query_part_str(@request_query)}"
308
+ else
309
+ query_str = Message.create_query_part_str(@request_query)
310
+ end
311
+ end
312
+ query_str
313
+ end
314
+
288
315
  private
289
316
 
290
317
  def request_line
291
- path = create_query_uri(@request_uri, @request_query)
292
- if @request_via_proxy
318
+ path = create_query_uri()
319
+ if @request_absolute_uri
293
320
  path = "#{ @request_uri.scheme }://#{ @request_uri.host }:#{ @request_uri.port }#{ path }"
294
321
  end
295
322
  "#{ @request_method } #{ path } HTTP/#{ @http_version }#{ CRLF }"
@@ -323,7 +350,7 @@ module HTTP
323
350
  elsif @body_size and (keep_alive or @body_size != 0)
324
351
  set('Content-Length', @body_size.to_s)
325
352
  end
326
- if @http_version >= 1.1
353
+ if @http_version >= '1.1' and get('Host').empty?
327
354
  if @request_uri.port == @request_uri.default_port
328
355
  # GFE/1.3 dislikes default port number (returns 404)
329
356
  set('Host', "#{@request_uri.host}")
@@ -358,29 +385,6 @@ module HTTP
358
385
  def charset_label(charset)
359
386
  CHARSET_MAP[charset] || 'us-ascii'
360
387
  end
361
-
362
- def create_query_uri(uri, query)
363
- if @request_method == 'CONNECT'
364
- return "#{uri.host}:#{uri.port}"
365
- end
366
- path = uri.path
367
- path = '/' if path.nil? or path.empty?
368
- query_str = nil
369
- if uri.query
370
- query_str = uri.query
371
- end
372
- if query
373
- if query_str
374
- query_str += "&#{Message.create_query_part_str(query)}"
375
- else
376
- query_str = Message.create_query_part_str(query)
377
- end
378
- end
379
- if query_str
380
- path += "?#{query_str}"
381
- end
382
- path
383
- end
384
388
  end
385
389
 
386
390
 
@@ -414,7 +418,9 @@ module HTTP
414
418
  # Initialize this instance as a response.
415
419
  def init_response(body = nil)
416
420
  @body = body
417
- if @body.respond_to?(:size)
421
+ if @body.respond_to?(:bytesize)
422
+ @size = @body.bytesize
423
+ elsif @body.respond_to?(:size)
418
424
  @size = @body.size
419
425
  else
420
426
  @size = nil
@@ -438,6 +444,7 @@ module HTTP
438
444
  while !part.read(@chunk_size, buf).nil?
439
445
  dev << buf
440
446
  end
447
+ part.rewind
441
448
  else
442
449
  dev << part
443
450
  end
@@ -496,7 +503,7 @@ module HTTP
496
503
  @size = @body.size
497
504
  else
498
505
  @body = Message.create_query_part_str(body)
499
- @size = @body.size
506
+ @size = @body.bytesize
500
507
  end
501
508
  end
502
509
 
@@ -517,7 +524,7 @@ module HTTP
517
524
  end
518
525
 
519
526
  def dump_chunk(str)
520
- dump_chunk_size(str.size) + (str + CRLF)
527
+ dump_chunk_size(str.bytesize) + (str + CRLF)
521
528
  end
522
529
 
523
530
  def dump_last_chunk
@@ -555,10 +562,10 @@ module HTTP
555
562
  end
556
563
  elsif @body[-1].is_a?(String)
557
564
  @body[-1] += part.to_s
558
- @size += part.to_s.size if @size
565
+ @size += part.to_s.bytesize if @size
559
566
  else
560
567
  @body << part.to_s
561
- @size += part.to_s.size if @size
568
+ @size += part.to_s.bytesize if @size
562
569
  end
563
570
  end
564
571
 
@@ -583,11 +590,21 @@ module HTTP
583
590
  }.join("; ")
584
591
  if value.respond_to?(:mime_type)
585
592
  content_type = value.mime_type
593
+ elsif value.respond_to?(:content_type)
594
+ content_type = value.content_type
586
595
  else
587
- content_type = Message.mime_type(value.path)
596
+ path = value.respond_to?(:path) ? value.path : nil
597
+ content_type = Message.mime_type(path)
588
598
  end
589
599
  headers << %{Content-Disposition: form-data; name="#{attr}"; #{param_str}}
590
600
  headers << %{Content-Type: #{content_type}}
601
+ elsif attr.is_a?(Hash)
602
+ h = attr
603
+ value = h[:content]
604
+ h.each do |h_key, h_val|
605
+ headers << %{#{h_key}: #{h_val}} if h_key != :content
606
+ end
607
+ remember_pos(value) if Message.file?(value)
591
608
  else
592
609
  headers << %{Content-Disposition: form-data; name="#{attr}"}
593
610
  end
@@ -601,7 +618,8 @@ module HTTP
601
618
 
602
619
  def params_from_file(value)
603
620
  params = {}
604
- params['filename'] = File.basename(value.path || '')
621
+ path = value.respond_to?(:path) ? value.path : nil
622
+ params['filename'] = File.basename(path || '')
605
623
  # Creation time is not available from File::Stat
606
624
  if value.respond_to?(:mtime)
607
625
  params['modification-date'] = value.mtime.rfc822
@@ -724,9 +742,9 @@ module HTTP
724
742
  end
725
743
 
726
744
  # Returns true if the given HTTP version allows keep alive connection.
727
- # version:: Float
745
+ # version:: String
728
746
  def keep_alive_enabled?(version)
729
- version >= 1.1
747
+ version >= '1.1'
730
748
  end
731
749
 
732
750
  # Returns true if the given query (or body) has a multiple parameter.
@@ -736,15 +754,14 @@ module HTTP
736
754
 
737
755
  # Returns true if the given object is a File. In HTTPClient, a file is;
738
756
  # * must respond to :read for retrieving String chunks.
739
- # * must respond to :path and returns a path for Content-Disposition.
740
757
  # * must respond to :pos and :pos= to rewind for reading.
741
758
  # Rewinding is only needed for following HTTP redirect. Some IO impl
742
759
  # defines :pos= but raises an Exception for pos= such as StringIO
743
760
  # but there's no problem as far as using it for non-following methods
744
761
  # (get/post/etc.)
745
762
  def file?(obj)
746
- obj.respond_to?(:read) and obj.respond_to?(:path) and
747
- obj.respond_to?(:pos) and obj.respond_to?(:pos=)
763
+ obj.respond_to?(:read) and obj.respond_to?(:pos) and
764
+ obj.respond_to?(:pos=)
748
765
  end
749
766
 
750
767
  def create_query_part_str(query) # :nodoc:
@@ -758,7 +775,7 @@ module HTTP
758
775
  end
759
776
 
760
777
  def escape_query(query) # :nodoc:
761
- query.collect { |attr, value|
778
+ query.sort_by { |attr, value| attr.to_s }.collect { |attr, value|
762
779
  if value.respond_to?(:read)
763
780
  value = value.read
764
781
  end
@@ -768,9 +785,36 @@ module HTTP
768
785
 
769
786
  # from CGI.escape
770
787
  def escape(str) # :nodoc:
771
- str.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
772
- '%' + $1.unpack('H2' * $1.size).join('%').upcase
773
- }.tr(' ', '+')
788
+ if defined?(Encoding::ASCII_8BIT)
789
+ str.dup.force_encoding(Encoding::ASCII_8BIT).gsub(/([^ a-zA-Z0-9_.-]+)/) {
790
+ '%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
791
+ }.tr(' ', '+')
792
+ else
793
+ str.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
794
+ '%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
795
+ }.tr(' ', '+')
796
+ end
797
+ end
798
+
799
+ # from CGI.parse
800
+ def parse(query)
801
+ params = Hash.new([].freeze)
802
+ query.split(/[&;]/n).each do |pairs|
803
+ key, value = pairs.split('=',2).collect{|v| unescape(v) }
804
+ if params.has_key?(key)
805
+ params[key].push(value)
806
+ else
807
+ params[key] = [value]
808
+ end
809
+ end
810
+ params
811
+ end
812
+
813
+ # from CGI.unescape
814
+ def unescape(string)
815
+ string.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n) do
816
+ [$1.delete('%')].pack('H*')
817
+ end
774
818
  end
775
819
  end
776
820
 
@@ -813,13 +857,24 @@ module HTTP
813
857
  @header.body_size = @body.size if @header
814
858
  end
815
859
 
816
- # Returns HTTP version in a HTTP header. Float.
817
- def version
860
+ # Returns HTTP version in a HTTP header. String.
861
+ def http_version
818
862
  @header.http_version
819
863
  end
820
864
 
821
- # Sets HTTP version in a HTTP header. Float.
865
+ # Sets HTTP version in a HTTP header. String.
866
+ def http_version=(http_version)
867
+ @header.http_version = http_version
868
+ end
869
+
870
+ VERSION_WARNING = 'Message#version (Float) is deprecated. Use Message#http_version (String) instead.'
871
+ def version
872
+ warn(VERSION_WARNING)
873
+ @header.http_version.to_f
874
+ end
875
+
822
876
  def version=(version)
877
+ warn(VERSION_WARNING)
823
878
  @header.http_version = version
824
879
  end
825
880
 
@@ -13,6 +13,7 @@
13
13
  require 'socket'
14
14
  require 'thread'
15
15
  require 'stringio'
16
+ require 'zlib'
16
17
 
17
18
  require 'httpclient/timeout'
18
19
  require 'httpclient/ssl_config'
@@ -106,6 +107,8 @@ class HTTPClient
106
107
 
107
108
  attr_reader :test_loopback_http_response
108
109
 
110
+ attr_accessor :transparent_gzip_decompression
111
+
109
112
  def initialize(client)
110
113
  @client = client
111
114
  @proxy = client.proxy
@@ -128,6 +131,7 @@ class HTTPClient
128
131
  @ssl_config = nil
129
132
  @test_loopback_http_response = []
130
133
 
134
+ @transparent_gzip_decompression = false
131
135
  @sess_pool = []
132
136
  @sess_pool_mutex = Mutex.new
133
137
  end
@@ -165,6 +169,16 @@ class HTTPClient
165
169
  add_cached_session(sess)
166
170
  end
167
171
 
172
+ def invalidate(site)
173
+ @sess_pool_mutex.synchronize do
174
+ @sess_pool.each do |sess|
175
+ if sess.dest == site
176
+ sess.invalidate
177
+ end
178
+ end
179
+ end
180
+ end
181
+
168
182
  private
169
183
 
170
184
  def open(uri, via_proxy = false)
@@ -185,6 +199,7 @@ class HTTPClient
185
199
  sess.ssl_config = @ssl_config
186
200
  sess.debug_dev = @debug_dev
187
201
  sess.test_loopback_http_response = @test_loopback_http_response
202
+ sess.transparent_gzip_decompression = @transparent_gzip_decompression
188
203
  end
189
204
  sess
190
205
  end
@@ -198,6 +213,7 @@ class HTTPClient
198
213
  @sess_pool.clear
199
214
  end
200
215
 
216
+ # This method might not work as you expected...
201
217
  def close(dest)
202
218
  if cached = get_cached_session(dest)
203
219
  cached.close
@@ -212,7 +228,9 @@ class HTTPClient
212
228
  @sess_pool_mutex.synchronize do
213
229
  new_pool = []
214
230
  @sess_pool.each do |s|
215
- if s.dest.match(uri)
231
+ if s.invalidated?
232
+ s.close # close & remove from the pool
233
+ elsif !cached && s.dest.match(uri)
216
234
  cached = s
217
235
  else
218
236
  new_pool << s
@@ -435,7 +453,15 @@ class HTTPClient
435
453
  private
436
454
 
437
455
  def debug(str)
438
- @debug_dev << str if str && @debug_dev
456
+ if str && @debug_dev
457
+ if str.index("\0")
458
+ require 'hexdump'
459
+ str.force_encoding('BINARY') if str.respond_to?(:force_encoding)
460
+ @debug_dev << HexDump.encode(str).join("\n")
461
+ else
462
+ @debug_dev << str
463
+ end
464
+ end
439
465
  end
440
466
  end
441
467
 
@@ -459,6 +485,7 @@ class HTTPClient
459
485
  # Manages a HTTP session with a Site.
460
486
  class Session
461
487
  include HTTPClient::Timeout
488
+ include Util
462
489
 
463
490
  # Destination site
464
491
  attr_reader :dest
@@ -482,9 +509,12 @@ class HTTPClient
482
509
  attr_reader :ssl_peer_cert
483
510
  attr_accessor :test_loopback_http_response
484
511
 
512
+ attr_accessor :transparent_gzip_decompression
513
+
485
514
  def initialize(client, dest, agent_name, from)
486
515
  @client = client
487
516
  @dest = dest
517
+ @invalidated = false
488
518
  @proxy = nil
489
519
  @socket_sync = true
490
520
  @requested_version = nil
@@ -515,12 +545,15 @@ class HTTPClient
515
545
 
516
546
  @socket = nil
517
547
  @readbuf = nil
548
+
549
+ @transparent_gzip_decompression = false
518
550
  end
519
551
 
520
552
  # Send a request to the server
521
553
  def query(req)
522
554
  connect if @state == :INIT
523
- req.header.request_via_proxy = !@proxy.nil?
555
+ # Use absolute URI (not absolute path) iif via proxy AND not HTTPS.
556
+ req.header.request_absolute_uri = !@proxy.nil? and !https?(@dest)
524
557
  begin
525
558
  timeout(@send_timeout, SendTimeoutError) do
526
559
  set_header(req)
@@ -529,14 +562,16 @@ class HTTPClient
529
562
  @socket.flush unless @socket_sync
530
563
  end
531
564
  rescue Errno::ECONNABORTED, Errno::ECONNRESET, Errno::EPIPE
565
+ # JRuby can raise IOError instead of ECONNRESET for now
532
566
  close
533
- raise KeepAliveDisconnected.new
567
+ raise KeepAliveDisconnected.new(self)
534
568
  rescue HTTPClient::TimeoutError
535
569
  close
536
570
  raise
537
571
  rescue
572
+ close
538
573
  if SSLEnabled and $!.is_a?(OpenSSL::SSL::SSLError)
539
- raise KeepAliveDisconnected.new
574
+ raise KeepAliveDisconnected.new(self)
540
575
  else
541
576
  raise
542
577
  end
@@ -560,6 +595,14 @@ class HTTPClient
560
595
  @state == :INIT
561
596
  end
562
597
 
598
+ def invalidate
599
+ @invalidated = true
600
+ end
601
+
602
+ def invalidated?
603
+ @invalidated
604
+ end
605
+
563
606
  def get_header
564
607
  begin
565
608
  if @state != :META
@@ -585,6 +628,19 @@ class HTTPClient
585
628
  begin
586
629
  read_header if @state == :META
587
630
  return nil if @state != :DATA
631
+ if @gzipped and @transparent_gzip_decompression
632
+ # zlib itself has a functionality to decompress gzip stream.
633
+ # - zlib 1.2.5 Manual
634
+ # http://www.zlib.net/manual.html#Advanced
635
+ # > windowBits can also be greater than 15 for optional gzip decoding. Add 32 to
636
+ # > windowBits to enable zlib and gzip decoding with automatic header detection,
637
+ # > or add 16 to decode only the gzip format
638
+ inflate_stream = Zlib::Inflate.new(Zlib::MAX_WBITS + 32)
639
+ original_block = block
640
+ block = Proc.new { |buf|
641
+ original_block.call(inflate_stream.inflate(buf))
642
+ }
643
+ end
588
644
  if @chunked
589
645
  read_body_chunked(&block)
590
646
  elsif @content_length
@@ -611,7 +667,7 @@ class HTTPClient
611
667
  def set_header(req)
612
668
  if @requested_version
613
669
  if /^(?:HTTP\/|)(\d+.\d+)$/ =~ @requested_version
614
- req.version = $1.to_f
670
+ req.http_version = $1
615
671
  end
616
672
  end
617
673
  if @agent_name
@@ -620,6 +676,9 @@ class HTTPClient
620
676
  if @from
621
677
  req.header.set('From', @from)
622
678
  end
679
+ if @transparent_gzip_decompression
680
+ req.header.set('Accept-Encoding', 'gzip,deflate')
681
+ end
623
682
  req.header.set('Date', Time.now.httpdate)
624
683
  end
625
684
 
@@ -630,7 +689,7 @@ class HTTPClient
630
689
  begin
631
690
  timeout(@connect_timeout, ConnectTimeoutError) do
632
691
  @socket = create_socket(site)
633
- if @dest.scheme == 'https'
692
+ if https?(@dest)
634
693
  if @socket.is_a?(LoopBackSocket)
635
694
  connect_ssl_proxy(@socket, URI.parse(@dest.to_s)) if @proxy
636
695
  else
@@ -704,7 +763,7 @@ class HTTPClient
704
763
  @socket.flush unless @socket_sync
705
764
  res = HTTP::Message.new_response('')
706
765
  parse_header
707
- res.version, res.status, res.reason = @version, @status, @reason
766
+ res.http_version, res.status, res.reason = @version, @status, @reason
708
767
  @headers.each do |key, value|
709
768
  res.header.set(key, value)
710
769
  end
@@ -723,14 +782,13 @@ class HTTPClient
723
782
  def read_header
724
783
  @content_length = nil
725
784
  @chunked = false
785
+ @gzipped = false
726
786
  @chunk_length = 0
727
787
  parse_header
728
-
729
- # Head of the request has been parsed.
788
+ # Header of the request has been parsed.
730
789
  @state = :DATA
731
790
  req = @requests.shift
732
-
733
- if req.header.request_method == 'HEAD'
791
+ if req.header.request_method == 'HEAD' or no_message_body?(@status)
734
792
  @content_length = 0
735
793
  if @next_connection
736
794
  @state = :WAIT
@@ -744,11 +802,19 @@ class HTTPClient
744
802
  StatusParseRegexp = %r(\AHTTP/(\d+\.\d+)\s+(\d\d\d)\s*([^\r\n]+)?\r?\n\z)
745
803
  def parse_header
746
804
  timeout(@receive_timeout, ReceiveTimeoutError) do
805
+ initial_line = nil
747
806
  begin
748
807
  initial_line = @socket.gets("\n")
749
808
  if initial_line.nil?
750
- raise KeepAliveDisconnected.new
809
+ close
810
+ raise KeepAliveDisconnected.new(self)
751
811
  end
812
+ rescue Errno::ECONNABORTED, Errno::ECONNRESET, Errno::EPIPE, IOError
813
+ # JRuby can raise IOError instead of ECONNRESET for now
814
+ close
815
+ raise KeepAliveDisconnected.new(self)
816
+ end
817
+ begin
752
818
  if StatusParseRegexp !~ initial_line
753
819
  @version = '0.9'
754
820
  @status = nil
@@ -759,7 +825,7 @@ class HTTPClient
759
825
  break
760
826
  end
761
827
  @version, @status, @reason = $1, $2.to_i, $3
762
- @next_connection = HTTP::Message.keep_alive_enabled?(@version.to_f)
828
+ @next_connection = HTTP::Message.keep_alive_enabled?(@version)
763
829
  @headers = []
764
830
  while true
765
831
  line = @socket.gets("\n")
@@ -768,18 +834,32 @@ class HTTPClient
768
834
  end
769
835
  line.chomp!
770
836
  break if line.empty?
771
- key, value = line.split(/\s*:\s*/, 2)
772
- parse_keepalive_header(key, value)
773
- @headers << [key, value]
837
+ if line[0] == ?\ or line[0] == ?\t
838
+ last = @headers.last[1]
839
+ last << ' ' unless last.empty?
840
+ last << line.strip
841
+ else
842
+ key, value = line.strip.split(/\s*:\s*/, 2)
843
+ parse_keepalive_header(key, value)
844
+ @headers << [key, value]
845
+ end
774
846
  end
775
847
  end while (@version == '1.1' && @status == 100)
776
848
  end
777
849
  end
778
850
 
851
+ def no_message_body?(status)
852
+ !status.nil? && # HTTP/0.9
853
+ ((status >= 100 && status < 200) || status == 204 || status == 304)
854
+ end
855
+
779
856
  def parse_keepalive_header(key, value)
780
857
  key = key.downcase
781
858
  if key == 'content-length'
782
859
  @content_length = value.to_i
860
+ elsif key == 'content-encoding' and ( value.downcase == 'gzip' or
861
+ value.downcase == 'x-gzip' or value.downcase == 'deflate' )
862
+ @gzipped = true
783
863
  elsif key == 'transfer-encoding' and value.downcase == 'chunked'
784
864
  @chunked = true
785
865
  @chunk_length = 0
@@ -806,8 +886,8 @@ class HTTPClient
806
886
  buf = nil
807
887
  end
808
888
  end
809
- if buf && buf.length > 0
810
- @content_length -= buf.length
889
+ if buf && buf.bytesize > 0
890
+ @content_length -= buf.bytesize
811
891
  yield buf
812
892
  else
813
893
  @content_length = 0
@@ -837,7 +917,7 @@ class HTTPClient
837
917
  end
838
918
 
839
919
  def read_body_rest
840
- if @readbuf and @readbuf.length > 0
920
+ if @readbuf and @readbuf.bytesize > 0
841
921
  yield @readbuf
842
922
  @readbuf = nil
843
923
  end
@@ -850,7 +930,7 @@ class HTTPClient
850
930
  buf = nil
851
931
  end
852
932
  end
853
- if buf && buf.length > 0
933
+ if buf && buf.bytesize > 0
854
934
  yield buf
855
935
  else
856
936
  return