dagger 1.7.1 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
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