sitehub 0.4.1
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 +6 -0
- data/.rspec +1 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +114 -0
- data/LICENSE +28 -0
- data/README.md +198 -0
- data/Rakefile +11 -0
- data/lib/sitehub.rb +9 -0
- data/lib/sitehub/builder.rb +82 -0
- data/lib/sitehub/collection.rb +35 -0
- data/lib/sitehub/collection/route_collection.rb +28 -0
- data/lib/sitehub/collection/split_route_collection.rb +50 -0
- data/lib/sitehub/collection/split_route_collection/split.rb +18 -0
- data/lib/sitehub/constants.rb +23 -0
- data/lib/sitehub/constants/http_header_keys.rb +25 -0
- data/lib/sitehub/constants/rack_http_header_keys.rb +17 -0
- data/lib/sitehub/cookie.rb +54 -0
- data/lib/sitehub/cookie/attribute.rb +22 -0
- data/lib/sitehub/cookie/flag.rb +22 -0
- data/lib/sitehub/cookie_rewriting.rb +35 -0
- data/lib/sitehub/forward_proxies.rb +50 -0
- data/lib/sitehub/forward_proxy.rb +67 -0
- data/lib/sitehub/forward_proxy_builder.rb +99 -0
- data/lib/sitehub/http_headers.rb +60 -0
- data/lib/sitehub/logging.rb +5 -0
- data/lib/sitehub/logging/access_logger.rb +74 -0
- data/lib/sitehub/logging/error_logger.rb +36 -0
- data/lib/sitehub/logging/log_entry.rb +14 -0
- data/lib/sitehub/logging/log_stash.rb +10 -0
- data/lib/sitehub/logging/log_wrapper.rb +23 -0
- data/lib/sitehub/middleware.rb +21 -0
- data/lib/sitehub/path_directive.rb +32 -0
- data/lib/sitehub/path_directives.rb +21 -0
- data/lib/sitehub/request_mapping.rb +43 -0
- data/lib/sitehub/resolver.rb +11 -0
- data/lib/sitehub/reverse_proxy.rb +53 -0
- data/lib/sitehub/rules.rb +13 -0
- data/lib/sitehub/string_sanitiser.rb +7 -0
- data/lib/sitehub/transaction_id.rb +16 -0
- data/lib/sitehub/version.rb +3 -0
- data/mem_usage.txt +1584 -0
- data/sitehub.gemspec +43 -0
- data/spec/basket_spec.rb +30 -0
- data/spec/sitehub/builder_spec.rb +203 -0
- data/spec/sitehub/collection/route_collection_spec.rb +91 -0
- data/spec/sitehub/collection/split_route_collection_spec.rb +111 -0
- data/spec/sitehub/collection_spec.rb +40 -0
- data/spec/sitehub/cookie/attribute_spec.rb +37 -0
- data/spec/sitehub/cookie/flag_spec.rb +27 -0
- data/spec/sitehub/cookie_rewriting_spec.rb +67 -0
- data/spec/sitehub/cookie_spec.rb +61 -0
- data/spec/sitehub/error_handling_spec.rb +21 -0
- data/spec/sitehub/forward_proxies_spec.rb +99 -0
- data/spec/sitehub/forward_proxy_builder_spec.rb +295 -0
- data/spec/sitehub/forward_proxy_spec.rb +138 -0
- data/spec/sitehub/http_headers_spec.rb +71 -0
- data/spec/sitehub/integration_spec.rb +21 -0
- data/spec/sitehub/logging/access_logger_spec.rb +127 -0
- data/spec/sitehub/logging/error_logger_spec.rb +80 -0
- data/spec/sitehub/logging/log_entry_spec.rb +34 -0
- data/spec/sitehub/logging/log_stash_spec.rb +21 -0
- data/spec/sitehub/logging/log_wrapper_spec.rb +33 -0
- data/spec/sitehub/middleware_spec.rb +69 -0
- data/spec/sitehub/path_directive_spec.rb +50 -0
- data/spec/sitehub/path_directives_spec.rb +45 -0
- data/spec/sitehub/request_mapping_spec.rb +71 -0
- data/spec/sitehub/resolver_spec.rb +15 -0
- data/spec/sitehub/reverse_proxy_spec.rb +105 -0
- data/spec/sitehub/transaction_id_spec.rb +28 -0
- data/spec/sitehub_spec.rb +19 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/support/patch/rack/response.rb +25 -0
- data/spec/support/shared_contexts/async_context.rb +69 -0
- data/spec/support/shared_contexts/middleware_context.rb +51 -0
- data/spec/support/shared_contexts/rack_test_context.rb +12 -0
- data/spec/support/shared_contexts/sitehub_context.rb +25 -0
- data/spec/support/silent_warnings.rb +5 -0
- metadata +359 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'sitehub/logging/log_stash'
|
2
|
+
|
3
|
+
class SiteHub
|
4
|
+
module Logging
|
5
|
+
describe LogStash do
|
6
|
+
|
7
|
+
it 'inherits Array' do
|
8
|
+
expect(subject).to be_a_kind_of(Array)
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '#<<' do
|
12
|
+
|
13
|
+
it 'adds a LogEntry' do
|
14
|
+
allow(Time).to receive(:now).and_return(:current_time)
|
15
|
+
subject << :message
|
16
|
+
expect(subject).to eq([LogEntry.new(:message)])
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'sitehub/logging/log_wrapper'
|
2
|
+
class SiteHub
|
3
|
+
module Logging
|
4
|
+
|
5
|
+
describe LogWrapper do
|
6
|
+
|
7
|
+
describe '#write' do
|
8
|
+
let(:logger) { double('logger') }
|
9
|
+
subject do
|
10
|
+
described_class.new(logger)
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'logger responds to <<' do
|
14
|
+
it 'calls << when writing out the log' do
|
15
|
+
message = 'message'
|
16
|
+
expect(logger).to receive(:<<).with(message)
|
17
|
+
subject.write(message)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'logger responds to write' do
|
22
|
+
|
23
|
+
it 'calls << when writing out the log' do
|
24
|
+
message = 'message'
|
25
|
+
expect(logger).to receive(:write).with(message)
|
26
|
+
subject.write(message)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'sitehub/middleware'
|
2
|
+
class SiteHub
|
3
|
+
describe Middleware do
|
4
|
+
include_context :middleware_test
|
5
|
+
|
6
|
+
subject do
|
7
|
+
Object.new.tap do |o|
|
8
|
+
o.extend(described_class)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '#use' do
|
13
|
+
it 'stores the middleware to be used by the forward proxies' do
|
14
|
+
block = proc {}
|
15
|
+
args = [:args]
|
16
|
+
subject.use :middleware, *args, &block
|
17
|
+
expect(subject.middlewares).to eq([[:middleware, args, block]])
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe '#apply_middleware' do
|
22
|
+
|
23
|
+
context 'middleware defined' do
|
24
|
+
|
25
|
+
it 'wraps the supplied app in the middleware' do
|
26
|
+
subject.use middleware
|
27
|
+
result = subject.apply_middleware(:app)
|
28
|
+
expect(result).to be_a(middleware)
|
29
|
+
expect(result.app).to eq(:app)
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'wraps the supplied app in the middleware in the order they were supplied' do
|
33
|
+
middleware_1 = create_middleware
|
34
|
+
middleware_2 = create_middleware
|
35
|
+
subject.use middleware_1
|
36
|
+
subject.use middleware_2
|
37
|
+
|
38
|
+
result = subject.apply_middleware(:app)
|
39
|
+
|
40
|
+
expect(result).to be_a(middleware_1)
|
41
|
+
expect(result).to be_using(middleware_2)
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'args supplied' do
|
45
|
+
it 'passes the arg to the middleware' do
|
46
|
+
subject.use middleware, :arg
|
47
|
+
result = subject.apply_middleware(:app)
|
48
|
+
expect(result.arg).to eq(:arg)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'block supplied' do
|
53
|
+
it 'passes the block to the middleware' do
|
54
|
+
block_passed = false
|
55
|
+
|
56
|
+
subject.use middleware do
|
57
|
+
block_passed = true
|
58
|
+
end
|
59
|
+
|
60
|
+
subject.apply_middleware(:app)
|
61
|
+
expect(block_passed).to be(true)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'sitehub/path_directive'
|
2
|
+
class SiteHub
|
3
|
+
describe PathDirective do
|
4
|
+
|
5
|
+
subject do
|
6
|
+
described_class.new(%r{/match}, '/path_template')
|
7
|
+
end
|
8
|
+
|
9
|
+
describe '#match?' do
|
10
|
+
|
11
|
+
context 'matcher applies' do
|
12
|
+
it 'returns true' do
|
13
|
+
expect(subject.match?('/match')).to eq(true)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'matcher does not apply' do
|
18
|
+
it 'returns false' do
|
19
|
+
expect(subject.match?('/mismatch')).to eq(false)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '#apply' do
|
25
|
+
subject do
|
26
|
+
described_class.new(Regexp.new('http://www.upstream.com/orders/(.*)'), '/$1')
|
27
|
+
end
|
28
|
+
|
29
|
+
let(:url){'http://www.upstream.com/orders/123'}
|
30
|
+
|
31
|
+
it 'uses the url to populate the path template' do
|
32
|
+
expect(subject.apply(url)).to eq('/123')
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'retains the query string' do
|
36
|
+
expect(subject.apply("#{url}?param=value")).to eq('/123?param=value')
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe '#path_template' do
|
41
|
+
it 'returns a duplicate of the path_template every time' do
|
42
|
+
version1, version2 = subject.path_template, subject.path_template
|
43
|
+
expect(version1).to eq(version2)
|
44
|
+
expect(version1).to_not be(version2)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'sitehub/path_directives'
|
2
|
+
class SiteHub
|
3
|
+
describe PathDirectives do
|
4
|
+
it 'is an array' do
|
5
|
+
expect(subject).to be_kind_of(Array)
|
6
|
+
end
|
7
|
+
|
8
|
+
describe '#initialize' do
|
9
|
+
context 'hash key is a regexp' do
|
10
|
+
it 'leaves the key has a regexp' do
|
11
|
+
key = %r{/.*/}
|
12
|
+
value = '/'
|
13
|
+
expect(described_class.new(key => value)).to eq([PathDirective.new(key,value)])
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'hash key is a string' do
|
18
|
+
it 'converts the key to a regexp' do
|
19
|
+
key = 'string'
|
20
|
+
value = '/'
|
21
|
+
expect(described_class.new(key => value)).to eq([PathDirective.new(Regexp.new(key),value)])
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#find' do
|
27
|
+
let(:matcher){Regexp.new('/orders/(.*)')}
|
28
|
+
let(:path_template){'/orders/$1'}
|
29
|
+
subject do
|
30
|
+
described_class.new(matcher => path_template)
|
31
|
+
end
|
32
|
+
context 'url matches matcher' do
|
33
|
+
it 'returns the path directive' do
|
34
|
+
expect(subject.find('http://url.com/orders/123')).to eq(PathDirective.new(matcher,path_template))
|
35
|
+
end
|
36
|
+
end
|
37
|
+
context 'url does not matches matcher' do
|
38
|
+
it 'returns nil' do
|
39
|
+
expect(subject.find('http://url.com/mismatch')).to eq(nil)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'sitehub/request_mapping'
|
2
|
+
|
3
|
+
class SiteHub
|
4
|
+
describe RequestMapping do
|
5
|
+
|
6
|
+
describe '#initialize' do
|
7
|
+
let(:mapped_url){'http://downstream_url'}
|
8
|
+
subject do
|
9
|
+
described_class.new(source_url: 'http://upstream.com/articles/123',
|
10
|
+
mapped_url: mapped_url,
|
11
|
+
mapped_path: %r{/articles/(.*)})
|
12
|
+
end
|
13
|
+
it 'duplicates the mapped url as we mutate it' do
|
14
|
+
expect(subject.mapped_url).to eq(mapped_url)
|
15
|
+
expect(subject.mapped_url).to_not be(mapped_url)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#cookie_path' do
|
20
|
+
subject do
|
21
|
+
described_class.new(source_url: 'http://upstream.com/articles/123',
|
22
|
+
mapped_url: 'http://downstream_url/$1/view',
|
23
|
+
mapped_path: %r{/articles/(.*)})
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'mapped_path is a regexp' do
|
27
|
+
it 'returns the literal part of the mapped path' do
|
28
|
+
expect(subject.cookie_path).to eq('/articles')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe '#computed_uri' do
|
34
|
+
context 'mapped_path is a regexp' do
|
35
|
+
subject do
|
36
|
+
described_class.new(source_url: 'http://upstream.com/articles/123',
|
37
|
+
mapped_url: 'http://downstream_url/$1/view',
|
38
|
+
mapped_path: %r{/articles/(.*)})
|
39
|
+
end
|
40
|
+
it 'returns the computed uri' do
|
41
|
+
expect(subject.computed_uri).to eq('http://downstream_url/123/view')
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'mapped_path is a string' do
|
46
|
+
subject do
|
47
|
+
described_class.new(source_url: 'http://upstream.com/articles',
|
48
|
+
mapped_url: 'http://downstream_url/articles',
|
49
|
+
mapped_path: '/articles')
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'returns the mapped url' do
|
53
|
+
expect(subject.computed_uri).to eq('http://downstream_url/articles')
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'when there is a query string on the source url' do
|
58
|
+
subject do
|
59
|
+
described_class.new(source_url: 'http://upstream.com/articles?param=value',
|
60
|
+
mapped_url: 'http://downstream_url/$1',
|
61
|
+
mapped_path: %r{/(.*)})
|
62
|
+
end
|
63
|
+
it 'keeps the querystring' do
|
64
|
+
expect(subject.computed_uri).to eq('http://downstream_url/articles?param=value')
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'sitehub/resolver'
|
2
|
+
class SiteHub
|
3
|
+
describe Resolver do
|
4
|
+
subject do
|
5
|
+
Object.new.tap do |o|
|
6
|
+
o.extend(described_class)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
describe '#resolve' do
|
10
|
+
it 'returns self' do
|
11
|
+
expect(subject.resolve).to be(subject)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
class SiteHub
|
2
|
+
|
3
|
+
describe ReverseProxy do
|
4
|
+
|
5
|
+
include_context :middleware_test
|
6
|
+
|
7
|
+
|
8
|
+
let(:mapped_path){'/orders'}
|
9
|
+
let(:user_facing_app_url){"http://example.org#{mapped_path}"}
|
10
|
+
|
11
|
+
|
12
|
+
let(:downstream_mapping){'https://downstream.com/app1/orders'}
|
13
|
+
let(:downstream_location){"#{downstream_mapping}/123/confirmation"}
|
14
|
+
let(:request_mapping){RequestMapping.new source_url: "#{user_facing_app_url}/123/payment", mapped_url: downstream_mapping, mapped_path: mapped_path}
|
15
|
+
let(:app) do
|
16
|
+
proc{downstream_response}
|
17
|
+
end
|
18
|
+
let(:downstream_response){Rack::Response.new('downstream', 200, {'header1' => 'header1'} )}
|
19
|
+
|
20
|
+
#subject do
|
21
|
+
# described_class.new(inner_app, [])
|
22
|
+
#end
|
23
|
+
|
24
|
+
subject(:reverse_proxy){described_class.new(app, [])}
|
25
|
+
let(:env){{REQUEST_MAPPING => request_mapping}}
|
26
|
+
|
27
|
+
describe '#call' do
|
28
|
+
|
29
|
+
|
30
|
+
|
31
|
+
subject(:response) do
|
32
|
+
status, headers, body = reverse_proxy.call(env).to_a
|
33
|
+
Rack::Response.new(body,status, headers)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'copies the downstream body in to the response' do
|
37
|
+
expect(response.body).to eq(downstream_response.body)
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'copies the downstream headers in to the response' do
|
41
|
+
expect(response.headers).to eq(downstream_response.headers)
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'copies the downstream status in to the response' do
|
45
|
+
expect(response.status).to eq(downstream_response.status)
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'cookies' do
|
49
|
+
context 'downstream response contains a cookie' do
|
50
|
+
it 'rewrites it to use the upstream domain' do
|
51
|
+
downstream_response.set_cookie('downstream.cookie', domain: '.downstream.com', value: 'value')
|
52
|
+
expect(reverse_proxy).to receive(:rewrite_cookies).with(downstream_response.headers, substitute_domain: URI(request_mapping.source_url).host)
|
53
|
+
reverse_proxy.call(env)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'downstream response does not contain a cookie' do
|
58
|
+
it 'does not attempt to rewrite the cookies' do
|
59
|
+
downstream_headers = downstream_response.headers
|
60
|
+
downstream_headers[HttpHeaders::LOCATION_HEADER] = downstream_location
|
61
|
+
expect(reverse_proxy).not_to receive(:rewrite_cookies)
|
62
|
+
reverse_proxy.call(env)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
#Location, Content-Location and URI
|
68
|
+
it 'rewrites the Location header' do
|
69
|
+
downstream_response.headers[HttpHeaders::LOCATION_HEADER] = downstream_location
|
70
|
+
expect(reverse_proxy).to receive(:interpolate_location).with(downstream_location, request_mapping.source_url).and_return(:interpolated_location)
|
71
|
+
reverse_proxy.call(env)
|
72
|
+
expect(downstream_response.headers[HttpHeaders::LOCATION_HEADER]).to eq(:interpolated_location)
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
describe '#interpolate_location' do
|
78
|
+
it 'changes the domain' do
|
79
|
+
expect(reverse_proxy.interpolate_location(downstream_location, request_mapping.source_url)).to eq("http://example.org/app1/orders/123/confirmation")
|
80
|
+
end
|
81
|
+
|
82
|
+
context 'there is a directive that applies' do
|
83
|
+
context 'matcher is a regexp' do
|
84
|
+
subject do
|
85
|
+
directive = {%r{#{downstream_mapping}/(.*)/confirmation} => '/confirmation/$1'}
|
86
|
+
described_class.new(inner_app,directive)
|
87
|
+
end
|
88
|
+
it 'changes the path' do
|
89
|
+
expect(subject.interpolate_location(downstream_location, request_mapping.source_url)).to eq("http://example.org/confirmation/123")
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
context 'matcher is a string' do
|
94
|
+
subject do
|
95
|
+
directive = {"http://downstream.com/confirmation" => '/congratulations'}
|
96
|
+
described_class.new(inner_app,directive)
|
97
|
+
end
|
98
|
+
it 'changes the path' do
|
99
|
+
expect(subject.interpolate_location('http://downstream.com/confirmation', request_mapping.source_url)).to eq("http://example.org/congratulations")
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'sitehub/transaction_id'
|
2
|
+
|
3
|
+
class SiteHub
|
4
|
+
describe TransactionId do
|
5
|
+
let(:transaction_id){Constants::RackHttpHeaderKeys::TRANSACTION_ID}
|
6
|
+
subject do
|
7
|
+
described_class.new(Proc.new{})
|
8
|
+
end
|
9
|
+
it 'adds a unique identifier to the request' do
|
10
|
+
uuid = UUID.generate(:compact)
|
11
|
+
expect(UUID).to receive(:generate).with(:compact).and_return(uuid)
|
12
|
+
env = {}
|
13
|
+
subject.call(env)
|
14
|
+
|
15
|
+
expect(env[transaction_id]).to eq(uuid)
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'transaction id header already exists' do
|
19
|
+
it 'leaves it intact' do
|
20
|
+
expect(UUID).to_not receive(:generate)
|
21
|
+
env = {transaction_id => :exiting_id}
|
22
|
+
subject.call(env)
|
23
|
+
|
24
|
+
expect(env[transaction_id]).to eq(:exiting_id)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|