glebtv-httpclient 3.0.0

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.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/README.rdoc +108 -0
  3. data/bin/httpclient +65 -0
  4. data/lib/glebtv-httpclient.rb +1 -0
  5. data/lib/hexdump.rb +50 -0
  6. data/lib/http-access2/cookie.rb +1 -0
  7. data/lib/http-access2/http.rb +1 -0
  8. data/lib/http-access2.rb +55 -0
  9. data/lib/httpclient/auth.rb +899 -0
  10. data/lib/httpclient/cacert.p7s +1912 -0
  11. data/lib/httpclient/connection.rb +88 -0
  12. data/lib/httpclient/cookie.rb +438 -0
  13. data/lib/httpclient/http.rb +1050 -0
  14. data/lib/httpclient/include_client.rb +83 -0
  15. data/lib/httpclient/session.rb +1031 -0
  16. data/lib/httpclient/ssl_config.rb +403 -0
  17. data/lib/httpclient/timeout.rb +140 -0
  18. data/lib/httpclient/util.rb +186 -0
  19. data/lib/httpclient/version.rb +3 -0
  20. data/lib/httpclient.rb +1157 -0
  21. data/lib/oauthclient.rb +110 -0
  22. data/sample/async.rb +8 -0
  23. data/sample/auth.rb +11 -0
  24. data/sample/cookie.rb +18 -0
  25. data/sample/dav.rb +103 -0
  26. data/sample/howto.rb +49 -0
  27. data/sample/oauth_buzz.rb +57 -0
  28. data/sample/oauth_friendfeed.rb +59 -0
  29. data/sample/oauth_twitter.rb +61 -0
  30. data/sample/ssl/0cert.pem +22 -0
  31. data/sample/ssl/0key.pem +30 -0
  32. data/sample/ssl/1000cert.pem +19 -0
  33. data/sample/ssl/1000key.pem +18 -0
  34. data/sample/ssl/htdocs/index.html +10 -0
  35. data/sample/ssl/ssl_client.rb +22 -0
  36. data/sample/ssl/webrick_httpsd.rb +29 -0
  37. data/sample/stream.rb +21 -0
  38. data/sample/thread.rb +27 -0
  39. data/sample/wcat.rb +21 -0
  40. data/test/ca-chain.cert +44 -0
  41. data/test/ca.cert +23 -0
  42. data/test/client.cert +19 -0
  43. data/test/client.key +15 -0
  44. data/test/helper.rb +129 -0
  45. data/test/htdigest +1 -0
  46. data/test/htpasswd +2 -0
  47. data/test/runner.rb +2 -0
  48. data/test/server.cert +19 -0
  49. data/test/server.key +15 -0
  50. data/test/sslsvr.rb +65 -0
  51. data/test/subca.cert +21 -0
  52. data/test/test_auth.rb +321 -0
  53. data/test/test_cookie.rb +412 -0
  54. data/test/test_hexdump.rb +14 -0
  55. data/test/test_http-access2.rb +507 -0
  56. data/test/test_httpclient.rb +1801 -0
  57. data/test/test_include_client.rb +52 -0
  58. data/test/test_ssl.rb +235 -0
  59. metadata +102 -0
