web_fetch 0.4.0 → 0.5.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/.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/lib/web_fetch/version.rb
CHANGED
data/manifest
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
.gitignore
|
2
|
+
.rspec
|
3
|
+
.rubocop.yml
|
4
|
+
.ruby-version
|
5
|
+
.strong_versions.yml
|
6
|
+
Gemfile
|
7
|
+
LICENSE
|
8
|
+
Makefile
|
9
|
+
README.md
|
10
|
+
Rakefile
|
11
|
+
TODO
|
12
|
+
bin/rspec
|
13
|
+
bin/rubocop
|
14
|
+
bin/strong_versions
|
15
|
+
bin/web_fetch_control
|
16
|
+
bin/web_fetch_server
|
17
|
+
config/locales/en.yml
|
18
|
+
doc/examples/blocking_requests.rb
|
19
|
+
doc/examples/non_blocking_requests.rb
|
20
|
+
doc/examples/use_uid_for_request.rb
|
21
|
+
doc/web_fetch_architecture.png
|
22
|
+
docker/Dockerfile
|
23
|
+
lib/web_fetch.rb
|
24
|
+
lib/web_fetch/client.rb
|
25
|
+
lib/web_fetch/concerns/client_http.rb
|
26
|
+
lib/web_fetch/concerns/http_helpers.rb
|
27
|
+
lib/web_fetch/concerns/validatable.rb
|
28
|
+
lib/web_fetch/errors.rb
|
29
|
+
lib/web_fetch/gatherer.rb
|
30
|
+
lib/web_fetch/helpers.rb
|
31
|
+
lib/web_fetch/logger.rb
|
32
|
+
lib/web_fetch/promise.rb
|
33
|
+
lib/web_fetch/request.rb
|
34
|
+
lib/web_fetch/resources.rb
|
35
|
+
lib/web_fetch/response.rb
|
36
|
+
lib/web_fetch/retriever.rb
|
37
|
+
lib/web_fetch/router.rb
|
38
|
+
lib/web_fetch/server.rb
|
39
|
+
lib/web_fetch/storage.rb
|
40
|
+
lib/web_fetch/storage/memcached.rb
|
41
|
+
lib/web_fetch/storage/memory.rb
|
42
|
+
lib/web_fetch/storage/redis.rb
|
43
|
+
lib/web_fetch/version.rb
|
44
|
+
manifest
|
45
|
+
spec/client_spec.rb
|
46
|
+
spec/concerns/validatable_spec.rb
|
47
|
+
spec/features/http_fetching_spec.rb
|
48
|
+
spec/gatherer_spec.rb
|
49
|
+
spec/helpers_spec.rb
|
50
|
+
spec/i18n_spec.rb
|
51
|
+
spec/promise_spec.rb
|
52
|
+
spec/request_spec.rb
|
53
|
+
spec/resources_spec.rb
|
54
|
+
spec/response_spec.rb
|
55
|
+
spec/retriever_spec.rb
|
56
|
+
spec/router_spec.rb
|
57
|
+
spec/server_spec.rb
|
58
|
+
spec/spec_helper.rb
|
59
|
+
spec/storage/memcached_spec.rb
|
60
|
+
spec/storage/memory_spec.rb
|
61
|
+
spec/storage/redis_spec.rb
|
62
|
+
spec/storage/shared_examples.rb
|
63
|
+
swagger.yaml
|
64
|
+
web_fetch.gemspec
|
data/spec/client_spec.rb
CHANGED
@@ -65,13 +65,10 @@ describe WebFetch::Client do
|
|
65
65
|
|
66
66
|
it { is_expected.to be_a WebFetch::Response }
|
67
67
|
|
68
|
-
context '
|
69
|
-
subject {
|
70
|
-
it { is_expected.to
|
68
|
+
context 'pending' do
|
69
|
+
subject { client.fetch('not-ready-yet') }
|
70
|
+
it { is_expected.to be_pending }
|
71
71
|
end
|
72
|
-
|
73
|
-
# Tested more extensively in supporting methods #retrieve_by_uid and
|
74
|
-
# #find_by_uid below
|
75
72
|
end
|
76
73
|
|
77
74
|
describe '#retrieve_by_uid' do
|
data/spec/gatherer_spec.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
describe WebFetch::Gatherer do
|
4
|
-
let(:
|
4
|
+
let(:storage) { double }
|
5
5
|
|
6
6
|
let(:valid_params) do
|
7
7
|
{ requests: [
|
@@ -11,31 +11,33 @@ describe WebFetch::Gatherer do
|
|
11
11
|
end
|
12
12
|
|
13
13
|
it 'is initialisable with params' do
|
14
|
-
expect(described_class.new(
|
14
|
+
expect(described_class.new(storage, valid_params)).to be_a described_class
|
15
15
|
end
|
16
16
|
|
17
17
|
describe 'validation' do
|
18
18
|
context 'invalid' do
|
19
19
|
it 'is invalid if `requests` parameter is not passed' do
|
20
|
-
gatherer = described_class.new(
|
20
|
+
gatherer = described_class.new(storage, {})
|
21
21
|
expect(gatherer.valid?).to be false
|
22
22
|
expect(gatherer.errors).to include I18n.t(:requests_missing)
|
23
23
|
end
|
24
24
|
|
25
25
|
it 'is invalid if `requests` is not an array parameter' do
|
26
|
-
gatherer = described_class.new(
|
26
|
+
gatherer = described_class.new(storage, requests: 'hello')
|
27
27
|
expect(gatherer.valid?).to be false
|
28
28
|
expect(gatherer.errors).to include I18n.t(:requests_not_array)
|
29
29
|
end
|
30
30
|
|
31
31
|
it 'is invalid if `requests` is an empty array' do
|
32
|
-
gatherer = described_class.new(
|
32
|
+
gatherer = described_class.new(storage, requests: [])
|
33
33
|
expect(gatherer.valid?).to be false
|
34
34
|
expect(gatherer.errors).to include I18n.t(:requests_empty)
|
35
35
|
end
|
36
36
|
|
37
37
|
it 'is invalid if `url` missing from any requests' do
|
38
|
-
gatherer = described_class.new(
|
38
|
+
gatherer = described_class.new(
|
39
|
+
storage, requests: [{ url: 'hello' }, {}]
|
40
|
+
)
|
39
41
|
expect(gatherer.valid?).to be false
|
40
42
|
expect(gatherer.errors).to include I18n.t(:missing_url)
|
41
43
|
end
|
@@ -43,14 +45,29 @@ describe WebFetch::Gatherer do
|
|
43
45
|
|
44
46
|
context 'valid' do
|
45
47
|
it 'is valid when passed valid params' do
|
46
|
-
expect(described_class.new(
|
48
|
+
expect(described_class.new(storage, valid_params).valid?).to be true
|
47
49
|
end
|
48
50
|
end
|
49
51
|
end
|
50
52
|
|
51
53
|
describe '#start' do
|
54
|
+
let(:http) do
|
55
|
+
double(new: double(public_send: double(callback: nil, errback: nil)))
|
56
|
+
end
|
57
|
+
|
58
|
+
let(:logger) { double(debug: nil) }
|
59
|
+
|
60
|
+
before do
|
61
|
+
stub_request(:get, 'http://remotehost:8089/').to_return(status: 200)
|
62
|
+
stub_request(:get, 'http://blah/').to_return(status: 200)
|
63
|
+
stub_request(:put, 'http://blah/').to_return(status: 200)
|
64
|
+
stub_request(:get, 'http://hello/').to_return(status: 200)
|
65
|
+
end
|
66
|
+
|
52
67
|
it 'returns a hash containing sha1 hashes of requests' do
|
53
|
-
|
68
|
+
deferred = double(callback: nil, errback: nil)
|
69
|
+
http = double(new: double(public_send: deferred))
|
70
|
+
response = described_class.new(storage, valid_params, logger, http).start
|
54
71
|
hash = Digest::SHA1.new.digest(JSON.dump(valid_params[:requests].first))
|
55
72
|
expect(response[:requests].first[:hash]).to eql Digest.hexencode(hash)
|
56
73
|
end
|
@@ -68,14 +85,16 @@ describe WebFetch::Gatherer do
|
|
68
85
|
headers: { 'Content-Type' => 'hello' },
|
69
86
|
method: 'PUT' }
|
70
87
|
responses = [req1, req2, req3, req4, req5].map do |req|
|
71
|
-
described_class.new(
|
88
|
+
described_class.new(
|
89
|
+
storage, { requests: [req] }, logger, http
|
90
|
+
).start
|
72
91
|
end
|
73
92
|
hashes = responses.map { |res| res[:requests].first[:hash] }
|
74
93
|
expect(hashes.uniq.length).to eql 5
|
75
94
|
end
|
76
95
|
|
77
96
|
it 'returns a hash containing unique IDs for requests' do
|
78
|
-
response = described_class.new(
|
97
|
+
response = described_class.new(storage, valid_params, logger, http).start
|
79
98
|
uid1 = response[:requests][0][:uid]
|
80
99
|
uid2 = response[:requests][1][:uid]
|
81
100
|
expect(uid1).to_not eql uid2
|
@@ -85,9 +104,8 @@ describe WebFetch::Gatherer do
|
|
85
104
|
it 'is included in response' do
|
86
105
|
# Ensure that the requester can embed their own identifiers to link to
|
87
106
|
# the uid of the delegated request
|
88
|
-
params = { requests: [url: '
|
89
|
-
|
90
|
-
response = described_class.new(server, params).start
|
107
|
+
params = { requests: [url: 'http://blah/', bob: 'hello'] }
|
108
|
+
response = described_class.new(storage, params, logger, http).start
|
91
109
|
expect(response[:requests].first[:request][:bob]).to eql 'hello'
|
92
110
|
end
|
93
111
|
|
@@ -95,14 +113,13 @@ describe WebFetch::Gatherer do
|
|
95
113
|
# Ensure that only pertinent values are used to compute hash (i.e.
|
96
114
|
# adding auxiliary data will still allow retrieval by hash for
|
97
115
|
# otherwise duplicate requests
|
98
|
-
params1 = { requests: [url: 'http://blah', bob: 'hello']
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
expect(response1[:requests][0][:hash]).to eql response2[:requests][0][:hash]
|
116
|
+
params1 = { requests: [url: 'http://blah', bob: 'hello'] }
|
117
|
+
response1 = described_class.new(storage, params1, logger, http).start
|
118
|
+
|
119
|
+
params2 = { requests: [url: 'http://blah', not_bob: 'good bye'] }
|
120
|
+
response2 = described_class.new(storage, params2, logger, http).start
|
121
|
+
expect(response1[:requests][0][:hash])
|
122
|
+
.to eql response2[:requests][0][:hash]
|
106
123
|
end
|
107
124
|
end
|
108
125
|
end
|
data/spec/promise_spec.rb
CHANGED
@@ -9,21 +9,28 @@ RSpec.describe WebFetch::Promise do
|
|
9
9
|
let(:find_url) { "http://#{client.host}:#{client.port}/find/#{uid}" }
|
10
10
|
|
11
11
|
let(:client_success) do
|
12
|
-
double(
|
13
|
-
|
14
|
-
|
12
|
+
double(
|
13
|
+
fetch: double('success',
|
14
|
+
complete?: true,
|
15
|
+
pending?: false,
|
16
|
+
success?: true,
|
17
|
+
error: nil)
|
18
|
+
)
|
15
19
|
end
|
16
20
|
|
17
21
|
let(:client_failure) do
|
18
|
-
double(
|
19
|
-
|
20
|
-
|
22
|
+
double(
|
23
|
+
fetch: double('failure',
|
24
|
+
complete?: true,
|
25
|
+
pending?: false,
|
26
|
+
success?: false,
|
27
|
+
error: 'foo')
|
28
|
+
)
|
21
29
|
end
|
22
30
|
|
23
31
|
let(:client_pending) do
|
24
32
|
double(fetch: double('pending',
|
25
|
-
|
26
|
-
))
|
33
|
+
complete?: false, pending?: true, error: nil))
|
27
34
|
end
|
28
35
|
|
29
36
|
let(:client_not_started) do
|
@@ -36,8 +43,30 @@ RSpec.describe WebFetch::Promise do
|
|
36
43
|
|
37
44
|
describe '#fetch' do
|
38
45
|
before do
|
39
|
-
stub_request(:get, retrieve_url)
|
40
|
-
|
46
|
+
stub_request(:get, retrieve_url)
|
47
|
+
.to_return(
|
48
|
+
body: {
|
49
|
+
request: {
|
50
|
+
uid: 123,
|
51
|
+
request: {},
|
52
|
+
response: {
|
53
|
+
success: true, status: 200, body: 'abc123', headers: {}
|
54
|
+
}
|
55
|
+
}
|
56
|
+
}.to_json
|
57
|
+
)
|
58
|
+
|
59
|
+
stub_request(:get, find_url).to_return(
|
60
|
+
body: {
|
61
|
+
request: {
|
62
|
+
uid: 123,
|
63
|
+
request: {},
|
64
|
+
response: {
|
65
|
+
success: true, status: 200, body: 'abc123', headers: {}
|
66
|
+
}
|
67
|
+
}
|
68
|
+
}.to_json
|
69
|
+
)
|
41
70
|
end
|
42
71
|
|
43
72
|
subject { promise.fetch(fetch_options) }
|
@@ -63,7 +92,16 @@ RSpec.describe WebFetch::Promise do
|
|
63
92
|
stub_request(:get, retrieve_url)
|
64
93
|
.to_return(
|
65
94
|
body: {
|
66
|
-
|
95
|
+
request: {
|
96
|
+
uid: 123,
|
97
|
+
request: {},
|
98
|
+
response: {
|
99
|
+
success: true,
|
100
|
+
status: 200,
|
101
|
+
body: Base64.encode64('abc123'),
|
102
|
+
headers: {}
|
103
|
+
}
|
104
|
+
}
|
67
105
|
}.to_json
|
68
106
|
)
|
69
107
|
promise.fetch
|
data/spec/resources_spec.rb
CHANGED
@@ -6,11 +6,16 @@ describe WebFetch::Resources do
|
|
6
6
|
describe '.root' do
|
7
7
|
it 'responds with application name' do
|
8
8
|
expect(described_class.root(nil, nil))
|
9
|
-
.to eql(
|
9
|
+
.to eql(
|
10
|
+
status: 200,
|
11
|
+
command: 'root',
|
12
|
+
payload: { application: 'WebFetch' }
|
13
|
+
)
|
10
14
|
end
|
11
15
|
end
|
12
16
|
|
13
17
|
describe '.gather' do
|
18
|
+
before { stub_request(:any, 'http://google.com').to_return(status: 200) }
|
14
19
|
let(:response) do
|
15
20
|
described_class.gather(server, requests: [{ url: 'http://google.com' }])
|
16
21
|
end
|
@@ -25,34 +30,26 @@ describe WebFetch::Resources do
|
|
25
30
|
end
|
26
31
|
|
27
32
|
describe '.retrieve' do
|
28
|
-
it 'gives
|
33
|
+
it 'gives pending when unrecognised uid requested' do
|
29
34
|
response = described_class.retrieve(server, uid: '123')
|
30
|
-
expect(response[:
|
31
|
-
error = response[:payload][:error]
|
32
|
-
expect(error).to eql I18n.t(:uid_not_found)
|
35
|
+
expect(response[:request][:pending]).to be true
|
33
36
|
end
|
34
37
|
|
35
|
-
it 'gives
|
38
|
+
it 'gives pending when unrecognised hash requested' do
|
36
39
|
response = described_class.retrieve(server, hash: 'abc')
|
37
|
-
expect(response[:
|
38
|
-
error = response[:payload][:error]
|
39
|
-
expect(error).to eql I18n.t(:hash_not_found)
|
40
|
+
expect(response[:request][:pending]).to be true
|
40
41
|
end
|
41
42
|
end
|
42
43
|
|
43
44
|
describe '.find' do
|
44
|
-
it 'gives
|
45
|
+
it 'gives pending when unrecognised uid requested' do
|
45
46
|
response = described_class.find(server, uid: '123')
|
46
|
-
expect(response[:
|
47
|
-
error = response[:payload][:error]
|
48
|
-
expect(error).to eql I18n.t(:uid_not_found)
|
47
|
+
expect(response[:request][:pending]).to be true
|
49
48
|
end
|
50
49
|
|
51
|
-
it 'gives
|
50
|
+
it 'gives pending when unrecognised hash requested' do
|
52
51
|
response = described_class.find(server, hash: 'abc')
|
53
|
-
expect(response[:
|
54
|
-
error = response[:payload][:error]
|
55
|
-
expect(error).to eql I18n.t(:hash_not_found)
|
52
|
+
expect(response[:request][:pending]).to be true
|
56
53
|
end
|
57
54
|
end
|
58
55
|
end
|
data/spec/response_spec.rb
CHANGED
@@ -3,15 +3,19 @@
|
|
3
3
|
RSpec.describe WebFetch::Response do
|
4
4
|
let(:response) do
|
5
5
|
described_class.new(
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
6
|
+
request: {
|
7
|
+
response: {
|
8
|
+
body: Base64.encode64('abc123'),
|
9
|
+
headers: { 'Foo' => 'Bar' },
|
10
|
+
error: 'foo error happened',
|
11
|
+
status: 200,
|
12
|
+
success: false
|
13
|
+
},
|
14
|
+
pending: false,
|
15
|
+
request: { url: 'http://blah/' },
|
16
|
+
uid: 'uid123',
|
17
|
+
response_time: 123.45
|
18
|
+
}
|
15
19
|
)
|
16
20
|
end
|
17
21
|
|
data/spec/retriever_spec.rb
CHANGED
@@ -1,23 +1,23 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
describe WebFetch::Retriever do
|
4
|
-
let(:
|
4
|
+
let(:storage) { double(fetch: nil, store: nil) }
|
5
5
|
let(:valid_params) { { uid: 'abc123' } }
|
6
6
|
|
7
7
|
it 'is initialisable with params' do
|
8
|
-
expect(described_class.new(
|
8
|
+
expect(described_class.new(storage, valid_params, {}))
|
9
9
|
.to be_a described_class
|
10
10
|
end
|
11
11
|
|
12
12
|
describe 'validation' do
|
13
13
|
context 'valid' do
|
14
14
|
it 'is valid when `uid` given' do
|
15
|
-
retriever = described_class.new(
|
15
|
+
retriever = described_class.new(storage, { uid: 'abc123' }, {})
|
16
16
|
expect(retriever.valid?).to be true
|
17
17
|
end
|
18
18
|
|
19
19
|
it 'is valid when `hash` given' do
|
20
|
-
retriever = described_class.new(
|
20
|
+
retriever = described_class.new(storage, { hash: 'def456' }, {})
|
21
21
|
expect(retriever.valid?).to be true
|
22
22
|
end
|
23
23
|
end
|
@@ -25,14 +25,14 @@ describe WebFetch::Retriever do
|
|
25
25
|
context 'invalid' do
|
26
26
|
it 'is invalid if both `hash` and `uid` given' do
|
27
27
|
retriever = described_class.new(
|
28
|
-
|
28
|
+
storage, { hash: 'def456', uid: 'abc123' }, {}
|
29
29
|
)
|
30
30
|
expect(retriever.valid?).to be false
|
31
31
|
expect(retriever.errors).to include I18n.t(:hash_or_uid_but_not_both)
|
32
32
|
end
|
33
33
|
|
34
34
|
it 'is invalid if neither `hash` nor `uid` given' do
|
35
|
-
retriever = described_class.new(
|
35
|
+
retriever = described_class.new(storage, {}, {})
|
36
36
|
expect(retriever.valid?).to be false
|
37
37
|
expect(retriever.errors).to include I18n.t(:missing_hash_and_uid)
|
38
38
|
end
|
@@ -40,14 +40,14 @@ describe WebFetch::Retriever do
|
|
40
40
|
end
|
41
41
|
|
42
42
|
describe '#find' do
|
43
|
-
it 'returns
|
44
|
-
retriever = described_class.new(
|
45
|
-
expect(retriever.find).to
|
43
|
+
it 'returns "pending" when given uid has not been requested' do
|
44
|
+
retriever = described_class.new(storage, { uid: 'nope' }, {})
|
45
|
+
expect(retriever.find[:pending]).to be true
|
46
46
|
end
|
47
47
|
|
48
|
-
it 'returns
|
49
|
-
retriever = described_class.new(
|
50
|
-
expect(retriever.find).to
|
48
|
+
it 'returns "pending" when given hash has not been requested' do
|
49
|
+
retriever = described_class.new(storage, { hash: 'also nope' }, {})
|
50
|
+
expect(retriever.find[:pending]).to be true
|
51
51
|
end
|
52
52
|
|
53
53
|
it 'returns payload when request has been retrieved' do
|
@@ -58,14 +58,13 @@ describe WebFetch::Retriever do
|
|
58
58
|
url = 'http://blah.blah/success'
|
59
59
|
stub_request(:any, url)
|
60
60
|
|
61
|
-
gatherer = WebFetch::Gatherer.new(
|
61
|
+
gatherer = WebFetch::Gatherer.new(storage, requests: [{ url: url }])
|
62
62
|
response = gatherer.start
|
63
63
|
uid = response[:requests].first[:uid]
|
64
|
-
|
65
|
-
.and_return(uid => { body: 'fake body' })
|
64
|
+
allow(storage).to receive(:fetch).and_return('test')
|
66
65
|
|
67
|
-
retriever = described_class.new(
|
68
|
-
expect(retriever.find
|
66
|
+
retriever = described_class.new(storage, { uid: uid }, {})
|
67
|
+
expect(retriever.find).to eql 'test'
|
69
68
|
end
|
70
69
|
end
|
71
70
|
end
|