sitehub 0.5.0.alpha5 → 0.5.0.alpha6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d7189b64635170892f88906d52c53a757bdffd12
4
- data.tar.gz: c928e7fb8bcb65d9375b6ac448298de8f6d6449f
3
+ metadata.gz: d1e90e8f9c72c559c959b6c4c837c6ae2e603d38
4
+ data.tar.gz: a2ff9aa2f206dd91a5e62ef74636dc415a518769
5
5
  SHA512:
6
- metadata.gz: 9c57e15219c0a8f28e450b294b143db8b6d9b7851cbca600618c174533020116eda0dbbfb989c7179f827b8c4e3f95fe7b47bc129b3a4a7be05f339b34148640
7
- data.tar.gz: d87d4cab4c1d81cea2cd40d269a579e916d89e023d598e44fe2210500981a317237508b3bbca11ed467b28d8da337c80afa0ed4a84dec655ccb5aacdfb33c20a
6
+ metadata.gz: a73a1a9a8903b3cab50b954dc9d32198c07491142ddae30fb8cde8dbc8812b878e6c01b3709150694515d600e11baeeb7728e0e9f092ce303d6ef96c0650b1b8
7
+ data.tar.gz: c94a341dc37f74025d6ce74295bf54bb96fa8ae44f2feacbcbfdff203b25ce785b1a7eec6c4b10b1ab6ad32eda2e044ef650d2cdcf7279a06d9b37a6ba3b4735
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- sitehub (0.5.0.alpha5)
4
+ sitehub (0.5.0.alpha6)
5
5
  activesupport
6
6
  em-http-request
7
7
  em-synchrony
@@ -37,7 +37,7 @@ GEM
37
37
  cookiejar (0.3.3)
38
38
  crack (0.4.3)
39
39
  safe_yaml (~> 1.0.0)
40
- daemons (1.2.3)
40
+ daemons (1.2.4)
41
41
  descendants_tracker (0.0.4)
42
42
  thread_safe (~> 0.3, >= 0.3.1)
43
43
  diff-lcs (1.2.5)
@@ -0,0 +1,42 @@
1
+ class SiteHub
2
+ class CandidateRoutes
3
+ module ClassMethods
4
+ extend CollectionMethods
5
+
6
+ # TODO: support nested routes, i.e. support rule name being passed in
7
+ def from_hash(hash, sitehub_cookie_name)
8
+ new(sitehub_cookie_name: sitehub_cookie_name,
9
+ sitehub_cookie_path: hash[:sitehub_cookie_path],
10
+ mapped_path: hash[:path], calling_scope: self) do
11
+ handle_routes(hash, self)
12
+ default url: hash[:default] if hash[:default]
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def handle_routes(hash, routes)
19
+ extract_splits(hash, routes)
20
+ extract_routes(hash, routes)
21
+ end
22
+
23
+ def extract_routes(hash, routes)
24
+ collection(hash, :routes).each do |route|
25
+ routes.route(url: route[:url], label: route[:label])
26
+ end
27
+ end
28
+
29
+ def extract_splits(hash, routes)
30
+ collection(hash, :splits).each do |split|
31
+ if split[:splits] || split[:routes]
32
+ routes.split(percentage: split[:percentage], label: split[:label]) do
33
+ handle_routes(split, self)
34
+ end
35
+ else
36
+ routes.split(percentage: split[:percentage], label: split[:label], url: split[:url])
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -3,6 +3,8 @@ require 'sitehub/equality'
3
3
  require 'sitehub/nil_route'
4
4
  require 'sitehub/identifier'
5
5
  require 'sitehub/getter_setter_methods'
6
+ require 'sitehub/candidate_routes/class_methods'
7
+
6
8
  require_relative 'collection/split_route_collection'
7
9
  require_relative 'rules'
8
10
  require_relative 'resolver'
@@ -12,81 +14,38 @@ require_relative 'forward_proxy'
12
14
  require_relative 'downstream_client'
13
15
 
