pebblebed 0.3.26 → 0.4.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/lib/pebblebed/http.rb +165 -94
- data/lib/pebblebed/version.rb +1 -1
- data/pebblebed.gemspec +2 -1
- data/spec/http_spec.rb +38 -16
- data/spec/spec_helper.rb +2 -0
- metadata +19 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5a72b667ab7760684fba215b9c8b1efcae9c1294
|
4
|
+
data.tar.gz: 2a0663c6a8a01c4996c5e4726d39efbeca6cb97e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 22492117f67d35017f0458d5d6b829378f8ae9583b4fcd1d7d6a36d88ddce51e8ea2f886c3b3060072c4834629ca8ef7a722c462dcd82aff232d77e0bcb0f495
|
7
|
+
data.tar.gz: ac85b869fe8d67e0f364ddf3054bb72bf107740c9fa45daf9a3c0174b0ed247a64af2835f8dad39a40c73e60511bd667f1ba00e6c920a371ff8c541b697bfe4d
|
data/lib/pebblebed/http.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
# A wrapper for all low level http client stuff
|
2
2
|
|
3
3
|
require 'uri'
|
4
|
-
require '
|
4
|
+
require 'excon'
|
5
5
|
require 'yajl/json_gem'
|
6
6
|
require 'queryparams'
|
7
7
|
require 'nokogiri'
|
8
8
|
require 'pathbuilder'
|
9
9
|
require 'active_support'
|
10
|
+
require 'timeout'
|
11
|
+
require 'resolv'
|
10
12
|
|
11
13
|
module Pebblebed
|
12
14
|
|
@@ -32,35 +34,43 @@ module Pebblebed
|
|
32
34
|
end
|
33
35
|
end
|
34
36
|
|
37
|
+
class HttpTransportError < StandardError
|
38
|
+
def initialize(e = nil)
|
39
|
+
super e
|
40
|
+
set_backtrace e.backtrace if e
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
35
44
|
class HttpNotFoundError < HttpError; end
|
45
|
+
class HttpSocketError < HttpTransportError; end
|
46
|
+
class HttpTimeoutError < HttpTransportError; end
|
36
47
|
|
37
48
|
module Http
|
38
49
|
|
39
50
|
DEFAULT_CONNECT_TIMEOUT = 30
|
40
|
-
DEFAULT_REQUEST_TIMEOUT = nil
|
41
51
|
DEFAULT_READ_TIMEOUT = 30
|
52
|
+
DEFAULT_WRITE_TIMEOUT = 60
|
42
53
|
|
43
54
|
class << self
|
44
|
-
attr_reader :connect_timeout, :
|
55
|
+
attr_reader :connect_timeout, :read_timeout, :write_timeout
|
45
56
|
def connect_timeout=(value)
|
46
57
|
@connect_timeout = value
|
47
|
-
|
48
|
-
end
|
49
|
-
def request_timeout=(value)
|
50
|
-
@request_timeout = value
|
51
|
-
self.current_easy = nil
|
58
|
+
Thread.current[:pebblebed_excon] = {}
|
52
59
|
end
|
53
60
|
def read_timeout=(value)
|
54
61
|
@read_timeout = value
|
55
|
-
|
62
|
+
Thread.current[:pebblebed_excon] = {}
|
63
|
+
end
|
64
|
+
def write_timeout=(value)
|
65
|
+
@write_timeout = value
|
66
|
+
Thread.current[:pebblebed_excon] = {}
|
56
67
|
end
|
57
68
|
end
|
58
69
|
|
59
70
|
class Response
|
60
|
-
def initialize(url,
|
71
|
+
def initialize(url, status, body)
|
61
72
|
@body = body
|
62
|
-
|
63
|
-
@status = header.scan(/HTTP\/\d\.\d\s(\d+)\s/).map(&:first).last.to_i
|
73
|
+
@status = status
|
64
74
|
@url = url
|
65
75
|
end
|
66
76
|
|
@@ -69,90 +79,123 @@ module Pebblebed
|
|
69
79
|
|
70
80
|
def self.get(url = nil, params = nil, &block)
|
71
81
|
url, params = url_and_params_from_args(url, params, &block)
|
72
|
-
return
|
73
|
-
|
74
|
-
|
82
|
+
return do_request(url) { |connection|
|
83
|
+
connection.get(
|
84
|
+
:host => url.host,
|
85
|
+
:path => url.path,
|
86
|
+
:query => params,
|
87
|
+
:persistent => true
|
88
|
+
)
|
75
89
|
}
|
76
90
|
end
|
77
91
|
|
78
92
|
def self.post(url, params, &block)
|
79
93
|
url, params = url_and_params_from_args(url, params, &block)
|
80
94
|
content_type, body = serialize_params(params)
|
81
|
-
return
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
95
|
+
return do_request(url, idempotent: false) { |connection|
|
96
|
+
connection.post(
|
97
|
+
:host => url.host,
|
98
|
+
:path => url.path,
|
99
|
+
:headers => {
|
100
|
+
'Accept' => 'application/json',
|
101
|
+
'Content-Type' => content_type
|
102
|
+
},
|
103
|
+
:body => body,
|
104
|
+
:persistent => true
|
105
|
+
)
|
86
106
|
}
|
87
107
|
end
|
88
108
|
|
89
109
|
def self.put(url, params, &block)
|
90
110
|
url, params = url_and_params_from_args(url, params, &block)
|
91
111
|
content_type, body = serialize_params(params)
|
92
|
-
return
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
112
|
+
return do_request(url) { |connection|
|
113
|
+
connection.put(
|
114
|
+
:host => url.host,
|
115
|
+
:path => url.path,
|
116
|
+
:headers => {
|
117
|
+
'Accept' => 'application/json',
|
118
|
+
'Content-Type' => content_type
|
119
|
+
},
|
120
|
+
:body => body,
|
121
|
+
:persistent => true
|
122
|
+
)
|
97
123
|
}
|
98
124
|
end
|
99
125
|
|
100
126
|
def self.delete(url, params, &block)
|
101
127
|
url, params = url_and_params_from_args(url, params, &block)
|
102
|
-
return
|
103
|
-
|
104
|
-
|
128
|
+
return do_request(url) { |connection|
|
129
|
+
connection.delete(
|
130
|
+
:host => url.host,
|
131
|
+
:path => url.path,
|
132
|
+
:query => params,
|
133
|
+
:persistent => true
|
134
|
+
)
|
105
135
|
}
|
106
136
|
end
|
107
137
|
|
108
|
-
def self.
|
109
|
-
|
110
|
-
on_data
|
111
|
-
|
112
|
-
|
138
|
+
def self.streamer(on_data)
|
139
|
+
lambda do |chunk, remaining_bytes, total_bytes|
|
140
|
+
on_data.call(chunk)
|
141
|
+
total_bytes
|
142
|
+
end
|
143
|
+
end
|
113
144
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
145
|
+
def self.stream_get(url = nil, params = nil, options = {})
|
146
|
+
on_data = options[:on_data] or raise "Option :on_data must be specified"
|
147
|
+
|
148
|
+
url, params = url_and_params_from_args(url, params)
|
149
|
+
return do_request(url) { |connection|
|
150
|
+
connection.get(
|
151
|
+
:host => url.host,
|
152
|
+
:path => url.path,
|
153
|
+
:query => params,
|
154
|
+
:persistent => false,
|
155
|
+
:response_block => streamer(on_data)
|
156
|
+
)
|
120
157
|
}
|
121
158
|
end
|
122
159
|
|
123
160
|
def self.stream_post(url, params, options = {})
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
161
|
+
on_data = options[:on_data] or raise "Option :on_data must be specified"
|
162
|
+
|
163
|
+
url, params = url_and_params_from_args(url, params)
|
164
|
+
content_type, body = serialize_params(params)
|
165
|
+
|
166
|
+
return do_request(url) { |connection|
|
167
|
+
connection.post(
|
168
|
+
:host => url.host,
|
169
|
+
:path => url.path,
|
170
|
+
:headers => {
|
171
|
+
'Accept' => 'application/json',
|
172
|
+
'Content-Type' => content_type
|
173
|
+
},
|
174
|
+
:body => body,
|
175
|
+
:persistent => false,
|
176
|
+
:response_block => streamer(on_data)
|
177
|
+
)
|
138
178
|
}
|
139
179
|
end
|
140
180
|
|
141
181
|
def self.stream_put(url, params, options = {})
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
182
|
+
on_data = options[:on_data] or raise "Option :on_data must be specified"
|
183
|
+
|
184
|
+
url, params = url_and_params_from_args(url, params)
|
185
|
+
content_type, body = serialize_params(params)
|
186
|
+
|
187
|
+
return do_request(url) { |connection|
|
188
|
+
connection.put(
|
189
|
+
:host => url.host,
|
190
|
+
:path => url.path,
|
191
|
+
:headers => {
|
192
|
+
'Accept' => 'application/json',
|
193
|
+
'Content-Type' => content_type
|
194
|
+
},
|
195
|
+
:body => body,
|
196
|
+
:persistent => false,
|
197
|
+
:response_block => streamer(on_data)
|
198
|
+
)
|
156
199
|
}
|
157
200
|
end
|
158
201
|
|
@@ -191,44 +234,72 @@ module Pebblebed
|
|
191
234
|
response
|
192
235
|
end
|
193
236
|
|
194
|
-
def self.
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
237
|
+
def self.do_request(url, idempotent: true, &block)
|
238
|
+
with_connection(url) { |connection|
|
239
|
+
begin
|
240
|
+
request = block.call(connection)
|
241
|
+
response = Response.new(url, request.status, request.body)
|
242
|
+
return handle_http_errors(response)
|
243
|
+
rescue Excon::Errors::Timeout => error
|
244
|
+
raise HttpTimeoutError.new(error)
|
245
|
+
rescue Excon::Errors::SocketError => error
|
246
|
+
raise HttpSocketError.new(error) unless idempotent
|
247
|
+
# Connection failed, close the connection and try again
|
248
|
+
connection.reset
|
249
|
+
begin
|
250
|
+
request = block.call(connection)
|
251
|
+
response = Response.new(url, request.status, request.body)
|
252
|
+
return handle_http_errors(response)
|
253
|
+
rescue Excon::Errors::SocketError => error
|
254
|
+
raise HttpSocketError.new(error)
|
255
|
+
end
|
256
|
+
end
|
257
|
+
}
|
258
|
+
end
|
259
|
+
|
260
|
+
def self.with_connection(url, &block)
|
261
|
+
connection = self.current_connection(url) || new_connection(url)
|
262
|
+
self.current_connection={:url => url, :connection => connection}
|
263
|
+
yield connection
|
264
|
+
end
|
265
|
+
|
266
|
+
def self.base_url(url)
|
267
|
+
if url.is_a?(URI)
|
268
|
+
uri = url
|
269
|
+
else
|
270
|
+
uri = URI.parse(url)
|
199
271
|
end
|
272
|
+
"#{uri.scheme}://#{uri.host}:#{uri.port}"
|
200
273
|
end
|
201
274
|
|
202
|
-
def self.
|
203
|
-
if
|
204
|
-
|
275
|
+
def self.cache_key(url)
|
276
|
+
if url.is_a?(URI)
|
277
|
+
uri = url
|
205
278
|
else
|
206
|
-
|
279
|
+
uri = URI.parse(url)
|
207
280
|
end
|
208
|
-
|
281
|
+
ip = Resolv.getaddress(uri.host)
|
282
|
+
"#{uri.scheme}://#{ip}:#{uri.port}"
|
209
283
|
end
|
210
284
|
|
211
|
-
def self.
|
212
|
-
Thread.current[:
|
285
|
+
def self.current_connection(url)
|
286
|
+
Thread.current[:pebblebed_excon] ||= {}
|
287
|
+
Thread.current[:pebblebed_excon][cache_key(url)]
|
213
288
|
end
|
214
289
|
|
215
|
-
def self.
|
216
|
-
|
217
|
-
|
218
|
-
current.reset
|
219
|
-
end
|
220
|
-
Thread.current[:pebblebed_curb_easy] = value
|
290
|
+
def self.current_connection=(value)
|
291
|
+
Thread.current[:pebblebed_excon] ||= {}
|
292
|
+
Thread.current[:pebblebed_excon][cache_key(value[:url])] = value[:connection]
|
221
293
|
end
|
222
294
|
|
223
|
-
# Returns new
|
224
|
-
def self.
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
easy
|
295
|
+
# Returns new Excon conection from current configuration.
|
296
|
+
def self.new_connection(url)
|
297
|
+
connection = Excon.new(base_url(url), {
|
298
|
+
:read_timeout => read_timeout || DEFAULT_READ_TIMEOUT,
|
299
|
+
:write_timeout => write_timeout || DEFAULT_WRITE_TIMEOUT,
|
300
|
+
:connect_timeout => connect_timeout || DEFAULT_CONNECT_TIMEOUT,
|
301
|
+
:thread_safe_sockets => false
|
302
|
+
})
|
232
303
|
end
|
233
304
|
|
234
305
|
def self.url_with_params(url, params)
|
data/lib/pebblebed/version.rb
CHANGED
data/pebblebed.gemspec
CHANGED
@@ -25,9 +25,10 @@ Gem::Specification.new do |s|
|
|
25
25
|
s.add_development_dependency "sinatra" # for testing purposes
|
26
26
|
s.add_development_dependency "rack-test" # for testing purposes
|
27
27
|
s.add_development_dependency "memcache_mock"
|
28
|
+
s.add_development_dependency "webmock"
|
28
29
|
|
29
30
|
s.add_runtime_dependency "deepstruct", ">= 0.0.4"
|
30
|
-
s.add_runtime_dependency "
|
31
|
+
s.add_runtime_dependency "excon", ">= 0.52.0"
|
31
32
|
s.add_runtime_dependency "yajl-ruby"
|
32
33
|
s.add_runtime_dependency "queryparams"
|
33
34
|
s.add_runtime_dependency "futurevalue"
|
data/spec/http_spec.rb
CHANGED
@@ -22,8 +22,8 @@ describe Pebblebed::Http do
|
|
22
22
|
|
23
23
|
before :all do
|
24
24
|
Pebblebed::Http.connect_timeout = nil
|
25
|
-
Pebblebed::Http.request_timeout = nil
|
26
25
|
Pebblebed::Http.read_timeout = nil
|
26
|
+
Pebblebed::Http.write_timeout = nil
|
27
27
|
|
28
28
|
# Starts the mock pebble at localhost:8666/api/mock/v1
|
29
29
|
mock_pebble.start
|
@@ -101,7 +101,7 @@ describe Pebblebed::Http do
|
|
101
101
|
})
|
102
102
|
result = JSON.parse(buf)
|
103
103
|
expect(result["QUERY_STRING"]).to eq "hello=world"
|
104
|
-
expect(response.body).to eq
|
104
|
+
expect(response.body).to eq ''
|
105
105
|
end
|
106
106
|
|
107
107
|
it "supports multiple sequential streaming request" do
|
@@ -113,31 +113,53 @@ describe Pebblebed::Http do
|
|
113
113
|
})
|
114
114
|
result = JSON.parse(buf)
|
115
115
|
expect(result["QUERY_STRING"]).to eq "hello=world"
|
116
|
-
expect(response.body).to eq
|
116
|
+
expect(response.body).to eq ''
|
117
117
|
end
|
118
118
|
end
|
119
119
|
end
|
120
120
|
end
|
121
|
-
|
122
|
-
it "enforces request timeout" do
|
123
|
-
Pebblebed::Http.request_timeout = 1
|
124
|
-
expect {
|
125
|
-
Pebblebed::Http.get(pebble_url, {slow: '2'})
|
126
|
-
}.to raise_error(Curl::Err::TimeoutError)
|
127
|
-
expect {
|
128
|
-
Pebblebed::Http.get(pebble_url, {slow: '0.5'})
|
129
|
-
}.not_to raise_error
|
130
|
-
end
|
131
|
-
|
132
121
|
it "enforces read timeout" do
|
133
|
-
Pebblebed::Http.request_timeout = 1000
|
134
122
|
Pebblebed::Http.read_timeout = 1
|
135
123
|
expect {
|
136
124
|
Pebblebed::Http.get(pebble_url, {slow: '30'})
|
137
|
-
}.to raise_error(
|
125
|
+
}.to raise_error(Pebblebed::HttpTimeoutError)
|
138
126
|
expect {
|
139
127
|
Pebblebed::Http.get(pebble_url, {slow: '0.5'})
|
140
128
|
}.not_to raise_error
|
141
129
|
end
|
142
130
|
|
131
|
+
describe 'retrying' do
|
132
|
+
run_count = 0
|
133
|
+
|
134
|
+
before do
|
135
|
+
Excon.stub({:method => :post}) {
|
136
|
+
raise Excon::Errors::SocketError.new(Exception.new "Mock Error")
|
137
|
+
}
|
138
|
+
Excon.stub({:method => :get}) { |params|
|
139
|
+
run_count += 1
|
140
|
+
if run_count <= 2
|
141
|
+
raise Excon::Errors::SocketError.new(Exception.new "Mock Error")
|
142
|
+
end
|
143
|
+
{:body => params[:body], :headers => params[:headers], :status => 200}
|
144
|
+
}
|
145
|
+
end
|
146
|
+
|
147
|
+
after do
|
148
|
+
Excon.stubs.clear
|
149
|
+
end
|
150
|
+
|
151
|
+
it "post with error doesn't try again" do
|
152
|
+
expect {
|
153
|
+
Pebblebed::Http.post(pebble_url, {hello: 'world'})
|
154
|
+
}.to raise_error(Pebblebed::HttpSocketError)
|
155
|
+
end
|
156
|
+
|
157
|
+
it "get request tries one more time" do
|
158
|
+
expect {
|
159
|
+
Pebblebed::Http.get(pebble_url, {hello: 'world'})
|
160
|
+
}.to raise_error(Pebblebed::HttpSocketError)
|
161
|
+
expect(run_count).to equal(2)
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
143
165
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'simplecov'
|
2
2
|
require 'rspec'
|
3
|
+
require 'webmock/rspec'
|
3
4
|
|
4
5
|
SimpleCov.add_filter 'spec'
|
5
6
|
SimpleCov.add_filter 'config'
|
@@ -11,6 +12,7 @@ require './spec/mock_pebble'
|
|
11
12
|
RSpec.configure do |c|
|
12
13
|
c.mock_with :rspec
|
13
14
|
c.before(:each) do
|
15
|
+
WebMock.allow_net_connect!
|
14
16
|
::Pebblebed.memcached = MemcacheMock.new
|
15
17
|
end
|
16
18
|
c.around(:each) do |example|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pebblebed
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Katrina Owen
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2018-
|
12
|
+
date: 2018-03-22 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
@@ -95,6 +95,20 @@ dependencies:
|
|
95
95
|
- - ">="
|
96
96
|
- !ruby/object:Gem::Version
|
97
97
|
version: '0'
|
98
|
+
- !ruby/object:Gem::Dependency
|
99
|
+
name: webmock
|
100
|
+
requirement: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
type: :development
|
106
|
+
prerelease: false
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
98
112
|
- !ruby/object:Gem::Dependency
|
99
113
|
name: deepstruct
|
100
114
|
requirement: !ruby/object:Gem::Requirement
|
@@ -110,19 +124,19 @@ dependencies:
|
|
110
124
|
- !ruby/object:Gem::Version
|
111
125
|
version: 0.0.4
|
112
126
|
- !ruby/object:Gem::Dependency
|
113
|
-
name:
|
127
|
+
name: excon
|
114
128
|
requirement: !ruby/object:Gem::Requirement
|
115
129
|
requirements:
|
116
130
|
- - ">="
|
117
131
|
- !ruby/object:Gem::Version
|
118
|
-
version: 0.
|
132
|
+
version: 0.52.0
|
119
133
|
type: :runtime
|
120
134
|
prerelease: false
|
121
135
|
version_requirements: !ruby/object:Gem::Requirement
|
122
136
|
requirements:
|
123
137
|
- - ">="
|
124
138
|
- !ruby/object:Gem::Version
|
125
|
-
version: 0.
|
139
|
+
version: 0.52.0
|
126
140
|
- !ruby/object:Gem::Dependency
|
127
141
|
name: yajl-ruby
|
128
142
|
requirement: !ruby/object:Gem::Requirement
|