dagger 1.5.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: a12a3d9db4308744812542f0e74b3d21ae3075fa91b2025114c2693ea20440f1
4
- data.tar.gz: ab01f2d4accd0dfe778936fd2a6a1075cbd45e335f4e34f82f2d6507ebfcd7d0
3
+ metadata.gz: a3be984806667b1144778e3695d47171000733ad0c2ecbc1323b36c8f97d666e
4
+ data.tar.gz: e27de03a5b62085bb6de4954e6be2a15f14bc97a7c6933dfa92467fdaec33ed5
5
5
  SHA512:
6
- metadata.gz: '0193ebca25917fb8d6be7194a533d6920128213b11a8bf05abb4c093439a5f7c0449b8637af0effcbb139d477a3d0c68bd966567277de4a3f0c670d103a34c75'
7
- data.tar.gz: 473e6a93ba6ba26fb70ec0fdf931e7a1a046c3713c95f49a0b1675ccd4aac7311f00025e7856faed052a7cc20d10a3d0d5b0fbb8848fc24c23b0e08656b2e7ce
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,7 +39,7 @@ module Dagger
34
39
  end
35
40
 
36
41
  def self.resolve_uri(uri, host = nil, query = nil)
37
- uri = host + uri if uri.to_s['//'].nil? && host
42
+ uri = host + uri if uri[0] == '/' && host
38
43
  uri = parse_uri(uri.to_s)
39
44
  uri.path.sub!(/\?.*|$/, '?' + Utils.encode(query)) if query and query.any?
40
45
  uri
@@ -50,7 +55,7 @@ module Dagger
50
55
  when Array then obj.map { |v| encode(v, "#{key}[]") }.join('&')
51
56
  when nil then ''
52
57
  else
53
- "#{key}=#{URI.escape(obj.to_s)}"
58
+ "#{key}=#{ERB::Util.url_encode(obj.to_s)}"
54
59
  end
55
60
  end
56
61
 
@@ -62,24 +67,40 @@ module Dagger
62
67
 
63
68
  class Client
64
69
 