14
16
  class SiteHub
15
- class RouteBuilder
17
+ class CandidateRoutes
16
18
  class InvalidDefinitionException < Exception
17
19
  end
18
20
 
19
21
  ROUTES_WITH_SPLITS_MSG = 'you cant register routes and splits at the same level'.freeze
20
22
  INVALID_SPLIT_MSG = 'url must be defined if not supplying a block'.freeze
21
- RULE_NOT_SPECIFIED_MSG = 'rule must be specified when supplying a block'.freeze
23
+ RULE_NOT_SPECIFIED_MSG = 'rule must be supplied'.freeze
24
+ PERCENTAGE_NOT_SPECIFIED_MSG = 'percentage must be supplied'.freeze
22
25
  IGNORING_URL_MSG = 'Block supplied, ignoring URL parameter'.freeze
23
26
  URL_REQUIRED_MSG = 'URL must be supplied for splits and routes'.freeze
24
27
 
25
- class << self
26
- # TODO: support nest splits and routes
27
- def from_hash(hash, sitehub_cookie_name)
28
- new(sitehub_cookie_name: sitehub_cookie_name,
29
- sitehub_cookie_path: hash[:sitehub_cookie_path],
30
- mapped_path: hash[:path]) do
31
- extend CollectionMethods
32
-
33
- collection(hash, :splits).each do |split|
34
- split(percentage: split[:percentage], url: split[:url], label: split[:label])
35
- end
36
-
37
- collection(hash, :routes).each do |route|
38
- route(url: route[:url], label: route[:label])
39
- end
40
-
41
- default url: hash[:default] if hash[:default]
42
- end
43
- end
44
- end
45
-
46
- extend GetterSetterMethods
28
+ extend CollectionMethods, ClassMethods, GetterSetterMethods
47
29
  include Rules, Equality, Middleware
48
30
 
49
- transient :id
50
-
51
31
  getter_setters :sitehub_cookie_path, :sitehub_cookie_name
52
- attr_reader :mapped_path, :id
53
-
54
- def initialize(id: nil, sitehub_cookie_name:, sitehub_cookie_path: nil, mapped_path:, rule: nil, &block)
55
- @id = Identifier.new(id)
56
- @mapped_path = mapped_path
57
- @sitehub_cookie_name = sitehub_cookie_name
58
- @sitehub_cookie_path = sitehub_cookie_path
59
- @splits = Collection::SplitRouteCollection.new
60
- @routes = Collection::RouteCollection.new
61
- rule(rule)
32
+ attr_reader :mapped_path, :id, :calling_scope
62
33
 
63
- return unless block_given?
34
+ transient :calling_scope
64
35
 
65
- instance_eval(&block)
66
- raise InvalidDefinitionException unless valid?
67
- end
68
-
69
- def add_route(label:, rule: nil, percentage: nil, url: nil, &block)
36
+ def add(label:, rule: nil, percentage: nil, url: nil, &block)
70
37
  child_label = id.child_label(label)
71
38
 
72
39
  route = if block
73
- raise InvalidDefinitionException, RULE_NOT_SPECIFIED_MSG unless percentage || rule
40
+ raise InvalidDefinitionException, candidate_definition_msg unless percentage || rule
74
41
  warn(IGNORING_URL_MSG) if url
75
42
  new(rule: rule, id: child_label, &block).build
76
43
  else
77
- raise InvalidDefinitionException, RULE_NOT_SPECIFIED_MSG unless url
44
+ raise InvalidDefinitionException, URL_REQUIRED_MSG unless url
78
45
  forward_proxy(url: url, label: child_label, rule: rule)
79
46
  end
80
47
 
81
- routes.add(Identifier.new(label), route, percentage)
82
- end
83
-
84
- def default_route
85
- routes.default
86
- end
87
-
88
- def default_route?
89
- !default_route.nil?
48
+ candidates.add(Identifier.new(label), route, percentage)
90
49
  end
91
50
 
92
51
  def build
