dagger 1.7.1 → 1.8.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/dagger.gemspec +0 -1
- data/lib/dagger.rb +45 -21
- data/lib/dagger/connection_manager.rb +59 -0
- data/lib/dagger/ox_extension.rb +25 -14
- data/lib/dagger/parsers.rb +1 -1
- data/lib/dagger/version.rb +2 -2
- data/spec/parsers_spec.rb +5 -2
- data/spec/persistent_spec.rb +46 -0
- metadata +3 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a3be984806667b1144778e3695d47171000733ad0c2ecbc1323b36c8f97d666e
|
4
|
+
data.tar.gz: e27de03a5b62085bb6de4954e6be2a15f14bc97a7c6933dfa92467fdaec33ed5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5237f119527a6a77f1075ab8b65048947e68c23d20fd523f1c220ebf5216997660f3b2acac75560082e956297087f7ba9f090ca118e973fbbb66c941d4ef393c
|
7
|
+
data.tar.gz: 9a9a509cb9ff96338fa73fd711532704c27267916c2dda6e2e05c35446eeba4ec449f6aeba5bea2d4387ecac73ea69f18cceed325e9dbac263bd4d5811b8bc84
|
data/dagger.gemspec
CHANGED
@@ -19,7 +19,6 @@ Gem::Specification.new do |s|
|
|
19
19
|
s.add_development_dependency "rspec-mocks"
|
20
20
|
s.add_development_dependency "rspec-expectations"
|
21
21
|
|
22
|
-
s.add_runtime_dependency "net-http-persistent", "~> 3.0"
|
23
22
|
s.add_runtime_dependency "oj", "~> 2.1"
|
24
23
|
s.add_runtime_dependency "ox", "~> 2.4"
|
25
24
|
|
data/lib/dagger.rb
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
require 'dagger/version'
|
2
2
|
require 'dagger/response'
|
3
3
|
require 'dagger/parsers'
|
4
|
-
|
5
|
-
require 'net/http/persistent'
|
4
|
+
require 'dagger/connection_manager'
|
6
5
|
require 'net/https'
|
7
6
|
require 'base64'
|
8
7
|
|
@@ -19,7 +18,13 @@ module Dagger
|
|
19
18
|
DEFAULT_RETRY_WAIT = 5.freeze # seconds
|
20
19
|
DEFAULT_HEADERS = {
|
21
20
|
'Accept' => '*/*',
|
22
|
-
'User-Agent' => "#{DAGGER_NAME} (Ruby Net::HTTP
|
21
|
+
'User-Agent' => "#{DAGGER_NAME} (Ruby Net::HTTP wrapper, like curl)"
|
22
|
+
}
|
23
|
+
|
24
|
+
DEFAULTS = {
|
25
|
+
open_timeout: 10,
|
26
|
+
read_timeout: 10,
|
27
|
+
keep_alive_timeout: 10
|
23
28
|
}
|
24
29
|
|
25
30
|
module Utils
|
@@ -34,6 +39,7 @@ module Dagger
|
|
34
39
|
end
|
35
40
|
|
36
41
|
def self.resolve_uri(uri, host = nil, query = nil)
|
42
|
+
uri = host + uri if uri[0] == '/' && host
|
37
43
|
uri = parse_uri(uri.to_s)
|
38
44
|
uri.path.sub!(/\?.*|$/, '?' + Utils.encode(query)) if query and query.any?
|
39
45
|
uri
|
@@ -61,25 +67,40 @@ module Dagger
|
|
61
67
|
|
62
68
|
class Client
|
63
69
|
|
64
|
-
def self.
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
70
|
+
def self.init_persistent(opts = {})
|
71
|
+
# this line below forces one connection manager between multiple threads
|
72
|
+
# @persistent ||= Dagger::ConnectionManager.new(opts)
|
73
|
+
|
74
|
+
# here we initialize a connection manager for each thread
|
75
|
+
Thread.current[:dagger_persistent] ||= begin
|
76
|
+
Dagger::ConnectionManager.new(opts)
|
71
77
|
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.init_connection(uri, opts = {})
|
81
|
+
http = Net::HTTP.new(opts[:ip] || uri.host, uri.port)
|
72
82
|
|
73
83
|
if uri.port == 443
|
74
84
|
http.use_ssl = true if http.respond_to?(:use_ssl=) # persistent does it automatically
|
75
85
|
http.verify_mode = opts[:verify_ssl] === false ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER
|
76
86
|
end
|
77
87
|
|
78
|
-
[:open_timeout, :read_timeout, :ssl_version, :ciphers].each do |key|
|
79
|
-
http.send("#{key}=", opts[key]) if opts.has_key?(key)
|
88
|
+
[:keep_alive_timeout, :open_timeout, :read_timeout, :ssl_version, :ciphers].each do |key|
|
89
|
+
http.send("#{key}=", opts[key] || DEFAULTS[key]) if (opts.has_key?(key) || DEFAULTS.has_key?(key))
|
90
|
+
end
|
91
|
+
|
92
|
+
http
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.init(uri, opts)
|
96
|
+
uri = Utils.parse_uri(uri)
|
97
|
+
|
98
|
+
http = if opts.delete(:persistent)
|
99
|
+
init_persistent(opts)
|
100
|
+
else
|
101
|
+
init_connection(uri, opts)
|
80
102
|
end
|
81
103
|
|
82
|
-
# new(http, [uri.scheme, uri.host].join('://'))
|
83
104
|
new(http, uri.scheme_and_host)
|
84
105
|
end
|
85
106
|
|
@@ -89,8 +110,9 @@ module Dagger
|
|
89
110
|
|
90
111
|
def get(uri, opts = {})
|
91
112
|
uri = Utils.resolve_uri(uri, @host, opts[:query])
|
113
|
+
|
92
114
|
if @host != uri.scheme_and_host
|
93
|
-
raise ArgumentError.new("#{uri.scheme_and_host}
|
115
|
+
raise ArgumentError.new("#{uri.scheme_and_host} does not match #{@host}")
|
94
116
|
end
|
95
117
|
|
96
118
|
opts[:follow] = 10 if opts[:follow] == true
|
@@ -109,7 +131,7 @@ module Dagger
|
|
109
131
|
@http.start unless @http.started?
|
110
132
|
resp, data = @http.request(request)
|
111
133
|
else # persistent
|
112
|
-
resp, data = @http.
|
134
|
+
resp, data = @http.send_request(uri, request)
|
113
135
|
end
|
114
136
|
|
115
137
|
if REDIRECT_CODES.include?(resp.code.to_i) && resp['Location'] && (opts[:follow] && opts[:follow] > 0)
|
@@ -121,7 +143,8 @@ module Dagger
|
|
121
143
|
@response = build_response(resp, data || resp.body)
|
122
144
|
|
123
145
|
rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ETIMEDOUT, Errno::EINVAL, Timeout::Error, \
|
124
|
-
|
146
|
+
Net::OpenTimeout, Net::ReadTimeout, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError, \
|
147
|
+
SocketError, EOFError, OpenSSL::SSL::SSLError => e
|
125
148
|
|
126
149
|
if retries = opts[:retries] and retries.to_i > 0
|
127
150
|
debug "Got #{e.class}! Retrying in a sec (#{retries} retries left)"
|
@@ -156,10 +179,11 @@ module Dagger
|
|
156
179
|
|
157
180
|
uri = Utils.resolve_uri(uri, @host)
|
158
181
|
if @host != uri.scheme_and_host
|
159
|
-
raise ArgumentError.new("#{uri.scheme_and_host}
|
182
|
+
raise ArgumentError.new("#{uri.scheme_and_host} does not match #{@host}")
|
160
183
|
end
|
161
184
|
|
162
185
|
headers = DEFAULT_HEADERS.merge(opts[:headers] || {})
|
186
|
+
|
163
187
|
query = if data.is_a?(String)
|
164
188
|
data
|
165
189
|
elsif opts[:json]
|
@@ -185,7 +209,7 @@ module Dagger
|
|
185
209
|
req = Kernel.const_get("Net::HTTP::#{method.capitalize}").new(uri.path, headers)
|
186
210
|
# req.set_form_data(query)
|
187
211
|
req.body = query
|
188
|
-
resp, data = @http.
|
212
|
+
resp, data = @http.send_request(uri, req)
|
189
213
|
end
|
190
214
|
|
191
215
|
@response = build_response(resp, data || resp.body)
|
@@ -208,7 +232,7 @@ module Dagger
|
|
208
232
|
end
|
209
233
|
|
210
234
|
def open(&block)
|
211
|
-
if @http.is_a?(
|
235
|
+
if @http.is_a?(Dagger::ConnectionManager)
|
212
236
|
instance_eval(&block)
|
213
237
|
else
|
214
238
|
@http.start do
|
@@ -218,7 +242,7 @@ module Dagger
|
|
218
242
|
end
|
219
243
|
|
220
244
|
def close
|
221
|
-
if @http.is_a?(
|
245
|
+
if @http.is_a?(Dagger::ConnectionManager)
|
222
246
|
@http.shutdown # calls finish on pool connections
|
223
247
|
else
|
224
248
|
@http.finish if @http.started?
|
@@ -276,4 +300,4 @@ module Dagger
|
|
276
300
|
|
277
301
|
end
|
278
302
|
|
279
|
-
end
|
303
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Dagger
|
2
|
+
|
3
|
+
class ConnectionManager
|
4
|
+
|
5
|
+
def initialize(opts = {})
|
6
|
+
@opts = {}
|
7
|
+
@active_connections = {}
|
8
|
+
@mutex = Mutex.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def shutdown
|
12
|
+
@mutex.synchronize do
|
13
|
+
# puts "Shutting down connections: #{@active_connections.count}"
|
14
|
+
@active_connections.each do |_, connection|
|
15
|
+
connection.finish
|
16
|
+
end
|
17
|
+
@active_connections = {}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Gets a connection for a given URI. This is for internal use only as it's
|
22
|
+
# subject to change (we've moved between HTTP client schemes in the past
|
23
|
+
# and may do it again).
|
24
|
+
#
|
25
|
+
# `uri` is expected to be a string.
|
26
|
+
def connection_for(uri)
|
27
|
+
@mutex.synchronize do
|
28
|
+
connection = @active_connections[[uri.host, uri.port]]
|
29
|
+
|
30
|
+
if connection.nil?
|
31
|
+
connection = Dagger::Client.init_connection(uri, @opts)
|
32
|
+
connection.start
|
33
|
+
|
34
|
+
@active_connections[[uri.host, uri.port]] = connection
|
35
|
+
# puts "#{@active_connections.count} connections"
|
36
|
+
end
|
37
|
+
|
38
|
+
connection
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Executes an HTTP request to the given URI with the given method. Also
|
43
|
+
# allows a request body, headers, and query string to be specified.
|
44
|
+
def send_request(uri, request)
|
45
|
+
connection = connection_for(uri)
|
46
|
+
@mutex.synchronize do
|
47
|
+
begin
|
48
|
+
connection.request(request)
|
49
|
+
rescue StandardError => err
|
50
|
+
err
|
51
|
+
end
|
52
|
+
end.tap do |result|
|
53
|
+
raise(result) if result.is_a?(StandardError)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
data/lib/dagger/ox_extension.rb
CHANGED
@@ -5,12 +5,20 @@ XMLNode = Struct.new(:name, :text, :attributes, :children) do
|
|
5
5
|
alias_method :to_s, :text
|
6
6
|
alias_method :value, :text
|
7
7
|
|
8
|
+
def to_node
|
9
|
+
self
|
10
|
+
end
|
11
|
+
|
8
12
|
def count
|
9
13
|
raise "Please call #children.count"
|
10
14
|
end
|
11
15
|
|
12
|
-
def
|
13
|
-
|
16
|
+
def keys
|
17
|
+
@keys ||= children.collect(&:name)
|
18
|
+
end
|
19
|
+
|
20
|
+
def is_array?
|
21
|
+
keys.count != keys.uniq.count
|
14
22
|
end
|
15
23
|
|
16
24
|
# this lets us traverse an parsed object like this:
|
@@ -20,30 +28,33 @@ XMLNode = Struct.new(:name, :text, :attributes, :children) do
|
|
20
28
|
found.empty? ? nil : found.size == 1 ? found.first : found
|
21
29
|
end
|
22
30
|
|
31
|
+
# returns list of XMLNodes with matching names
|
23
32
|
def slice(*arr)
|
24
33
|
Array(arr).flatten.map { |key| self[key] }
|
25
34
|
end
|
26
35
|
|
27
|
-
def values(
|
28
|
-
if
|
29
|
-
Array(
|
36
|
+
def values(keys_arr = nil, include_empty: false)
|
37
|
+
if keys_arr
|
38
|
+
Array(keys_arr).flatten.each_with_object({}) do |key, memo|
|
30
39
|
if found = self[key] and (found.to_s || include_empty)
|
31
40
|
memo[key] = found.to_s
|
32
41
|
end
|
33
42
|
end
|
43
|
+
elsif is_array?
|
44
|
+
children.map(&:values)
|
34
45
|
else
|
35
|
-
children.each_with_object({}) do |child, memo|
|
36
|
-
|
37
|
-
memo[child.name] = child.to_s
|
38
|
-
end
|
46
|
+
children.each_with_object({}) do |child, memo|
|
47
|
+
memo[child.name] = child.children.any? ? child.values : child.text
|
39
48
|
end
|
40
49
|
end
|
41
50
|
end
|
42
51
|
|
52
|
+
alias_method :to_hash, :values
|
53
|
+
alias_method :to_h, :values
|
54
|
+
|
43
55
|
def dig(*paths)
|
44
56
|
list = Array(paths).flatten
|
45
57
|
res = list.reduce([self]) do |parents, key|
|
46
|
-
|
47
58
|
if parents
|
48
59
|
found = parents.map do |parent|
|
49
60
|
parent.children.select { |node| node.name.to_s == key.to_s }
|
@@ -79,14 +90,14 @@ XMLNode = Struct.new(:name, :text, :attributes, :children) do
|
|
79
90
|
end
|
80
91
|
|
81
92
|
class Ox::Document
|
82
|
-
def
|
83
|
-
nodes.first.
|
93
|
+
def to_node
|
94
|
+
nodes.first.to_node
|
84
95
|
end
|
85
96
|
end
|
86
97
|
|
87
98
|
class Ox::Element
|
88
|
-
def
|
89
|
-
children = nodes.map { |n| n.class == self.class ? n.
|
99
|
+
def to_node
|
100
|
+
children = nodes.map { |n| n.class == self.class ? n.to_node : nil }.compact
|
90
101
|
XMLNode.new(value, text, attributes, children)
|
91
102
|
end
|
92
103
|
end
|
data/lib/dagger/parsers.rb
CHANGED
data/lib/dagger/version.rb
CHANGED
data/spec/parsers_spec.rb
CHANGED
@@ -14,6 +14,9 @@ describe 'Parsers' do
|
|
14
14
|
|
15
15
|
before do
|
16
16
|
allow(Net::HTTP).to receive(:new).and_return(fake_http)
|
17
|
+
allow(fake_http).to receive(:keep_alive_timeout=).and_return(true)
|
18
|
+
allow(fake_http).to receive(:read_timeout=).and_return(true)
|
19
|
+
allow(fake_http).to receive(:open_timeout=).and_return(true)
|
17
20
|
allow(fake_http).to receive(:verify_mode=).and_return(true)
|
18
21
|
allow(fake_http).to receive(:post).and_return(fake_resp)
|
19
22
|
end
|
@@ -100,7 +103,7 @@ describe 'Parsers' do
|
|
100
103
|
it 'returns XMLNode obj' do
|
101
104
|
res = send_request.data
|
102
105
|
expect(res).to be_a(XMLNode)
|
103
|
-
expect(res.
|
106
|
+
expect(res.to_node).to eql(res)
|
104
107
|
expect(res['foo']).to be_a(XMLNode)
|
105
108
|
expect(res['foo'].text).to eql('123')
|
106
109
|
|
@@ -126,7 +129,7 @@ describe 'Parsers' do
|
|
126
129
|
|
127
130
|
it 'works' do
|
128
131
|
doc = Ox.parse(xml)
|
129
|
-
obj = doc.
|
132
|
+
obj = doc.to_node
|
130
133
|
|
131
134
|
expect(obj[:nested][:item][:title].text).to eql('foobar')
|
132
135
|
end
|
data/spec/persistent_spec.rb
CHANGED
@@ -17,4 +17,50 @@ describe 'Persistent mode' do
|
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
|
+
end
|
21
|
+
|
22
|
+
describe 'using threads' do
|
23
|
+
|
24
|
+
def get(url)
|
25
|
+
@http.get(url)
|
26
|
+
end
|
27
|
+
|
28
|
+
def connect(host)
|
29
|
+
raise if @http
|
30
|
+
@http = Dagger.open(host)
|
31
|
+
end
|
32
|
+
|
33
|
+
def disconnect
|
34
|
+
raise if @http.nil?
|
35
|
+
@http.close
|
36
|
+
@http = nil
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'works' do
|
40
|
+
thread_count = 10
|
41
|
+
urls_count = 100
|
42
|
+
host = 'https://postman-echo.com'
|
43
|
+
urls = urls_count.times.map { |i| "/get?page/#{i}" }
|
44
|
+
result = []
|
45
|
+
|
46
|
+
mutex = Mutex.new
|
47
|
+
thread_count.times.map do
|
48
|
+
Thread.new(urls, result) do |urls, result|
|
49
|
+
# mutex.synchronize { Dagger.open(host) }
|
50
|
+
http = Dagger.open(host)
|
51
|
+
while url = mutex.synchronize { urls.pop }
|
52
|
+
puts "Fetching #{url}"
|
53
|
+
resp = http.get(url)
|
54
|
+
mutex.synchronize do
|
55
|
+
result.push(resp.code)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
# mutex.synchronize { http.close }
|
59
|
+
http.close
|
60
|
+
end
|
61
|
+
end.each(&:join)
|
62
|
+
|
63
|
+
expect(result.count).to eq(urls_count)
|
64
|
+
end
|
65
|
+
|
20
66
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dagger
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tomás Pollak
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-10-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -66,20 +66,6 @@ dependencies:
|
|
66
66
|
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: net-http-persistent
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - "~>"
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '3.0'
|
76
|
-
type: :runtime
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - "~>"
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '3.0'
|
83
69
|
- !ruby/object:Gem::Dependency
|
84
70
|
name: oj
|
85
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -124,6 +110,7 @@ files:
|
|
124
110
|
- bin/dagger
|
125
111
|
- dagger.gemspec
|
126
112
|
- lib/dagger.rb
|
113
|
+
- lib/dagger/connection_manager.rb
|
127
114
|
- lib/dagger/ox_extension.rb
|
128
115
|
- lib/dagger/parsers.rb
|
129
116
|
- lib/dagger/response.rb
|