dagger 1.7.0 → 2.0.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/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/lib/dagger/wrapper.rb +151 -0
- data/lib/dagger.rb +96 -50
- data/spec/arguments_spec.rb +10 -1
- data/spec/parsers_spec.rb +22 -5
- data/spec/persistent_spec.rb +55 -6
- data/spec/sending_data_spec.rb +19 -0
- metadata +5 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 447d77e66ec69445f5040231d19c038279d4ec7de624461deda078c544423ca9
|
4
|
+
data.tar.gz: c5eb814e71fd6a37b3b4272f6f747654eeb157655dec65ae6ec6f0cf86d4e5ff
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 31108057fbc3cd1d2e0c319d4c64244c596cbbb90d8ae6067945caedb2f8f7ff5c0fd144b16be55ac24cd36bbd466f08659b995b47ecd67425d26224bd505ede
|
7
|
+
data.tar.gz: d996eaefa87c30682eb127d9f2f28cbb28363fcf2a2f758e32610d553937c05a35750316f043a6f4d2a9eb6727aad8f0351ca761ee62b326313b46e902764fd2
|
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
|
|
@@ -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
@@ -0,0 +1,151 @@
|
|
1
|
+
require_relative '../dagger'
|
2
|
+
|
3
|
+
module Dagger
|
4
|
+
|
5
|
+
module Wrapper
|
6
|
+
|
7
|
+
def self.included(base)
|
8
|
+
base.extend(ClassMethods)
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def base_url(str = nil)
|
13
|
+
if str # set
|
14
|
+
@base_url = str
|
15
|
+
else
|
16
|
+
@base_url or raise "base_url unset!" # get
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def base_options(opts = nil)
|
21
|
+
if opts # set
|
22
|
+
@base_options = opts
|
23
|
+
else
|
24
|
+
@base_options or raise "base_url unset!" # get
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(opts = {})
|
30
|
+
@logger = opts.delete(:logger)
|
31
|
+
@options = opts
|
32
|
+
end
|
33
|
+
|
34
|
+
def get(path, params = {}, opts = {})
|
35
|
+
request(:get, path, params, opts)
|
36
|
+
end
|
37
|
+
|
38
|
+
def post(path, params = {}, opts = {})
|
39
|
+
request(:post, path, params, opts)
|
40
|
+
end
|
41
|
+
|
42
|
+
def put(path, params = {}, opts = {})
|
43
|
+
request(:put, path, params, opts)
|
44
|
+
end
|
45
|
+
|
46
|
+
def patch(path, params = {}, opts = {})
|
47
|
+
request(:patch, path, params, opts)
|
48
|
+
end
|
49
|
+
|
50
|
+
def delete(path, params = {}, opts = {})
|
51
|
+
request(:delete, path, params, opts)
|
52
|
+
end
|
53
|
+
|
54
|
+
def request(method, path, params = {}, opts = nil)
|
55
|
+
url = self.class.base_url + path
|
56
|
+
resp = benchmark("#{method} #{path}") do
|
57
|
+
http.request(method, url, params, base_options.merge(opts))
|
58
|
+
end
|
59
|
+
|
60
|
+
handle_response(resp, method, path, params)
|
61
|
+
end
|
62
|
+
|
63
|
+
def connect(&block)
|
64
|
+
open_http
|
65
|
+
if block_given?
|
66
|
+
yield
|
67
|
+
close_http
|
68
|
+
else
|
69
|
+
at_exit { close_http }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def disconnect
|
74
|
+
close_http
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
attr_reader :options
|
79
|
+
|
80
|
+
def handle_response(resp, method, path, params)
|
81
|
+
resp
|
82
|
+
end
|
83
|
+
|
84
|
+
def base_options
|
85
|
+
{}
|
86
|
+
end
|
87
|
+
|
88
|
+
def request_options
|
89
|
+
self.class.base_options.merge(base_options)
|
90
|
+
end
|
91
|
+
|
92
|
+
def benchmark(message, &block)
|
93
|
+
log(message)
|
94
|
+
start = Time.now
|
95
|
+
resp = yield
|
96
|
+
time = Time.now - start
|
97
|
+
log("Got response in #{time.round(2)} secs")
|
98
|
+
resp
|
99
|
+
end
|
100
|
+
|
101
|
+
def log(str)
|
102
|
+
logger.info(str)
|
103
|
+
end
|
104
|
+
|
105
|
+
def logger
|
106
|
+
@logger ||= begin
|
107
|
+
require 'logger'
|
108
|
+
Logger.new(@options[:logfile])
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def http
|
113
|
+
@http || Dagger
|
114
|
+
end
|
115
|
+
|
116
|
+
def open_http
|
117
|
+
raise "Already open!" if @http
|
118
|
+
@http = Dagger.open(self.class.base_url)
|
119
|
+
end
|
120
|
+
|
121
|
+
def close_http
|
122
|
+
@http.close if @http
|
123
|
+
@http = nil
|
124
|
+
end
|
125
|
+
|
126
|
+
# def wrap(hash)
|
127
|
+
# Entity.new(hash)
|
128
|
+
# end
|
129
|
+
|
130
|
+
# class Entity
|
131
|
+
# def initialize(props)
|
132
|
+
# @props = props
|
133
|
+
# end
|
134
|
+
|
135
|
+
# def get(prop)
|
136
|
+
# val = @props[name.to_s]
|
137
|
+
# end
|
138
|
+
|
139
|
+
# def method_missing(name, args, &block)
|
140
|
+
# if @props.key?(name.to_s)
|
141
|
+
# get(name)
|
142
|
+
# else
|
143
|
+
# # raise NoMethodError, "undefined method #{name}"
|
144
|
+
# super
|
145
|
+
# end
|
146
|
+
# end
|
147
|
+
# end
|
148
|
+
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
data/lib/dagger.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
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'
|
7
|
+
require 'erb'
|
8
8
|
|
9
9
|
class URI::HTTP
|
10
10
|
def scheme_and_host
|
@@ -14,13 +14,19 @@ end
|
|
14
14
|
|
15
15
|
module Dagger
|
16
16
|
|
17
|
-
DAGGER_NAME = "Dagger/#{VERSION}"
|
17
|
+
DAGGER_NAME = "Dagger/#{VERSION}".freeze
|
18
18
|
REDIRECT_CODES = [301, 302, 303].freeze
|
19
19
|
DEFAULT_RETRY_WAIT = 5.freeze # seconds
|
20
20
|
DEFAULT_HEADERS = {
|
21
21
|
'Accept' => '*/*',
|
22
|
-
'User-Agent' => "#{DAGGER_NAME} (Ruby Net::HTTP
|
23
|
-
}
|
22
|
+
'User-Agent' => "#{DAGGER_NAME} (Ruby Net::HTTP wrapper, like curl)"
|
23
|
+
}.freeze
|
24
|
+
|
25
|
+
DEFAULTS = {
|
26
|
+
open_timeout: 10,
|
27
|
+
read_timeout: 10,
|
28
|
+
keep_alive_timeout: 10
|
29
|
+
}.freeze
|
24
30
|
|
25
31
|
module Utils
|
26
32
|
|
@@ -34,20 +40,30 @@ module Dagger
|
|
34
40
|
end
|
35
41
|
|
36
42
|
def self.resolve_uri(uri, host = nil, query = nil)
|
37
|
-
uri = host + uri if uri
|
43
|
+
uri = host + uri if uri[0] == '/' && host
|
38
44
|
uri = parse_uri(uri.to_s)
|
39
|
-
uri.path.sub!(/\?.*|$/, '?' +
|
45
|
+
uri.path.sub!(/\?.*|$/, '?' + to_query_string(query)) if query and query.any?
|
40
46
|
uri
|
41
47
|
end
|
42
48
|
|
43
|
-
def self.
|
49
|
+
def self.encode_body(obj, opts = {})
|
50
|
+
if obj.is_a?(String)
|
51
|
+
obj
|
52
|
+
elsif opts[:json]
|
53
|
+
Oj.dump(obj, mode: :compat) # compat ensures symbols are converted to strings
|
54
|
+
else
|
55
|
+
to_query_string(obj)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.to_query_string(obj, key = nil)
|
44
60
|
if key.nil? && obj.is_a?(String) # && obj['=']
|
45
61
|
return obj
|
46
62
|
end
|
47
63
|
|
48
64
|
case obj
|
49
|
-
when Hash then obj.map { |k, v|
|
50
|
-
when Array then obj.map { |v|
|
65
|
+
when Hash then obj.map { |k, v| to_query_string(v, append_key(key, k)) }.join('&')
|
66
|
+
when Array then obj.map { |v| to_query_string(v, "#{key}[]") }.join('&')
|
51
67
|
when nil then ''
|
52
68
|
else
|
53
69
|
"#{key}=#{ERB::Util.url_encode(obj.to_s)}"
|
@@ -62,25 +78,40 @@ module Dagger
|
|
62
78
|
|
63
79
|
class Client
|
64
80
|
|
65
|
-
def self.
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
81
|
+
def self.init_persistent(opts = {})
|
82
|
+
# this line below forces one connection manager between multiple threads
|
83
|
+
# @persistent ||= Dagger::ConnectionManager.new(opts)
|
84
|
+
|
85
|
+
# here we initialize a connection manager for each thread
|
86
|
+
Thread.current[:dagger_persistent] ||= begin
|
87
|
+
Dagger::ConnectionManager.new(opts)
|
72
88
|
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.init_connection(uri, opts = {})
|
92
|
+
http = Net::HTTP.new(opts[:ip] || uri.host, uri.port)
|
73
93
|
|
74
94
|
if uri.port == 443
|
75
95
|
http.use_ssl = true if http.respond_to?(:use_ssl=) # persistent does it automatically
|
76
96
|
http.verify_mode = opts[:verify_ssl] === false ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER
|
77
97
|
end
|
78
98
|
|
79
|
-
[:open_timeout, :read_timeout, :ssl_version, :ciphers].each do |key|
|
80
|
-
http.send("#{key}=", opts[key]) if opts.has_key?(key)
|
99
|
+
[:keep_alive_timeout, :open_timeout, :read_timeout, :ssl_version, :ciphers].each do |key|
|
100
|
+
http.send("#{key}=", opts[key] || DEFAULTS[key]) if (opts.has_key?(key) || DEFAULTS.has_key?(key))
|
101
|
+
end
|
102
|
+
|
103
|
+
http
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.init(uri, opts)
|
107
|
+
uri = Utils.parse_uri(uri)
|
108
|
+
|
109
|
+
http = if opts.delete(:persistent)
|
110
|
+
init_persistent(opts)
|
111
|
+
else
|
112
|
+
init_connection(uri, opts)
|
81
113
|
end
|
82
114
|
|
83
|
-
# new(http, [uri.scheme, uri.host].join('://'))
|
84
115
|
new(http, uri.scheme_and_host)
|
85
116
|
end
|
86
117
|
|
@@ -91,11 +122,14 @@ module Dagger
|
|
91
122
|
def get(uri, opts = {})
|
92
123
|
uri = Utils.resolve_uri(uri, @host, opts[:query])
|
93
124
|
|
94
|
-
|
125
|
+
if @host != uri.scheme_and_host
|
126
|
+
raise ArgumentError.new("#{uri.scheme_and_host} does not match #{@host}")
|
127
|
+
end
|
95
128
|
|
96
129
|
opts[:follow] = 10 if opts[:follow] == true
|
97
130
|
headers = opts[:headers] || {}
|
98
131
|
headers['Accept'] = 'application/json' if opts[:json] && headers['Accept'].nil?
|
132
|
+
headers['Content-Type'] = 'application/json' if opts[:json] && opts[:body]
|
99
133
|
|
100
134
|
if opts[:ip]
|
101
135
|
headers['Host'] = uri.host
|
@@ -104,27 +138,29 @@ module Dagger
|
|
104
138
|
|
105
139
|
request = Net::HTTP::Get.new(uri, DEFAULT_HEADERS.merge(headers))
|
106
140
|
request.basic_auth(opts.delete(:username), opts.delete(:password)) if opts[:username]
|
141
|
+
request.body = Utils.encode_body(opts[:body], opts) if opts[:body]
|
107
142
|
|
108
143
|
if @http.respond_to?(:started?) # regular Net::HTTP
|
109
144
|
@http.start unless @http.started?
|
110
145
|
resp, data = @http.request(request)
|
111
146
|
else # persistent
|
112
|
-
resp, data = @http.
|
147
|
+
resp, data = @http.send_request(uri, request)
|
113
148
|
end
|
114
149
|
|
115
150
|
if REDIRECT_CODES.include?(resp.code.to_i) && resp['Location'] && (opts[:follow] && opts[:follow] > 0)
|
116
151
|
opts[:follow] -= 1
|
117
|
-
debug "Following redirect to #{resp['Location']}"
|
152
|
+
debug { "Following redirect to #{resp['Location']}" }
|
118
153
|
return get(resp['Location'], opts)
|
119
154
|
end
|
120
155
|
|
121
156
|
@response = build_response(resp, data || resp.body)
|
122
157
|
|
123
158
|
rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ETIMEDOUT, Errno::EINVAL, Timeout::Error, \
|
124
|
-
|
159
|
+
Net::OpenTimeout, Net::ReadTimeout, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError, \
|
160
|
+
SocketError, EOFError, OpenSSL::SSL::SSLError => e
|
125
161
|
|
126
162
|
if retries = opts[:retries] and retries.to_i > 0
|
127
|
-
debug "Got #{e.class}! Retrying in a sec (#{retries} retries left)"
|
163
|
+
debug { "Got #{e.class}! Retrying in a sec (#{retries} retries left)" }
|
128
164
|
sleep (opts[:retry_wait] || DEFAULT_RETRY_WAIT)
|
129
165
|
get(uri, opts.merge(retries: retries - 1))
|
130
166
|
else
|
@@ -150,42 +186,44 @@ module Dagger
|
|
150
186
|
|
151
187
|
def request(method, uri, data, opts = {})
|
152
188
|
if method.to_s.downcase == 'get'
|
153
|
-
|
154
|
-
return get(uri, opts.merge(
|
189
|
+
data ||= opts[:body]
|
190
|
+
return get(uri, opts.merge(body: data))
|
155
191
|
end
|
156
192
|
|
157
|
-
uri = Utils.resolve_uri(uri, @host)
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
query = if data.is_a?(String)
|
162
|
-
data
|
163
|
-
elsif opts[:json]
|
164
|
-
headers['Content-Type'] = 'application/json'
|
165
|
-
headers['Accept'] = 'application/json' if headers['Accept'].nil?
|
166
|
-
Oj.dump(data, mode: :compat) # compat ensures symbols are converted to strings
|
167
|
-
else # querystring, then
|
168
|
-
Utils.encode(data)
|
193
|
+
uri = Utils.resolve_uri(uri, @host, opts[:query])
|
194
|
+
if @host != uri.scheme_and_host
|
195
|
+
raise ArgumentError.new("#{uri.scheme_and_host} does not match #{@host}")
|
169
196
|
end
|
170
197
|
|
198
|
+
headers = DEFAULT_HEADERS.merge(opts[:headers] || {})
|
199
|
+
body = Utils.encode_body(data, opts)
|
200
|
+
|
171
201
|
if opts[:username] # opts[:password] is optional
|
172
202
|
str = [opts[:username], opts[:password]].compact.join(':')
|
173
203
|
headers['Authorization'] = 'Basic ' + Base64.encode64(str)
|
174
204
|
end
|
175
205
|
|
206
|
+
if opts[:json]
|
207
|
+
headers['Content-Type'] = 'application/json'
|
208
|
+
headers['Accept'] = 'application/json' if headers['Accept'].nil?
|
209
|
+
end
|
210
|
+
|
211
|
+
start = Time.now
|
212
|
+
debug { "Sending #{method} request to #{uri.request_uri} with headers #{headers.inspect} -- #{query}" }
|
213
|
+
|
176
214
|
if @http.respond_to?(:started?) # regular Net::HTTP
|
177
|
-
args = [method.to_s.downcase, uri.
|
215
|
+
args = [method.to_s.downcase, uri.request_uri, body, headers]
|
178
216
|
args.delete_at(2) if args[0] == 'delete' # Net::HTTP's delete does not accept data
|
179
217
|
|
180
218
|
@http.start unless @http.started?
|
181
219
|
resp, data = @http.send(*args)
|
182
220
|
else # Net::HTTP::Persistent
|
183
|
-
req = Kernel.const_get("Net::HTTP::#{method.capitalize}").new(uri.
|
184
|
-
|
185
|
-
|
186
|
-
resp, data = @http.request(uri, req)
|
221
|
+
req = Kernel.const_get("Net::HTTP::#{method.capitalize}").new(uri.request_uri, headers)
|
222
|
+
req.body = body
|
223
|
+
resp, data = @http.send_request(uri, req)
|
187
224
|
end
|
188
225
|
|
226
|
+
debug { "Got response #{resp.code} in #{(Time.now - start).round(2)}s: #{data || resp.body}" }
|
189
227
|
@response = build_response(resp, data || resp.body)
|
190
228
|
|
191
229
|
rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ETIMEDOUT, Errno::EINVAL, Timeout::Error, \
|
@@ -193,7 +231,7 @@ module Dagger
|
|
193
231
|
SocketError, EOFError, OpenSSL::SSL::SSLError => e
|
194
232
|
|
195
233
|
if method.to_s.downcase != 'get' && retries = opts[:retries] and retries.to_i > 0
|
196
|
-
debug
|
234
|
+
debug { "Got #{e.class}! Retrying in a sec (#{retries} retries left)" }
|
197
235
|
sleep (opts[:retry_wait] || DEFAULT_RETRY_WAIT)
|
198
236
|
request(method, uri, data, opts.merge(retries: retries - 1))
|
199
237
|
else
|
@@ -206,7 +244,7 @@ module Dagger
|
|
206
244
|
end
|
207
245
|
|
208
246
|
def open(&block)
|
209
|
-
if @http.is_a?(
|
247
|
+
if @http.is_a?(Dagger::ConnectionManager)
|
210
248
|
instance_eval(&block)
|
211
249
|
else
|
212
250
|
@http.start do
|
@@ -216,7 +254,7 @@ module Dagger
|
|
216
254
|
end
|
217
255
|
|
218
256
|
def close
|
219
|
-
if @http.is_a?(
|
257
|
+
if @http.is_a?(Dagger::ConnectionManager)
|
220
258
|
@http.shutdown # calls finish on pool connections
|
221
259
|
else
|
222
260
|
@http.finish if @http.started?
|
@@ -225,8 +263,16 @@ module Dagger
|
|
225
263
|
|
226
264
|
private
|
227
265
|
|
228
|
-
def debug(
|
229
|
-
|
266
|
+
def debug(&block)
|
267
|
+
if ENV['DEBUGGING'] || ENV['DEBUG']
|
268
|
+
str = yield
|
269
|
+
logger.info "[#{DAGGER_NAME}] #{str}"
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
def logger
|
274
|
+
require 'logger'
|
275
|
+
@logger ||= Logger.new(@logfile || STDOUT)
|
230
276
|
end
|
231
277
|
|
232
278
|
def build_response(resp, body)
|
@@ -274,4 +320,4 @@ module Dagger
|
|
274
320
|
|
275
321
|
end
|
276
322
|
|
277
|
-
end
|
323
|
+
end
|
data/spec/arguments_spec.rb
CHANGED
@@ -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(
|
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
|
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
|
|
@@ -119,16 +122,30 @@ describe 'Parsers' do
|
|
119
122
|
<nested>
|
120
123
|
<item>
|
121
124
|
<title attr="downcased">foobar</title>
|
125
|
+
<category>cat</category>
|
126
|
+
<published>true</published>
|
122
127
|
</item>
|
123
128
|
</nested>
|
124
129
|
</xml>
|
125
130
|
)
|
126
131
|
|
127
|
-
|
128
|
-
|
129
|
-
|
132
|
+
describe '#to_node' do
|
133
|
+
it 'works as expected' do
|
134
|
+
doc = Ox.parse(xml)
|
135
|
+
obj = doc.to_node
|
136
|
+
expect(obj[:nested][:item][:title].text).to eql('foobar')
|
137
|
+
end
|
138
|
+
end
|
130
139
|
|
131
|
-
|
140
|
+
describe '#values' do
|
141
|
+
it 'works as expected' do
|
142
|
+
doc = Ox.parse(xml)
|
143
|
+
obj = doc.to_node.values
|
144
|
+
expect(obj).to eql({
|
145
|
+
"foo" => "test",
|
146
|
+
"nested" => {"item"=>{"category"=>"cat", "published"=>"true", "title"=>"foobar"}},
|
147
|
+
})
|
148
|
+
end
|
132
149
|
end
|
133
150
|
|
134
151
|
end
|
data/spec/persistent_spec.rb
CHANGED
@@ -6,15 +6,64 @@ require 'rspec/expectations'
|
|
6
6
|
describe 'Persistent mode' do
|
7
7
|
|
8
8
|
it 'works' do
|
9
|
-
fake_client = double('Client')
|
10
|
-
expect(Dagger::Client).to receive(:new).once.and_return(fake_client)
|
11
|
-
expect(fake_client).to receive(:open).once #.and_return(fake_resp)
|
12
|
-
expect(fake_client).to receive(:close).once #.and_return(fake_resp)
|
9
|
+
# fake_client = double('Client')
|
10
|
+
# expect(Dagger::Client).to receive(:new).once.and_return(fake_client)
|
11
|
+
# expect(fake_client).to receive(:open).once #.and_return(fake_resp)
|
12
|
+
# expect(fake_client).to receive(:close).once #.and_return(fake_resp)
|
13
13
|
|
14
|
+
res1, res2 = nil, nil
|
14
15
|
obj = Dagger.open('https://www.google.com') do
|
15
|
-
get('/search?q=dagger+http+client')
|
16
|
-
get('google.com/search?q=thank+you+ruby')
|
16
|
+
res1 = get('/search?q=dagger+http+client', { body: 'foo' })
|
17
|
+
res2 = get('https://www.google.com/search?q=thank+you+ruby')
|
18
|
+
res3 = post('https://www.google.com/search?q=foobar', { foo: 'bar' })
|
17
19
|
end
|
20
|
+
|
21
|
+
expect(res1.code).to eq(400)
|
22
|
+
expect(res2.code).to eq(200)
|
23
|
+
expect(res2.code).to eq(200)
|
24
|
+
expect(obj).to be_a(Dagger::Client)
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
describe 'using threads' do
|
30
|
+
|
31
|
+
def connect(host)
|
32
|
+
raise if @http
|
33
|
+
@http = Dagger.open(host)
|
34
|
+
end
|
35
|
+
|
36
|
+
def disconnect
|
37
|
+
raise if @http.nil?
|
38
|
+
@http.close
|
39
|
+
@http = nil
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'works' do
|
43
|
+
thread_count = 10
|
44
|
+
urls_count = 100
|
45
|
+
host = 'https://postman-echo.com'
|
46
|
+
urls = urls_count.times.map { |i| "/get?page/#{i}" }
|
47
|
+
result = []
|
48
|
+
|
49
|
+
mutex = Mutex.new
|
50
|
+
thread_count.times.map do
|
51
|
+
Thread.new(urls, result) do |urls, result|
|
52
|
+
# mutex.synchronize { Dagger.open(host) }
|
53
|
+
http = Dagger.open(host)
|
54
|
+
while url = mutex.synchronize { urls.pop }
|
55
|
+
# puts "Fetching #{url}"
|
56
|
+
resp = http.get(url)
|
57
|
+
mutex.synchronize do
|
58
|
+
result.push(resp.code)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
# mutex.synchronize { http.close }
|
62
|
+
http.close
|
63
|
+
end
|
64
|
+
end.each(&:join)
|
65
|
+
|
66
|
+
expect(result.count).to eq(urls_count)
|
18
67
|
end
|
19
68
|
|
20
69
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require './lib/dagger'
|
2
|
+
|
3
|
+
require 'rspec/mocks'
|
4
|
+
require 'rspec/expectations'
|
5
|
+
|
6
|
+
describe 'sending data' do
|
7
|
+
|
8
|
+
it 'works with get if using .request' do
|
9
|
+
resp = Dagger.request('get', 'https://httpbingo.org/get?x=123', { foo: 'bar', testing: 1 }, { json: true })
|
10
|
+
expect(resp.ok?).to eq(true)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'works with get if passing body as option' do
|
14
|
+
resp = Dagger.get('https://httpbingo.org/get?x=123', { body: { foo: 'bar', testing: 1 }, json: true })
|
15
|
+
expect(resp.ok?).to eq(true)
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
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:
|
4
|
+
version: 2.0.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:
|
11
|
+
date: 2021-09-24 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,14 +110,17 @@ 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
|
118
|
+
- lib/dagger/wrapper.rb
|
131
119
|
- spec/arguments_spec.rb
|
132
120
|
- spec/ip_connect_spec.rb
|
133
121
|
- spec/parsers_spec.rb
|
134
122
|
- spec/persistent_spec.rb
|
123
|
+
- spec/sending_data_spec.rb
|
135
124
|
homepage: https://github.com/tomas/dagger
|
136
125
|
licenses: []
|
137
126
|
metadata: {}
|