httpclient 2.1.2 → 2.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,855 @@
1
+ # HTTPClient - HTTP client library.
2
+ # Copyright (C) 2000-2008 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
3
+ #
4
+ # This program is copyrighted free software by NAKAMURA, Hiroshi. You can
5
+ # redistribute it and/or modify it under the same terms of Ruby's license;
6
+ # either the dual license version in 2003, or any later version.
7
+
8
+ # httpclient/session.rb is based on http-access.rb in http-access/0.0.4.
9
+ # Some part of code in http-access.rb was recycled in httpclient.rb.
10
+ # Those part is copyrighted by Maehashi-san.
11
+
12
+
13
+ require 'socket'
14
+ require 'thread'
15
+ require 'stringio'
16
+
17
+ require 'httpclient/timeout'
18
+ require 'httpclient/ssl_config'
19
+ require 'httpclient/http'
20
+
21
+
22
+ class HTTPClient
23
+
24
+
25
+ # Represents a Site: protocol scheme, host String and port Number.
26
+ class Site
27
+ # Protocol scheme.
28
+ attr_accessor :scheme
29
+ # Host String.
30
+ attr_reader :host
31
+ # Port number.
32
+ attr_reader :port
33
+
34
+ # Creates a new Site based on the given URI.
35
+ def initialize(uri = nil)
36
+ if uri
37
+ @scheme = uri.scheme
38
+ @host = uri.host
39
+ @port = uri.port.to_i
40
+ else
41
+ @scheme = 'tcp'
42
+ @host = '0.0.0.0'
43
+ @port = 0
44
+ end
45
+ end
46
+
47
+ # Returns address String.
48
+ def addr
49
+ "#{@scheme}://#{@host}:#{@port.to_s}"
50
+ end
51
+
52
+ # Returns true is scheme, host and port are '=='
53
+ def ==(rhs)
54
+ (@scheme == rhs.scheme) and (@host == rhs.host) and (@port == rhs.port)
55
+ end
56
+
57
+ # Same as ==.
58
+ def eql?(rhs)
59
+ self == rhs
60
+ end
61
+
62
+ def hash # :nodoc:
63
+ [@scheme, @host, @port].hash
64
+ end
65
+
66
+ def to_s # :nodoc:
67
+ addr
68
+ end
69
+
70
+ # Returns true if scheme, host and port of the given URI matches with this.
71
+ def match(uri)
72
+ (@scheme == uri.scheme) and (@host == uri.host) and (@port == uri.port.to_i)
73
+ end
74
+
75
+ def inspect # :nodoc:
76
+ sprintf("#<%s:0x%x %s>", self.class.name, __id__, addr)
77
+ end
78
+ end
79
+
80
+
81
+ # Manages sessions for a HTTPClient instance.
82
+ class SessionManager
83
+ # Name of this client. Used for 'User-Agent' header in HTTP request.
84
+ attr_accessor :agent_name
85
+ # Owner of this client. Used for 'From' header in HTTP request.
86
+ attr_accessor :from
87
+
88
+ # Requested protocol version
89
+ attr_accessor :protocol_version
90
+ # Chunk size for chunked request
91
+ attr_accessor :chunk_size
92
+ # Device for dumping log for debugging
93
+ attr_accessor :debug_dev
94
+ # Boolean value for Socket#sync
95
+ attr_accessor :socket_sync
96
+
97
+ attr_accessor :connect_timeout
98
+ # Maximum retry count. 0 for infinite.
99
+ attr_accessor :connect_retry
100
+ attr_accessor :send_timeout
101
+ attr_accessor :receive_timeout
102
+ attr_accessor :read_block_size
103
+ attr_accessor :protocol_retry_count
104
+
105
+ attr_accessor :ssl_config
106
+
107
+ attr_reader :test_loopback_http_response
108
+
109
+ def initialize(client)
110
+ @client = client
111
+ @proxy = nil
112
+
113
+ @agent_name = nil
114
+ @from = nil
115
+
116
+ @protocol_version = nil
117
+ @debug_dev = nil
118
+ @socket_sync = true
119
+ @chunk_size = 4096
120
+
121
+ @connect_timeout = 60
122
+ @connect_retry = 1
123
+ @send_timeout = 120
124
+ @receive_timeout = 60 # For each read_block_size bytes
125
+ @read_block_size = 1024 * 16 # follows net/http change in 1.8.7
126
+ @protocol_retry_count = 5
127
+
128
+ @ssl_config = nil
129
+ @test_loopback_http_response = []
130
+
131
+ @sess_pool = []
132
+ @sess_pool_mutex = Mutex.new
133
+ end
134
+
135
+ def proxy=(proxy)
136
+ if proxy.nil?
137
+ @proxy = nil
138
+ else
139
+ @proxy = Site.new(proxy)
140
+ end
141
+ end
142
+
143
+ def query(req, via_proxy)
144
+ req.body.chunk_size = @chunk_size
145
+ sess = open(req.header.request_uri, via_proxy)
146
+ begin
147
+ sess.query(req)
148
+ rescue
149
+ sess.close
150
+ raise
151
+ end
152
+ sess
153
+ end
154
+
155
+ def reset(uri)
156
+ site = Site.new(uri)
157
+ close(site)
158
+ end
159
+
160
+ def reset_all
161
+ close_all
162
+ end
163
+
164
+ def keep(sess)
165
+ add_cached_session(sess)
166
+ end
167
+
168
+ private
169
+
170
+ def open(uri, via_proxy = false)
171
+ sess = nil
172
+ if cached = get_cached_session(uri)
173
+ sess = cached
174
+ else
175
+ sess = Session.new(@client, Site.new(uri), @agent_name, @from)
176
+ sess.proxy = via_proxy ? @proxy : nil
177
+ sess.socket_sync = @socket_sync
178
+ sess.requested_version = @protocol_version if @protocol_version
179
+ sess.connect_timeout = @connect_timeout
180
+ sess.connect_retry = @connect_retry
181
+ sess.send_timeout = @send_timeout
182
+ sess.receive_timeout = @receive_timeout
183
+ sess.read_block_size = @read_block_size
184
+ sess.protocol_retry_count = @protocol_retry_count
185
+ sess.ssl_config = @ssl_config
186
+ sess.debug_dev = @debug_dev
187
+ sess.test_loopback_http_response = @test_loopback_http_response
188
+ end
189
+ sess
190
+ end
191
+
192
+ def close_all
193
+ @sess_pool_mutex.synchronize do
194
+ @sess_pool.each do |sess|
195
+ sess.close
196
+ end
197
+ end
198
+ @sess_pool.clear
199
+ end
200
+
201
+ def close(dest)
202
+ if cached = get_cached_session(dest)
203
+ cached.close
204
+ true
205
+ else
206
+ false
207
+ end
208
+ end
209
+
210
+ def get_cached_session(uri)
211
+ cached = nil
212
+ @sess_pool_mutex.synchronize do
213
+ new_pool = []
214
+ @sess_pool.each do |s|
215
+ if s.dest.match(uri)
216
+ cached = s
217
+ else
218
+ new_pool << s
219
+ end
220
+ end
221
+ @sess_pool = new_pool
222
+ end
223
+ cached
224
+ end
225
+
226
+ def add_cached_session(sess)
227
+ @sess_pool_mutex.synchronize do
228
+ @sess_pool << sess
229
+ end
230
+ end
231
+ end
232
+
233
+
234
+ # Wraps up OpenSSL::SSL::SSLSocket and offers debugging features.
235
+ class SSLSocketWrap
236
+ def initialize(socket, context, debug_dev = nil)
237
+ unless SSLEnabled
238
+ raise ConfigurationError.new('Ruby/OpenSSL module is required')
239
+ end
240
+ @context = context
241
+ @socket = socket
242
+ @ssl_socket = create_openssl_socket(@socket)
243
+ @debug_dev = debug_dev
244
+ end
245
+
246
+ def ssl_connect
247
+ @ssl_socket.connect
248
+ end
249
+
250
+ def post_connection_check(host)
251
+ verify_mode = @context.verify_mode || OpenSSL::SSL::VERIFY_NONE
252
+ if verify_mode == OpenSSL::SSL::VERIFY_NONE
253
+ return
254
+ elsif @ssl_socket.peer_cert.nil? and
255
+ check_mask(verify_mode, OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT)
256
+ raise OpenSSL::SSL::SSLError.new('no peer cert')
257
+ end
258
+ hostname = host.host
259
+ if @ssl_socket.respond_to?(:post_connection_check) and RUBY_VERSION > "1.8.4"
260
+ @ssl_socket.post_connection_check(hostname)
261
+ else
262
+ @context.post_connection_check(@ssl_socket.peer_cert, hostname)
263
+ end
264
+ end
265
+
266
+ def peer_cert
267
+ @ssl_socket.peer_cert
268
+ end
269
+
270
+ def close
271
+ @ssl_socket.close
272
+ @socket.close
273
+ end
274
+
275
+ def closed?
276
+ @socket.closed?
277
+ end
278
+
279
+ def eof?
280
+ @ssl_socket.eof?
281
+ end
282
+
283
+ def gets(*args)
284
+ str = @ssl_socket.gets(*args)
285
+ debug(str)
286
+ str
287
+ end
288
+
289
+ def read(*args)
290
+ str = @ssl_socket.read(*args)
291
+ debug(str)
292
+ str
293
+ end
294
+
295
+ def readpartial(*args)
296
+ str = @ssl_socket.readpartial(*args)
297
+ debug(str)
298
+ str
299
+ end
300
+
301
+ def <<(str)
302
+ rv = @ssl_socket.write(str)
303
+ debug(str)
304
+ rv
305
+ end
306
+
307
+ def flush
308
+ @ssl_socket.flush
309
+ end
310
+
311
+ def sync
312
+ @ssl_socket.sync
313
+ end
314
+
315
+ def sync=(sync)
316
+ @ssl_socket.sync = sync
317
+ end
318
+
319
+ private
320
+
321
+ def check_mask(value, mask)
322
+ value & mask == mask
323
+ end
324
+
325
+ def create_openssl_socket(socket)
326
+ ssl_socket = nil
327
+ if OpenSSL::SSL.const_defined?("SSLContext")
328
+ ctx = OpenSSL::SSL::SSLContext.new
329
+ @context.set_context(ctx)
330
+ ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, ctx)
331
+ else
332
+ ssl_socket = OpenSSL::SSL::SSLSocket.new(socket)
333
+ @context.set_context(ssl_socket)
334
+ end
335
+ ssl_socket
336
+ end
337
+
338
+ def debug(str)
339
+ @debug_dev << str if @debug_dev && str
340
+ end
341
+ end
342
+
343
+
344
+ # Wraps up a Socket for method interception.
345
+ module SocketWrap
346
+ def initialize(socket, *args)
347
+ super(*args)
348
+ @socket = socket
349
+ end
350
+
351
+ def close
352
+ @socket.close
353
+ end
354
+
355
+ def closed?
356
+ @socket.closed?
357
+ end
358
+
359
+ def eof?
360
+ @socket.eof?
361
+ end
362
+
363
+ def gets(*args)
364
+ @socket.gets(*args)
365
+ end
366
+
367
+ def read(*args)
368
+ @socket.read(*args)
369
+ end
370
+
371
+ def readpartial(*args)
372
+ # StringIO doesn't support :readpartial
373
+ if @socket.respond_to?(:readpartial)
374
+ @socket.readpartial(*args)
375
+ else
376
+ @socket.read(*args)
377
+ end
378
+ end
379
+
380
+ def <<(str)
381
+ @socket << str
382
+ end
383
+
384
+ def flush
385
+ @socket.flush
386
+ end
387
+
388
+ def sync
389
+ @socket.sync
390
+ end
391
+
392
+ def sync=(sync)
393
+ @socket.sync = sync
394
+ end
395
+ end
396
+
397
+
398
+ # Module for intercepting Socket methods and dumps in/out to given debugging
399
+ # device. debug_dev must respond to <<.
400
+ module DebugSocket
401
+ extend SocketWrap
402
+
403
+ def debug_dev=(debug_dev)
404
+ @debug_dev = debug_dev
405
+ end
406
+
407
+ def close
408
+ super
409
+ debug("! CONNECTION CLOSED\n")
410
+ end
411
+
412
+ def gets(*args)
413
+ str = super
414
+ debug(str)
415
+ str
416
+ end
417
+
418
+ def read(*args)
419
+ str = super
420
+ debug(str)
421
+ str
422
+ end
423
+
424
+ def readpartial(*args)
425
+ str = super
426
+ debug(str)
427
+ str
428
+ end
429
+
430
+ def <<(str)
431
+ super
432
+ debug(str)
433
+ end
434
+
435
+ private
436
+
437
+ def debug(str)
438
+ @debug_dev << str if str && @debug_dev
439
+ end
440
+ end
441
+
442
+
443
+ # Dummy Socket for emulating loopback test.
444
+ class LoopBackSocket
445
+ include SocketWrap
446
+
447
+ def initialize(host, port, response)
448
+ super(StringIO.new(response))
449
+ @host = host
450
+ @port = port
451
+ end
452
+
453
+ def <<(str)
454
+ # ignored
455
+ end
456
+ end
457
+
458
+
459
+ # Manages a HTTP session with a Site.
460
+ class Session
461
+ include HTTPClient::Timeout
462
+
463
+ # Destination site
464
+ attr_reader :dest
465
+ # Proxy site
466
+ attr_accessor :proxy
467
+ # Boolean value for Socket#sync
468
+ attr_accessor :socket_sync
469
+ # Requested protocol version
470
+ attr_accessor :requested_version
471
+ # Device for dumping log for debugging
472
+ attr_accessor :debug_dev
473
+
474
+ attr_accessor :connect_timeout
475
+ attr_accessor :connect_retry
476
+ attr_accessor :send_timeout
477
+ attr_accessor :receive_timeout
478
+ attr_accessor :read_block_size
479
+ attr_accessor :protocol_retry_count
480
+
481
+ attr_accessor :ssl_config
482
+ attr_reader :ssl_peer_cert
483
+ attr_accessor :test_loopback_http_response
484
+
485
+ def initialize(client, dest, agent_name, from)
486
+ @client = client
487
+ @dest = dest
488
+ @proxy = nil
489
+ @socket_sync = true
490
+ @requested_version = nil
491
+
492
+ @debug_dev = nil
493
+
494
+ @connect_timeout = nil
495
+ @connect_retry = 1
496
+ @send_timeout = nil
497
+ @receive_timeout = nil
498
+ @read_block_size = nil
499
+ @protocol_retry_count = 5
500
+
501
+ @ssl_config = nil
502
+ @ssl_peer_cert = nil
503
+
504
+ @test_loopback_http_response = nil
505
+
506
+ @agent_name = agent_name
507
+ @from = from
508
+ @state = :INIT
509
+
510
+ @requests = []
511
+
512
+ @status = nil
513
+ @reason = nil
514
+ @headers = []
515
+
516
+ @socket = nil
517
+ @readbuf = nil
518
+ end
519
+
520
+ # Send a request to the server
521
+ def query(req)
522
+ connect if @state == :INIT
523
+ req.header.request_via_proxy = !@proxy.nil?
524
+ begin
525
+ timeout(@send_timeout, SendTimeoutError) do
526
+ set_header(req)
527
+ req.dump(@socket)
528
+ # flush the IO stream as IO::sync mode is false
529
+ @socket.flush unless @socket_sync
530
+ end
531
+ rescue Errno::ECONNABORTED, Errno::ECONNRESET, Errno::EPIPE
532
+ close
533
+ raise KeepAliveDisconnected.new
534
+ rescue HTTPClient::TimeoutError
535
+ close
536
+ raise
537
+ rescue
538
+ if SSLEnabled and $!.is_a?(OpenSSL::SSL::SSLError)
539
+ raise KeepAliveDisconnected.new
540
+ else
541
+ raise
542
+ end
543
+ end
544
+
545
+ @state = :META if @state == :WAIT
546
+ @next_connection = nil
547
+ @requests.push(req)
548
+ end
549
+
550
+ def close
551
+ if !@socket.nil? and !@socket.closed?
552
+ # @socket.flush may block when it the socket is already closed by
553
+ # foreign host and the client runs under MT-condition.
554
+ @socket.close
555
+ end
556
+ @state = :INIT
557
+ end
558
+
559
+ def closed?
560
+ @state == :INIT
561
+ end
562
+
563
+ def get_header
564
+ begin
565
+ if @state != :META
566
+ raise RuntimeError.new("get_status must be called at the beginning of a session")
567
+ end
568
+ read_header
569
+ rescue
570
+ close
571
+ raise
572
+ end
573
+ [@version, @status, @reason, @headers]
574
+ end
575
+
576
+ def eof?
577
+ if !@content_length.nil?
578
+ @content_length == 0
579
+ else
580
+ @socket.closed? or @socket.eof?
581
+ end
582
+ end
583
+
584
+ def get_body(&block)
585
+ begin
586
+ read_header if @state == :META
587
+ return nil if @state != :DATA
588
+ if @chunked
589
+ read_body_chunked(&block)
590
+ elsif @content_length
591
+ read_body_length(&block)
592
+ else
593
+ read_body_rest(&block)
594
+ end
595
+ rescue
596
+ close
597
+ raise
598
+ end
599
+ if eof?
600
+ if @next_connection
601
+ @state = :WAIT
602
+ else
603
+ close
604
+ end
605
+ end
606
+ nil
607
+ end
608
+
609
+ private
610
+
611
+ def set_header(req)
612
+ if @requested_version
613
+ if /^(?:HTTP\/|)(\d+.\d+)$/ =~ @requested_version
614
+ req.version = $1.to_f
615
+ end
616
+ end
617
+ if @agent_name
618
+ req.header.set('User-Agent', "#{@agent_name} #{LIB_NAME}")
619
+ end
620
+ if @from
621
+ req.header.set('From', @from)
622
+ end
623
+ req.header.set('Date', Time.now.httpdate)
624
+ end
625
+
626
+ # Connect to the server
627
+ def connect
628
+ site = @proxy || @dest
629
+ retry_number = 0
630
+ begin
631
+ timeout(@connect_timeout, ConnectTimeoutError) do
632
+ @socket = create_socket(site)
633
+ if @dest.scheme == 'https'
634
+ if @socket.is_a?(LoopBackSocket)
635
+ connect_ssl_proxy(@socket, URI.parse(@dest.to_s)) if @proxy
636
+ else
637
+ @socket = create_ssl_socket(@socket)
638
+ connect_ssl_proxy(@socket, URI.parse(@dest.to_s)) if @proxy
639
+ @socket.ssl_connect
640
+ @socket.post_connection_check(@dest)
641
+ @ssl_peer_cert = @socket.peer_cert
642
+ end
643
+ end
644
+ # Use Ruby internal buffering instead of passing data immediately
645
+ # to the underlying layer
646
+ # => we need to to call explicitly flush on the socket
647
+ @socket.sync = @socket_sync
648
+ end
649
+ rescue RetryableResponse
650
+ retry_number += 1
651
+ if retry_number < @protocol_retry_count
652
+ retry
653
+ end
654
+ raise BadResponseError.new("connect to the server failed with status #{@status} #{@reason}")
655
+ rescue TimeoutError
656
+ if @connect_retry == 0
657
+ retry
658
+ else
659
+ retry_number += 1
660
+ retry if retry_number < @connect_retry
661
+ end
662
+ close
663
+ raise
664
+ end
665
+ @state = :WAIT
666
+ end
667
+
668
+ def create_socket(site)
669
+ socket = nil
670
+ begin
671
+ @debug_dev << "! CONNECT TO #{site.host}:#{site.port}\n" if @debug_dev
672
+ if str = @test_loopback_http_response.shift
673
+ socket = LoopBackSocket.new(site.host, site.port, str)
674
+ else
675
+ socket = TCPSocket.new(site.host, site.port)
676
+ end
677
+ if @debug_dev
678
+ @debug_dev << "! CONNECTION ESTABLISHED\n"
679
+ socket.extend(DebugSocket)
680
+ socket.debug_dev = @debug_dev
681
+ end
682
+ rescue SystemCallError => e
683
+ e.message << " (#{site})"
684
+ raise
685
+ rescue SocketError => e
686
+ e.message << " (#{site})"
687
+ raise
688
+ end
689
+ socket
690
+ end
691
+
692
+ # wrap socket with OpenSSL.
693
+ def create_ssl_socket(raw_socket)
694
+ SSLSocketWrap.new(raw_socket, @ssl_config, @debug_dev)
695
+ end
696
+
697
+ def connect_ssl_proxy(socket, uri)
698
+ req = HTTP::Message.new_connect_request(uri)
699
+ @client.request_filter.each do |filter|
700
+ filter.filter_request(req)
701
+ end
702
+ set_header(req)
703
+ req.dump(@socket)
704
+ @socket.flush unless @socket_sync
705
+ res = HTTP::Message.new_response('')
706
+ parse_header
707
+ res.version, res.status, res.reason = @version, @status, @reason
708
+ @headers.each do |key, value|
709
+ res.header.set(key, value)
710
+ end
711
+ commands = @client.request_filter.collect { |filter|
712
+ filter.filter_response(req, res)
713
+ }
714
+ if commands.find { |command| command == :retry }
715
+ raise RetryableResponse.new
716
+ end
717
+ unless @status == 200
718
+ raise BadResponseError.new("connect to ssl proxy failed with status #{@status} #{@reason}", res)
719
+ end
720
+ end
721
+
722
+ # Read status block.
723
+ def read_header
724
+ @content_length = 0
725
+ @chunked = false
726
+ @chunk_length = 0
727
+ parse_header
728
+
729
+ # Head of the request has been parsed.
730
+ @state = :DATA
731
+ req = @requests.shift
732
+
733
+ if req.header.request_method == 'HEAD'
734
+ @content_length = 0
735
+ if @next_connection
736
+ @state = :WAIT
737
+ else
738
+ close
739
+ end
740
+ end
741
+ @next_connection = false unless @content_length
742
+ end
743
+
744
+ StatusParseRegexp = %r(\AHTTP/(\d+\.\d+)\s+(\d\d\d)\s*([^\r\n]+)?\r?\n\z)
745
+ def parse_header
746
+ timeout(@receive_timeout, ReceiveTimeoutError) do
747
+ begin
748
+ initial_line = @socket.gets("\n")
749
+ if initial_line.nil?
750
+ raise KeepAliveDisconnected.new
751
+ end
752
+ if StatusParseRegexp !~ initial_line
753
+ @version = '0.9'
754
+ @status = nil
755
+ @reason = nil
756
+ @next_connection = false
757
+ @content_length = nil
758
+ @readbuf = initial_line
759
+ break
760
+ end
761
+ @version, @status, @reason = $1, $2.to_i, $3
762
+ @next_connection = HTTP::Message.keep_alive_enabled?(@version.to_f)
763
+ @headers = []
764
+ while true
765
+ line = @socket.gets("\n")
766
+ unless line
767
+ raise BadResponseError.new('unexpected EOF')
768
+ end
769
+ line.chomp!
770
+ break if line.empty?
771
+ key, value = line.split(/\s*:\s*/, 2)
772
+ parse_keepalive_header(key, value)
773
+ @headers << [key, value]
774
+ end
775
+ end while (@version == '1.1' && @status == 100)
776
+ end
777
+ end
778
+
779
+ def parse_keepalive_header(key, value)
780
+ key = key.downcase
781
+ if key == 'content-length'
782
+ @content_length = value.to_i
783
+ elsif key == 'transfer-encoding' and value.downcase == 'chunked'
784
+ @chunked = true
785
+ @chunk_length = 0
786
+ @content_length = nil
787
+ elsif key == 'connection' or key == 'proxy-connection'
788
+ if value.downcase == 'keep-alive'
789
+ @next_connection = true
790
+ else
791
+ @next_connection = false
792
+ end
793
+ end
794
+ end
795
+
796
+ def read_body_length(&block)
797
+ return nil if @content_length == 0
798
+ buf = ''
799
+ while true
800
+ maxbytes = @read_block_size
801
+ maxbytes = @content_length if maxbytes > @content_length
802
+ timeout(@receive_timeout, ReceiveTimeoutError) do
803
+ @socket.readpartial(maxbytes, buf) rescue EOFError
804
+ end
805
+ if buf.length > 0
806
+ @content_length -= buf.length
807
+ yield buf
808
+ else
809
+ @content_length = 0
810
+ end
811
+ return if @content_length == 0
812
+ end
813
+ end
814
+
815
+ RS = "\r\n"
816
+ def read_body_chunked(&block)
817
+ buf = ''
818
+ while true
819
+ len = @socket.gets(RS)
820
+ @chunk_length = len.hex
821
+ if @chunk_length == 0
822
+ @content_length = 0
823
+ @socket.gets(RS)
824
+ return
825
+ end
826
+ timeout(@receive_timeout, ReceiveTimeoutError) do
827
+ @socket.read(@chunk_length + 2, buf)
828
+ end
829
+ unless buf.empty?
830
+ yield buf.slice(0, @chunk_length)
831
+ end
832
+ end
833
+ end
834
+
835
+ def read_body_rest
836
+ if @readbuf.length > 0
837
+ yield @readbuf
838
+ @readbuf = nil
839
+ end
840
+ buf = ''
841
+ while true
842
+ timeout(@receive_timeout, ReceiveTimeoutError) do
843
+ @socket.read(@read_block_size, buf)
844
+ end
845
+ if buf && buf.length > 0
846
+ yield buf
847
+ else
848
+ return
849
+ end
850
+ end
851
+ end
852
+ end
853
+
854
+
855
+ end