web_fetch 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.strong_versions.yml +6 -0
- data/Makefile +11 -2
- data/README.md +25 -6
- data/Rakefile +5 -3
- data/bin/strong_versions +29 -0
- data/config/locales/en.yml +0 -3
- data/docker/Dockerfile +2 -2
- data/lib/web_fetch.rb +4 -1
- data/lib/web_fetch/client.rb +28 -29
- data/lib/web_fetch/concerns/http_helpers.rb +8 -35
- data/lib/web_fetch/gatherer.rb +65 -3
- data/lib/web_fetch/logger.rb +1 -2
- data/lib/web_fetch/promise.rb +3 -3
- data/lib/web_fetch/request.rb +8 -15
- data/lib/web_fetch/resources.rb +12 -22
- data/lib/web_fetch/response.rb +15 -10
- data/lib/web_fetch/retriever.rb +6 -25
- data/lib/web_fetch/server.rb +15 -28
- data/lib/web_fetch/storage.rb +17 -13
- data/lib/web_fetch/storage/memcached.rb +45 -0
- data/lib/web_fetch/storage/memory.rb +35 -0
- data/lib/web_fetch/storage/redis.rb +45 -0
- data/lib/web_fetch/version.rb +1 -1
- data/manifest +64 -0
- data/spec/client_spec.rb +3 -6
- data/spec/gatherer_spec.rb +38 -21
- data/spec/promise_spec.rb +49 -11
- data/spec/resources_spec.rb +14 -17
- data/spec/response_spec.rb +13 -9
- data/spec/retriever_spec.rb +16 -17
- data/spec/router_spec.rb +6 -4
- data/spec/spec_helper.rb +15 -7
- data/spec/storage/memcached_spec.rb +27 -0
- data/spec/storage/memory_spec.rb +5 -0
- data/spec/storage/redis_spec.rb +27 -0
- data/spec/storage/shared_examples.rb +27 -0
- data/web_fetch.gemspec +9 -5
- metadata +75 -11
- data/lib/web_fetch/concerns/event_machine_helpers.rb +0 -57
- data/spec/storage_spec.rb +0 -27
data/spec/router_spec.rb
CHANGED
@@ -10,7 +10,9 @@ describe WebFetch::Router do
|
|
10
10
|
describe '#route' do
|
11
11
|
it 'provides a route to GET /' do
|
12
12
|
expect(router.route('/'))
|
13
|
-
.to eql(
|
13
|
+
.to eql(
|
14
|
+
status: 200, command: 'root', payload: { application: 'WebFetch' }
|
15
|
+
)
|
14
16
|
end
|
15
17
|
|
16
18
|
it 'provides a route to POST /gather' do
|
@@ -39,9 +41,9 @@ describe WebFetch::Router do
|
|
39
41
|
|
40
42
|
it 'returns appropriate response when invaid json provided' do
|
41
43
|
response = router.route('/gather',
|
42
|
-
|
43
|
-
|
44
|
-
|
44
|
+
method: 'POST',
|
45
|
+
query_string: 'json=uh oh :(',
|
46
|
+
server: nil)
|
45
47
|
expect(response).to eql(status: 400, payload: I18n.t(:bad_json))
|
46
48
|
end
|
47
49
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,10 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
4
|
-
require 'pp'
|
3
|
+
require 'betterp'
|
5
4
|
require 'byebug'
|
6
|
-
require '
|
5
|
+
require 'pp'
|
7
6
|
require 'rspec/its'
|
7
|
+
require 'web_fetch'
|
8
|
+
require 'webmock/rspec'
|
9
|
+
|
10
|
+
require File.join(__dir__, 'storage', 'shared_examples')
|
8
11
|
|
9
12
|
WebMock.disable_net_connect!(allow_localhost: true)
|
10
13
|
|
@@ -33,8 +36,12 @@ module WebFetch
|
|
33
36
|
class MockServer
|
34
37
|
def gather(requests); end
|
35
38
|
|
39
|
+
def self.storage
|
40
|
+
@storage ||= Storage::Memory.new
|
41
|
+
end
|
42
|
+
|
36
43
|
def storage
|
37
|
-
|
44
|
+
self.class.storage
|
38
45
|
end
|
39
46
|
end
|
40
47
|
end
|
@@ -44,13 +51,14 @@ RSpec.configure do |config|
|
|
44
51
|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
45
52
|
end
|
46
53
|
|
54
|
+
config.before(:each) { WebFetch::MockServer.storage.clear }
|
47
55
|
config.mock_with :rspec do |mocks|
|
48
56
|
mocks.verify_partial_doubles = true
|
49
57
|
end
|
50
58
|
|
51
59
|
config.shared_context_metadata_behavior = :apply_to_host_groups
|
52
60
|
|
53
|
-
config.order = :random
|
54
|
-
|
55
|
-
Kernel.srand config.seed
|
61
|
+
# config.order = :random
|
62
|
+
#
|
63
|
+
# Kernel.srand config.seed
|
56
64
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe WebFetch::Storage::Memcached do
|
4
|
+
before(:all) do
|
5
|
+
class MockDalliClient
|
6
|
+
def initialize(*_args)
|
7
|
+
@state = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def set(key, value)
|
11
|
+
@state[key] = value
|
12
|
+
end
|
13
|
+
|
14
|
+
def get(key)
|
15
|
+
@state[key]
|
16
|
+
end
|
17
|
+
|
18
|
+
def delete(key)
|
19
|
+
@state.delete(key)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
subject { described_class.new(MockDalliClient) }
|
25
|
+
|
26
|
+
it_behaves_like 'a storage adapter'
|
27
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe WebFetch::Storage::Redis do
|
4
|
+
before(:all) do
|
5
|
+
class MockRedisClient
|
6
|
+
def initialize(*_args)
|
7
|
+
@state = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def set(key, value, _options = {})
|
11
|
+
@state[key] = value
|
12
|
+
end
|
13
|
+
|
14
|
+
def get(key)
|
15
|
+
@state[key]
|
16
|
+
end
|
17
|
+
|
18
|
+
def del(key)
|
19
|
+
@state.delete(key)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
subject { described_class.new(MockRedisClient) }
|
25
|
+
|
26
|
+
it_behaves_like 'a storage adapter'
|
27
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.shared_examples 'a storage adapter' do
|
4
|
+
describe '.store' do
|
5
|
+
it 'accepts a key and value to store' do
|
6
|
+
expect do
|
7
|
+
subject.store(:key, 'value')
|
8
|
+
end.to_not raise_error
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '.fetch' do
|
13
|
+
it 'fetches stored values' do
|
14
|
+
subject.store(:key, 'value')
|
15
|
+
expect(subject.fetch(:key)).to eql 'value'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '.delete' do
|
20
|
+
it 'deletes stored values' do
|
21
|
+
subject.store(:key, 'value')
|
22
|
+
expect(subject.fetch(:key)).to eql 'value'
|
23
|
+
subject.delete(:key)
|
24
|
+
expect(subject.fetch(:key)).to eql nil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/web_fetch.gemspec
CHANGED
@@ -13,7 +13,7 @@ Gem::Specification.new do |s|
|
|
13
13
|
s.description = 'Fetches HTTP responses as batch requests concurrently'
|
14
14
|
s.authors = ['Bob Farrell']
|
15
15
|
s.email = 'robertanthonyfarrell@gmail.com'
|
16
|
-
s.files =
|
16
|
+
s.files = File.read(File.join(__dir__, 'manifest')).split
|
17
17
|
s.homepage = 'https://github.com/bobf/web_fetch'
|
18
18
|
s.licenses = ['MIT']
|
19
19
|
s.require_paths = ['lib']
|
@@ -22,22 +22,26 @@ Gem::Specification.new do |s|
|
|
22
22
|
|
23
23
|
s.add_dependency 'activesupport', '>= 4.0'
|
24
24
|
s.add_dependency 'daemons', '~> 1.2'
|
25
|
+
s.add_dependency 'dalli', '~> 2.7'
|
25
26
|
s.add_dependency 'em-http-request', '~> 1.1'
|
26
|
-
s.add_dependency 'em-logger', '~> 0.1'
|
27
|
+
s.add_dependency 'em-logger', '~> 0.1.0'
|
27
28
|
s.add_dependency 'eventmachine', '~> 1.0'
|
28
|
-
s.add_dependency 'eventmachine_httpserver', '~> 0.2'
|
29
|
-
s.add_dependency 'faraday', '~> 0.
|
29
|
+
s.add_dependency 'eventmachine_httpserver', '~> 0.2.1'
|
30
|
+
s.add_dependency 'faraday', '~> 0.15.4'
|
30
31
|
s.add_dependency 'hanami-router', '~> 1.0'
|
31
32
|
s.add_dependency 'hanami-utils', '~> 1.0'
|
32
33
|
s.add_dependency 'i18n', '>= 0.7'
|
33
34
|
s.add_dependency 'rack', '>= 1.6'
|
35
|
+
s.add_dependency 'redis', '~> 4.1'
|
34
36
|
s.add_dependency 'subprocess', '~> 1.3'
|
35
37
|
|
38
|
+
s.add_development_dependency 'betterp', '~> 0.1.2'
|
36
39
|
s.add_development_dependency 'byebug', '~> 9.0'
|
37
40
|
s.add_development_dependency 'rake', '~> 12.3'
|
38
41
|
s.add_development_dependency 'rspec', '~> 3.5'
|
39
42
|
s.add_development_dependency 'rspec-its', '~> 1.2'
|
40
|
-
s.add_development_dependency 'rubocop', '~> 0.
|
43
|
+
s.add_development_dependency 'rubocop', '~> 0.60.0'
|
44
|
+
s.add_development_dependency 'strong_versions', '~> 0.3.2'
|
41
45
|
s.add_development_dependency 'webmock', '~> 3.4'
|
42
46
|
end
|
43
47
|
# rubocop:enable Metrics/BlockLength
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: web_fetch
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bob Farrell
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '1.2'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: dalli
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.7'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.7'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: em-http-request
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -58,14 +72,14 @@ dependencies:
|
|
58
72
|
requirements:
|
59
73
|
- - "~>"
|
60
74
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
75
|
+
version: 0.1.0
|
62
76
|
type: :runtime
|
63
77
|
prerelease: false
|
64
78
|
version_requirements: !ruby/object:Gem::Requirement
|
65
79
|
requirements:
|
66
80
|
- - "~>"
|
67
81
|
- !ruby/object:Gem::Version
|
68
|
-
version:
|
82
|
+
version: 0.1.0
|
69
83
|
- !ruby/object:Gem::Dependency
|
70
84
|
name: eventmachine
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -86,28 +100,28 @@ dependencies:
|
|
86
100
|
requirements:
|
87
101
|
- - "~>"
|
88
102
|
- !ruby/object:Gem::Version
|
89
|
-
version:
|
103
|
+
version: 0.2.1
|
90
104
|
type: :runtime
|
91
105
|
prerelease: false
|
92
106
|
version_requirements: !ruby/object:Gem::Requirement
|
93
107
|
requirements:
|
94
108
|
- - "~>"
|
95
109
|
- !ruby/object:Gem::Version
|
96
|
-
version:
|
110
|
+
version: 0.2.1
|
97
111
|
- !ruby/object:Gem::Dependency
|
98
112
|
name: faraday
|
99
113
|
requirement: !ruby/object:Gem::Requirement
|
100
114
|
requirements:
|
101
115
|
- - "~>"
|
102
116
|
- !ruby/object:Gem::Version
|
103
|
-
version:
|
117
|
+
version: 0.15.4
|
104
118
|
type: :runtime
|
105
119
|
prerelease: false
|
106
120
|
version_requirements: !ruby/object:Gem::Requirement
|
107
121
|
requirements:
|
108
122
|
- - "~>"
|
109
123
|
- !ruby/object:Gem::Version
|
110
|
-
version:
|
124
|
+
version: 0.15.4
|
111
125
|
- !ruby/object:Gem::Dependency
|
112
126
|
name: hanami-router
|
113
127
|
requirement: !ruby/object:Gem::Requirement
|
@@ -164,6 +178,20 @@ dependencies:
|
|
164
178
|
- - ">="
|
165
179
|
- !ruby/object:Gem::Version
|
166
180
|
version: '1.6'
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: redis
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - "~>"
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: '4.1'
|
188
|
+
type: :runtime
|
189
|
+
prerelease: false
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - "~>"
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '4.1'
|
167
195
|
- !ruby/object:Gem::Dependency
|
168
196
|
name: subprocess
|
169
197
|
requirement: !ruby/object:Gem::Requirement
|
@@ -178,6 +206,20 @@ dependencies:
|
|
178
206
|
- - "~>"
|
179
207
|
- !ruby/object:Gem::Version
|
180
208
|
version: '1.3'
|
209
|
+
- !ruby/object:Gem::Dependency
|
210
|
+
name: betterp
|
211
|
+
requirement: !ruby/object:Gem::Requirement
|
212
|
+
requirements:
|
213
|
+
- - "~>"
|
214
|
+
- !ruby/object:Gem::Version
|
215
|
+
version: 0.1.2
|
216
|
+
type: :development
|
217
|
+
prerelease: false
|
218
|
+
version_requirements: !ruby/object:Gem::Requirement
|
219
|
+
requirements:
|
220
|
+
- - "~>"
|
221
|
+
- !ruby/object:Gem::Version
|
222
|
+
version: 0.1.2
|
181
223
|
- !ruby/object:Gem::Dependency
|
182
224
|
name: byebug
|
183
225
|
requirement: !ruby/object:Gem::Requirement
|
@@ -240,14 +282,28 @@ dependencies:
|
|
240
282
|
requirements:
|
241
283
|
- - "~>"
|
242
284
|
- !ruby/object:Gem::Version
|
243
|
-
version: 0.
|
285
|
+
version: 0.60.0
|
286
|
+
type: :development
|
287
|
+
prerelease: false
|
288
|
+
version_requirements: !ruby/object:Gem::Requirement
|
289
|
+
requirements:
|
290
|
+
- - "~>"
|
291
|
+
- !ruby/object:Gem::Version
|
292
|
+
version: 0.60.0
|
293
|
+
- !ruby/object:Gem::Dependency
|
294
|
+
name: strong_versions
|
295
|
+
requirement: !ruby/object:Gem::Requirement
|
296
|
+
requirements:
|
297
|
+
- - "~>"
|
298
|
+
- !ruby/object:Gem::Version
|
299
|
+
version: 0.3.2
|
244
300
|
type: :development
|
245
301
|
prerelease: false
|
246
302
|
version_requirements: !ruby/object:Gem::Requirement
|
247
303
|
requirements:
|
248
304
|
- - "~>"
|
249
305
|
- !ruby/object:Gem::Version
|
250
|
-
version: 0.
|
306
|
+
version: 0.3.2
|
251
307
|
- !ruby/object:Gem::Dependency
|
252
308
|
name: webmock
|
253
309
|
requirement: !ruby/object:Gem::Requirement
|
@@ -274,6 +330,7 @@ files:
|
|
274
330
|
- ".rspec"
|
275
331
|
- ".rubocop.yml"
|
276
332
|
- ".ruby-version"
|
333
|
+
- ".strong_versions.yml"
|
277
334
|
- Gemfile
|
278
335
|
- LICENSE
|
279
336
|
- Makefile
|
@@ -282,6 +339,7 @@ files:
|
|
282
339
|
- TODO
|
283
340
|
- bin/rspec
|
284
341
|
- bin/rubocop
|
342
|
+
- bin/strong_versions
|
285
343
|
- bin/web_fetch_control
|
286
344
|
- bin/web_fetch_server
|
287
345
|
- config/locales/en.yml
|
@@ -293,7 +351,6 @@ files:
|
|
293
351
|
- lib/web_fetch.rb
|
294
352
|
- lib/web_fetch/client.rb
|
295
353
|
- lib/web_fetch/concerns/client_http.rb
|
296
|
-
- lib/web_fetch/concerns/event_machine_helpers.rb
|
297
354
|
- lib/web_fetch/concerns/http_helpers.rb
|
298
355
|
- lib/web_fetch/concerns/validatable.rb
|
299
356
|
- lib/web_fetch/errors.rb
|
@@ -308,7 +365,11 @@ files:
|
|
308
365
|
- lib/web_fetch/router.rb
|
309
366
|
- lib/web_fetch/server.rb
|
310
367
|
- lib/web_fetch/storage.rb
|
368
|
+
- lib/web_fetch/storage/memcached.rb
|
369
|
+
- lib/web_fetch/storage/memory.rb
|
370
|
+
- lib/web_fetch/storage/redis.rb
|
311
371
|
- lib/web_fetch/version.rb
|
372
|
+
- manifest
|
312
373
|
- spec/client_spec.rb
|
313
374
|
- spec/concerns/validatable_spec.rb
|
314
375
|
- spec/features/http_fetching_spec.rb
|
@@ -323,7 +384,10 @@ files:
|
|
323
384
|
- spec/router_spec.rb
|
324
385
|
- spec/server_spec.rb
|
325
386
|
- spec/spec_helper.rb
|
326
|
-
- spec/
|
387
|
+
- spec/storage/memcached_spec.rb
|
388
|
+
- spec/storage/memory_spec.rb
|
389
|
+
- spec/storage/redis_spec.rb
|
390
|
+
- spec/storage/shared_examples.rb
|
327
391
|
- swagger.yaml
|
328
392
|
- web_fetch.gemspec
|
329
393
|
homepage: https://github.com/bobf/web_fetch
|
@@ -1,57 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module WebFetch
|
4
|
-
# EventMachine layer-specific helpers
|
5
|
-
module EventMachineHelpers
|
6
|
-
def request_async(target)
|
7
|
-
request = target[:request]
|
8
|
-
target[:start_time] = Time.now.utc
|
9
|
-
async_request = EM::HttpRequest.new(request[:url])
|
10
|
-
method = request.fetch(:method, 'GET').downcase.to_sym
|
11
|
-
async_request.public_send(
|
12
|
-
method,
|
13
|
-
head: request[:headers],
|
14
|
-
query: request.fetch(:query, {}),
|
15
|
-
body: request.fetch(:body, nil)
|
16
|
-
)
|
17
|
-
end
|
18
|
-
|
19
|
-
def apply_callbacks(request)
|
20
|
-
request[:deferred].callback do
|
21
|
-
Logger.debug("HTTP fetch complete for uid: #{request[:uid]}")
|
22
|
-
save_response_time(request)
|
23
|
-
request[:succeeded] = true
|
24
|
-
end
|
25
|
-
|
26
|
-
request[:deferred].errback do
|
27
|
-
Logger.debug("HTTP fetch failed for uid: #{request[:uid]}")
|
28
|
-
save_response_time(request)
|
29
|
-
request[:failed] = true
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
def wait_for_response(request, response)
|
34
|
-
tick_loop(request, response)
|
35
|
-
end
|
36
|
-
|
37
|
-
def tick_loop(request, response)
|
38
|
-
# XXX There may be a much nicer way to wait for an async task to complete
|
39
|
-
# before returning a response but I couldn't figure it out, so I used
|
40
|
-
# EM.tick_loop which effectively does the same as a Twisted deferred
|
41
|
-
# callback chain, just much more explicitly.
|
42
|
-
EM.tick_loop do
|
43
|
-
if request[:succeeded]
|
44
|
-
succeed(request, response)
|
45
|
-
:stop
|
46
|
-
elsif request[:failed]
|
47
|
-
fail_(request, response)
|
48
|
-
:stop
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
def save_response_time(request)
|
54
|
-
request[:response_time] = Time.now.utc - request[:start_time]
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|