glebtv-httpclient 3.1.1 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.coveralls.yml +1 -0
  3. data/.gitignore +8 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +6 -0
  7. data/CHANGELOG.rdoc +653 -0
  8. data/Gemfile +12 -0
  9. data/Gemfile.lock +284 -0
  10. data/README.md +7 -2
  11. data/Rakefile +22 -0
  12. data/bin/httpclient +1 -1
  13. data/dist_key/cacerts.pem +1808 -0
  14. data/dist_key/cert.pem +24 -0
  15. data/dist_key/gen_dist_cert.rb +29 -0
  16. data/httpclient.gemspec +33 -0
  17. data/lib/httpclient.rb +59 -15
  18. data/lib/httpclient/lru_cache.rb +171 -0
  19. data/lib/httpclient/lru_threadsafe.rb +29 -0
  20. data/lib/httpclient/session.rb +52 -13
  21. data/lib/httpclient/ssl_config.rb +4 -3
  22. data/lib/httpclient/version.rb +1 -1
  23. data/sample/ssl/trust_certs/.keep_me +0 -0
  24. data/spec/http_message_spec.rb +124 -0
  25. data/spec/httpclient_spec.rb +322 -0
  26. data/spec/keepalive_spec.rb +129 -0
  27. data/spec/spec_helper.rb +40 -0
  28. data/spec/support/1024x768.gif +0 -0
  29. data/spec/support/1x1.png +0 -0
  30. data/spec/support/base_server.rb +36 -0
  31. data/spec/support/file.txt +1 -0
  32. data/spec/support/ht_helpers.rb +10 -0
  33. data/spec/support/main_server.rb +155 -0
  34. data/spec/support/proxy_server.rb +14 -0
  35. data/spec/support/test_servlet.rb +73 -0
  36. data/stress-test/Gemfile +4 -0
  37. data/stress-test/Gemfile.lock +16 -0
  38. data/stress-test/client.rb +72 -0
  39. data/stress-test/config.ru +4 -0
  40. data/stress-test/unicorn.conf +2 -0
  41. data/test.rb +19 -0
  42. data/test/helper.rb +4 -3
  43. data/test/test_httpclient.rb +19 -677
  44. metadata +226 -38
  45. data/lib/httpclient/timeout.rb +0 -140
  46. data/test/runner.rb +0 -2
