vmc 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,864 @@
1
+ # HTTPClient - HTTP client library.
2
+ # Copyright (C) 2000-2009 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 = client.proxy
112
+
113
+ @agent_name = nil
114
+ @from = nil
115
+
116
+ @protocol_version = nil
117
+ @debug_dev = client.debug_dev
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(response.is_a?(StringIO) ? response : 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
+ return nil if @status == HTTP::Status::NO_CONTENT
594
+ read_body_rest(&block)
595
+ end
596
+ rescue
597
+ close
598
+ raise
599
+ end
600
+ if eof?
601
+ if @next_connection
602
+ @state = :WAIT
603
+ else
604
+ close
605
+ end
606
+ end
607
+ nil
608
+ end
609
+
610
+ private
611
+
612
+ def set_header(req)
613
+ if @requested_version
614
+ if /^(?:HTTP\/|)(\d+.\d+)$/ =~ @requested_version
615
+ req.version = $1.to_f
616
+ end
617
+ end
618
+ if @agent_name
619
+ req.header.set('User-Agent', "#{@agent_name} #{LIB_NAME}")
620
+ end
621
+ if @from
622
+ req.header.set('From', @from)
623
+ end
624
+ req.header.set('Date', Time.now.httpdate)
625
+ end
626
+
627
+ # Connect to the server
628
+ def connect
629
+ site = @proxy || @dest
630
+ retry_number = 0
631
+ begin
632
+ timeout(@connect_timeout, ConnectTimeoutError) do
633
+ @socket = create_socket(site)
634
+ if @dest.scheme == 'https'
635
+ if @socket.is_a?(LoopBackSocket)
636
+ connect_ssl_proxy(@socket, URI.parse(@dest.to_s)) if @proxy
637
+ else
638
+ @socket = create_ssl_socket(@socket)
639
+ connect_ssl_proxy(@socket, URI.parse(@dest.to_s)) if @proxy
640
+ @socket.ssl_connect
641
+ @socket.post_connection_check(@dest)
642
+ @ssl_peer_cert = @socket.peer_cert
643
+ end
644
+ end
645
+ # Use Ruby internal buffering instead of passing data immediately
646
+ # to the underlying layer
647
+ # => we need to to call explicitly flush on the socket
648
+ @socket.sync = @socket_sync
649
+ end
650
+ rescue RetryableResponse
651
+ retry_number += 1
652
+ if retry_number < @protocol_retry_count
653
+ retry
654
+ end
655
+ raise BadResponseError.new("connect to the server failed with status #{@status} #{@reason}")
656
+ rescue TimeoutError
657
+ if @connect_retry == 0
658
+ retry
659
+ else
660
+ retry_number += 1
661
+ retry if retry_number < @connect_retry
662
+ end
663
+ close
664
+ raise
665
+ end
666
+ @state = :WAIT
667
+ end
668
+
669
+ def create_socket(site)
670
+ socket = nil
671
+ begin
672
+ @debug_dev << "! CONNECT TO #{site.host}:#{site.port}\n" if @debug_dev
673
+ if str = @test_loopback_http_response.shift
674
+ socket = LoopBackSocket.new(site.host, site.port, str)
675
+ else
676
+ socket = TCPSocket.new(site.host, site.port)
677
+ end
678
+ if @debug_dev
679
+ @debug_dev << "! CONNECTION ESTABLISHED\n"
680
+ socket.extend(DebugSocket)
681
+ socket.debug_dev = @debug_dev
682
+ end
683
+ rescue SystemCallError => e
684
+ e.message << " (#{site})"
685
+ raise
686
+ rescue SocketError => e
687
+ e.message << " (#{site})"
688
+ raise
689
+ end
690
+ socket
691
+ end
692
+
693
+ # wrap socket with OpenSSL.
694
+ def create_ssl_socket(raw_socket)
695
+ SSLSocketWrap.new(raw_socket, @ssl_config, @debug_dev)
696
+ end
697
+
698
+ def connect_ssl_proxy(socket, uri)
699
+ req = HTTP::Message.new_connect_request(uri)
700
+ @client.request_filter.each do |filter|
701
+ filter.filter_request(req)
702
+ end
703
+ set_header(req)
704
+ req.dump(@socket)
705
+ @socket.flush unless @socket_sync
706
+ res = HTTP::Message.new_response('')
707
+ parse_header
708
+ res.version, res.status, res.reason = @version, @status, @reason
709
+ @headers.each do |key, value|
710
+ res.header.set(key, value)
711
+ end
712
+ commands = @client.request_filter.collect { |filter|
713
+ filter.filter_response(req, res)
714
+ }
715
+ if commands.find { |command| command == :retry }
716
+ raise RetryableResponse.new
717
+ end
718
+ unless @status == 200
719
+ raise BadResponseError.new("connect to ssl proxy failed with status #{@status} #{@reason}", res)
720
+ end
721
+ end
722
+
723
+ # Read status block.
724
+ def read_header
725
+ @content_length = nil
726
+ @chunked = false
727
+ @chunk_length = 0
728
+ parse_header
729
+
730
+ # Head of the request has been parsed.
731
+ @state = :DATA
732
+ req = @requests.shift
733
+
734
+ if req.header.request_method == 'HEAD'
735
+ @content_length = 0
736
+ if @next_connection
737
+ @state = :WAIT
738
+ else
739
+ close
740
+ end
741
+ end
742
+ @next_connection = false unless @content_length
743
+ end
744
+
745
+ StatusParseRegexp = %r(\AHTTP/(\d+\.\d+)\s+(\d\d\d)\s*([^\r\n]+)?\r?\n\z)
746
+ def parse_header
747
+ timeout(@receive_timeout, ReceiveTimeoutError) do
748
+ begin
749
+ initial_line = @socket.gets("\n")
750
+ if initial_line.nil?
751
+ raise KeepAliveDisconnected.new
752
+ end
753
+ if StatusParseRegexp !~ initial_line
754
+ @version = '0.9'
755
+ @status = nil
756
+ @reason = nil
757
+ @next_connection = false
758
+ @content_length = nil
759
+ @readbuf = initial_line
760
+ break
761
+ end
762
+ @version, @status, @reason = $1, $2.to_i, $3
763
+ @next_connection = HTTP::Message.keep_alive_enabled?(@version.to_f)
764
+ @headers = []
765
+ while true
766
+ line = @socket.gets("\n")
767
+ unless line
768
+ raise BadResponseError.new('unexpected EOF')
769
+ end
770
+ line.chomp!
771
+ break if line.empty?
772
+ key, value = line.split(/\s*:\s*/, 2)
773
+ parse_keepalive_header(key, value)
774
+ @headers << [key, value]
775
+ end
776
+ end while (@version == '1.1' && @status == 100)
777
+ end
778
+ end
779
+
780
+ def parse_keepalive_header(key, value)
781
+ key = key.downcase
782
+ if key == 'content-length'
783
+ @content_length = value.to_i
784
+ elsif key == 'transfer-encoding' and value.downcase == 'chunked'
785
+ @chunked = true
786
+ @chunk_length = 0
787
+ @content_length = nil
788
+ elsif key == 'connection' or key == 'proxy-connection'
789
+ if value.downcase == 'keep-alive'
790
+ @next_connection = true
791
+ else
792
+ @next_connection = false
793
+ end
794
+ end
795
+ end
796
+
797
+ def read_body_length(&block)
798
+ return nil if @content_length == 0
799
+ buf = ''
800
+ while true
801
+ maxbytes = @read_block_size
802
+ maxbytes = @content_length if maxbytes > @content_length
803
+ timeout(@receive_timeout, ReceiveTimeoutError) do
804
+ begin
805
+ @socket.readpartial(maxbytes, buf)
806
+ rescue EOFError
807
+ buf = nil
808
+ end
809
+ end
810
+ if buf && buf.length > 0
811
+ @content_length -= buf.length
812
+ yield buf
813
+ else
814
+ @content_length = 0
815
+ end
816
+ return if @content_length == 0
817
+ end
818
+ end
819
+
820
+ RS = "\r\n"
821
+ def read_body_chunked(&block)
822
+ buf = ''
823
+ while true
824
+ len = @socket.gets(RS)
825
+ @chunk_length = len.hex
826
+ if @chunk_length == 0
827
+ @content_length = 0
828
+ @socket.gets(RS)
829
+ return
830
+ end
831
+ timeout(@receive_timeout, ReceiveTimeoutError) do
832
+ @socket.read(@chunk_length + 2, buf)
833
+ end
834
+ unless buf.empty?
835
+ yield buf.slice(0, @chunk_length)
836
+ end
837
+ end
838
+ end
839
+
840
+ def read_body_rest
841
+ if @readbuf and @readbuf.length > 0
842
+ yield @readbuf
843
+ @readbuf = nil
844
+ end
845
+ buf = ''
846
+ while true
847
+ timeout(@receive_timeout, ReceiveTimeoutError) do
848
+ begin
849
+ @socket.readpartial(@read_block_size, buf)
850
+ rescue EOFError
851
+ buf = nil
852
+ end
853
+ end
854
+ if buf && buf.length > 0
855
+ yield buf
856
+ else
857
+ return
858
+ end
859
+ end
860
+ end
861
+ end
862
+
863
+
864
+ end