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