data/dist_key/cert.pem ADDED
@@ -0,0 +1,24 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIID/TCCAuWgAwIBAgIBATANBgkqhkiG9w0BAQ0FADBLMQswCQYDVQQGEwJKUDER
3
+ MA8GA1UECgwIY3Rvci5vcmcxFDASBgNVBAsMC0RldmVsb3BtZW50MRMwEQYDVQQD
4
+ DApodHRwY2xpZW50MB4XDTA5MDUyMTEyMzkwNVoXDTM3MTIzMTIzNTk1OVowSzEL
5
+ MAkGA1UEBhMCSlAxETAPBgNVBAoMCGN0b3Iub3JnMRQwEgYDVQQLDAtEZXZlbG9w
6
+ bWVudDETMBEGA1UEAwwKaHR0cGNsaWVudDCCASIwDQYJKoZIhvcNAQEBBQADggEP
7
+ ADCCAQoCggEBAM2PlkdTH97zvIHoPIMj87wnNvpqIQUD7L/hlysO0XBsmR/XZUeU
8
+ ZKB10JQqMXviWpTnU9KU6xGTx3EI4wfd2dpLwH/d4d7K4LngW1kY7kJlZeJhakno
9
+ GzQ40RSI9WkQ0R9KOE888f7OkTBafcL8UyWFVIMhQBw2d9iNl4Jc69QojayCDoSX
10
+ XbbEP0n8yi7HwIU3RFuX6DtMpOx4/1K7Z002ccOGJ3J9kHgeDQSQtF42cQYC7qj2
11
+ 67I/OQgnB7ycxTCP0E7bdXQg+zqsngrhaoNn/+I+CoO7nD4t4uQ+B4agALh4PPxs
12
+ bQD9MCL+VurNGLYv0HVd+ZlLblpddC9PLTsCAwEAAaOB6zCB6DAPBgNVHRMBAf8E
13
+ BTADAQH/MDEGCWCGSAGG+EIBDQQkFiJSdWJ5L09wZW5TU0wgR2VuZXJhdGVkIENl
14
+ cnRpZmljYXRlMB0GA1UdDgQWBBRAnB6XlMoOcm7HVAw+JWxY205PHTAOBgNVHQ8B
15
+ Af8EBAMCAQYwcwYDVR0jBGwwaoAUQJwel5TKDnJux1QMPiVsWNtOTx2hT6RNMEsx
16
+ CzAJBgNVBAYTAkpQMREwDwYDVQQKDAhjdG9yLm9yZzEUMBIGA1UECwwLRGV2ZWxv
17
+ cG1lbnQxEzARBgNVBAMMCmh0dHBjbGllbnSCAQEwDQYJKoZIhvcNAQENBQADggEB
18
+ ABVFepybD5XqsBnOn/oDHvK0xAPMF4Ap4Ht1yMQLObg8paVhANSdqIevPlCr/mPL
19
+ DRjcy+J1fCnE6lCfsfLdTgAjirqt8pm92NccxmJ8hTmMd3LWC1n+eYWaolqTCVRM
20
+ Bpe8UY9enyXrFoudHlr9epr18E6As6VrCSfpXFZkD9WHVSWpzkB3qATu5qcDCzCH
21
+ bI0755Mdm/1hKJCD4l69h3J3OhRIEUPJfHnPvM5wtiyC2dcE9itwE/wdVzBJeIBX
22
+ JQm+Qj+K8qXcRTzZZGIBjw2n46xJgW6YncNCHU/WWfNCYwdkngHS/aN8IbEjhCwf
23
+ viXFisVrDN/+pZZGMf67ZaY=
24
+ -----END CERTIFICATE-----
@@ -0,0 +1,29 @@
1
+ require 'openssl'
2
+ include OpenSSL
3
+
4
+ keypair = PKey::RSA.new(File.read("keypair.pem"))
5
+
6
+ now = Time.now
7
+ cert = X509::Certificate.new
8
+ name = X509::Name.parse("/C=JP/O=ctor.org/OU=Development/CN=httpclient")
9
+ cert.subject = cert.issuer = X509::Name.new(name)
10
+ cert.not_before = now
11
+ cert.not_after = Time.mktime(2038, 1, 1, 8, 59, 59)
12
+ cert.public_key = keypair.public_key
13
+ cert.serial = 0x1
14
+ cert.version = 2 # X509v3
15
+
16
+ key_usage = ["cRLSign", "keyCertSign"]
17
+ ef = X509::ExtensionFactory.new
18
+ ef.subject_certificate = cert
19
+ ef.issuer_certificate = cert # we needed subjectKeyInfo inside, now we have it
20
+ ext1 = ef.create_extension("basicConstraints","CA:TRUE", true)
21
+ ext2 = ef.create_extension("nsComment","Ruby/OpenSSL Generated Certificate")
22
+ ext3 = ef.create_extension("subjectKeyIdentifier", "hash")
23
+ ext4 = ef.create_extension("keyUsage", key_usage.join(","), true)
24
+ cert.extensions = [ext1, ext2, ext3, ext4]
25
+ ext0 = ef.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always")
26
+ cert.add_extension(ext0)
27
+ cert.sign(keypair, OpenSSL::Digest::SHA512.new)
28
+
29
+ print cert.to_pem
@@ -0,0 +1,33 @@
1
+ # encoding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'httpclient/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'glebtv-httpclient'
8
+ spec.version = HTTPClient::VERSION
9
+ spec.authors = ['glebtv', 'Hiroshi Nakamura']
10
+ spec.email = 'glebtv@gmail.com'
11
+ spec.executables = ['httpclient']
12
+ spec.homepage = 'http://github.com/glebtv/httpclient'
13
+ spec.platform = Gem::Platform::RUBY
14
+ spec.summary = 'Fork of httpclient with some fixes and patches I needed. Please use original gem instead'
15
+
16
+ spec.license = 'ruby'
17
+
18
+ spec.files = `git ls-files`.split($/)
19
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.add_dependency "activesupport"
24
+ spec.add_dependency "PriorityQueue"
25
+ spec.add_dependency "addressable"
26
+
27
+ spec.add_development_dependency "bundler"
28
+ spec.add_development_dependency "rake"
29
+ spec.add_development_dependency "test-unit"
30
+ spec.add_development_dependency "rspec"
31
+ spec.add_development_dependency "rdoc"
32
+ spec.add_development_dependency "coveralls"
33
+ end
data/lib/httpclient.rb CHANGED
@@ -18,6 +18,12 @@ require 'httpclient/session'
18
18
  require 'httpclient/http'
