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.
Files changed (79) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +6 -0
  3. data/.rspec +1 -0
  4. data/Gemfile +6 -0
  5. data/Gemfile.lock +114 -0
  6. data/LICENSE +28 -0
  7. data/README.md +198 -0
  8. data/Rakefile +11 -0
  9. data/lib/sitehub.rb +9 -0
  10. data/lib/sitehub/builder.rb +82 -0
  11. data/lib/sitehub/collection.rb +35 -0
  12. data/lib/sitehub/collection/route_collection.rb +28 -0
  13. data/lib/sitehub/collection/split_route_collection.rb +50 -0
  14. data/lib/sitehub/collection/split_route_collection/split.rb +18 -0
  15. data/lib/sitehub/constants.rb +23 -0
  16. data/lib/sitehub/constants/http_header_keys.rb +25 -0
  17. data/lib/sitehub/constants/rack_http_header_keys.rb +17 -0
  18. data/lib/sitehub/cookie.rb +54 -0
  19. data/lib/sitehub/cookie/attribute.rb +22 -0
  20. data/lib/sitehub/cookie/flag.rb +22 -0
  21. data/lib/sitehub/cookie_rewriting.rb +35 -0
  22. data/lib/sitehub/forward_proxies.rb +50 -0
  23. data/lib/sitehub/forward_proxy.rb +67 -0
  24. data/lib/sitehub/forward_proxy_builder.rb +99 -0
  25. data/lib/sitehub/http_headers.rb +60 -0
  26. data/lib/sitehub/logging.rb +5 -0
  27. data/lib/sitehub/logging/access_logger.rb +74 -0
  28. data/lib/sitehub/logging/error_logger.rb +36 -0
  29. data/lib/sitehub/logging/log_entry.rb +14 -0
  30. data/lib/sitehub/logging/log_stash.rb +10 -0
  31. data/lib/sitehub/logging/log_wrapper.rb +23 -0
  32. data/lib/sitehub/middleware.rb +21 -0
  33. data/lib/sitehub/path_directive.rb +32 -0
  34. data/lib/sitehub/path_directives.rb +21 -0
  35. data/lib/sitehub/request_mapping.rb +43 -0
  36. data/lib/sitehub/resolver.rb +11 -0
  37. data/lib/sitehub/reverse_proxy.rb +53 -0
  38. data/lib/sitehub/rules.rb +13 -0
  39. data/lib/sitehub/string_sanitiser.rb +7 -0
  40. data/lib/sitehub/transaction_id.rb +16 -0
  41. data/lib/sitehub/version.rb +3 -0
  42. data/mem_usage.txt +1584 -0
  43. data/sitehub.gemspec +43 -0
  44. data/spec/basket_spec.rb +30 -0
  45. data/spec/sitehub/builder_spec.rb +203 -0
  46. data/spec/sitehub/collection/route_collection_spec.rb +91 -0
  47. data/spec/sitehub/collection/split_route_collection_spec.rb +111 -0
  48. data/spec/sitehub/collection_spec.rb +40 -0
  49. data/spec/sitehub/cookie/attribute_spec.rb +37 -0
  50. data/spec/sitehub/cookie/flag_spec.rb +27 -0
  51. data/spec/sitehub/cookie_rewriting_spec.rb +67 -0
  52. data/spec/sitehub/cookie_spec.rb +61 -0
  53. data/spec/sitehub/error_handling_spec.rb +21 -0
  54. data/spec/sitehub/forward_proxies_spec.rb +99 -0
  55. data/spec/sitehub/forward_proxy_builder_spec.rb +295 -0
  56. data/spec/sitehub/forward_proxy_spec.rb +138 -0
  57. data/spec/sitehub/http_headers_spec.rb +71 -0
  58. data/spec/sitehub/integration_spec.rb +21 -0
  59. data/spec/sitehub/logging/access_logger_spec.rb +127 -0
  60. data/spec/sitehub/logging/error_logger_spec.rb +80 -0
  61. data/spec/sitehub/logging/log_entry_spec.rb +34 -0
  62. data/spec/sitehub/logging/log_stash_spec.rb +21 -0
  63. data/spec/sitehub/logging/log_wrapper_spec.rb +33 -0
  64. data/spec/sitehub/middleware_spec.rb +69 -0
  65. data/spec/sitehub/path_directive_spec.rb +50 -0
  66. data/spec/sitehub/path_directives_spec.rb +45 -0
  67. data/spec/sitehub/request_mapping_spec.rb +71 -0
  68. data/spec/sitehub/resolver_spec.rb +15 -0
  69. data/spec/sitehub/reverse_proxy_spec.rb +105 -0
  70. data/spec/sitehub/transaction_id_spec.rb +28 -0
  71. data/spec/sitehub_spec.rb +19 -0
  72. data/spec/spec_helper.rb +26 -0
  73. data/spec/support/patch/rack/response.rb +25 -0
  74. data/spec/support/shared_contexts/async_context.rb +69 -0
  75. data/spec/support/shared_contexts/middleware_context.rb +51 -0
  76. data/spec/support/shared_contexts/rack_test_context.rb +12 -0
  77. data/spec/support/shared_contexts/sitehub_context.rb +25 -0
  78. data/spec/support/silent_warnings.rb +5 -0
  79. 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