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.
- 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
|
|