19
19
  require 'httpclient/auth'
20
20
  require 'httpclient/cookie'
21
+ require 'httpclient/cookie'
22
+
23
+ require 'httpclient/lru_cache'
24
+ require 'httpclient/lru_threadsafe'
25
+
26
+ require 'active_support/core_ext'
21
27
 
22
28
  # :main:HTTPClient
23
29
  # The HTTPClient class provides several methods for accessing Web resources
@@ -74,7 +80,7 @@ require 'httpclient/cookie'
74
80
  #
75
81
  # === Invoking other HTTP methods
76
82
  #
77
- # See head, get, post, put, delete, options, propfind, proppatch and trace.
83
+ # See head, get, post, put, patch, delete, options, propfind, proppatch and trace.
78
84
  # It returns a HTTP::Message instance as a response.
79
85
  #
80
86
  # 1. Do HEAD request.
@@ -229,6 +235,13 @@ require 'httpclient/cookie'
229
235
  # ruby -rhttpclient -e 'p HTTPClient.head(ARGV.shift).header["last-modified"]' http://dev.ctor.org/
230
236
  #
231
237
  class HTTPClient
238
+ @@dns_cache = HTTPClient::ThreadSafeCache.new(ttl: 20.minutes, soft_ttl: 10.minute, retry_delay: 5.minutes)
239
+ cattr_accessor :dns_cache
240
+
241
+ def own_methods
242
+ (methods - (self.class.ancestors - [self.class]).collect { |k| k.instance_methods }.flatten).sort
243
+ end
244
+
232
245
  RUBY_VERSION_STRING = "ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE})"
233
246
  LIB_NAME = "(#{VERSION}, #{RUBY_VERSION_STRING})"
234
247
 
@@ -358,7 +371,7 @@ class HTTPClient
358
371
  PROPFIND_DEFAULT_EXTHEADER = { 'Depth' => '0' }
359
372
 
360
373
  # Default User-Agent header
361
- DEFAULT_AGENT_NAME = 'HTTPClient/1.0'
374
+ DEFAULT_AGENT_NAME = "HTTPClient #{VERSION}"
362
375
 
363
376
  # Creates a HTTPClient instance which manages sessions, cookies, etc.
364
377
  #
@@ -661,7 +674,9 @@ class HTTPClient
661
674
  warn("'The field value consists of a single absolute URI' in HTTP spec")
662
675
  end
663
676
  if https?(uri) && !https?(newuri)
664
- raise BadResponseError.new("redirecting to non-https resource")
677
+ #raise BadResponseError.new("redirecting to non-https resource")
678
+ # allow redirect to non-https but warn
679
+ warn("redirecting to non-https resource")
665
680
  end
666
681
  puts "redirect to: #{newuri}" if $DEBUG
667
682
  newuri
@@ -689,6 +704,11 @@ class HTTPClient
689
704
  request(:put, uri, argument_to_hash(args, :body, :header), &block)
690
705
  end
691
706
 
707
+ # Sends PATCH request to the specified URL. See request for arguments.
708
+ def patch(uri, *args, &block)
709
+ request(:patch, uri, argument_to_hash(args, :body, :header), &block)
710
+ end
711
+
692
712
  # Sends DELETE request to the specified URL. See request for arguments.