@@ -0,0 +1,1031 @@
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. Some
9
+ # part of it is copyrighted by Maebashi-san who made and published
10
+ # http-access/0.0.4. http-access/0.0.4 did not include license notice but when
11
+ # I asked Maebashi-san he agreed that I can redistribute it under the same terms
12
+ # of Ruby. Many thanks to Maebashi-san.
13
+
14
+
15
+ require 'socket'
16
+ require 'thread'
17
+ require 'stringio'
18
+ require 'zlib'
19
+
20
+ require 'httpclient/timeout'
21
+ require 'httpclient/ssl_config'
22
+ require 'httpclient/http'
23
+ require 'httpclient/util'
24
+
25
+
26
+ class HTTPClient
27
+
28
+
29
+ # Represents a Site: protocol scheme, host String and port Number.
30
+ class Site
31
+ # Protocol scheme.
32
+ attr_accessor :scheme
33
+ # Host String.
34
+ attr_accessor :host
35
+ alias hostname host
36
+ # Port number.
37
+ attr_accessor :port
38
+
39
+ # Creates a new Site based on the given URI.
40
+ def initialize(uri = nil)
41
+ if uri
42
+ @scheme = uri.scheme
43
+ @host = uri.hostname
44
+ @port = uri.port.to_i
45
+ else
46
+ @scheme = 'tcp'
47
+ @host = '0.0.0.0'
48
+ @port = 0
49
+ end
50
+ end
51
+
52
+ # Returns address String.
53
+ def addr
54
+ "#{@scheme}://#{@host}:#{@port.to_s}"
55
+ end
56
+
57
+ # Returns true is scheme, host and port are '=='
58
+ def ==(rhs)
59
+ (@scheme == rhs.scheme) and (@host == rhs.host) and (@port == rhs.port)
60
+ end
61
+
62
+ # Same as ==.
63
+ def eql?(rhs)
64
+ self == rhs
65
+ end
66
+
67
+ def hash # :nodoc:
68
+ [@scheme, @host, @port].hash
69
+ end
70
+
71
+ def to_s # :nodoc:
72
+ addr
73
+ end
74
+
75
+ # Returns true if scheme, host and port of the given URI matches with this.
76
+ def match(uri)
77
+ (@scheme == uri.scheme) and (@host == uri.host) and (@port == uri.port.to_i)
78
+ end
79
+
80
+ def inspect # :nodoc:
81
+ sprintf("#<%s:0x%x %s>", self.class.name, __id__, addr)
82
+ end
83
+
84
+ EMPTY = Site.new.freeze
85
+ end
86
+
87
+
88
+ # Manages sessions for a HTTPClient instance.
89
+ class SessionManager
90
+ # Name of this client. Used for 'User-Agent' header in HTTP request.
91
+ attr_accessor :agent_name
92
+ # Owner of this client. Used for 'From' header in HTTP request.
93
+ attr_accessor :from
94
+
95
+ # Requested protocol version
96
+ attr_accessor :protocol_version
97
+ # Chunk size for chunked request
98
+ attr_accessor :chunk_size
99
+ # Device for dumping log for debugging
100
+ attr_accessor :debug_dev
101
+ # Boolean value for Socket#sync
102
+ attr_accessor :socket_sync
103
+
104
+ attr_accessor :connect_timeout
105
+ # Maximum retry count. 0 for infinite.
106
+ attr_accessor :connect_retry
107
+ attr_accessor :send_timeout
108
+ attr_accessor :receive_timeout
109
+ attr_accessor :keep_alive_timeout
110
+ attr_accessor :read_block_size
111
+ attr_accessor :protocol_retry_count
112
+
113
+ # Local address to bind local side of the socket to
114
+ attr_accessor :socket_local
115
+
116
+ attr_accessor :ssl_config
117
+
118
+ attr_reader :test_loopback_http_response
119
+
120
+ attr_accessor :transparent_gzip_decompression
121
+
122
+ def initialize(client)
123
+ @client = client
124
+ @proxy = client.proxy
125
+
126
+ @agent_name = nil
127
+ @from = nil
128
+
129
+ @protocol_version = nil
130
+ @debug_dev = client.debug_dev
131
+ @socket_sync = true
132
+ @chunk_size = ::HTTP::Message::Body::DEFAULT_CHUNK_SIZE
133
+
134
+ @connect_timeout = 60
135
+ @connect_retry = 1
136
+ @send_timeout = 120
137
+ @receive_timeout = 60 # For each read_block_size bytes
138
+ @keep_alive_timeout = 15 # '15' is from Apache 2 default
139
+ @read_block_size = 1024 * 16 # follows net/http change in 1.8.7
140
+ @protocol_retry_count = 5
141
+
142
+ @ssl_config = nil
143
+ @test_loopback_http_response = []
144
+
145
+ @transparent_gzip_decompression = false
146
+ @socket_local = Site.new
147
+
148
+ @sess_pool = {}
149
+ @sess_pool_mutex = Mutex.new
150
+ @sess_pool_last_checked = Time.now
151
+ end
152
+
153
+ def proxy=(proxy)
154
+ if proxy.nil?
155
+ @proxy = nil
156
+ else
157
+ @proxy = Site.new(proxy)
158
+ end
159
+ end
160
+
161
+ def query(req, via_proxy)
162
+ req.http_body.chunk_size = @chunk_size
163
+ sess = open(req.header.request_uri, via_proxy)
164
+ begin
165
+ sess.query(req)
166
+ rescue
167
+ sess.close
168
+ raise
169
+ end
170
+ sess
171
+ end
172
+
173
+ def reset(uri)
174
+ site = Site.new(uri)
175
+ close(site)
176
+ end
177
+
178
+ def reset_all
179
+ close_all
180
+ end
181
+
182
+ # assert: sess.last_used must not be nil
183
+ def keep(sess)
184
+ add_cached_session(sess)
185
+ end
186
+
187
+ def invalidate(site)
188
+ @sess_pool_mutex.synchronize do
189
+ if pool = @sess_pool[site]
190
+ pool.each do |sess|
191
+ sess.invalidate
192
+ end
193
+ end
194
+ end
195
+ end
196
+
197
+ private
198
+
199
+ def open(uri, via_proxy = false)
200
+ site = Site.new(uri)
201
+ sess = nil
202
+ if cached = get_cached_session(site)
203
+ sess = cached
204
+ else
205
+ sess = Session.new(@client, site, @agent_name, @from)
206
+ sess.proxy = via_proxy ? @proxy : nil
207
+ sess.socket_sync = @socket_sync
208
+ sess.requested_version = @protocol_version if @protocol_version
209
+ sess.connect_timeout = @connect_timeout
210
+ sess.connect_retry = @connect_retry
211
+ sess.send_timeout = @send_timeout
212
+ sess.receive_timeout = @receive_timeout
213
+ sess.read_block_size = @read_block_size
214
+ sess.protocol_retry_count = @protocol_retry_count
215
+ sess.ssl_config = @ssl_config
216
+ sess.debug_dev = @debug_dev
217
+ sess.socket_local = @socket_local
218
+ sess.test_loopback_http_response = @test_loopback_http_response
219
+ sess.transparent_gzip_decompression = @transparent_gzip_decompression
220
+ end
221
+ sess
222
+ end
223
+
224
+ def close_all
225
+ @sess_pool_mutex.synchronize do
226
+ @sess_pool.each do |site, pool|
227
+ pool.each do |sess|
228
+ sess.close
229
+ end
230
+ end
231
+ end
232
+ @sess_pool.clear
233
+ end
234
+
235
+ # This method might not work as you expected...
236
+ def close(dest)
237
+ if cached = get_cached_session(Site.new(dest))
238
+ cached.close
239
+ true
240
+ else
241
+ false
242
+ end
243
+ end
244
+
245
+ def get_cached_session(site)
246
+ @sess_pool_mutex.synchronize do
247
+ now = Time.now
248
+ if now > @sess_pool_last_checked + @keep_alive_timeout
249
+ scrub_cached_session(now)
250
+ @sess_pool_last_checked = now
251
+ end
252
+ if pool = @sess_pool[site]
253
+ pool.each_with_index do |sess, idx|
254
+ if valid_session?(sess, now)
255
+ return pool.slice!(idx)
256
+ end
257
+ end
258
+ end
259
+ end
260
+ nil
261
+ end
262
+
263
+ def scrub_cached_session(now)
264
+ @sess_pool.each do |site, pool|
265
+ pool.replace(pool.select { |sess|
266
+ if valid_session?(sess, now)
267
+ true
268
+ else
269
+ sess.close # close & remove from the pool
270
+ false
271
+ end
272
+ })
273
+ end
274
+ end
275
+
276
+ def valid_session?(sess, now)
277
+ !sess.invalidated? and (now <= sess.last_used + @keep_alive_timeout)
278
+ end
279
+
280
+ def add_cached_session(sess)
281
+ @sess_pool_mutex.synchronize do
282
+ (@sess_pool[sess.dest] ||= []).unshift(sess)
283
+ end
284
+ end
285
+ end
286
+
287
+
288
+ # Wraps up OpenSSL::SSL::SSLSocket and offers debugging features.
289
+ class SSLSocketWrap
290
+ def initialize(socket, context, debug_dev = nil)
291
+ unless SSLEnabled
292
+ raise ConfigurationError.new('Ruby/OpenSSL module is required')
293
+ end
294
+ @context = context
295
+ @socket = socket
296
+ @ssl_socket = create_openssl_socket(@socket)
297
+ @debug_dev = debug_dev
298
+ end
299
+
300
+ def ssl_connect(hostname = nil)
301
+ if hostname && @ssl_socket.respond_to?(:hostname=)
302
+ @ssl_socket.hostname = hostname
303
+ end
304
+ @ssl_socket.connect
305
+ end
306
+
307
+ def post_connection_check(host)
308
+ verify_mode = @context.verify_mode || OpenSSL::SSL::VERIFY_NONE
309
+ if verify_mode == OpenSSL::SSL::VERIFY_NONE
310
+ return
311
+ elsif @ssl_socket.peer_cert.nil? and
312
+ check_mask(verify_mode, OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT)
313
+ raise OpenSSL::SSL::SSLError.new('no peer cert')
314
+ end
315
+ hostname = host.host
316
+ if @ssl_socket.respond_to?(:post_connection_check) and RUBY_VERSION > "1.8.4"
317
+ @ssl_socket.post_connection_check(hostname)
318
+ else
319
+ @context.post_connection_check(@ssl_socket.peer_cert, hostname)
320
+ end
321
+ end
322
+
323
+ def ssl_version
324
+ @ssl_socket.ssl_version if @ssl_socket.respond_to?(:ssl_version)
325
+ end
326
+
327
+ def ssl_cipher
328
+ @ssl_socket.cipher
329
+ end
330
+
331
+ def ssl_state
332
+ @ssl_socket.state
333
+ end
334
+
335
+ def peer_cert
336
+ @ssl_socket.peer_cert
337
+ end
338
+
339
+ def close
340
+ @ssl_socket.close
341
+ @socket.close
342
+ end
343
+
344
+ def closed?
345
+ @socket.closed?
346
+ end
347
+
348
+ def eof?
349
+ @ssl_socket.eof?
350
+ end
351
+
352
+ def gets(*args)
353
+ str = @ssl_socket.gets(*args)
354
+ debug(str)
355
+ str
356
+ end
357
+
358
+ def read(*args)
359
+ str = @ssl_socket.read(*args)
360
+ debug(str)
361
+ str
362
+ end
363
+
364
+ def readpartial(*args)
365
+ str = @ssl_socket.readpartial(*args)
366
+ debug(str)
367
+ str
368
+ end
369
+
370
+ def <<(str)
371
+ rv = @ssl_socket.write(str)
372
+ debug(str)
373
+ rv
374
+ end
375
+
376
+ def flush
377
+ @ssl_socket.flush
378
+ end
379
+
380
+ def sync
381
+ @ssl_socket.sync
382
+ end
383
+
384
+ def sync=(sync)
385
+ @ssl_socket.sync = sync
386
+ end
387
+
388
+ private
389
+
390
+ def check_mask(value, mask)
391
+ value & mask == mask
392
+ end
393
+
394
+ def create_openssl_socket(socket)
395
+ ssl_socket = nil
396
+ if OpenSSL::SSL.const_defined?("SSLContext")
397
+ ctx = OpenSSL::SSL::SSLContext.new
398
+ @context.set_context(ctx)
399
+ ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, ctx)
400
+ else
401
+ ssl_socket = OpenSSL::SSL::SSLSocket.new(socket)
402
+ @context.set_context(ssl_socket)
403
+ end
404
+ ssl_socket
405
+ end
406
+
407
+ def debug(str)
408
+ @debug_dev << str if @debug_dev && str
409
+ end
410
+ end
411
+
412
+
413
+ # Wraps up a Socket for method interception.
414
+ module SocketWrap
415
+ def initialize(socket, *args)
416
+ super(*args)
417
+ @socket = socket
418
+ end
419
+
420
+ def close
421
+ @socket.close
422
+ end
423
+
424
+ def closed?
425
+ @socket.closed?
426
+ end
427
+
428
+ def eof?
429
+ @socket.eof?
430
+ end
431
+
432
+ def gets(*args)
433
+ @socket.gets(*args)
434
+ end
435
+
436
+ def read(*args)
437
+ @socket.read(*args)
438
+ end
439
+
440
+ def readpartial(*args)
441
+ # StringIO doesn't support :readpartial
442
+ if @socket.respond_to?(:readpartial)
443
+ @socket.readpartial(*args)
444
+ else
445
+ @socket.read(*args)
446
+ end
447
+ end
448
+
449
+ def <<(str)
450
+ @socket << str
451
+ end
452
+
453
+ def flush
454
+ @socket.flush
455
+ end
456
+
457
+ def sync
458
+ @socket.sync
459
+ end
460
+
461
+ def sync=(sync)
462
+ @socket.sync = sync
463
+ end
464
+ end
465
+
466
+
467
+ # Module for intercepting Socket methods and dumps in/out to given debugging
468
+ # device. debug_dev must respond to <<.
469
+ module DebugSocket
470
+ extend SocketWrap
471
+
472
+ def debug_dev=(debug_dev)
473
+ @debug_dev = debug_dev
474
+ end
475
+
476
+ def close
477
+ super
478
+ debug("! CONNECTION CLOSED\n")
479
+ end
480
+
481
+ def gets(*args)
482
+ str = super
483
+ debug(str)
484
+ str
485
+ end
486
+
487
+ def read(*args)
488
+ str = super
489
+ debug(str)
490
+ str
491
+ end
492
+
493
+ def readpartial(*args)
494
+ str = super
495
+ debug(str)
496
+ str
497
+ end
498
+
499
+ def <<(str)
500
+ super
501
+ debug(str)
502
+ end
503
+
504
+ private
505
+
506
+ def debug(str)
507
+ if str && @debug_dev
508
+ if str.index("\0")
509
+ require 'hexdump'
510
+ str.force_encoding('BINARY') if str.respond_to?(:force_encoding)
511
+ @debug_dev << HexDump.encode(str).join("\n")
512
+ else
513
+ @debug_dev << str
514
+ end
515
+ end
516
+ end
517
+ end
518
+
519
+
520
+ # Dummy Socket for emulating loopback test.
521
+ class LoopBackSocket
522
+ include SocketWrap
523
+
524
+ def initialize(host, port, response)
525
+ super(response.is_a?(StringIO) ? response : StringIO.new(response))
526
+ @host = host
527
+ @port = port
528
+ end
529
+
530
+ def <<(str)
531
+ # ignored
532
+ end
533
+ end
534
+
535
+
536
+ # Manages a HTTP session with a Site.
537
+ class Session
538
+ include HTTPClient::Timeout
539
+ include Util
540
+
541
+ # Destination site
542
+ attr_reader :dest
543
+ # Proxy site
544
+ attr_accessor :proxy
545
+ # Boolean value for Socket#sync
546
+ attr_accessor :socket_sync
547
+ # Requested protocol version
548
+ attr_accessor :requested_version
549
+ # Device for dumping log for debugging
550
+ attr_accessor :debug_dev
551
+
552
+ attr_accessor :connect_timeout
553
+ attr_accessor :connect_retry
554
+ attr_accessor :send_timeout
555
+ attr_accessor :receive_timeout
556
+ attr_accessor :read_block_size
557
+ attr_accessor :protocol_retry_count
558
+
559
+ attr_accessor :socket_local
560
+
561
+ attr_accessor :ssl_config
562
+ attr_reader :ssl_peer_cert
563
+ attr_accessor :test_loopback_http_response
564
+
565
+ attr_accessor :transparent_gzip_decompression
566
+ attr_reader :last_used
567
+
568
+ def initialize(client, dest, agent_name, from)
569
+ @client = client
570
+ @dest = dest
571
+ @invalidated = false
572
+ @proxy = nil
573
+ @socket_sync = true
574
+ @requested_version = nil
575
+
576
+ @debug_dev = nil
577
+
578
+ @connect_timeout = nil
579
+ @connect_retry = 1
580
+ @send_timeout = nil
581
+ @receive_timeout = nil
582
+ @read_block_size = nil
583
+ @protocol_retry_count = 5
584
+
585
+ @ssl_config = nil
586
+ @ssl_peer_cert = nil
587
+
588
+ @test_loopback_http_response = nil
589
+ @socket_local = Site::EMPTY
590
+
591
+ @agent_name = agent_name
592
+ @from = from
593
+ @state = :INIT
594
+
595
+ @requests = []
596
+
597
+ @status = nil
598
+ @reason = nil
599
+ @headers = []
600
+
601
+ @socket = nil
602
+ @readbuf = nil
603
+
604
+ @transparent_gzip_decompression = false
605
+ @last_used = nil
606
+ end
607
+
608
+ # Send a request to the server
609
+ def query(req)
610
+ connect if @state == :INIT
611
+ # Use absolute URI (not absolute path) iif via proxy AND not HTTPS.
612
+ req.header.request_absolute_uri = !@proxy.nil? and !https?(@dest)
613
+ begin
614
+ timeout(@send_timeout, SendTimeoutError) do
615
+ set_header(req)
616
+ req.dump(@socket)
617
+ # flush the IO stream as IO::sync mode is false
618
+ @socket.flush unless @socket_sync
619
+ end
620
+ rescue Errno::ECONNABORTED, Errno::ECONNRESET, Errno::EPIPE, IOError
621
+ # JRuby can raise IOError instead of ECONNRESET for now
622
+ close
623
+ raise KeepAliveDisconnected.new(self)
624
+ rescue HTTPClient::TimeoutError
625
+ close
626
+ raise
627
+ rescue
628
+ close
629
+ if SSLEnabled and $!.is_a?(OpenSSL::SSL::SSLError)
630
+ raise KeepAliveDisconnected.new(self)
631
+ else
632
+ raise
633
+ end
634
+ end
635
+
636
+ @state = :META if @state == :WAIT
637
+ @next_connection = nil
638
+ @requests.push(req)
639
+ @last_used = Time.now
640
+ end
641
+
642
+ def close
643
+ if !@socket.nil? and !@socket.closed?
644
+ # @socket.flush may block when it the socket is already closed by
645
+ # foreign host and the client runs under MT-condition.
646
+ @socket.close
647
+ end
648
+ @state = :INIT
649
+ end
650
+
651
+ def closed?
652
+ @state == :INIT
653
+ end
654
+
655
+ def invalidate
656
+ @invalidated = true
657
+ end
658
+
659
+ def invalidated?
660
+ @invalidated
661
+ end
662
+
663
+ def get_header
664
+ begin
665
+ if @state != :META
666
+ raise RuntimeError.new("get_status must be called at the beginning of a session")
667
+ end
668
+ read_header
669
+ rescue
670
+ close
671
+ raise
672
+ end
673
+ [@version, @status, @reason, @headers]
674
+ end
675
+
676
+ def eof?
677
+ if !@content_length.nil?
678
+ @content_length == 0
679
+ else
680
+ @socket.closed? or @socket.eof?
681
+ end
682
+ end
683
+
684
+ def get_body(&block)
685
+ begin
686
+ read_header if @state == :META
687
+ return nil if @state != :DATA
688
+ if @gzipped and @transparent_gzip_decompression
689
+ # zlib itself has a functionality to decompress gzip stream.
690
+ # - zlib 1.2.5 Manual
691
+ # http://www.zlib.net/manual.html#Advanced
692
+ # > windowBits can also be greater than 15 for optional gzip decoding. Add 32 to
693
+ # > windowBits to enable zlib and gzip decoding with automatic header detection,
694
+ # > or add 16 to decode only the gzip format
695
+ inflate_stream = Zlib::Inflate.new(Zlib::MAX_WBITS + 32)
696
+ original_block = block
697
+ if @chunked
698
+ buffer = ''
699
+ block = Proc.new { |buf|
700
+ # we need to strip last \r\n from string for proper gzip decompression
701
+ buffer += buf[0..-3]
702
+ }
703
+ else
704
+ block = Proc.new { |buf|
705
+ original_block.call(inflate_stream.inflate(buf))
706
+ }
707
+ end
708
+ end
709
+ if @chunked
710
+ read_body_chunked(&block)
711
+ if @gzipped
712
+ original_block.call(inflate_stream.inflate(buffer))
713
+ end
714
+ elsif @content_length
715
+ read_body_length(&block)
716
+ else
717
+ read_body_rest(&block)
718
+ end
719
+ rescue
720
+ close
721
+ raise
722
+ end
723
+ if eof?
724
+ if @next_connection
725
+ @state = :WAIT
726
+ else
727
+ close
728
+ end
729
+ end
730
+ nil
731
+ end
732
+
733
+ private
734
+
735
+ def set_header(req)
736
+ if @requested_version
737
+ if /^(?:HTTP\/|)(\d+.\d+)$/ =~ @requested_version
738
+ req.http_version = $1
739
+ end
740
+ end
741
+ if @agent_name && req.header.get('User-Agent').empty?
742
+ req.header.set('User-Agent', "#{@agent_name} #{LIB_NAME}")
743
+ end
744
+ if @from && req.header.get('From').empty?
745
+ req.header.set('From', @from)
746
+ end
747
+ if req.header.get('Accept').empty?
748
+ req.header.set('Accept', '*/*')
749
+ end
750
+ if @transparent_gzip_decompression
751
+ req.header.set('Accept-Encoding', 'gzip,deflate')
752
+ end
753
+ if req.header.get('Date').empty?
754
+ req.header.set_date_header
755
+ end
756
+ end
757
+
758
+ # Connect to the server
759
+ def connect
760
+ site = @proxy || @dest
761
+ retry_number = 0
762
+ begin
763
+ timeout(@connect_timeout, ConnectTimeoutError) do
764
+ @socket = create_socket(site)
765
+ if https?(@dest)
766
+ if @socket.is_a?(LoopBackSocket)
767
+ connect_ssl_proxy(@socket, urify(@dest.to_s)) if @proxy
768
+ else
769
+ @socket = create_ssl_socket(@socket)
770
+ connect_ssl_proxy(@socket, urify(@dest.to_s)) if @proxy
771
+ begin
772
+ @socket.ssl_connect(@dest.host)
773
+ ensure
774
+ if $DEBUG
775
+ warn("Protocol version: #{@socket.ssl_version}")
776
+ warn("Cipher: #{@socket.ssl_cipher.inspect}")
777
+ warn("State: #{@socket.ssl_state}")
778
+ end
779
+ end
780
+ @socket.post_connection_check(@dest)
781
+ @ssl_peer_cert = @socket.peer_cert
782
+ end
783
+ end
784
+ # Use Ruby internal buffering instead of passing data immediately
785
+ # to the underlying layer
786
+ # => we need to to call explicitly flush on the socket
787
+ @socket.sync = @socket_sync
788
+ end
789
+ rescue RetryableResponse
790
+ retry_number += 1
791
+ if retry_number < @protocol_retry_count
792
+ retry
793
+ end
794
+ raise BadResponseError.new("connect to the server failed with status #{@status} #{@reason}")
795
+ rescue TimeoutError
796
+ if @connect_retry == 0
797
+ retry
798
+ else
799
+ retry_number += 1
800
+ retry if retry_number < @connect_retry
801
+ end
802
+ close
803
+ raise
804
+ end
805
+ @state = :WAIT
806
+ end
807
+
808
+ def create_socket(site)
809
+ socket = nil
810
+ begin
811
+ @debug_dev << "! CONNECT TO #{site.host}:#{site.port}\n" if @debug_dev
812
+ if str = @test_loopback_http_response.shift
813
+ socket = LoopBackSocket.new(site.host, site.port, str)
814
+ elsif @socket_local == Site::EMPTY
815
+ socket = TCPSocket.new(site.host, site.port)
816
+ else
817
+ socket = TCPSocket.new(site.host, site.port, @socket_local.host, @socket_local.port)
818
+ end
819
+ if @debug_dev
820
+ @debug_dev << "! CONNECTION ESTABLISHED\n"
821
+ socket.extend(DebugSocket)
822
+ socket.debug_dev = @debug_dev
823
+ end
824
+ rescue SystemCallError => e
825
+ e.message << " (#{site})"
826
+ raise
827
+ rescue SocketError => e
828
+ e.message << " (#{site})"
829
+ raise
830
+ end
831
+ socket
832
+ end
833
+
834
+ # wrap socket with OpenSSL.
835
+ def create_ssl_socket(raw_socket)
836
+ SSLSocketWrap.new(raw_socket, @ssl_config, @debug_dev)
837
+ end
838
+
839
+ def connect_ssl_proxy(socket, uri)
840
+ req = HTTP::Message.new_connect_request(uri)
841
+ @client.request_filter.each do |filter|
842
+ filter.filter_request(req)
843
+ end
844
+ set_header(req)
845
+ req.dump(@socket)
846
+ @socket.flush unless @socket_sync
847
+ res = HTTP::Message.new_response('')
848
+ parse_header
849
+ res.http_version, res.status, res.reason = @version, @status, @reason
850
+ @headers.each do |key, value|
851
+ res.header.set(key.to_s, value)
852
+ end
853
+ commands = @client.request_filter.collect { |filter|
854
+ filter.filter_response(req, res)
855
+ }
856
+ if commands.find { |command| command == :retry }
857
+ raise RetryableResponse.new
858
+ end
859
+ unless @status == 200
860
+ raise BadResponseError.new("connect to ssl proxy failed with status #{@status} #{@reason}", res)
861
+ end
862
+ end
863
+
864
+ # Read status block.
865
+ def read_header
866
+ @content_length = nil
867
+ @chunked = false
868
+ @gzipped = false
869
+ @chunk_length = 0
870
+ parse_header
871
+ # Header of the request has been parsed.
872
+ @state = :DATA
873
+ req = @requests.shift
874
+ if req.header.request_method == 'HEAD' or no_message_body?(@status)
875
+ @content_length = 0
876
+ if @next_connection
877
+ @state = :WAIT
878
+ else
879
+ close
880
+ end
881
+ end
882
+ @next_connection = false if !@content_length and !@chunked
883
+ end
884
+
885
+ StatusParseRegexp = %r(\AHTTP/(\d+\.\d+)\s+(\d\d\d)\s*([^\r\n]+)?\r?\n\z)
886
+ def parse_header
887
+ timeout(@receive_timeout, ReceiveTimeoutError) do
888
+ initial_line = nil
889
+ begin
890
+ begin
891
+ initial_line = @socket.gets("\n")
892
+ if initial_line.nil?
893
+ close
894
+ raise KeepAliveDisconnected.new(self)
895
+ end
896
+ rescue Errno::ECONNABORTED, Errno::ECONNRESET, Errno::EPIPE, IOError
897
+ # JRuby can raise IOError instead of ECONNRESET for now
898
+ close
899
+ raise KeepAliveDisconnected.new(self)
900
+ end
901
+ if StatusParseRegexp !~ initial_line
902
+ @version = '0.9'
903
+ @status = nil
904
+ @reason = nil
905
+ @next_connection = false
906
+ @content_length = nil
907
+ @readbuf = initial_line
908
+ break
909
+ end
910
+ @version, @status, @reason = $1, $2.to_i, $3
911
+ @next_connection = HTTP::Message.keep_alive_enabled?(@version)
912
+ @headers = []
913
+ while true
914
+ line = @socket.gets("\n")
915
+ unless line
916
+ raise BadResponseError.new('unexpected EOF')
917
+ end
918
+ line.chomp!
919
+ break if line.empty?
920
+ if line[0] == ?\ or line[0] == ?\t
921
+ last = @headers.last[1]
922
+ last << ' ' unless last.empty?
923
+ last << line.strip
924
+ else
925
+ key, value = line.strip.split(/\s*:\s*/, 2)
926
+ parse_keepalive_header(key, value)
927
+ @headers << [key, value]
928
+ end
929
+ end
930
+ end while (@version == '1.1' && @status == 100)
931
+ end
932
+ end
933
+
934
+ def no_message_body?(status)
935
+ !status.nil? && # HTTP/0.9
936
+ ((status >= 100 && status < 200) || status == 204 || status == 304)
937
+ end
938
+
939
+ def parse_keepalive_header(key, value)
940
+ key = key.downcase
941
+ if key == 'content-length'
942
+ @content_length = value.to_i
943
+ elsif key == 'content-encoding' and ( value.downcase == 'gzip' or
944
+ value.downcase == 'x-gzip' or value.downcase == 'deflate' )
945
+ @gzipped = true
946
+ elsif key == 'transfer-encoding' and value.downcase == 'chunked'
947
+ @chunked = true
948
+ @chunk_length = 0
949
+ @content_length = nil
950
+ elsif key == 'connection' or key == 'proxy-connection'
951
+ if value.downcase == 'keep-alive'
952
+ @next_connection = true
953
+ else
954
+ @next_connection = false
955
+ end
956
+ end
957
+ end
958
+
959
+ def read_body_length(&block)
960
+ return nil if @content_length == 0
961
+ while true
962
+ buf = HTTPClient::Util.get_buf
963
+ maxbytes = @read_block_size
964
+ maxbytes = @content_length if maxbytes > @content_length
965
+ timeout(@receive_timeout, ReceiveTimeoutError) do
966
+ begin
967
+ @socket.readpartial(maxbytes, buf)
968
+ rescue EOFError
969
+ close
970
+ buf = nil
971
+ end
972
+ end
973
+ if buf && buf.bytesize > 0
974
+ @content_length -= buf.bytesize
975
+ yield buf
976
+ else
977
+ @content_length = 0
978
+ end
979
+ return if @content_length == 0
980
+ end
981
+ end
982
+
983
+ RS = "\r\n"
984
+ def read_body_chunked(&block)
985
+ buf = HTTPClient::Util.get_buf
986
+ while true
987
+ len = @socket.gets(RS)
988
+ if len.nil? # EOF
989
+ close
990
+ return
991
+ end
992
+ @chunk_length = len.hex
993
+ if @chunk_length == 0
994
+ @content_length = 0
995
+ @socket.gets(RS)
996
+ return
997
+ end
998
+ timeout(@receive_timeout, ReceiveTimeoutError) do
999
+ @socket.read(@chunk_length + 2, buf)
1000
+ end
1001
+ unless buf.empty?
1002
+ yield buf.slice(0, @chunk_length)
1003
+ end
1004
+ end
1005
+ end
1006
+
1007
+ def read_body_rest
1008
+ if @readbuf and @readbuf.bytesize > 0
1009
+ yield @readbuf
1010
+ @readbuf = nil
1011
+ end
1012
+ while true
1013
+ buf = HTTPClient::Util.get_buf
1014
+ timeout(@receive_timeout, ReceiveTimeoutError) do
1015
+ begin
1016
+ @socket.readpartial(@read_block_size, buf)
1017
+ rescue EOFError
1018
+ buf = nil
1019
+ end
1020
+ end
1021
+ if buf && buf.bytesize > 0
1022
+ yield buf
1023
+ else
1024
+ return
1025
+ end
1026
+ end
1027
+ end
1028
+ end
1029
+
1030
+
1031
+ end