sitehub 0.5.0.alpha5 → 0.5.0.alpha6

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