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,43 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'sitehub/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "sitehub"
8
+ spec.version = SiteHub::VERSION
9
+ spec.authors = ["Ladtech"]
10
+ spec.email = ["team@lad-tech.com"]
11
+ spec.summary = %q{A/B testing enabled HTTP proxy}
12
+ spec.description = %q{A/B testing enabled HTTP proxy}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(spec)/*.rb})
19
+ spec.require_paths = ["lib"]
20
+
21
+
22
+ spec.add_dependency "rack"
23
+ spec.add_dependency "uuid"
24
+ spec.add_dependency "em-http-request"
25
+ spec.add_dependency "rack-ssl-enforcer"
26
+ spec.add_dependency "rack-fiber_pool"
27
+ spec.add_dependency "faraday"
28
+ spec.add_dependency "em-synchrony"
29
+ spec.add_dependency "thin"
30
+
31
+ spec.add_development_dependency "bundler", "~> 1.5"
32
+ spec.add_development_dependency "rake"
33
+ spec.add_development_dependency "rspec", "~> 3.2.0"
34
+ spec.add_development_dependency "webmock"
35
+ spec.add_development_dependency "rack-test"
36
+ spec.add_development_dependency "geminabox"
37
+ spec.add_development_dependency "simplecov"
38
+ #spec.add_development_dependency "rubocop"
39
+ spec.add_development_dependency "memory_profiler"
40
+
41
+ spec.add_development_dependency "ruby-prof"
42
+
43
+ end
@@ -0,0 +1,30 @@
1
+ class Basket
2
+
3
+ attr_reader :items
4
+ def initialize shopping_list
5
+ @items = parse(shopping_list)
6
+ end
7
+
8
+ def parse shopping_list
9
+ shopping_list.lines[1..-1].collect { |item|
10
+ item.chomp.to_sym
11
+ }
12
+ end
13
+ end
14
+
15
+ describe Basket do
16
+
17
+ describe '#initialize' do
18
+ it 'takes a shopping list' do
19
+
20
+ shopping_list=<<LIST
21
+ list
22
+ apple
23
+ carrot
24
+ LIST
25
+
26
+ basket = described_class.new(shopping_list)
27
+ expect(basket.items).to eq([:apple, :carrot])
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,203 @@
1
+ require 'sitehub/builder'
2
+
3
+ class SiteHub
4
+
5
+
6
+ describe Builder do
7
+
8
+ include_context :middleware_test
9
+
10
+ subject do
11
+ described_class.new do
12
+ proxy '/app1' => :endpoint
13
+ end
14
+ end
15
+
16
+ it 'supports middleware' do
17
+ expect(subject).to be_kind_of(Middleware)
18
+ end
19
+
20
+ describe '#proxy' do
21
+ context 'no version explicitly defined' do
22
+
23
+ subject do
24
+ described_class.new do
25
+ proxy '/app1' => :endpoint
26
+ end
27
+ end
28
+
29
+
30
+ it 'the defined route is used 100% of the time' do
31
+ expected_proxy = ForwardProxyBuilder.new(mapped_path: '/path').tap do |route|
32
+ route.default(url: :endpoint)
33
+ end
34
+ expect(subject.forward_proxies.forward_proxies['/app1']).to eq(expected_proxy)
35
+ end
36
+ end
37
+
38
+ context 'custom route defined' do
39
+ subject do
40
+ described_class.new do
41
+ proxy('/app') do
42
+ split url: :endpoint, label: :label, percentage: 100
43
+ end
44
+ end
45
+ end
46
+
47
+ it 'passes the block to the route constructor' do
48
+ expected_route = ForwardProxyBuilder.new(mapped_path: '/path').tap do |route|
49
+ route.split url: :endpoint, percentage: 100, label: :label
50
+ end
51
+
52
+ expect(subject.forward_proxies.forward_proxies['/app'].endpoints).to eq(expected_route.endpoints)
53
+ end
54
+ end
55
+ end
56
+
57
+ describe "#access_logger" do
58
+ it ' sets the logger' do
59
+ subject.access_logger :access_logger
60
+ sitehub = subject.build
61
+ logger_middleware = find_middleware(sitehub, Logging::AccessLogger)
62
+ expect(logger_middleware.logger).to eq(Logging::LogWrapper.new(:access_logger))
63
+ end
64
+
65
+ it 'defaults to STDOUT' do
66
+ allow(::Logger).to receive(:new).and_return(:a_logger)
67
+ expect(::Logger).to receive(:new).with(STDOUT).and_return(:stdout_logger)
68
+ sitehub = subject.build
69
+ logger_middleware = find_middleware(sitehub, Logging::AccessLogger)
70
+ expect(logger_middleware.logger).to eq(Logging::LogWrapper.new(:stdout_logger))
71
+ end
72
+
73
+ end
74
+
75
+ describe '#reverse_proxy' do
76
+ it 'registers a reverse proxy' do
77
+ subject.reverse_proxy(:downstream_url => :upstream_path)
78
+ expect(subject.reverse_proxies).to eq({:downstream_url => :upstream_path})
79
+ end
80
+ end
81
+
82
+ describe '#sitehub_cookie_name' do
83
+ it 'defaults to sitehub.recorded_route' do
84
+ expect(subject.sitehub_cookie_name).to eq(RECORDED_ROUTES_COOKIE)
85
+ end
86
+
87
+ it 'is passed to forward_proxy_builders' do
88
+ subject.sitehub_cookie_name :expected_cookie_name
89
+ proxy = subject.proxy '/app1' => :endpoint
90
+ expect(proxy.sitehub_cookie_name).to eq(:expected_cookie_name)
91
+ end
92
+ end
93
+
94
+ describe "#error_logger" do
95
+ it 'sets the logger' do
96
+ subject.error_logger :error_logger
97
+ sitehub = subject.build
98
+ logger_middleware = find_middleware(sitehub, SiteHub::Logging::ErrorLogger)
99
+ expect(logger_middleware.logger).to eq(Logging::LogWrapper.new(:error_logger))
100
+ end
101
+
102
+ it 'defaults to STDERR' do
103
+ allow(::Logger).to receive(:new).and_return(:a_logger)
104
+ expect(::Logger).to receive(:new).with(STDERR).and_return(:stderr_logger)
105
+ sitehub = subject.build
106
+ logger_middleware = find_middleware(sitehub, SiteHub::Logging::ErrorLogger)
107
+ expect(logger_middleware.logger).to eq(Logging::LogWrapper.new(:stderr_logger))
108
+ end
109
+
110
+ end
111
+
112
+ describe '#build' do
113
+
114
+ it 'initializes the forward_proxies' do
115
+ expect(subject.forward_proxies).to receive(:init).and_call_original
116
+ subject.build
117
+ end
118
+
119
+ context 'default middleware' do
120
+ it 'adds TransactionId middleware to the sitehub' do
121
+ expect(subject.build).to be_using(TransactionId)
122
+ end
123
+
124
+ it 'adds a forward proxies' do
125
+ expect(subject.build).to be_using(ForwardProxies)
126
+ end
127
+
128
+ it 'adds a AccessLogger' do
129
+ expect(subject.build).to be_using(Logging::AccessLogger)
130
+ end
131
+
132
+ it 'adds a ErrorLogger' do
133
+ expect(subject.build).to be_using(Logging::ErrorLogger)
134
+ end
135
+ it 'adds a Rack FiberPool' do
136
+ expect(subject.build).to be_using(Rack::FiberPool)
137
+ end
138
+
139
+ context 'reverse proxy' do
140
+ it 'adds a reverse proxy' do
141
+ expect(subject.build).to be_using(ReverseProxy)
142
+ end
143
+
144
+ it 'uses configured reverse proxy directives' do
145
+ subject.reverse_proxy({:downstream_url => :upstream_path.to_s})
146
+ reverse_proxy = find_middleware(subject.build, ReverseProxy)
147
+
148
+ expect(reverse_proxy.path_directives).to eq(PathDirectives.new(:downstream_url => :upstream_path.to_s))
149
+ end
150
+ end
151
+
152
+
153
+ it 'adds them in the right order' do
154
+ middleware_stack = collect_middleware(subject.build).collect{|m| m.class}
155
+ expect(middleware_stack).to eq([Rack::FiberPool, Logging::ErrorLogger, Logging::AccessLogger,TransactionId, ReverseProxy, ForwardProxies])
156
+ end
157
+ end
158
+
159
+ context 'middleware defined' do
160
+ it 'wraps the sitehub with it' do
161
+ subject.use middleware
162
+ expect(subject.build).to be_using(middleware)
163
+ end
164
+ end
165
+
166
+ context '#force_ssl' do
167
+ context 'true' do
168
+ subject do
169
+ described_class.new do
170
+ force_ssl
171
+ end.build
172
+ end
173
+
174
+ it 'adds SslEnforcer Middleware to the sitehub at the top level' do
175
+ expect(subject).to be_a(Rack::SslEnforcer)
176
+ end
177
+
178
+ context 'exclusions supplied' do
179
+ subject do
180
+ described_class.new do
181
+ force_ssl except: :google
182
+ end.build
183
+ end
184
+ it 'gives them to the ssl enforcer middleware' do
185
+ exclusions = subject.instance_variable_get(:@options)[:except]
186
+ expect(exclusions).to eq(:google)
187
+ end
188
+ end
189
+ end
190
+
191
+ context 'false' do
192
+ it 'does not add SslEnforcer middleware' do
193
+ sitehub = described_class.new.build
194
+
195
+ expect(sitehub).to_not be_using(Rack::SslEnforcer)
196
+ end
197
+ end
198
+
199
+ end
200
+ end
201
+
202
+ end
203
+ end
@@ -0,0 +1,91 @@
1
+ require 'sitehub/collection/route_collection'
2
+ require 'sitehub/forward_proxy'
3
+
4
+ class SiteHub
5
+
6
+ describe Collection::RouteCollection do
7
+
8
+ let(:route_without_rule) { ForwardProxy.new(url: :url, id: :id,sitehub_cookie_name: :cookie_name) }
9
+
10
+ it 'is a collection' do
11
+ expect(subject).to be_a(Collection)
12
+ end
13
+
14
+ describe '#add' do
15
+ it 'stores a value' do
16
+ subject.add :id, route_without_rule
17
+ expect(subject[:id]).to be(route_without_rule)
18
+ end
19
+ end
20
+
21
+ describe '#transform' do
22
+ it "replaces the stores values with what's returned from the block" do
23
+ subject.add :id, route_without_rule
24
+ value_before_transform = subject[:id]
25
+ subject.transform do |value|
26
+ expect(value).to be(value_before_transform)
27
+ :transformed_value
28
+ end
29
+
30
+ expect(subject[:id]).to eq(:transformed_value)
31
+ end
32
+ end
33
+
34
+ describe '#valid?' do
35
+ context 'route added' do
36
+ it 'returns true' do
37
+ subject.add :id,route_without_rule
38
+ expect(subject).to be_valid
39
+ end
40
+ end
41
+
42
+ context 'no routes added' do
43
+ it 'returns false' do
44
+ expect(subject).to_not be_valid
45
+ end
46
+ end
47
+ end
48
+
49
+ describe '#resolve' do
50
+
51
+ context 'no rule on route' do
52
+ it 'returns the route' do
53
+ route_without_rule = ForwardProxy.new(url: :url, id: :id,sitehub_cookie_name: :cookie_name)
54
+ subject.add(:id, route_without_rule)
55
+ expect(subject.resolve({})).to be(route_without_rule)
56
+ end
57
+ end
58
+ context 'rule on route' do
59
+
60
+ it 'passes the environment to the rule' do
61
+ request_env = {}
62
+ rule = proc {|env| env[:env_passed_in] = true}
63
+
64
+ proxy = ForwardProxy.new(url: :url, id: :id, sitehub_cookie_name: :cookie_name)
65
+ proxy.rule(rule)
66
+ subject.add(:id, proxy)
67
+ subject.resolve(env: request_env)
68
+ expect(request_env[:env_passed_in]).to eq(true)
69
+ end
70
+
71
+ context 'rule applies' do
72
+ it 'returns the route' do
73
+ route_with_rule = ForwardProxy.new(url: :url, id: :id, rule: proc { true }, sitehub_cookie_name: :cookie_name)
74
+ subject.add(:id, route_with_rule)
75
+ expect(subject.resolve({})).to be(route_with_rule)
76
+ end
77
+
78
+ end
79
+
80
+ context 'rule does not apply' do
81
+ it 'returns nil' do
82
+ route_with_rule = ForwardProxy.new(url: :url, id: :id, sitehub_cookie_name: :cookie_name, rule: proc { false })
83
+ subject.add(:id, route_with_rule)
84
+ expect(subject.resolve({})).to eq(nil)
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ end
@@ -0,0 +1,111 @@
1
+ require 'sitehub/collection/split_route_collection'
2
+ require 'sitehub/forward_proxy'
3
+
4
+ class SiteHub
5
+
6
+ describe Collection::SplitRouteCollection do
7
+
8
+ let(:route_1) { ForwardProxy.new(url: :url, id: :id1,sitehub_cookie_name: :cookie_name) }
9
+ let(:route_2) { ForwardProxy.new(url: :url, id: :id2,sitehub_cookie_name: :cookie_name) }
10
+
11
+ it 'is a collection' do
12
+ expect(subject).to be_a(Collection)
13
+ end
14
+
15
+ describe '#add' do
16
+ before do
17
+ subject.add route_1.id, route_1, 50
18
+ end
19
+ it 'stores a value' do
20
+ expect(subject[route_1.id]).to be(route_1)
21
+ end
22
+
23
+ it 'sets the selection boundary which is used to choose routes' do
24
+ subject.add route_2.id, route_2, 50
25
+ first = subject.values.first
26
+ second = subject.values.last
27
+
28
+ expect(first.lower).to eq(0)
29
+ expect(first.upper).to eq(50)
30
+
31
+ expect(second.lower).to eq(50)
32
+ expect(second.upper).to eq(100)
33
+ end
34
+
35
+ context 'entry is added which takes splits total over 100%' do
36
+ it 'raises an error' do
37
+ expect{subject.add route_2.id, route_2, 101}.to raise_exception described_class::InvalidSplitException, 'total split percentages can not be greater than 100%'
38
+ end
39
+ end
40
+
41
+ context 'non fixnum passed in' do
42
+ it 'raises and error' do
43
+ expect{subject.add route_2.id, route_2, 1.1}.to raise_exception described_class::InvalidSplitException, 'splits must be a Fixnum'
44
+ end
45
+ end
46
+ end
47
+
48
+ describe '#resolve' do
49
+ before do
50
+ subject.add route_1.id, route_1, 50
51
+ subject.add route_2.id, route_2, 50
52
+ end
53
+
54
+ context 'rand returns a number within a boundry' do
55
+ it 'it returns the entry with that set of boundaries' do
56
+ expect(subject).to receive(:rand).and_return(15)
57
+ expect(subject.resolve).to eq(route_1)
58
+ end
59
+ end
60
+
61
+ context 'rand returns a number equal to the lower boundary of an entry' do
62
+ it 'it returns the entry whos lower boundary is equal to that number' do
63
+ expect(subject).to receive(:rand).and_return(50)
64
+ expect(subject.resolve).to eq(route_2)
65
+ end
66
+ end
67
+ end
68
+
69
+ describe '#transform' do
70
+ it "replaces the stores values with what's returned from the block" do
71
+ subject.add route_1.id, route_1, 50
72
+ value_before_transform = subject[route_1.id]
73
+ subject.transform do |value|
74
+ expect(value).to be(value_before_transform)
75
+ :transformed_value
76
+ end
77
+
78
+ expect(subject[route_1.id]).to eq(:transformed_value)
79
+ end
80
+ end
81
+
82
+ describe "#valid?" do
83
+
84
+ context 'splits == to 100' do
85
+ it 'returns true' do
86
+ subject.add route_1.id, route_1, 50
87
+ subject.add route_2.id, route_2, 50
88
+ expect(subject).to be_valid
89
+ end
90
+ end
91
+
92
+ context 'splits not == 100' do
93
+ it 'returns false' do
94
+ subject.add route_1.id, route_1, 50
95
+ expect(subject).to_not be_valid
96
+ end
97
+ it 'gives a warning' do
98
+ expect(subject).to receive(:warn).with('splits do not add up to 100% and no default has been specified')
99
+ subject.valid?
100
+ end
101
+ end
102
+
103
+ context 'no entries added' do
104
+ it 'returns false' do
105
+ expect(subject).to_not be_valid
106
+ end
107
+ end
108
+ end
109
+ end
110
+
111
+ end