@@ -94,8 +53,23 @@ class SiteHub
94
53
  self
95
54
  end
96
55
 
56
+ def candidates(collection = nil)
57
+ return @endpoints ||= Collection::RouteCollection.new unless collection
58
+
59
+ raise InvalidDefinitionException, ROUTES_WITH_SPLITS_MSG if @endpoints && !@endpoints.equal?(collection)
60
+ @endpoints = collection
61
+ end
62
+
97
63
  def default(url:)
98
- routes.default = forward_proxy(label: :default, url: url)
64
+ candidates.default = forward_proxy(label: :default, url: url)
65
+ end
66
+
67
+ def default_route
68
+ candidates.default
69
+ end
70
+
71
+ def default_route?
72
+ !default_route.nil?
99
73
  end
100
74
 
101
75
  def forward_proxy(label:, url:, rule: nil)
@@ -109,35 +83,59 @@ class SiteHub
109
83
  end
110
84
  end
111
85
 
86
+ def initialize(id: nil, sitehub_cookie_name:, sitehub_cookie_path: nil, mapped_path:, rule: nil, calling_scope: nil, &block)
87
+ @id = Identifier.new(id)
88
+ @calling_scope = calling_scope
89
+ @mapped_path = mapped_path
90
+ @sitehub_cookie_name = sitehub_cookie_name
91
+ @sitehub_cookie_path = sitehub_cookie_path
92
+ @splits = Collection::SplitRouteCollection.new
93
+ @routes = Collection::RouteCollection.new
94
+ rule(rule)
95
+
96
+ return unless block_given?
97
+
98
+ instance_eval(&block)
99
+ raise InvalidDefinitionException unless valid?
100
+ end
101
+
102
+ def method_missing(method, *args, &block)
103
+ super unless calling_scope
104
+ calling_scope.send(method, *args, &block)
105
+ rescue NoMethodError
106
+ super
107
+ end
108
+
112
109
  def resolve(id: nil, env:)
113
110
  id = Identifier.new(id)
114
- if id.valid? && (route = routes[id.root])
111
+ if id.valid? && (route = candidates[id.root])
115
112
  route.resolve(id: id.sub_id, env: env)
116
113
  else
117
- routes.resolve(env: env) || default_route
114
+ candidates.resolve(env: env) || default_route
118
115
  end
119
116
  end
120
117
 
121
118
  def route(url: nil, label:, rule: nil, &block)
122
- routes(@routes)
123
- add_route(label: label, rule: rule, url: url, &block)
119
+ candidates(@routes)
120
+ add(label: label, rule: rule, url: url, &block)
124
121
  end
125
122
 
126
- def routes(collection = nil)
127
- return @endpoints ||= Collection::RouteCollection.new unless collection
128
-
129
- raise InvalidDefinitionException, ROUTES_WITH_SPLITS_MSG if @endpoints && !@endpoints.equal?(collection)
130
- @endpoints = collection
123
+ def split(percentage:, url: nil, label:, &block)
124
+ candidates(@splits)
125
+ add(label: label, percentage: percentage, url: url, &block)
131
126
  end
132
127
 
133
- def split(percentage:, url: nil, label:, &block)
134
- routes(@splits)
135
- add_route(label: label, percentage: percentage, url: url, &block)
128
+ def splits?
129
+ candidates.is_a?(Collection::SplitRouteCollection)
136
130
  end
137
131
 
138
132
  def valid?
139
133
  return true if default_route?
140
- routes.valid?
134
+ candidates.valid?
135
+ end
136
+
137
+ def [](key)
138
+ candidates[Identifier.new(key)]
141
139
  end
142
140
 
143
141
  private
@@ -150,7 +148,7 @@ class SiteHub
150
148
  end
151
149
 
152
150
  def build_with_middleware
153
- routes = routes().values.find_all { |route| route.is_a?(Route) }
151
+ routes = candidates.values.find_all { |route| route.is_a?(Route) }
154
152
 