693
713
  def delete(uri, *args, &block)
694
714
  request(:delete, uri, argument_to_hash(args, :body, :header), &block)
@@ -713,7 +733,16 @@ class HTTPClient
713
733
  def trace(uri, *args, &block)
714
734
  request('TRACE', uri, argument_to_hash(args, :query, :header), &block)
715
735
  end
716
-
736
+
737
+ def download_file(uri, file, *args)
738
+ io = File.open(file, 'a+b')
739
+ request(:get, uri, argument_to_hash(args, :query, :header, :follow_redirect)) do |s|
740
+ io.write(s)
741
+ end
742
+ io.flush
743
+ io
744
+ end
745
+
717
746
  # Sends a request to the specified URL.
718
747
  #
719
748
  # method:: HTTP method to be sent. method.to_s.upcase is used.
@@ -753,8 +782,8 @@ class HTTPClient
753
782
  # respond to :read. Bear in mind that some server application does not support
754
783
  # chunked request. At least cgi.rb does not support it.
755
784
  def request(method, uri, *args, &block)
756
- query, body, header, follow_redirect = keyword_argument(args, :query, :body, :header, :follow_redirect)
757
- if [:post, :put].include?(method)
785
+ query, body, header, is_follow_redirect, filter_block = keyword_argument(args, :query, :body, :header, :follow_redirect, :filter_block)
786
+ if [:post, :put, :patch].include?(method)
758
787
  body ||= ''
759
788
  end
760
789
  if method == :propfind
@@ -764,12 +793,16 @@ class HTTPClient
764
793
  end
765
794
  uri = urify(uri)
766
795
  if block
767
- filtered_block = proc { |res, str|
768
- block.call(str)
769
- }
796
+ if filter_block === false
797
+ filtered_block = block
798
+ else
799
+ filtered_block = proc { |res, str|
800
+ block.call(str)
801
+ }
802
+ end
770
803
  end
771
- if follow_redirect
772
- follow_redirect(method, uri, query, body, header, &block)
804
+ if is_follow_redirect
805
+ follow_redirect(method, uri, query, body, header, filter_block, &block)
773
806
  else
774
807
  do_request(method, uri, query, body, header, &filtered_block)
775
808
  end
@@ -803,6 +836,13 @@ class HTTPClient
803
836
  request_async(:put, uri, nil, body || '', header || {})
804
837
  end
805
838
 
839
+ # Sends PATCH request in async style. See request_async for arguments.
840
+ # It immediately returns a HTTPClient::Connection instance as a result.
841
+ def patch_async(uri, *args)
842
+ body, header = keyword_argument(args, :body, :header)
843
+ request_async(:patch, uri, nil, body || '', header || {})
844
+ end
845
+
806
846
  # Sends DELETE request in async style. See request_async for arguments.
807
847
  # It immediately returns a HTTPClient::Connection instance as a result.
808
848
  def delete_async(uri, *args)
@@ -943,12 +983,16 @@ private
943
983
  ENV[name.downcase] || ENV[name.upcase]
944
984
  end
945
985
 
946
- def follow_redirect(method, uri, query, body, header, &block)
986
+ def follow_redirect(method, uri, query, body, header, filter_block = true, &block)
947
987
  uri = urify(uri)
948
988
  if block
949
- filtered_block = proc { |r, str|
950
- block.call(str) if r.ok?
951
- }
989
+ if filter_block
990
+ filtered_block = proc { |r, str|
991
+ block.call(str) if r.ok?
992
+ }
993
+ else
994
+ filtered_block = block
995
+ end
952
996
  end
953
997
  if HTTP::Message.file?(body)
954
998
  pos = body.pos rescue nil
