httpclient 2.1.2 → 2.1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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