web_fetch 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|