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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a94245616c7abe732448eb4b06a2a04182e217c1e9123a2591b7d8b770804b0d
4
- data.tar.gz: '08b40cfc31554215b9f1cf38e8d4e3e820c5711a566941cc2a3ae31319cfe63a'
3
+ metadata.gz: a3be984806667b1144778e3695d47171000733ad0c2ecbc1323b36c8f97d666e
4
+ data.tar.gz: e27de03a5b62085bb6de4954e6be2a15f14bc97a7c6933dfa92467fdaec33ed5
5
5
  SHA512:
6
- metadata.gz: f0cc209b26fbcae81e42f3e304459db4a7c17bcf344f461f3f4949bbef6dd99585b5c307dabb17f2e11c74ad0e945d3793204cfd566f04f3f270e10c917f6ffc
7
- data.tar.gz: 0175b3cde3920b06273bce7041adc7628deac7dbbb120970cbcd4e86b61b9399c705f76694add8a07e9fec5c0cebfa11bd4fd21c4602d139a1b724cd4495537d
6
+ metadata.gz: 5237f119527a6a77f1075ab8b65048947e68c23d20fd523f1c220ebf5216997660f3b2acac75560082e956297087f7ba9f090ca118e973fbbb66c941d4ef393c
7
+ data.tar.gz: 9a9a509cb9ff96338fa73fd711532704c27267916c2dda6e2e05c35446eeba4ec449f6aeba5bea2d4387ecac73ea69f18cceed325e9dbac263bd4d5811b8bc84
@@ -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
 
@@ -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 Wrapper, like curl)"
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.init(uri, opts)
65
- uri = Utils.parse_uri(uri)
66
- http = if opts.delete(:persistent)
67
- pool_size = opts[:pool_size] || Net::HTTP::Persistent::DEFAULT_POOL_SIZE
68
- Net::HTTP::Persistent.new(name: DAGGER_NAME, pool_size: pool_size)
69
- else
70
- Net::HTTP.new(opts[:ip] || uri.host, uri.port)
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} doesn't match #{@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.request(uri, request)
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
- SocketError, EOFError, Net::ReadTimeout, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError, OpenSSL::SSL::SSLError => e
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} doesn't match #{@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.request(uri, req)
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?(Net::HTTP::Persistent)
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?(Net::HTTP::Persistent)
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
@@ -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 to_hash
13
- self # for backwards compat
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(arr = nil, include_empty: false)
28
- if arr
29
- Array(arr).flatten.each_with_object({}) do |key, memo|
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
- if child.to_s || include_empty
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 to_hash
83
- nodes.first.to_hash
93
+ def to_node
94
+ nodes.first.to_node
84
95
  end
85
96
  end
86
97
 
87
98
  class Ox::Element
88
- def to_hash
89
- children = nodes.map { |n| n.class == self.class ? n.to_hash : nil }.compact
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
@@ -26,7 +26,7 @@ class Parsers
26
26
 
27
27
  def text_xml(body)
28
28
  if res = Ox.parse(body)
29
- res.to_hash
29
+ res.to_node
30
30
  end
31
31
  rescue Ox::ParseError
32
32
  nil
@@ -1,7 +1,7 @@
1
1
  module Dagger
2
2
  MAJOR = 1
3
- MINOR = 7
4
- PATCH = 1
3
+ MINOR = 8
4
+ PATCH = 0
5
5
 
6
6
  VERSION = [MAJOR, MINOR, PATCH].join('.')
7
7
  end
@@ -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.to_hash).to eql(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.to_hash
132
+ obj = doc.to_node
130
133
 
131
134
  expect(obj[:nested][:item][:title].text).to eql('foobar')
132
135
  end
@@ -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.7.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-05-13 00:00:00.000000000 Z
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