@@ -0,0 +1,171 @@
1
+ # for DNS cache
2
+ # We do it manually because Ruby won't do it for us
3
+ # Taken from: https://github.com/kindkid/lrucache/blob/master/lib/lrucache.rb
4
+ # (MIT Licensed). Thanks!
5
+
6
+ require "priority_queue"
7
+
8
+ # Not thread-safe!
9
+ class HTTPClient
10
+ class LRUCache
11
+
12
+ attr_reader :default, :max_size, :ttl, :soft_ttl, :retry_delay
13
+
14
+ def initialize(opts={})
15
+ @max_size = Integer(opts[:max_size] || 100)
16
+ @default = opts[:default]
17
+ @eviction_handler = opts[:eviction_handler]
18
+ @ttl = Float(opts[:ttl] || 0)
19
+ @soft_ttl = Float(opts[:soft_ttl] || 0)
20
+ @retry_delay = Float(opts[:retry_delay] || 0)
21
+ raise "max_size must not be negative" if @max_size < 0
22
+ raise "ttl must not be negative" if @ttl < 0
23
+ raise "soft_ttl must not be negative" if @soft_ttl < 0
24
+ raise "retry_delay must not be negative" if @retry_delay < 0
25
+
26
+ @pqueue = PriorityQueue.new
27
+ @data = {}
28
+ @counter = 0
29
+ end
30
+
31
+ def clear
32
+ @data.clear
33
+ @pqueue.delete_min until @pqueue.empty?
34
+ @counter = 0 #might as well
35
+ end
36
+
37
+ def include?(key)
38
+ datum = @data[key]
39
+ return false if datum.nil?
40
+ if datum.expired?
41
+ delete(key)
42
+ false
43
+ else
44
+ access(key)
45
+ true
46
+ end
47
+ end
48
+
49
+ def store(key, value, args={})
50
+ evict_lru! unless @data.include?(key) || @data.size < max_size
51
+ ttl, soft_ttl, retry_delay = extract_arguments(args)
52
+ expiration = expiration_date(ttl)
53
+ soft_expiration = expiration_date(soft_ttl)
54
+ @data[key] = Datum.new(value, expiration, soft_expiration)
55
+ access(key)
56
+ value
57
+ end
58
+
59
+ alias :[]= :store
60
+
61
+ def fetch(key, args={})
62
+ datum = @data[key]
63
+ if datum.nil?
64
+ if block_given?
65
+ store(key, value = yield, args)
66
+ else
67
+ @default
68
+ end
69
+ elsif datum.expired?
70
+ delete(key)
71
+ if block_given?
72
+ store(key, value = yield, args)
73
+ else
74
+ @default
75
+ end
76
+ elsif datum.soft_expired?
77
+ if block_given?
78
+ begin
79
+ store(key, value = yield, args)
80
+ rescue RuntimeError => e
81
+ access(key)
82
+ ttl, soft_ttl, retry_delay = extract_arguments(args)
83
+ datum.soft_expiration = (Time.now + retry_delay) if retry_delay > 0
84
+ datum.value
85
+ end
86
+ else
87
+ access(key)
88
+ datum.value
89
+ end
90
+ else
91
+ access(key)
92
+ datum.value
93
+ end
94
+ end
95
+
96
+ alias :[] :fetch
97
+
98
+ def empty?
99
+ size == 0
100
+ end
101
+
102
+ def size
103
+ @data.size
104
+ end
105
+
106
+ def keys
107
+ @data.keys
108
+ end
109
+
110
+ def delete(key)
111
+ @pqueue.delete(key)
112
+ datum = @data.delete(key)
113
+ datum.value unless datum.nil?
114
+ end
115
+
116
+ private
117
+
118
+ class Datum
119
+ attr_reader :value, :expiration, :soft_expiration
120
+ attr_writer :soft_expiration
121
+ def initialize(value, expiration, soft_expiration)
122
+ @value = value
123
+ @expiration = expiration
124
+ @soft_expiration = soft_expiration
125
+ end
126
+
127
+ def expired?
128
+ !@expiration.nil? && @expiration <= Time.now
129
+ end
130
+
131
+ def soft_expired?
132
+ !@soft_expiration.nil? && @soft_expiration <= Time.now
133
+ end
134
+ end
135
+
136
+ def expiration_date(ttl)
137
+ if ttl.is_a?(Time)
138
+ ttl
139
+ else
140
+ ttl = Float(ttl)
141
+ (ttl > 0) ? (Time.now + ttl) : nil
142
+ end
143
+ end
144
+
145
+ def extract_arguments(args)
146
+ if args.is_a?(Hash)
147
+ ttl = args[:ttl] || @ttl
148
+ soft_ttl = args[:soft_ttl] || @soft_ttl
149
+ retry_delay = args[:retry_delay] || @retry_delay
150
+ [ttl, soft_ttl, retry_delay]
151
+ else
152
+ # legacy arg
153
+ ttl = args || @ttl
154
+ [ttl, @soft_ttl, @retry_delay]
155
+ end
156
+ end
157
+
158
+ def evict_lru!
159
+ key, priority = @pqueue.delete_min
160
+ unless priority.nil?
161
+ datum = @data.delete(key)
162
+ @eviction_handler.call(datum.value) if @eviction_handler && datum
163
+ end
164
+ end
165
+
166
+ def access(key)
167
+ @pqueue.change_priority(key, @counter += 1)
168
+ end
169
+
170
+ end
171
+ end
@@ -0,0 +1,29 @@
1
+ # Make LRU cache Threadsafe
2
+ # Idea from: https://github.com/SamSaffron/lru_redux/blob/master/lib/lru_redux/thread_safe_cache.rb
3
+ # (MIT License)
4
+
5
+ require 'thread'
6
+ require 'monitor'
7
+
8
+ class HTTPClient::ThreadSafeCache < HTTPClient::LRUCache
9
+ include MonitorMixin
10
+
11
+ def initialize(opts={})
12
+ super(opts)
13
+ end
14
+
15
+ def self.synchronize(*methods)
16
+ methods.each do |method|
17
+ define_method method do |*args, &blk|
18
+ synchronize do
19
+ super(*args, &blk)
20
+ end
21
+ end
22
+ end
23
+
24
+ alias :[]= :store
25
+ alias :[] :fetch
26
+ end
27
+
28
+ synchronize :fetch, :store, :delete, :clear, :include?, :empty, :size, :keys
29
+ end
@@ -11,21 +11,19 @@
11
11
  # I asked Maebashi-san he agreed that I can redistribute it under the same terms
