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,99 @@
1
+ require 'uuid'
2
+ require_relative 'collection/split_route_collection'
3
+ require_relative 'rules'
4
+ require_relative 'resolver'
5
+ require_relative 'collection/route_collection'
6
+ require_relative 'middleware'
7
+
8
+ class SiteHub
9
+
10
+ class ForwardProxyBuilder
11
+ include Middleware
12
+ include Rules, Resolver
13
+
14
+ class InvalidDefinitionException < Exception;
15
+ end
16
+
17
+ attr_reader :mapped_path, :default_proxy, :routes, :middlewares, :splits, :sitehub_cookie_name
18
+
19
+ def initialize(url: nil, mapped_path:, rule:nil, sitehub_cookie_name: nil, &block)
20
+ @mapped_path = mapped_path
21
+ @middlewares = []
22
+ @splits = Collection::SplitRouteCollection.new
23
+ @routes = Collection::RouteCollection.new
24
+ @sitehub_cookie_name = sitehub_cookie_name
25
+ rule(rule) if rule
26
+ default(url: url) if url
27
+ if block
28
+ instance_eval(&block)
29
+ raise InvalidDefinitionException unless valid?
30
+ end
31
+ end
32
+
33
+ def valid?
34
+ return true if @default_proxy
35
+ endpoints.valid?
36
+ end
37
+
38
+ def endpoints collection=nil
39
+ return @endpoints || Collection::RouteCollection.new unless collection
40
+
41
+ raise InvalidDefinitionException, 'you cant register routes and splits at the same level' if @endpoints && @endpoints != collection
42
+ @endpoints = collection
43
+ end
44
+
45
+ def split(percentage:, url:, label:, &block)
46
+ endpoints(splits)
47
+ endpoints.add label.to_sym, forward_proxy(label: label, url: url), percentage
48
+ end
49
+
50
+ def route url:nil, label:nil, rule: nil, &block
51
+ endpoints(routes)
52
+
53
+ if block
54
+ raise InvalidDefinitionException, 'rule must be specified when supplying a block'unless rule
55
+ builder = self.class.new(mapped_path: mapped_path, rule: rule, &block).build
56
+ endpoints.add UUID.generate(:compact), builder
57
+ else
58
+ endpoints.add label.to_sym, forward_proxy(url: url, label: label, rule: rule)
59
+ end
60
+ end
61
+
62
+ def default(url:)
63
+ @default_proxy = forward_proxy(label: :default, url: url)
64
+ end
65
+
66
+ def sitehub_cookie_path path=nil
67
+ return @sitehub_cookie_path unless path
68
+ @sitehub_cookie_path = path
69
+ end
70
+
71
+
72
+ def build
73
+ endpoints.transform do |proxy|
74
+ apply_middleware(proxy).tap do |wrapped_proxy|
75
+ wrapped_proxy.extend(Rules)
76
+ wrapped_proxy.extend(Resolver) unless wrapped_proxy.is_a?(Resolver)
77
+ wrapped_proxy.rule(proxy.rule)
78
+ end
79
+ end
80
+ @default_proxy = apply_middleware(default_proxy) if default_proxy
81
+ self
82
+ end
83
+
84
+ def resolve(id: nil, env:)
85
+ id = id.to_s.to_sym
86
+ endpoints[id] || endpoints.resolve(env: env) || default_proxy
87
+ end
88
+
89
+ def == other
90
+ other.is_a?(ForwardProxyBuilder) && self.default_proxy == other.default_proxy && self.endpoints == other.endpoints
91
+ end
92
+
93
+
94
+ def forward_proxy(label:, url:, rule: nil)
95
+ ForwardProxy.new(url: url, id: label.to_sym, mapped_path: mapped_path, sitehub_cookie_path: sitehub_cookie_path, sitehub_cookie_name: sitehub_cookie_name, rule: rule)
96
+ end
97
+ end
98
+
99
+ end
@@ -0,0 +1,60 @@
1
+ require 'sitehub/constants'
2
+ class SiteHub
3
+ module HttpHeaders
4
+ include Constants::HttpHeaderKeys
5
+ include Constants
6
+
7
+ HTTP_OR_SSL_PORT = /:80(?!\d+)|:443/
8
+ HTTP_PREFIX = /^HTTP_/
9
+ RACK_HTTP_HEADER_ID = /#{HTTP_PREFIX.source}[A-Z_]+$/
10
+ COMMAND_FOLLOWED_BY_SPACES = /,\s+/
11
+
12
+ def split_field(f)
13
+ f ? f.split(COMMAND_FOLLOWED_BY_SPACES).collect { |i| i.downcase } : []
14
+ end
15
+
16
+ def sanitise_headers(src)
17
+
18
+ sanitised_headers = {}
19
+
20
+ connections = split_field(src[CONNECTION_HEADER])
21
+ src.each do |key, value|
22
+ key = key.downcase.gsub(UNDERSCORE, HYPHEN)
23
+ if HopByHop.member?(key) ||
24
+ connections.member?(key) ||
25
+ ShouldNotTransfer.member?(key)
26
+ next
27
+ end
28
+ sanitised_headers[key] = value
29
+ end
30
+
31
+ sanitised_headers[LOCATION_HEADER].gsub!(HTTP_OR_SSL_PORT, EMPTY_STRING) if sanitised_headers[LOCATION_HEADER]
32
+
33
+ sanitised_headers
34
+ end
35
+
36
+
37
+
38
+ def extract_http_headers(env)
39
+ headers = env.reject do |k, v|
40
+ !RackHttpHeaderKeys::HTTP_HEADER_FILTER_EXCEPTIONS.include?(k.to_s.upcase) && (!(RACK_HTTP_HEADER_ID === k) || v.nil?)
41
+ end.map do |k, v|
42
+ [reconstruct_header_name(k), v]
43
+ end.inject(Rack::Utils::HeaderHash.new) do |hash, k_v|
44
+ k, v = k_v
45
+ hash[k] = v
46
+ hash
47
+ end
48
+
49
+ x_forwarded_for = (headers[X_FORWARDED_FOR_HEADER].to_s.split(COMMAND_FOLLOWED_BY_SPACES) << env[RackHttpHeaderKeys::REMOTE_ADDRESS_ENV_KEY]).join(COMMA_WITH_SPACE)
50
+
51
+ headers.merge!(X_FORWARDED_FOR_HEADER => x_forwarded_for)
52
+ end
53
+
54
+
55
+
56
+ def reconstruct_header_name(name)
57
+ name.sub(HTTP_PREFIX, EMPTY_STRING).gsub(UNDERSCORE, HYPHEN)
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,5 @@
1
+ require_relative 'logging/access_logger'
2
+ require_relative 'logging/error_logger'
3
+ require_relative 'logging/log_entry'
4
+ require_relative 'logging/log_stash'
5
+ require_relative 'logging/log_wrapper'
@@ -0,0 +1,74 @@
1
+ require 'logger'
2
+ require 'rack/commonlogger'
3
+ require_relative 'log_wrapper'
4
+ require 'sitehub/constants'
5
+
6
+ #Very heavily based on Rack::CommonLogger
7
+ class SiteHub
8
+ module Logging
9
+ class AccessLogger
10
+ attr_reader :logger, :start_time
11
+
12
+ include Constants
13
+
14
+ FORMAT = %{%s - %s [%s] transaction_id:%s: "%s %s%s => %s %s" %d %s %0.4f\n}.freeze
15
+ PATH_INFO = RackHttpHeaderKeys::PATH_INFO
16
+ REQUEST_METHOD = RackHttpHeaderKeys::REQUEST_METHOD
17
+ SCRIPT_NAME = RackHttpHeaderKeys::SCRIPT_NAME
18
+ QUERY_STRING = RackHttpHeaderKeys::QUERY_STRING
19
+ X_FORWARDED_FOR = RackHttpHeaderKeys::X_FORWARDED_FOR
20
+ REMOTE_ADDR = RackHttpHeaderKeys::REMOTE_ADDR
21
+ HTTP_VERSION = RackHttpHeaderKeys::HTTP_VERSION
22
+ REMOTE_USER = RackHttpHeaderKeys::REMOTE_USER
23
+ TRANSACTION_ID = RackHttpHeaderKeys::TRANSACTION_ID
24
+ CONTENT_LENGTH = HttpHeaderKeys::CONTENT_LENGTH
25
+ ZERO_STRING = '0'
26
+ STATUS_RANGE = 0..3
27
+
28
+ def initialize app, logger = ::Logger.new(STDOUT)
29
+ @app = app
30
+ @logger = LogWrapper.new(logger)
31
+ end
32
+
33
+ def call env
34
+ start_time = Time.now
35
+
36
+ @app.call(env).tap do |response|
37
+ status, headers, body = response.to_a
38
+ log env, status, headers, env[REQUEST_MAPPING], start_time
39
+ end
40
+ end
41
+
42
+ def log(env, status, header, mapped_request, began_at)
43
+ now = Time.now
44
+ length = extract_content_length(header)
45
+
46
+
47
+ msg = log_template % [
48
+ env[X_FORWARDED_FOR] || env[REMOTE_ADDR] || HYPHEN,
49
+ env[REMOTE_USER] || "-",
50
+ now.strftime(TIME_STAMP_FORMAT),
51
+ env[TRANSACTION_ID],
52
+ env[REQUEST_METHOD],
53
+ env[PATH_INFO],
54
+ env[QUERY_STRING].empty? ? EMPTY_STRING : QUESTION_MARK+env[QUERY_STRING],
55
+ mapped_request.mapped_url.to_s,
56
+ env[HTTP_VERSION],
57
+ status.to_s[STATUS_RANGE],
58
+ length,
59
+ now - began_at]
60
+
61
+ logger.write(msg)
62
+ end
63
+
64
+ def log_template
65
+ FORMAT
66
+ end
67
+
68
+ def extract_content_length(headers)
69
+ value = headers[CONTENT_LENGTH] or return HYPHEN
70
+ value.to_s == ZERO_STRING ? HYPHEN : value
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,36 @@
1
+ require 'logger'
2
+ require 'sitehub/constants'
3
+ require_relative 'log_wrapper'
4
+ require_relative 'log_stash'
5
+ class SiteHub
6
+ module Logging
7
+ class ErrorLogger
8
+ include Constants
9
+ LOG_TEMPLATE = '[%s] ERROR: %s - %s'
10
+
11
+
12
+ attr_reader :logger
13
+
14
+ def initialize(app, logger = Logger.new(STDERR))
15
+ @app = app
16
+ @logger = LogWrapper.new(logger)
17
+ end
18
+
19
+ def call env
20
+ env[ERRORS] ||= LogStash.new
21
+ @app.call(env).tap do
22
+ unless env[ERRORS].empty?
23
+ messages = env[ERRORS].collect { |log_entry| log_message(error: log_entry.message, transaction_id: env[RackHttpHeaderKeys::TRANSACTION_ID]) }
24
+
25
+ logger.write(messages.join(NEW_LINE))
26
+ end
27
+ end
28
+ end
29
+
30
+ def log_message(error:, transaction_id:)
31
+ LOG_TEMPLATE % [Time.now.strftime(TIME_STAMP_FORMAT), transaction_id, error]
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,14 @@
1
+ class SiteHub
2
+ module Logging
3
+ class LogEntry
4
+ attr_reader :message, :time
5
+ def initialize message, time=Time.now
6
+ @message, @time = message, time
7
+ end
8
+
9
+ def == other
10
+ other.is_a?(LogEntry) && self.message == other.message && self.time == other.time
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,10 @@
1
+ require_relative 'log_entry'
2
+ class SiteHub
3
+ module Logging
4
+ class LogStash < Array
5
+ def << message
6
+ super(LogEntry.new(message))
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,23 @@
1
+ class SiteHub
2
+ module Logging
3
+ class LogWrapper
4
+ attr_reader :logger
5
+
6
+ def initialize logger
7
+ @logger = logger
8
+ end
9
+
10
+ def write msg
11
+ if logger.respond_to?(:<<)
12
+ logger << msg
13
+ elsif logger.respond_to?(:write)
14
+ logger.write(msg)
15
+ end
16
+ end
17
+
18
+ def == other
19
+ other.is_a?(LogWrapper) && self.logger == other.logger
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,21 @@
1
+ class SiteHub
2
+ module Middleware
3
+ def middlewares
4
+ @middleware ||= []
5
+ end
6
+
7
+ def use(middleware_clazz, *args, &block)
8
+ middlewares << [middleware_clazz, args, block|| proc{}]
9
+ end
10
+
11
+ def apply_middleware(forward_proxy)
12
+ middlewares.reverse.inject(forward_proxy) do |app, middleware_def|
13
+ middleware = middleware_def[0]
14
+ args = middleware_def[1] || []
15
+ block = middleware_def[2] || proc {}
16
+
17
+ middleware.new(app, *args, &block)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,32 @@
1
+ class SiteHub
2
+ class PathDirective
3
+ attr_reader :matcher, :path_template
4
+
5
+ def initialize matcher, path_template
6
+ @matcher, @path_template = matcher, path_template
7
+ end
8
+
9
+ def match? url
10
+ !!matcher.match(url)
11
+ end
12
+
13
+ def path_template
14
+ @path_template.dup
15
+ end
16
+
17
+ def apply url
18
+ url_components = matcher.match(url).captures
19
+
20
+ path_template.tap do |p|
21
+ url_components.each_with_index do |m, index|
22
+ p.gsub!(RequestMapping::CAPTURE_GROUP_REFERENCE % (index+1), m)
23
+ end
24
+ end
25
+
26
+ end
27
+
28
+ def == other
29
+ other.is_a?(PathDirective) && self.matcher == other.matcher && self.path_template == other.path_template
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,21 @@
1
+ require 'sitehub/path_directive'
2
+ class SiteHub
3
+ class PathDirectives < Array
4
+ def initialize map={}
5
+ enriched = map.map do |array|
6
+ matcher,path_template = array.first, array.last
7
+
8
+ matcher = matcher.is_a?(Regexp) ? matcher : %r{#{matcher}}
9
+ PathDirective.new(matcher, path_template)
10
+ end
11
+
12
+ super enriched
13
+ end
14
+
15
+ def find(url)
16
+ super() do |directive|
17
+ directive.match?(url)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,43 @@
1
+ require 'sitehub/constants'
2
+ class SiteHub
3
+ class RequestMapping
4
+ attr_reader :source_url, :mapped_url, :mapped_path
5
+
6
+ BASE_URL_MATCHER = %r{^\w+://[\w+\.-]+(:\d+)?}
7
+ CAPTURE_GROUP_REFERENCE='$%s'
8
+ USER_SUPPLIED_CAPTURE = 1..-1
9
+
10
+ def initialize(source_url:, downstream_url: EMPTY_STRING, mapped_url: EMPTY_STRING, mapped_path:)
11
+ @source_url, @mapped_url = source_url, mapped_url.dup
12
+ @mapped_path = mapped_path.is_a?(Regexp) ? mapped_path : Regexp.new(mapped_path)
13
+ @downstream_url = downstream_url
14
+ end
15
+
16
+ def cookie_path
17
+ if mapped_path.is_a?(Regexp)
18
+ mapped_path.source[/^(.*)?\(/,1].gsub(/\/$/, '')
19
+ end
20
+ end
21
+
22
+
23
+ def computed_uri
24
+ @computed_uri ||= begin
25
+ url_components = url_scanner_regex.match(source_url).captures[USER_SUPPLIED_CAPTURE]
26
+ mapped_url.tap do |url|
27
+ url_components.each_with_index do |match, index|
28
+ url.gsub!(CAPTURE_GROUP_REFERENCE % (index+1), match)
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ def == other
35
+ other.is_a?(RequestMapping) && source_url == other.source_url && mapped_url == other.mapped_url && mapped_path == other.mapped_path
36
+ end
37
+
38
+ private
39
+ def url_scanner_regex
40
+ %r{#{BASE_URL_MATCHER.source}#{mapped_path.source}}
41
+ end
42
+ end
43
+ end