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