155
153
  routes << default_route if default_route?
156
154
 
@@ -160,13 +158,22 @@ class SiteHub
160
158
  end
161
159
  end
162
160
 
161
+ def candidate_definition_msg
162
+ splits? ? PERCENTAGE_NOT_SPECIFIED_MSG : RULE_NOT_SPECIFIED_MSG
163
+ end
164
+
163
165
  def new(id:, rule: nil, &block)
166
+ inherited_middleware = middlewares
167
+
164
168
  self.class.new(id: id,
165
169
  sitehub_cookie_name: sitehub_cookie_name,
166
170
  sitehub_cookie_path: sitehub_cookie_path,
167
171
  mapped_path: mapped_path,
168
172
  rule: rule,
169
- &block)
173
+ calling_scope: calling_scope) do
174
+ middlewares.concat(inherited_middleware)
175
+ instance_eval(&block)
176
+ end
170
177
  end
171
178
  end
172
179
  end
@@ -0,0 +1,12 @@
1
+ class SiteHub
2
+ module CollectionMethods
3
+ def collection(hash, item)
4
+ hash[item] || []
5
+ end
6
+
7
+ def collection!(hash, item)
8
+ return hash[item] if hash[item]
9
+ raise ConfigError, "missing: #{item}"
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,42 @@
1
+ require 'sitehub/cookie'
2
+ require 'sitehub/constants'
3
+ class SiteHub
4
+ # TODO: - change in to object and remove .reek exclusions for UtilityFunction
5
+ class Cookie
6
+ module Rewriting
7
+ ENDING_WITH_NEWLINE = /#{NEW_LINE}$/
8
+
9
+ def rewrite_cookies(headers, substitute_domain:)
10
+ cookies_hash = cookies_string_as_hash(headers[Constants::HttpHeaderKeys::SET_COOKIE])
11
+
12
+ cookies_hash.values.each do |cookie|
13
+ domain_attribute = cookie.find(:domain) || next
14
+ update_domain(domain_attribute, substitute_domain)
15
+ end
16
+ headers[Constants::HttpHeaderKeys::SET_COOKIE] = cookies_hash_to_string(cookies_hash)
17
+ end
18
+
19
+ def update_domain(domain_attribute, substitute_domain)
20
+ substitute = substitute_domain.dup
21
+ if domain_attribute.value.start_with?(FULL_STOP)
22
+ domain_attribute.update(substitute.prepend(FULL_STOP))
23
+ else
24
+ domain_attribute.update(substitute)
25
+ end
26
+ end
27
+
28
+ def cookies_hash_to_string(cookies_hash)
29
+ cookies_hash.values.inject(EMPTY_STRING.dup) do |cookie_string, cookie|
30
+ cookie_string << "#{cookie}#{NEW_LINE}"
31
+ end.sub(ENDING_WITH_NEWLINE, EMPTY_STRING)
32
+ end
33
+
34
+ def cookies_string_as_hash(cookie_string)
35
+ cookie_string.lines.each_with_object({}) do |cookie_line, cookies|
36
+ cookie = SiteHub::Cookie.new(cookie_line)
37
+ cookies[cookie.name] = cookie
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
data/lib/sitehub/core.rb CHANGED
@@ -1,4 +1,5 @@
1
- require 'sitehub/route_builder'
1
+ require 'sitehub/collection_methods'
2
+ require 'sitehub/candidate_routes'
2
3
  require 'forwardable'
3
4
 
4
5
  class SiteHub
@@ -8,17 +9,6 @@ class SiteHub
8
9
  class ConfigError < Exception
9
10
  end
10
11
 
11
- module CollectionMethods
12
- def collection(hash, item)
13
- hash[item] || []
14
- end
15
-
16
- def collection!(hash, item)
17
- return hash[item] if hash[item]
18
- raise ConfigError, "missing: #{item}"
19
- end
20
- end
21
-
22
12
  class Core
23
13
  class << self
24
14
  # TODO: default action for missing key, throw exception?
