glebtv-httpclient 3.1.1 → 3.2.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 (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