glebtv-httpclient 3.0.0

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