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.
- checksums.yaml +4 -4
- data/.coveralls.yml +1 -0
- data/.gitignore +8 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +6 -0
- data/CHANGELOG.rdoc +653 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +284 -0
- data/README.md +7 -2
- data/Rakefile +22 -0
- data/bin/httpclient +1 -1
- data/dist_key/cacerts.pem +1808 -0
- data/dist_key/cert.pem +24 -0
- data/dist_key/gen_dist_cert.rb +29 -0
- data/httpclient.gemspec +33 -0
- data/lib/httpclient.rb +59 -15
- data/lib/httpclient/lru_cache.rb +171 -0
- data/lib/httpclient/lru_threadsafe.rb +29 -0
- data/lib/httpclient/session.rb +52 -13
- data/lib/httpclient/ssl_config.rb +4 -3
- data/lib/httpclient/version.rb +1 -1
- data/sample/ssl/trust_certs/.keep_me +0 -0
- data/spec/http_message_spec.rb +124 -0
- data/spec/httpclient_spec.rb +322 -0
- data/spec/keepalive_spec.rb +129 -0
- data/spec/spec_helper.rb +40 -0
- data/spec/support/1024x768.gif +0 -0
- data/spec/support/1x1.png +0 -0
- data/spec/support/base_server.rb +36 -0
- data/spec/support/file.txt +1 -0
- data/spec/support/ht_helpers.rb +10 -0
- data/spec/support/main_server.rb +155 -0
- data/spec/support/proxy_server.rb +14 -0
- data/spec/support/test_servlet.rb +73 -0
- data/stress-test/Gemfile +4 -0
- data/stress-test/Gemfile.lock +16 -0
- data/stress-test/client.rb +72 -0
- data/stress-test/config.ru +4 -0
- data/stress-test/unicorn.conf +2 -0
- data/test.rb +19 -0
- data/test/helper.rb +4 -3
- data/test/test_httpclient.rb +19 -677
- metadata +226 -38
- data/lib/httpclient/timeout.rb +0 -140
- 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
|
data/httpclient.gemspec
ADDED
@@ -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 =
|
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,
|
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
|
-
|
768
|
-
block
|
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
|
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
|
-
|
950
|
-
|
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
|
data/lib/httpclient/session.rb
CHANGED
@@ -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
|
-
|
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}
|
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(
|
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
|
-
|
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
|
|