65
- def self.init(uri, opts)
66
- uri = Utils.parse_uri(uri)
67
- http = if opts.delete(:persistent)
68
- Net::HTTP::Persistent.new(name: DAGGER_NAME)
69
- else
70
- Net::HTTP.new(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,12 +110,20 @@ module Dagger
89
110
 
90
111
  def get(uri, opts = {})
91
112
  uri = Utils.resolve_uri(uri, @host, opts[:query])
92
- raise ArgumentError.new("#{uri.scheme_and_host} does not match #{@host}") if @host != uri.scheme_and_host
113
+
114
+ if @host != uri.scheme_and_host
115
+ raise ArgumentError.new("#{uri.scheme_and_host} does not match #{@host}")
116
+ end
93
117
 
94
118
  opts[:follow] = 10 if opts[:follow] == true
95
119
  headers = opts[:headers] || {}
96
120
  headers['Accept'] = 'application/json' if opts[:json] && headers['Accept'].nil?
97
121
 
122
+ if opts[:ip]
123
+ headers['Host'] = uri.host
124
+ uri = opts[:ip]
125
+ end
126
+
98
127
  request = Net::HTTP::Get.new(uri, DEFAULT_HEADERS.merge(headers))
99
128
  request.basic_auth(opts.delete(:username), opts.delete(:password)) if opts[:username]
100
129
 
@@ -102,22 +131,23 @@ module Dagger
102
131
  @http.start unless @http.started?
103
132
  resp, data = @http.request(request)
104
133
  else # persistent
105
- resp, data = @http.request(uri, request)
134
+ resp, data = @http.send_request(uri, request)
106
135
  end
107
136
 
108
137
  if REDIRECT_CODES.include?(resp.code.to_i) && resp['Location'] && (opts[:follow] && opts[:follow] > 0)
109
138
  opts[:follow] -= 1
110
- puts "Following redirect to #{resp['Location']}"
139
+ debug "Following redirect to #{resp['Location']}"
111
140
  return get(resp['Location'], opts)
112
141
  end
113
142
 
114
143
  @response = build_response(resp, data || resp.body)
115
144
 
116
145
  rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ETIMEDOUT, Errno::EINVAL, Timeout::Error, \
117
- SocketError, EOFError, 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
118
148
 
119
149
  if retries = opts[:retries] and retries.to_i > 0
120
- puts "Got #{e.class}! Retrying in a sec (#{retries} retries left)"
150
+ debug "Got #{e.class}! Retrying in a sec (#{retries} retries left)"
121
151
  sleep (opts[:retry_wait] || DEFAULT_RETRY_WAIT)
122
152
  get(uri, opts.merge(retries: retries - 1))
123
153
  else
@@ -148,7 +178,10 @@ module Dagger
148
178
  end
149
179
 
150
180
  uri = Utils.resolve_uri(uri, @host)
151
- raise ArgumentError.new("#{uri.scheme_and_host} does not match #{@host}") if @host != uri.scheme_and_host
181
+ if @host != uri.scheme_and_host
182
+ raise ArgumentError.new("#{uri.scheme_and_host} does not match #{@host}")
183
+ end
184
+
152
185
  headers = DEFAULT_HEADERS.merge(opts[:headers] || {})
153
186
 
154
187
  query = if data.is_a?(String)
@@ -176,16 +209,17 @@ module Dagger
176
209
  req = Kernel.const_get("Net::HTTP::#{method.capitalize}").new(uri.path, headers)
177
210
  # req.set_form_data(query)
178
211
  req.body = query
179
- resp, data = @http.request(uri, req)
212
+ resp, data = @http.send_request(uri, req)
180
213
  end
181
214
 
182
215
  @response = build_response(resp, data || resp.body)
183
216
 
184
217
  rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ETIMEDOUT, Errno::EINVAL, Timeout::Error, \
185
- SocketError, EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError, OpenSSL::SSL::SSLError => e
218
+ Net::OpenTimeout, Net::ReadTimeout, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError, \
219
+ SocketError, EOFError, OpenSSL::SSL::SSLError => e
186
220
 
187
221
  if method.to_s.downcase != 'get' && retries = opts[:retries] and retries.to_i > 0
188
- puts "[#{DAGGER_NAME}] Got #{e.class}! Retrying in a sec (#{retries} retries left)"
222
+ debug "[#{DAGGER_NAME}] Got #{e.class}! Retrying in a sec (#{retries} retries left)"
189
223
  sleep (opts[:retry_wait] || DEFAULT_RETRY_WAIT)
190
224
  request(method, uri, data, opts.merge(retries: retries - 1))
191
225
  else
@@ -198,7 +232,7 @@ module Dagger
198
232
  end
199
233
 
200
234
  def open(&block)
201
- if @http.is_a?(Net::HTTP::Persistent)
235
+ if @http.is_a?(Dagger::ConnectionManager)
202
236
  instance_eval(&block)
203
237
  else
204
238
  @http.start do
@@ -208,7 +242,7 @@ module Dagger
208
242
  end
209
243
 
210
244
  def close
211
- if @http.is_a?(Net::HTTP::Persistent)
245
+ if @http.is_a?(Dagger::ConnectionManager)
212
246
  @http.shutdown # calls finish on pool connections
213
247
  else
214
248
  @http.finish if @http.started?
@@ -217,6 +251,10 @@ module Dagger
217
251
 
218
252
  private
219
253
 
254
+ def debug(str)
255
+ puts str if ENV['DEBUGGING']
256
+ end
257
+
220
258
  def build_response(resp, body)
221
259
  resp.extend(Response)
222
260
  resp.set_body(body) unless resp.body
@@ -262,4 +300,4 @@ module Dagger
262
300
 
263
301
  end
264
302
 
265
- 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 = 5
4
- PATCH = 1
3
+ MINOR = 8
4
+ PATCH = 0
5
5
 
6
6
  VERSION = [MAJOR, MINOR, PATCH].join('.')
7
7
  end
@@ -23,7 +23,7 @@ describe 'arguments' do
23
23
  describe 'invalid URL' do
24
24
 
25
25
  it 'raises error' do
26
- expect { send_request('asd123.rewqw') }.to raise_error(ArgumentError)
26
+ expect { send_request('asd123.rewqw') }.to raise_error(SocketError)
27
27
  end
28
28
 
29
29
  end
@@ -36,6 +36,15 @@ describe 'arguments' do
36
36
 
37
37
  end
38
38
 
39
+ describe 'host without protocol' do
40
+
41
+ it 'works' do
42
+ expect(send_request('www.google.com')).to be_a(Net::HTTPResponse)
43
+ end
44
+
45
+ end
46
+
47
+
39
48
  describe 'valid host' do
40
49
 
41
50
  it 'works' do
@@ -0,0 +1,17 @@
1
+ require './lib/dagger'
2
+
3
+ require 'rspec/mocks'
4
+ require 'rspec/expectations'
5
+
6
+ describe 'IP Connection' do
7
+
8
+ it 'works' do
9
+ expect do
10
+ Dagger.get('http://www.awiefjoawijfaowef.com')
11
+ end.to raise_error(SocketError, /getaddrinfo/)
12
+
13
+ resp = Dagger.get('http://www.awiefjoawijfaowef.com', { ip: '1.1.1.1'} )
14
+ expect(resp.body).to match('<center>cloudflare</center>')
15
+ end
16
+
17
+ 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.5.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-03-07 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,11 +110,13 @@ 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
130
117
  - lib/dagger/version.rb
131
118
  - spec/arguments_spec.rb
119
+ - spec/ip_connect_spec.rb
132
120
  - spec/parsers_spec.rb
133
121
  - spec/persistent_spec.rb
134
122
  homepage: https://github.com/tomas/dagger