@@ -28,7 +18,7 @@ class SiteHub
28
18
  sitehub_cookie_name config[:sitehub_cookie_name] if config[:sitehub_cookie_name]
29
19
 
30
20
  collection!(config, :proxies).each do |proxy|
31
- routes.add_route route_builder: RouteBuilder.from_hash(proxy, sitehub_cookie_name)
21
+ mappings.add_route route_builder: CandidateRoutes.from_hash(proxy, sitehub_cookie_name)
32
22
  end
33
23
 
34
24
  collection(config, :reverse_proxies).each do |proxy|
@@ -41,25 +31,25 @@ class SiteHub
41
31
  include Equality
42
32
  extend Forwardable
43
33
 
44
- attr_reader :routes, :reverse_proxies
45
- def_delegator :routes, :sitehub_cookie_name
34
+ attr_reader :mappings, :reverse_proxies
35
+ def_delegator :mappings, :sitehub_cookie_name
46
36
 
47
37
  def initialize(&block)
48
38
  @reverse_proxies = {}
49
- @routes = Middleware::Routes.new
39
+ @mappings = Middleware::CandidateRouteMappings.new
50
40
  instance_eval(&block) if block
51
41
  end
52
42
 
53
43
  def build
54
- Middleware::ReverseProxy.new(routes.init, reverse_proxies)
44
+ Middleware::ReverseProxy.new(mappings.init, reverse_proxies)
55
45
  end
56
46
 
57
47
  def proxy(opts = {}, &block)
58
48
  mapped_path, url = *(opts.respond_to?(:to_a) ? opts.to_a : [opts]).flatten
59
49
 
60
- routes.add_route(url: url,
61
- mapped_path: mapped_path,
62
- &block)
50
+ mappings.add_route(url: url,
51
+ mapped_path: mapped_path,
52
+ &block)
63
53
  end
64
54
 
65
55
  def reverse_proxy(hash)
@@ -34,10 +34,10 @@ class SiteHub
34
34
  other.respond_to?(:to_sym) && to_sym == other.to_sym
35
35
  end
36
36
 
37
+ alias eql? ==
38
+
37
39
  def hash
38
40
  components.hash
39
41
  end
40
-
41
- alias eql? ==
42
42
  end
43
43
  end
@@ -8,7 +8,7 @@ require 'em-http'
8
8
 
9
9
  class SiteHub
10
10
  module Middleware
11
- class Routes < Hash
11
+ class CandidateRouteMappings < Hash
12
12
  NIL_ROUTE = NilRoute.new
13
13
 
14
14
  include Equality
@@ -39,9 +39,9 @@ class SiteHub
39
39
  return
40
40
  end
41
41
 
42
- self[mapped_path] = RouteBuilder.new(sitehub_cookie_name: sitehub_cookie_name,
43
- mapped_path: mapped_path,
44
- &block).tap do |builder|
42
+ self[mapped_path] = CandidateRoutes.new(sitehub_cookie_name: sitehub_cookie_name,
43
+ mapped_path: mapped_path,
44
+ &block).tap do |builder|
45
45
  builder.default(url: url) if url
46
46
  end
47
47
  end
@@ -28,6 +28,7 @@ class SiteHub
28
28
  @app.call env
29
29
  end
30
30
 
31
+ # TODO: handle errors connecting to the config server
31
32
  def load_config
32
33
  config = cache.fetch(:sitehub_config, expires_in: 30) do
33
34
  config_server.get
@@ -1,4 +1,4 @@
1
- require 'sitehub/cookie_rewriting'
1
+ require 'sitehub/cookie/rewriting'
2
2
  require 'sitehub/location_rewriters'
3
3
  require 'sitehub/request_mapping'
4
4
  require 'sitehub/constants'
@@ -6,7 +6,7 @@ require 'sitehub/constants'
6
6
  class SiteHub
7
7
  module Middleware
8
8
  class ReverseProxy