12
12
  # of Ruby. Many thanks to Maebashi-san.
13
13
 
14
-
14
+ require 'ipaddr'
15
15
  require 'socket'
16
16
  require 'thread'
17
17
  require 'stringio'
18
18
  require 'zlib'
19
+ require 'timeout'
20
+ require 'resolv'
19
21
 
20
- require 'httpclient/timeout'
21
22
  require 'httpclient/ssl_config'
22
23
  require 'httpclient/http'
23
24
  require 'httpclient/util'
24
25
 
25
-
26
26
  class HTTPClient
27
-
28
-
29
27
  # Represents a Site: protocol scheme, host String and port Number.
30
28
  class Site
31
29
  # Protocol scheme.
@@ -157,6 +155,13 @@ class HTTPClient
157
155
  @proxy = Site.new(proxy)
158
156
  end
159
157
  end
158
+
159
+ def timeout(time, exn = nil, &block)
160
+ return yield if time.nil? || time.zero?
161
+ Timeout.timeout(time, exn) do
162
+ yield
163
+ end
164
+ end
160
165
 
161
166
  def query(req, via_proxy)
162
167
  req.http_body.chunk_size = @chunk_size
@@ -510,7 +515,13 @@ class HTTPClient
510
515
  str.force_encoding('BINARY') if str.respond_to?(:force_encoding)
511
516
  @debug_dev << HexDump.encode(str).join("\n")
512
517
  else
513
- @debug_dev << str
518
+ begin
519
+ @debug_dev << str
520
+ rescue
521
+ require 'hexdump'
522
+ str.force_encoding('BINARY') if str.respond_to?(:force_encoding)
523
+ @debug_dev << HexDump.encode(str).join("\n")
524
+ end
514
525
  end
515
526
  end
516
527
  end
@@ -535,7 +546,6 @@ class HTTPClient
535
546
 
536
547
  # Manages a HTTP session with a Site.
537
548
  class Session
