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,35 @@
1
+ class SiteHub
2
+ class Collection < Hash
3
+ class DuplicateVersionException < Exception;
4
+ end
5
+
6
+ def valid?
7
+ raise "implement me"
8
+ end
9
+
10
+ def resolve
11
+ raise "implement me"
12
+ end
13
+
14
+ class << self
15
+ def inherited clazz
16
+
17
+ def clazz.overrides
18
+ @overrides ||=[]
19
+ end
20
+
21
+ def clazz.method_added(name)
22
+ if name == :add && !overrides.include?(name)
23
+ overrides << name
24
+ alias_method :add_backup, :add
25
+
26
+ self.send(:define_method, :add) do |id, value, *args|
27
+ raise DuplicateVersionException, 'supply unique labels' if self[id]
28
+ add_backup id, value, *args
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,28 @@
1
+ require_relative '../collection'
2
+ class SiteHub
3
+ class Collection < Hash
4
+ class RouteCollection < Collection
5
+
6
+ def add id, route, *opts
7
+ self[id] = route
8
+ end
9
+
10
+ def valid?
11
+ !self.empty?
12
+ end
13
+
14
+ def resolve(env: nil)
15
+ return self.values.first unless self.values.find { |route| route.rule }
16
+ result = self.values.find { |route| route.applies?(env) }
17
+ result && result.resolve(env: env)
18
+ end
19
+
20
+ def transform &block
21
+ each do |id, value|
22
+ self[id] = block.call(value)
23
+ end
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,50 @@
1
+ require_relative '../collection'
2
+ require_relative 'split_route_collection/split'
3
+ class SiteHub
4
+ class Collection < Hash
5
+ class SplitRouteCollection < Collection
6
+ class InvalidSplitException < Exception;
7
+ end
8
+
9
+ def initialize(hash={})
10
+ hash.each do |value, percentage|
11
+ add(value.id, value, percentage)
12
+ end
13
+ end
14
+
15
+ def add id, value, percentage
16
+ raise InvalidSplitException, 'splits must be a Fixnum' unless percentage.is_a?(Fixnum)
17
+ lower = values.last ? values.last.upper : 0
18
+ upper = lower + percentage
19
+
20
+ raise InvalidSplitException, 'total split percentages can not be greater than 100%' if upper > 100
21
+ self[id] = Split.new(lower, upper, value)
22
+ end
23
+
24
+ def resolve(*args)
25
+ random = rand(100)
26
+ result = values.find { |split| random >= split.lower && random < split.upper }
27
+ result ? result.value : nil
28
+ end
29
+
30
+ def transform &block
31
+ values.each do |split|
32
+ split.value = block.call(split.value)
33
+ end
34
+ end
35
+
36
+ def [] key
37
+ result = super
38
+ result && result.value
39
+ end
40
+
41
+ def valid?
42
+ last = values.last
43
+ return true if last && last.upper == 100
44
+
45
+ warn('splits do not add up to 100% and no default has been specified')
46
+ false
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,18 @@
1
+ class SiteHub
2
+ class Collection
3
+ class SplitRouteCollection < Collection
4
+ class Split
5
+ attr_reader :upper, :lower
6
+ attr_accessor :value
7
+
8
+ def initialize lower, upper, value
9
+ @upper, @lower, @value = upper, lower, value
10
+ end
11
+
12
+ def == other
13
+ other.is_a?(Split) && other.lower == self.lower && other.upper == self.upper
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,23 @@
1
+ require 'sitehub/constants/http_header_keys'
2
+ require 'sitehub/constants/rack_http_header_keys'
3
+
4
+ class SiteHub
5
+ REQUEST_MAPPING = 'sitehub.request_mapping'.freeze
6
+ RESPONSE = 'sitehub.response'.freeze
7
+ ASYNC_CALLBACK = 'async.callback'.freeze
8
+ RECORDED_ROUTES_COOKIE = 'sitehub.recorded_route'.freeze
9
+ ERRORS = 'sitehub.errors'.freeze
10
+ TIME_STAMP_FORMAT = "%d/%b/%Y:%H:%M:%S %z".freeze
11
+ EMPTY_STRING = ''.freeze
12
+ UNDERSCORE = '_'.freeze
13
+ SEMICOLON = ';'.freeze
14
+ SPACE = ' '.freeze
15
+ SEMICOLON_WITH_SPACE = "#{SEMICOLON}#{SPACE}".freeze
16
+ COMMA_WITH_SPACE = ", "
17
+
18
+ HYPHEN = '-'.freeze
19
+ QUESTION_MARK = '?'.freeze
20
+ EQUALS_SIGN = '='.freeze
21
+ FULL_STOP = '.'.freeze
22
+ NEW_LINE = "\n".freeze
23
+ end
@@ -0,0 +1,25 @@
1
+ class SiteHub
2
+ module Constants
3
+ module HttpHeaderKeys
4
+
5
+ ShouldNotTransfer = %w(proxy-connection).freeze
6
+
7
+ LOCATION_HEADER = 'location'.freeze
8
+ HOST_HEADER = 'host'.freeze
9
+ X_FORWARDED_HOST_HEADER = 'x-forwarded-host'.freeze
10
+ X_FORWARDED_FOR_HEADER = 'x-forwarded-for'.freeze
11
+ CONNECTION_HEADER = 'connection'.freeze
12
+ KEEP_ALIVE = 'keep-alive'.freeze
13
+ PROXY_AUTHENTICATE = 'proxy-authenticate'.freeze
14
+ PROXY_AUTHORIZATION = 'proxy-authorization'.freeze
15
+ TE = 'te'.freeze
16
+ TRAILERS = 'trailers'.freeze
17
+ TRANSFER_ENCODING = 'transfer-encoding'.freeze
18
+ CONTENT_ENCODING = 'content-encoding'.freeze
19
+ SET_COOKIE = 'Set-Cookie'.freeze
20
+ CONTENT_LENGTH = 'Content-Length'.freeze
21
+
22
+ HopByHop = [CONNECTION_HEADER, KEEP_ALIVE, PROXY_AUTHENTICATE, PROXY_AUTHORIZATION, TE, TRAILERS, TRANSFER_ENCODING, CONTENT_ENCODING].freeze
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,17 @@
1
+ class SiteHub
2
+ module Constants
3
+ module RackHttpHeaderKeys
4
+ PATH_INFO = 'PATH_INFO'.freeze
5
+ REQUEST_METHOD = 'REQUEST_METHOD'.freeze
6
+ SCRIPT_NAME = 'SCRIPT_NAME'.freeze
7
+ QUERY_STRING = 'QUERY_STRING'.freeze
8
+ X_FORWARDED_FOR = 'HTTP_X_FORWARDED_FOR'.freeze
9
+ REMOTE_ADDR = 'REMOTE_ADDR'.freeze
10
+ REMOTE_USER = 'REMOTE_USER'.freeze
11
+ HTTP_VERSION = 'HTTP_VERSION'.freeze
12
+ REMOTE_ADDRESS_ENV_KEY = "REMOTE_ADDR".freeze
13
+ HTTP_HEADER_FILTER_EXCEPTIONS = %w(CONTENT_TYPE CONTENT_LENGTH).freeze
14
+ TRANSACTION_ID = 'HTTP_SITEHUB_TRANSACTION_ID'.freeze
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,54 @@
1
+ require 'sitehub/cookie/attribute'
2
+ require 'sitehub/cookie/flag'
3
+ require 'sitehub/constants'
4
+ class SiteHub
5
+ class Cookie
6
+ attr_reader :attributes_and_flags, :name_attribute
7
+ include Constants
8
+
9
+ FIRST = 0
10
+
11
+ def initialize cookie_string
12
+ @attributes_and_flags =[]
13
+ pairs = cookie_string.split(SEMICOLON).map do |entry|
14
+ if entry.include?(EQUALS_SIGN)
15
+ Cookie::Attribute.new(*entry.split(EQUALS_SIGN))
16
+ else
17
+ Cookie::Flag.new(entry)
18
+ end
19
+ end
20
+
21
+ name_attribute = pairs.delete_at(FIRST)
22
+ attributes_and_flags.concat(pairs)
23
+ @name_attribute = Cookie::Attribute.new(name_attribute.name.to_s, name_attribute.value)
24
+
25
+ end
26
+
27
+ def name
28
+ name_attribute.name
29
+ end
30
+
31
+ def value
32
+ name_attribute.value
33
+ end
34
+
35
+ def find name
36
+ attributes_and_flags.find{|entry| entry.name == name}
37
+ end
38
+
39
+ def delete name
40
+ entry = find(name)
41
+ @attributes_and_flags = attributes_and_flags.delete_if{|e| e.name == name}
42
+ entry
43
+ end
44
+
45
+ def == other
46
+ other.is_a?(self.class) && self.attributes_and_flags.sort{|entry1, entry2| entry1.name <=> entry2.name} == other.attributes_and_flags.sort{|entry1, entry2| entry1.name <=> entry2.name}
47
+ end
48
+
49
+ def to_s
50
+ [name_attribute].concat(attributes_and_flags).join(SEMICOLON_WITH_SPACE)
51
+ end
52
+
53
+ end
54
+ end
@@ -0,0 +1,22 @@
1
+ require 'sitehub/string_sanitiser'
2
+ class SiteHub
3
+ class Cookie
4
+ class Attribute
5
+ include StringSanitiser
6
+ attr_accessor :name, :value
7
+
8
+ def initialize name, value
9
+ @name = sanitise(name).to_s.to_sym
10
+ @value = sanitise(value || EMPTY_STRING)
11
+ end
12
+
13
+ def to_s
14
+ "#{name}=#{value}"
15
+ end
16
+
17
+ def == other
18
+ other.is_a?(Attribute) && self.name == other.name && self.value == other.value
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ require 'sitehub/string_sanitiser'
2
+ class SiteHub
3
+ class Cookie
4
+ class Flag
5
+ include StringSanitiser
6
+ attr_accessor :name
7
+
8
+ def initialize flag
9
+ @name = sanitise(flag).to_sym
10
+ end
11
+
12
+ def to_s
13
+ name.to_s
14
+ end
15
+
16
+ def == other
17
+ other.is_a?(Flag) && self.name == other.name
18
+ end
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,35 @@
1
+ require 'sitehub/cookie'
2
+ require 'sitehub/constants'
3
+ class SiteHub
4
+
5
+ module CookieRewriting
6
+
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
+ if domain_attribute = cookie.find(:domain)
14
+ value = domain_attribute.value
15
+ domain_attribute.value = substitute_domain.dup
16
+ domain_attribute.value.prepend(FULL_STOP) if value.start_with?(FULL_STOP)
17
+ end
18
+ end
19
+ headers[HttpHeaders::SET_COOKIE] = cookies_hash_to_string(cookies_hash)
20
+ end
21
+
22
+ def cookies_hash_to_string cookies_hash
23
+ cookies_hash.values.inject(EMPTY_STRING.dup) do |cookie_string, cookie|
24
+ cookie_string << "#{cookie.to_s}#{NEW_LINE}"
25
+ end.sub(ENDING_WITH_NEWLINE, EMPTY_STRING)
26
+ end
27
+
28
+ def cookies_string_as_hash cookie_string
29
+ cookie_string.lines.inject({}) do |cookies, cookie_line|
30
+ cookie = SiteHub::Cookie.new(cookie_line)
31
+ cookies[cookie.name] = cookie; cookies
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,50 @@
1
+ require 'sitehub/constants'
2
+ require 'sitehub/forward_proxy'
3
+ require 'rack/request'
4
+ require 'rack/response'
5
+ require 'rack/utils'
6
+ require 'em-http'
7
+
8
+ class SiteHub
9
+ class ForwardProxies
10
+
11
+ NOT_FOUND = Rack::Response.new(['page not found'], 404, {})
12
+ def call env
13
+ source_request = Rack::Request.new(env)
14
+
15
+ forward_proxy = mapped_route(path: source_request.path, request: source_request)
16
+
17
+ return forward_proxy.call(env) if forward_proxy
18
+ NOT_FOUND
19
+ end
20
+
21
+ def init
22
+ forward_proxies.values.each { |proxy| proxy.build }
23
+ self
24
+ end
25
+
26
+ def << route
27
+ forward_proxies[route.mapped_path] = route
28
+ end
29
+
30
+ def mapped_route(path:, request:)
31
+
32
+ key = forward_proxies.keys.find do |key|
33
+ case key
34
+ when Regexp
35
+ key.match(path)
36
+ else
37
+ path == key
38
+ end
39
+ end
40
+
41
+ forward_proxy_builder = forward_proxies[key]
42
+ forward_proxy_builder ? forward_proxy_builder.resolve(id: request.cookies[RECORDED_ROUTES_COOKIE], env: request.env) : nil
43
+ end
44
+
45
+ def forward_proxies
46
+ @forward_proxies ||= {}
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,67 @@
1
+ require 'sitehub/http_headers'
2
+ require 'sitehub/request_mapping'
3
+ require 'sitehub/rules'
4
+ require 'sitehub/resolver'
5
+ require 'faraday'
6
+ require 'sitehub/constants'
7
+ class SiteHub
8
+ class ForwardProxy
9
+
10
+ ERROR_RESPONSE = Rack::Response.new(['error'], 500, {})
11
+
12
+ include HttpHeaders, Rules, Resolver, Constants
13
+
14
+ attr_reader :url, :id, :mapped_path, :http_client, :sitehub_cookie_path, :sitehub_cookie_name
15
+
16
+ def initialize(url:, id:, mapped_path: nil, rule: nil, sitehub_cookie_path: nil, sitehub_cookie_name:)
17
+ @id, @url, @rule, @mapped_path = id, url, rule, mapped_path
18
+ @sitehub_cookie_path = sitehub_cookie_path
19
+ @sitehub_cookie_name = sitehub_cookie_name
20
+ @http_client = Faraday.new(ssl: {verify:false}) do |con|
21
+ con.adapter :em_synchrony
22
+ end
23
+ end
24
+
25
+ def call env
26
+ source_request = Rack::Request.new(env)
27
+
28
+ request_mapping = request_mapping(source_request)
29
+
30
+ mapped_uri = URI(request_mapping.computed_uri)
31
+
32
+ request_headers = sanitise_headers(extract_http_headers(source_request.env))
33
+ request_headers[HOST_HEADER] = "#{mapped_uri.host}:#{mapped_uri.port}"
34
+ request_headers[X_FORWARDED_HOST_HEADER] = append_host(request_headers[X_FORWARDED_HOST_HEADER].to_s, source_request.url)
35
+ env[REQUEST_MAPPING] = request_mapping
36
+
37
+ begin
38
+ response = http_client.send(source_request.request_method.downcase, mapped_uri) do |request|
39
+ request.headers = request_headers
40
+ request.body = source_request.body.read
41
+ request.params = source_request.params
42
+ end
43
+
44
+ Rack::Response.new(response.body, response.status, sanitise_headers(response.headers)).tap do |r|
45
+ r.set_cookie(sitehub_cookie_name, path: (sitehub_cookie_path || source_request.path), value: id)
46
+ end
47
+ rescue Exception => e
48
+ env[ERRORS] << e.message
49
+ ERROR_RESPONSE.dup
50
+ end
51
+ end
52
+
53
+ def request_mapping(source_request)
54
+ RequestMapping.new(source_url: source_request.url, mapped_url: url, mapped_path: mapped_path)
55
+ end
56
+
57
+ def == other
58
+ other.is_a?(ForwardProxy) && url == other.url
59
+ end
60
+
61
+ private
62
+ def append_host(forwarded_host, destination_uri)
63
+ destination_uri = URI(destination_uri)
64
+ forwarded_host == EMPTY_STRING ? "#{destination_uri.host}:#{destination_uri.port}" : "#{forwarded_host},#{destination_uri.host}"
65
+ end
66
+ end
67
+ end