9
- include CookieRewriting, Constants::HttpHeaderKeys, Equality
9
+ include Cookie::Rewriting, Constants::HttpHeaderKeys, Equality
10
10
 
11
11
  attr_reader :path_directives
12
12
 
@@ -2,7 +2,7 @@ $LOAD_PATH.unshift(__dir__)
2
2
  require 'middleware/logging'
3
3
  require 'middleware/transaction_id'
4
4
  require 'middleware/error_handling'
5
- require 'middleware/routes'
5
+ require 'middleware/candidate_route_mappings'
6
6
  require 'middleware/reverse_proxy'
7
7
  require 'middleware/config_loader'
8
8
  require 'rack/ssl-enforcer'
@@ -77,7 +77,7 @@ class SiteHub
77
77
  mapping.computed_uri
78
78
  end
79
79
 
80
- memoize :url, :path, :uri, :mapped?, :mapping, :headers, :body, :request_method
80
+ memoize :url, :path, :uri, :mapping, :headers, :body, :request_method
81
81
 
82
82
  private
83
83
 
@@ -1,3 +1,3 @@
1
1
  class SiteHub
2
- VERSION = '0.5.0.alpha5'.freeze
2
+ VERSION = '0.5.0.alpha6'.freeze
3
3
  end
@@ -0,0 +1,75 @@
1
+ require 'cgi'
2
+ describe 'access_logs' do
3
+ let(:downstream_url) { 'http://localhost:12345/experiment1' }
4
+
5
+ let(:experiment_body_1) { 'experiment1_body' }
6
+
7
+ let(:access_logger) { StringIO.new }
8
+
9
+ before do
10
+ WebMock.enable!
11
+ end
12
+
13
+ let(:app) do
14
+ downstream_url = downstream_url()
15
+ access_logger = access_logger()
16
+
17
+ sitehub = SiteHub.build do
18
+ access_logger access_logger
19
+ error_logger StringIO.new
20
+
21
+ proxy '/endpoint' do
22
+ split(label: :experiment1, percentage: 100) do
23
+ split percentage: 100, label: 'variant1', url: downstream_url
24
+ end
25
+ end
26
+ end
27
+ Async::Middleware.new(sitehub)
28
+ end
29
+
30
+ let(:query_string) { '' }
31
+ let(:request_url) { "/endpoint#{query_string.empty? ? '' : "?#{query_string}"}" }
32
+
33
+ subject do
34
+ query_string_hash = CGI.parse(query_string).collect { |key, value| [key, value.first] }.to_h
35
+ stub_request(:get, downstream_url).with(query: query_string_hash)
36
+ get(request_url)
37
+ access_logger.string
38
+ end
39
+
40
+ context 'query string' do
41
+ context 'present' do
42
+ let(:query_string) { 'key=value' }
43
+ it 'logs it' do
44
+ expect(subject).to include(query_string)
45
+ end
46
+ end
47
+
48
+ context 'not present' do
49
+ let(:query_string) { '' }
50
+ it 'is not logged' do
51
+ expect(subject).to include(query_string)
52
+ end
53
+ end
54
+ end
55
+
56
+ it 'logs the transaction id' do
57
+ expect(subject).to match(/transaction_id:.*?\s/)
58
+ end
59
+
60
+ it 'logs the response status' do
61
+ expect(subject).to include('200')
62
+ end
63
+
64
+ it 'logs the downstream url that was proxied to' do
65
+ expect(subject).to include("#{request_url} => #{downstream_url}")
66
+ end
67
+
68
+ it 'has the required format' do
69
+ processing_time_matcher = '\d{1}\.\d{4}'
70
+ transaction_id_matcher = '[a-z\d]+'
71
+ expected_response_status = 200
72
+ puts subject
73
+ expect(subject).to match(/transaction_id:#{transaction_id_matcher}:\s"GET\s#{request_url}\s=>\s#{downstream_url}\s"\s#{expected_response_status}\s-\s#{processing_time_matcher}/)
74
+ end
75
+ end