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,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