sitehub 0.4.1

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