maedana-httpclient 2.1.5.2.1

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,872 @@
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
+ 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 = nil
725
+ @chunked = false
726
+ @chunk_length = 0
727
+ parse_header
728
+ # Header of the request has been parsed.
729
+ @state = :DATA
730
+ req = @requests.shift
731
+ if req.header.request_method == 'HEAD' or no_message_body?(@status)
732
+ @content_length = 0
733
+ if @next_connection
734
+ @state = :WAIT
735
+ else
736
+ close
737
+ end
738
+ end
739
+ @next_connection = false unless @content_length
740
+ end
741
+
742
+ StatusParseRegexp = %r(\AHTTP/(\d+\.\d+)\s+(\d\d\d)\s*([^\r\n]+)?\r?\n\z)
743
+ def parse_header
744
+ timeout(@receive_timeout, ReceiveTimeoutError) do
745
+ begin
746
+ initial_line = @socket.gets("\n")
747
+ if initial_line.nil?
748
+ raise KeepAliveDisconnected.new
749
+ end
750
+ if StatusParseRegexp !~ initial_line
751
+ @version = '0.9'
752
+ @status = nil
753
+ @reason = nil
754
+ @next_connection = false
755
+ @content_length = nil
756
+ @readbuf = initial_line
757
+ break
758
+ end
759
+ @version, @status, @reason = $1, $2.to_i, $3
760
+ @next_connection = HTTP::Message.keep_alive_enabled?(@version.to_f)
761
+ @headers = []
762
+ while true
763
+ line = @socket.gets("\n")
764
+ unless line
765
+ raise BadResponseError.new('unexpected EOF')
766
+ end
767
+ line.chomp!
768
+ break if line.empty?
769
+ if line[0] == ?\ or line[0] == ?\t
770
+ last = @headers.last[1]
771
+ last << ' ' unless last.empty?
772
+ last << line.strip
773
+ else
774
+ key, value = line.strip.split(/\s*:\s*/, 2)
775
+ parse_keepalive_header(key, value)
776
+ @headers << [key, value]
777
+ end
778
+ end
779
+ end while (@version == '1.1' && @status == 100)
780
+ end
781
+ end
782
+
783
+ def no_message_body?(status)
784
+ !status.nil? && # HTTP/0.9
785
+ ((status >= 100 && status < 200) || status == 204 || status == 304)
786
+ end
787
+
788
+ def parse_keepalive_header(key, value)
789
+ key = key.downcase
790
+ if key == 'content-length'
791
+ @content_length = value.to_i
792
+ elsif key == 'transfer-encoding' and value.downcase == 'chunked'
793
+ @chunked = true
794
+ @chunk_length = 0
795
+ @content_length = nil
796
+ elsif key == 'connection' or key == 'proxy-connection'
797
+ if value.downcase == 'keep-alive'
798
+ @next_connection = true
799
+ else
800
+ @next_connection = false
801
+ end
802
+ end
803
+ end
804
+
805
+ def read_body_length(&block)
806
+ return nil if @content_length == 0
807
+ buf = ''
808
+ while true
809
+ maxbytes = @read_block_size
810
+ maxbytes = @content_length if maxbytes > @content_length
811
+ timeout(@receive_timeout, ReceiveTimeoutError) do
812
+ begin
813
+ @socket.readpartial(maxbytes, buf)
814
+ rescue EOFError
815
+ buf = nil
816
+ end
817
+ end
818
+ if buf && buf.bytesize > 0
819
+ @content_length -= buf.bytesize
820
+ yield buf
821
+ else
822
+ @content_length = 0
823
+ end
824
+ return if @content_length == 0
825
+ end
826
+ end
827
+
828
+ RS = "\r\n"
829
+ def read_body_chunked(&block)
830
+ buf = ''
831
+ while true
832
+ len = @socket.gets(RS)
833
+ @chunk_length = len.hex
834
+ if @chunk_length == 0
835
+ @content_length = 0
836
+ @socket.gets(RS)
837
+ return
838
+ end
839
+ timeout(@receive_timeout, ReceiveTimeoutError) do
840
+ @socket.read(@chunk_length + 2, buf)
841
+ end
842
+ unless buf.empty?
843
+ yield buf.slice(0, @chunk_length)
844
+ end
845
+ end
846
+ end
847
+
848
+ def read_body_rest
849
+ if @readbuf and @readbuf.bytesize > 0
850
+ yield @readbuf
851
+ @readbuf = nil
852
+ end
853
+ buf = ''
854
+ while true
855
+ timeout(@receive_timeout, ReceiveTimeoutError) do
856
+ begin
857
+ @socket.readpartial(@read_block_size, buf)
858
+ rescue EOFError
859
+ buf = nil
860
+ end
861
+ end
862
+ if buf && buf.bytesize > 0
863
+ yield buf
864
+ else
865
+ return
866
+ end
867
+ end
868
+ end
869
+ end
870
+
871
+
872
+ end