web_fetch 0.1.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 +7 -0
- data/.gitignore +15 -0
- data/.rspec +3 -0
- data/.rubocop.yml +10 -0
- data/.ruby-version +1 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +120 -0
- data/LICENSE +7 -0
- data/README.md +149 -0
- data/TODO +0 -0
- data/bin/rspec +29 -0
- data/bin/rubocop +29 -0
- data/bin/web_fetch_control +6 -0
- data/bin/web_fetch_server +30 -0
- data/config/locales/en.yml +12 -0
- data/doc/client_example.rb +19 -0
- data/doc/web_fetch_architecture.png +0 -0
- data/lib/web_fetch/client.rb +101 -0
- data/lib/web_fetch/concerns/http_helpers.rb +64 -0
- data/lib/web_fetch/concerns/validatable.rb +31 -0
- data/lib/web_fetch/event_machine_helpers.rb +36 -0
- data/lib/web_fetch/gatherer.rb +62 -0
- data/lib/web_fetch/helpers.rb +11 -0
- data/lib/web_fetch/http_helpers.rb +71 -0
- data/lib/web_fetch/logger.rb +29 -0
- data/lib/web_fetch/resources.rb +59 -0
- data/lib/web_fetch/retriever.rb +39 -0
- data/lib/web_fetch/router.rb +71 -0
- data/lib/web_fetch/server.rb +49 -0
- data/lib/web_fetch/storage.rb +16 -0
- data/lib/web_fetch/version.rb +5 -0
- data/lib/web_fetch.rb +40 -0
- data/spec/client_spec.rb +63 -0
- data/spec/concerns/validatable_spec.rb +53 -0
- data/spec/features/http_fetching_spec.rb +0 -0
- data/spec/gatherer_spec.rb +109 -0
- data/spec/helpers_spec.rb +18 -0
- data/spec/i18n_spec.rb +8 -0
- data/spec/resources_spec.rb +42 -0
- data/spec/retriever_spec.rb +68 -0
- data/spec/router_spec.rb +43 -0
- data/spec/server_spec.rb +96 -0
- data/spec/spec_helper.rb +55 -0
- data/spec/storage_spec.rb +24 -0
- data/swagger.yaml +115 -0
- data/web_fetch.gemspec +41 -0
- metadata +314 -0
@@ -0,0 +1,109 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe WebFetch::Gatherer do
|
4
|
+
let(:server) { WebFetch::MockServer.new }
|
5
|
+
|
6
|
+
let(:valid_params) do
|
7
|
+
{ requests: [
|
8
|
+
{ url: 'http://localhost:8089' },
|
9
|
+
{ url: 'http://remotehost:8089' }
|
10
|
+
] }
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'is initialisable with params' do
|
14
|
+
expect(described_class.new(server, valid_params)).to be_a described_class
|
15
|
+
end
|
16
|
+
|
17
|
+
describe 'validation' do
|
18
|
+
context 'invalid' do
|
19
|
+
it 'is invalid if `requests` parameter is not passed' do
|
20
|
+
gatherer = described_class.new(server, {})
|
21
|
+
expect(gatherer.valid?).to be false
|
22
|
+
expect(gatherer.errors).to include I18n.t(:requests_missing)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'is invalid if `requests` is not an array parameter' do
|
26
|
+
gatherer = described_class.new(server, requests: 'hello')
|
27
|
+
expect(gatherer.valid?).to be false
|
28
|
+
expect(gatherer.errors).to include I18n.t(:requests_not_array)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'is invalid if `requests` is an empty array' do
|
32
|
+
gatherer = described_class.new(server, requests: [])
|
33
|
+
expect(gatherer.valid?).to be false
|
34
|
+
expect(gatherer.errors).to include I18n.t(:requests_empty)
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'is invalid if `url` missing from any requests' do
|
38
|
+
gatherer = described_class.new(server, requests: [{ url: 'hello' }, {}])
|
39
|
+
expect(gatherer.valid?).to be false
|
40
|
+
expect(gatherer.errors).to include I18n.t(:missing_url)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'valid' do
|
45
|
+
it 'is valid when passed valid params' do
|
46
|
+
expect(described_class.new(server, valid_params).valid?).to be true
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe '#start' do
|
52
|
+
it 'returns a hash containing sha1 hashes of requests' do
|
53
|
+
result = described_class.new(server, valid_params).start
|
54
|
+
hash = Digest::SHA1.new.digest(JSON.dump(valid_params[:requests].first))
|
55
|
+
expect(result[:requests].first[:hash]).to eql Digest.hexencode(hash)
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'respects url, headers, http method and query when calculating sha1' do
|
59
|
+
req1 = { url: 'http://blah', query_string: 'a=1',
|
60
|
+
headers: { 'Content-Type' => 'whatever' } }
|
61
|
+
req2 = { url: 'http://blah', query_string: 'b=2',
|
62
|
+
headers: { 'Content-Type' => 'whatever' } }
|
63
|
+
req3 = { url: 'http://hello', query_string: 'a=1',
|
64
|
+
headers: { 'Content-Type' => 'whatever' } }
|
65
|
+
req4 = { url: 'http://blah', query_string: 'a=1',
|
66
|
+
headers: { 'Content-Type' => 'hello' } }
|
67
|
+
req5 = { url: 'http://blah', query_string: 'a=1',
|
68
|
+
headers: { 'Content-Type' => 'hello' },
|
69
|
+
method: 'PUT' }
|
70
|
+
results = [req1, req2, req3, req4, req5].map do |req|
|
71
|
+
described_class.new(server, requests: [req], _server: server).start
|
72
|
+
end
|
73
|
+
hashes = results.map { |res| res[:requests].first[:hash] }
|
74
|
+
expect(hashes.uniq.length).to eql 5
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'returns a hash containing unique IDs for requests' do
|
78
|
+
result = described_class.new(server, valid_params).start
|
79
|
+
uid1 = result[:requests][0][:uid]
|
80
|
+
uid2 = result[:requests][1][:uid]
|
81
|
+
expect(uid1).to_not eql uid2
|
82
|
+
end
|
83
|
+
|
84
|
+
describe 'auxiliary request data' do
|
85
|
+
it 'is included in response' do
|
86
|
+
# Ensure that the requester can embed their own identifiers to link to
|
87
|
+
# the uid of the delegated request
|
88
|
+
params = { requests: [url: '-', bob: 'hello'],
|
89
|
+
_server: server }
|
90
|
+
result = described_class.new(server, params).start
|
91
|
+
expect(result[:requests].first[:request][:bob]).to eql 'hello'
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'does not affect request hash' do
|
95
|
+
# Ensure that only pertinent values are used to compute hash (i.e.
|
96
|
+
# adding auxiliary data will still allow retrieval by hash for
|
97
|
+
# otherwise duplicate requests
|
98
|
+
params1 = { requests: [url: 'http://blah', bob: 'hello'],
|
99
|
+
_server: server }
|
100
|
+
result1 = described_class.new(server, params1).start
|
101
|
+
|
102
|
+
params2 = { requests: [url: 'http://blah', not_bob: 'good bye'],
|
103
|
+
_server: server }
|
104
|
+
result2 = described_class.new(server, params2).start
|
105
|
+
expect(result1[:requests][0][:hash]).to eql result2[:requests][0][:hash]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe WebFetch::Helpers do
|
4
|
+
class HelperIncluder
|
5
|
+
include WebFetch::Helpers
|
6
|
+
end
|
7
|
+
|
8
|
+
subject { HelperIncluder.new }
|
9
|
+
|
10
|
+
describe '#symbolize' do
|
11
|
+
it 'handles variously-nested hashes and symbolizes all keys' do
|
12
|
+
nested_hash = { 'a': 1, 'b': { 'c': 2, 'd': [{ 'e': 3 }] } }
|
13
|
+
expect(subject.symbolize(nested_hash)).to eql(
|
14
|
+
a: 1, b: { c: 2, d: [{ e: 3 }] }
|
15
|
+
)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/spec/i18n_spec.rb
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe 'Internationalisation' do
|
4
|
+
it 'accesses translation files and generates translations' do
|
5
|
+
# Just a quick sanity check to make sure the translation file is loaded
|
6
|
+
expect(I18n.t(:requests_missing)).to eql '`requests` parameter missing'
|
7
|
+
end
|
8
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe WebFetch::Resources do
|
4
|
+
let(:server) { WebFetch::MockServer.new }
|
5
|
+
|
6
|
+
describe '.root' do
|
7
|
+
it 'responds with application name' do
|
8
|
+
expect(described_class.root(nil, nil))
|
9
|
+
.to eql(status: 200, payload: { application: 'WebFetch' })
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe '.gather' do
|
14
|
+
let(:result) do
|
15
|
+
described_class.gather(server, requests: [{ url: 'http://google.com' }])
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'provides a `gather` resource' do
|
19
|
+
expect(result[:status]).to eql 200
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'responds with hash' do
|
23
|
+
expect(result[:payload]).to be_a Hash
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe 'retrieve' do
|
28
|
+
it 'gives 404 not found when unrecognised uid requested' do
|
29
|
+
result = described_class.retrieve(server, uid: '123')
|
30
|
+
expect(result[:status]).to eql 404
|
31
|
+
error = result[:payload][:error]
|
32
|
+
expect(error).to eql I18n.t(:uid_not_found)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'gives 404 not found when unrecognised hash requested' do
|
36
|
+
result = described_class.retrieve(server, hash: 'abc')
|
37
|
+
expect(result[:status]).to eql 404
|
38
|
+
error = result[:payload][:error]
|
39
|
+
expect(error).to eql I18n.t(:hash_not_found)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe WebFetch::Retriever do
|
4
|
+
let(:server) { WebFetch::MockServer.new }
|
5
|
+
let(:valid_params) { { uid: 'abc123' } }
|
6
|
+
|
7
|
+
it 'is initialisable with params' do
|
8
|
+
expect(described_class.new(server, valid_params)).to be_a described_class
|
9
|
+
end
|
10
|
+
|
11
|
+
describe 'validation' do
|
12
|
+
context 'valid' do
|
13
|
+
it 'is valid when `uid` given' do
|
14
|
+
retriever = described_class.new(server, uid: 'abc123')
|
15
|
+
expect(retriever.valid?).to be true
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'is valid when `hash` given' do
|
19
|
+
retriever = described_class.new(server, hash: 'def456')
|
20
|
+
expect(retriever.valid?).to be true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'invalid' do
|
25
|
+
it 'is invalid if both `hash` and `uid` given' do
|
26
|
+
retriever = described_class.new(server, hash: 'def456', uid: 'abc123')
|
27
|
+
expect(retriever.valid?).to be false
|
28
|
+
expect(retriever.errors).to include I18n.t(:hash_or_uid_but_not_both)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'is invalid if neither `hash` nor `uid` given' do
|
32
|
+
retriever = described_class.new(server, {})
|
33
|
+
expect(retriever.valid?).to be false
|
34
|
+
expect(retriever.errors).to include I18n.t(:missing_hash_and_uid)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe '#find' do
|
40
|
+
it 'returns `nil` when given uid has not been requested' do
|
41
|
+
retriever = described_class.new(server, uid: 'nope')
|
42
|
+
expect(retriever.find).to be_nil
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'returns `nil` when given hash has not been requested' do
|
46
|
+
retriever = described_class.new(server, hash: 'also nope')
|
47
|
+
expect(retriever.find).to be_nil
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'returns payload when request has been retrieved' do
|
51
|
+
# This test is somewhat useless as we have to mock the behaviour of our
|
52
|
+
# fake Server instance a little too much (since the actual Server object
|
53
|
+
# we're interested in exists in a separate EventMachine thread while we
|
54
|
+
# run our tests). The full stack is tested by the Client specs, however.
|
55
|
+
url = 'http://blah.blah/success'
|
56
|
+
stub_request(:any, url)
|
57
|
+
|
58
|
+
gatherer = WebFetch::Gatherer.new(server, requests: [{ url: url }])
|
59
|
+
response = gatherer.start
|
60
|
+
uid = response[:requests].first[:uid]
|
61
|
+
expect(server).to receive(:storage)
|
62
|
+
.and_return(uid => { body: 'fake body' })
|
63
|
+
|
64
|
+
retriever = described_class.new(server, uid: uid)
|
65
|
+
expect(retriever.find[:body]).to eql 'fake body'
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/spec/router_spec.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe WebFetch::Router do
|
4
|
+
let(:router) { described_class.new }
|
5
|
+
|
6
|
+
it 'can be initialised' do
|
7
|
+
expect(router).to be_a described_class
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '#route' do
|
11
|
+
it 'provides a route to GET /' do
|
12
|
+
expect(router.route('/'))
|
13
|
+
.to eql(status: 200, payload: { application: 'WebFetch' })
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'provides a route to POST /gather' do
|
17
|
+
expect(WebFetch::Resources).to receive(:gather).and_return('hello')
|
18
|
+
expect(router.route('/gather', method: 'POST')).to eql 'hello'
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'provides a route to GET /retrieve' do
|
22
|
+
expect(WebFetch::Resources).to receive(:retrieve).and_return('hello')
|
23
|
+
expect(router.route('/retrieve', method: 'GET')).to eql 'hello'
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'decodes `json` parameter and merges into request params' do
|
27
|
+
json = { a: 10, b: [1, 2, 3] }
|
28
|
+
expect(WebFetch::Resources).to receive(:gather).with(nil, json)
|
29
|
+
router.route('/gather',
|
30
|
+
method: 'POST',
|
31
|
+
query_string: "json=#{JSON.dump(json)}",
|
32
|
+
server: nil)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'returns appropriate response when invaid json provided' do
|
36
|
+
result = router.route('/gather',
|
37
|
+
method: 'POST',
|
38
|
+
query_string: 'json=uh oh :(',
|
39
|
+
server: nil)
|
40
|
+
expect(result).to eql(status: 400, payload: I18n.t(:bad_json))
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/spec/server_spec.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe WebFetch::Server do
|
4
|
+
let(:port) { 8089 }
|
5
|
+
let(:host) { 'localhost' }
|
6
|
+
let(:host_uri) { "http://#{host}:#{port}" }
|
7
|
+
|
8
|
+
it 'accepts HTTP connections' do
|
9
|
+
response = get(host_uri)
|
10
|
+
expect(response.success?).to be true
|
11
|
+
expect(JSON.parse(response.body)['application']).to eql 'WebFetch'
|
12
|
+
end
|
13
|
+
|
14
|
+
describe '/gather' do
|
15
|
+
before(:each) do
|
16
|
+
stub_request(:any, 'http://blah.blah/success')
|
17
|
+
stub_request(:any, 'http://blah.blah/success?a=1')
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'responds "Unprocessable Entity" when incomplete params passed' do
|
21
|
+
response = post("#{host_uri}/gather", bob: ':(')
|
22
|
+
expect(response.status).to eql 422
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'responds with uid for each request' do
|
26
|
+
params = { requests: [{ url: 'http://blah.blah/success' }] }
|
27
|
+
response = post("#{host_uri}/gather", params)
|
28
|
+
expect(JSON.parse(response.body)['requests'].first['uid'])
|
29
|
+
.to_not be_empty
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'responds with a hash that respects url, query string and headers' do
|
33
|
+
params1 = { url: 'http://blah.blah/success' }
|
34
|
+
params2 = { url: 'http://blah.blah/success?a=1' }
|
35
|
+
params3 = { url: 'http://blah.blah/success',
|
36
|
+
headers: { 'Content-Type' => 'whatever' } }
|
37
|
+
|
38
|
+
responses = [params1, params2, params3].map do |params|
|
39
|
+
post("#{host_uri}/gather", requests: [params])
|
40
|
+
end
|
41
|
+
hashes = responses.map do |res|
|
42
|
+
JSON.parse(res.body)['requests'].first['hash']
|
43
|
+
end
|
44
|
+
expect(hashes.uniq.length).to eql 3
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe '#gather' do
|
49
|
+
it 'respects a given url' do
|
50
|
+
stub = stub_request(:any, 'http://blah.blah/success')
|
51
|
+
params = { requests: [{ url: 'http://blah.blah/success' }] }
|
52
|
+
post("#{host_uri}/gather", params)
|
53
|
+
expect(stub).to have_been_requested
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'respects given query parameters' do
|
57
|
+
stub = stub_request(:any, 'http://blah.blah/success?a=1')
|
58
|
+
params = { requests: [{ url: 'http://blah.blah/success?a=1' }] }
|
59
|
+
post("#{host_uri}/gather", params)
|
60
|
+
expect(stub).to have_been_requested
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'respects given headers' do
|
64
|
+
stub = stub_request(:any, 'http://blah.blah/success')
|
65
|
+
.with(headers: { 'Content-Type' => 'whatever' })
|
66
|
+
request = { url: 'http://blah.blah/success',
|
67
|
+
headers: { 'Content-Type' => 'whatever' } }
|
68
|
+
post("#{host_uri}/gather", requests: [request])
|
69
|
+
expect(stub).to have_been_requested
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'respects given http method' do
|
73
|
+
stub = stub_request(:post, 'http://blah.blah/success?a=1')
|
74
|
+
params = { requests: [{ url: 'http://blah.blah/success?a=1',
|
75
|
+
method: 'POST' }] }
|
76
|
+
post("#{host_uri}/gather", params)
|
77
|
+
expect(stub).to have_been_requested
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def get(uri)
|
84
|
+
Faraday.get(uri)
|
85
|
+
end
|
86
|
+
|
87
|
+
def post(uri, params)
|
88
|
+
parsed = URI.parse(uri)
|
89
|
+
base_uri = "#{parsed.scheme}://#{parsed.host}:#{parsed.port}"
|
90
|
+
conn = Faraday.new(url: base_uri)
|
91
|
+
conn.post do |request|
|
92
|
+
request.url parsed.path
|
93
|
+
request.body = params.to_json
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'web_fetch'
|
4
|
+
require 'pp'
|
5
|
+
require 'byebug'
|
6
|
+
require 'webmock/rspec'
|
7
|
+
|
8
|
+
WebMock.disable_net_connect!(allow_localhost: true)
|
9
|
+
|
10
|
+
# This is pretty ugly but seems to do the job
|
11
|
+
puts 'Starting test server'
|
12
|
+
WebFetch::Logger.logger(File::NULL)
|
13
|
+
|
14
|
+
Thread.new do
|
15
|
+
EM.run do
|
16
|
+
EM.start_server 'localhost', 8089, WebFetch::Server
|
17
|
+
end
|
18
|
+
end
|
19
|
+
waiting = true
|
20
|
+
while waiting
|
21
|
+
begin
|
22
|
+
res = Faraday.get('http://localhost:8089/')
|
23
|
+
rescue Faraday::ConnectionFailed
|
24
|
+
res = nil
|
25
|
+
end
|
26
|
+
waiting = !res.nil? && res.status != 200
|
27
|
+
sleep 0.1
|
28
|
+
end
|
29
|
+
puts 'Test server started'
|
30
|
+
|
31
|
+
module WebFetch
|
32
|
+
class MockServer
|
33
|
+
def gather(requests); end
|
34
|
+
|
35
|
+
def storage
|
36
|
+
Storage
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
RSpec.configure do |config|
|
42
|
+
config.expect_with :rspec do |expectations|
|
43
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
44
|
+
end
|
45
|
+
|
46
|
+
config.mock_with :rspec do |mocks|
|
47
|
+
mocks.verify_partial_doubles = true
|
48
|
+
end
|
49
|
+
|
50
|
+
config.shared_context_metadata_behavior = :apply_to_host_groups
|
51
|
+
|
52
|
+
config.order = :random
|
53
|
+
|
54
|
+
Kernel.srand config.seed
|
55
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe WebFetch::Storage do
|
4
|
+
describe '.store' do
|
5
|
+
it 'accepts a key and value to store' do
|
6
|
+
expect do
|
7
|
+
described_class.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
|
+
described_class.store(:key, :value)
|
15
|
+
expect(described_class.fetch(:key)).to eql :value
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'removes item from storage after retrieval' do
|
19
|
+
described_class.store(:key, :value)
|
20
|
+
described_class.fetch(:key)
|
21
|
+
expect(described_class.fetch(:key)).to be_nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/swagger.yaml
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
swagger: '2.0'
|
2
|
+
info:
|
3
|
+
title: WebFetch API
|
4
|
+
description: Asynchronously fetch HTTP entities
|
5
|
+
version: "0.0.1"
|
6
|
+
schemes:
|
7
|
+
- http
|
8
|
+
produces:
|
9
|
+
- application/json
|
10
|
+
paths:
|
11
|
+
/:
|
12
|
+
get:
|
13
|
+
summary: Status
|
14
|
+
description: |
|
15
|
+
Identifies WebFetch server.
|
16
|
+
responses:
|
17
|
+
200:
|
18
|
+
description: An object with an application identifier
|
19
|
+
schema:
|
20
|
+
type: object
|
21
|
+
properties:
|
22
|
+
application:
|
23
|
+
type: string
|
24
|
+
description: WebFetch
|
25
|
+
|
26
|
+
/gather:
|
27
|
+
post:
|
28
|
+
summary: Initiate fetching one or more HTTP entities.
|
29
|
+
description: |
|
30
|
+
Receives an array of HTTP entities as objects and returns an array of objects providing a unique identifier, a hash, and the original request parameters. The unique identifier can be used to retrieve the entity when it has completed downloading.
|
31
|
+
parameters:
|
32
|
+
- name: requests
|
33
|
+
in: body
|
34
|
+
description: HTTP entities to be gathered.
|
35
|
+
required: true
|
36
|
+
schema:
|
37
|
+
type: array
|
38
|
+
items:
|
39
|
+
$ref: '#/definitions/Request'
|
40
|
+
responses:
|
41
|
+
200:
|
42
|
+
description: An array of objects providing job IDs and original parameters.
|
43
|
+
schema:
|
44
|
+
$ref: '#/definitions/GatherResponse'
|
45
|
+
|
46
|
+
|
47
|
+
/retrieve/{id}:
|
48
|
+
get:
|
49
|
+
summary: Retrieve a gathered HTTP entity.
|
50
|
+
description: |
|
51
|
+
Receives a unique identifier and returns the previously requested HTTP entity. This action will block until the entity has been successfully downloaded.
|
52
|
+
parameters:
|
53
|
+
- name: id
|
54
|
+
in: path
|
55
|
+
type: string
|
56
|
+
description: Unique identifier for HTTP entity.
|
57
|
+
required: true
|
58
|
+
responses:
|
59
|
+
200:
|
60
|
+
description: An object containing HTTP entity elements.
|
61
|
+
schema:
|
62
|
+
type: object
|
63
|
+
items:
|
64
|
+
$ref: '#/definitions/Retrieved'
|
65
|
+
|
66
|
+
definitions:
|
67
|
+
Request:
|
68
|
+
type: object
|
69
|
+
properties:
|
70
|
+
url:
|
71
|
+
type: string
|
72
|
+
description: URL of desired HTTP entity.
|
73
|
+
method:
|
74
|
+
type: string
|
75
|
+
default: GET
|
76
|
+
description: HTTP method.
|
77
|
+
headers:
|
78
|
+
type: object
|
79
|
+
description: HTTP headers.
|
80
|
+
query:
|
81
|
+
type: object
|
82
|
+
description: Query parameters.
|
83
|
+
body:
|
84
|
+
type: string
|
85
|
+
description: HTTP body.
|
86
|
+
Retrieved:
|
87
|
+
type: object
|
88
|
+
properties:
|
89
|
+
response:
|
90
|
+
type: object
|
91
|
+
description: Requested HTTP entity elements.
|
92
|
+
properties:
|
93
|
+
success:
|
94
|
+
type: boolean
|
95
|
+
body:
|
96
|
+
type: string
|
97
|
+
headers:
|
98
|
+
type: object
|
99
|
+
status:
|
100
|
+
type: integer
|
101
|
+
|
102
|
+
GatherResponse:
|
103
|
+
type: array
|
104
|
+
items:
|
105
|
+
type: object
|
106
|
+
properties:
|
107
|
+
uid:
|
108
|
+
type: string
|
109
|
+
description: Unique identifier for requested HTTP entity.
|
110
|
+
hash:
|
111
|
+
type: string
|
112
|
+
description: SHA1 hash of request based on url, query parameters, headers, method [currently this serves no purpose].
|
113
|
+
request:
|
114
|
+
type: object
|
115
|
+
description: Original requested HTTP parameters.
|
data/web_fetch.gemspec
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'web_fetch/version'
|
6
|
+
|
7
|
+
# rubocop:disable Metrics/BlockLength
|
8
|
+
Gem::Specification.new do |s|
|
9
|
+
s.name = 'web_fetch'
|
10
|
+
s.version = WebFetch::VERSION
|
11
|
+
s.date = '2016-10-16'
|
12
|
+
s.summary = 'Async HTTP fetcher'
|
13
|
+
s.description = 'Fetches HTTP responses as batch requests concurrently'
|
14
|
+
s.authors = ['Bob Farrell']
|
15
|
+
s.email = 'robertanthonyfarrell@gmail.com'
|
16
|
+
s.files = `git ls-files`.split($RS)
|
17
|
+
s.homepage = 'https://github.com/bobf/web_fetch'
|
18
|
+
s.licenses = ['MIT']
|
19
|
+
s.require_paths = ['lib']
|
20
|
+
s.executables << 'web_fetch_server'
|
21
|
+
s.executables << 'web_fetch_control'
|
22
|
+
|
23
|
+
s.add_dependency 'activesupport', '~> 4.0'
|
24
|
+
s.add_dependency 'childprocess', '~> 0.5'
|
25
|
+
s.add_dependency 'daemons', '~> 1.2'
|
26
|
+
s.add_dependency 'em-http-request', '~> 1.1'
|
27
|
+
s.add_dependency 'em-logger', '~> 0.1'
|
28
|
+
s.add_dependency 'eventmachine', '~> 1.0'
|
29
|
+
s.add_dependency 'eventmachine_httpserver', '~> 0.2'
|
30
|
+
s.add_dependency 'faraday', '~> 0.9'
|
31
|
+
s.add_dependency 'hanami-router', '~> 0.7'
|
32
|
+
s.add_dependency 'hanami-utils', '0.8.0'
|
33
|
+
s.add_dependency 'i18n', '~> 0.7'
|
34
|
+
s.add_dependency 'rack', '~> 1.6'
|
35
|
+
|
36
|
+
s.add_development_dependency 'byebug', '~> 9.0'
|
37
|
+
s.add_development_dependency 'rspec', '~> 3.5'
|
38
|
+
s.add_development_dependency 'rubocop', '~> 0.59.2'
|
39
|
+
s.add_development_dependency 'webmock', '~> 3.4'
|
40
|
+
end
|
41
|
+
# rubocop:enable Metrics/BlockLength
|