dagger 1.5.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: 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