538
- include HTTPClient::Timeout
539
549
  include Util
540
550
 
541
551
  # Destination site
@@ -736,7 +746,7 @@ class HTTPClient
736
746
  end
737
747
  end
738
748
  if @agent_name && req.header.get('User-Agent').empty?
739
- req.header.set('User-Agent', "#{@agent_name} #{LIB_NAME}")
749
+ req.header.set('User-Agent', "#{@agent_name}")
740
750
  end
741
751
  if @from && req.header.get('From').empty?
742
752
  req.header.set('From', @from)
@@ -806,12 +816,32 @@ class HTTPClient
806
816
  socket = nil
807
817
  begin
808
818
  @debug_dev << "! CONNECT TO #{site.host}:#{site.port}\n" if @debug_dev
819
+ clean_host = site.host.delete("[]")
820
+ clean_local = @socket_local.host.delete("[]")
809
821
  if str = @test_loopback_http_response.shift
810
- socket = LoopBackSocket.new(site.host, site.port, str)
811
- elsif @socket_local == Site::EMPTY
812
- socket = TCPSocket.new(site.host, site.port)
822
+ socket = LoopBackSocket.new(clean_host, site.port, str)
813
823
  else
814
- socket = TCPSocket.new(site.host, site.port, @socket_local.host, @socket_local.port)
824
+ begin
825
+ ip = IPAddr.new(clean_host).to_s
826
+ # puts "! #{site.host} IS AN IP!\n"
827
+ @debug_dev << "! #{site.host} IS AN IP!\n" if @debug_dev
828
+ rescue
829
+ ip = HTTPClient.dns_cache.fetch clean_host do
830
+ Timeout.timeout(10) do
831
+ @debug_dev << "! RESOLVING #{clean_host}" if @debug_dev
832
+ ip = Resolv.getaddress(clean_host)
833
+ # puts "! RESOLVED #{clean_host} TO #{ip}\n"
834
+ @debug_dev << "! RESOLVED #{clean_host} TO #{ip}\n" if @debug_dev
835
+ ip
836
+ end
837
+ end
838
+ end
839
+
840
+ if @socket_local == Site::EMPTY
841
+ socket = TCPSocket.new(ip, site.port)
842
+ else
843
+ socket = TCPSocket.new(ip, site.port, clean_local, @socket_local.port)
844
+ end
815
845
  end
816
846
  if @debug_dev
817
847
  @debug_dev << "! CONNECTION ESTABLISHED\n"
@@ -957,8 +987,9 @@ class HTTPClient
957
987
  return nil if @content_length == 0
958
988
  while true
959
989
  buf = HTTPClient::Util.get_buf
990
+
960
991
  maxbytes = @read_block_size
961
- maxbytes = @content_length if maxbytes > @content_length
992
+ maxbytes = @content_length if maxbytes > @content_length && @content_length > 0
962
993
  timeout(@receive_timeout, ReceiveTimeoutError) do
963
994
  begin
964
995
  @socket.readpartial(maxbytes, buf)
@@ -980,6 +1011,7 @@ class HTTPClient
980
1011
  RS = "\r\n"
981
1012
  def read_body_chunked(&block)
982
1013
  buf = HTTPClient::Util.get_buf
1014
+
983
1015
  while true
984
1016
  len = @socket.gets(RS)
985
1017
  if len.nil? # EOF
@@ -1009,6 +1041,7 @@ class HTTPClient
1009
1041
  end
1010
1042
  while true
1011
1043
  buf = HTTPClient::Util.get_buf
1044
+
1012
1045
  timeout(@receive_timeout, ReceiveTimeoutError) do
1013
1046
  begin
1014
1047
  @socket.readpartial(@read_block_size, buf)
@@ -1023,6 +1056,12 @@ class HTTPClient
1023
1056
  end
1024
1057
  end
1025
1058
  end
1059
+
1060
+ def empty_bin_str
1061
+ str = ''
1062
+ str.force_encoding('BINARY') if str.respond_to?(:force_encoding)
1063
+ str
1064
+ end
1026
1065
  end
1027
1066
 
1028
1067