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,138 @@
1
+ require 'sitehub/forward_proxy'
2
+
3
+ class SiteHub
4
+ describe ForwardProxy do
5
+
6
+ let(:current_version_url) { 'http://does.not.exist.com' }
7
+ let(:mapped_path) { '/path' }
8
+
9
+ subject do
10
+ described_class.new(id: :id, url: current_version_url, mapped_path: mapped_path,sitehub_cookie_name: :cookie_name)
11
+ end
12
+
13
+ let(:app) do
14
+ subject
15
+ end
16
+
17
+ it 'includes Resolver' do
18
+ expect(subject).to be_a(Resolver)
19
+ end
20
+
21
+ it 'includes Rules' do
22
+ expect(subject).to be_a(Rules)
23
+ end
24
+
25
+ describe '#call' do
26
+ before do
27
+ WebMock.enable!
28
+ stub_request(:get, current_version_url).to_return(:body => 'body')
29
+ end
30
+
31
+ context 'recorded routes cookie' do
32
+ it 'drops a cookie using the name of the sitehub_cookie_name containing the id' do
33
+ get(mapped_path, {})
34
+ expect(last_response.cookies[:cookie_name.to_s]).to eq(value: :id.to_s, path: subject.mapped_path)
35
+ end
36
+
37
+ context 'recorded_routes_cookie_path not set' do
38
+ it 'sets the path to be the request path' do
39
+ get(mapped_path, {})
40
+ expect(last_response.cookies[:cookie_name.to_s][:path]).to eq(mapped_path)
41
+ end
42
+ end
43
+
44
+ context 'recorded_routes_cookie_path set' do
45
+
46
+ let(:expected_path){'/expected_path'}
47
+
48
+ subject do
49
+ described_class.new(id: :id,
50
+ url: current_version_url,
51
+ mapped_path: mapped_path,
52
+ sitehub_cookie_path: expected_path,
53
+ sitehub_cookie_name: :cookie_name)
54
+ end
55
+
56
+ it 'is set as the path' do
57
+ get(mapped_path, {})
58
+ expect(last_response.cookies[:cookie_name.to_s][:path]).to eq(expected_path)
59
+ end
60
+ end
61
+ end
62
+
63
+
64
+
65
+ it 'passes request mapping information in to the environment hash' do
66
+ get(mapped_path, {})
67
+ expect(last_request.env[REQUEST_MAPPING]).to eq(RequestMapping.new(source_url: "http://example.org#{mapped_path}", mapped_url: current_version_url, mapped_path: mapped_path))
68
+ end
69
+
70
+ context 'downstream call' do
71
+ context 'it fails' do
72
+ before do
73
+ WebMock.disable!
74
+ end
75
+ it 'adds an error to be logged' do
76
+ env = {ERRORS.to_s => []}
77
+ get(mapped_path, {}, env)
78
+ expect(last_request.env[ERRORS]).to eq(["unable to resolve server address"])
79
+ end
80
+
81
+ describe 'parameters to callback' do
82
+ it 'calls the callback with an error response' do
83
+ expect(described_class::ERROR_RESPONSE).to receive(:dup).and_return(described_class::ERROR_RESPONSE)
84
+ env = {ERRORS.to_s => []}
85
+ get(mapped_path, {}, env)
86
+
87
+ expect(last_response.body).to eq(described_class::ERROR_RESPONSE.body.join)
88
+ expect(last_response.headers).to eq(described_class::ERROR_RESPONSE.headers)
89
+ expect(last_response.status).to eq(described_class::ERROR_RESPONSE.status)
90
+ end
91
+
92
+ it 'passes the request mapping' do
93
+ env = { ERRORS.to_s => []}
94
+ get(mapped_path, {}, env)
95
+ expect(last_request.env[REQUEST_MAPPING]).to eq(RequestMapping.new(source_url: "http://example.org#{mapped_path}", mapped_url: current_version_url, mapped_path: mapped_path))
96
+ end
97
+ end
98
+ end
99
+
100
+
101
+ it 'translates the header names back in to the http compatible names' do
102
+ get(mapped_path, {})
103
+ expect(last_response.headers).to include('Content-Length')
104
+ expect(last_response.headers).to_not include('CONTENT_LENGTH')
105
+ end
106
+
107
+ context 'adding http_x_forwarded_host header' do
108
+ context 'when not present in the original request' do
109
+ it 'appends original request url with port' do
110
+ get(mapped_path, {})
111
+ assert_requested :get, current_version_url, headers: {'X-FORWARDED-HOST' => 'example.org:80'}
112
+ end
113
+ end
114
+
115
+ context 'when present in the original request' do
116
+ it 'appends original request url without port' do
117
+ get(mapped_path, {}, 'HTTP_X_FORWARDED_HOST' => 'staging.com')
118
+ assert_requested :get, current_version_url, headers: {'X-FORWARDED-HOST' => 'staging.com,staging.com'}
119
+ end
120
+ end
121
+
122
+ it 'preserves the body when forwarding request' do
123
+ body = {"key" => "value"}
124
+ stub_request(:put, current_version_url).with(:body => body)
125
+
126
+ put(mapped_path, body)
127
+ end
128
+
129
+ it 'preserves the headers when forwarding request' do
130
+ get(mapped_path, '', 'HTTP_HEADER' => 'value')
131
+ assert_requested :get, current_version_url, headers: {'Header' => 'value'}
132
+ end
133
+ end
134
+
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,71 @@
1
+ require 'sitehub/http_headers'
2
+ class SiteHub
3
+ describe HttpHeaders do
4
+
5
+ subject do
6
+ Object.new.tap do |o|
7
+ o.extend(described_class)
8
+ end
9
+ end
10
+
11
+ let(:headers_underscored) do
12
+ {'CONNECTION' => 'close',
13
+ 'KEEP_ALIVE' => 'something',
14
+ 'PROXY_AUTHENTICATE' => 'something',
15
+ 'PROXY_AUTHORIZATION' => 'something',
16
+ 'TE' => 'something',
17
+ 'TRAILERS' => 'something',
18
+ 'TRANSFER_ENCODING' => 'something',
19
+ 'CONTENT_ENCODING' => 'something',
20
+ 'PROXY_CONNECTION' => 'something'}
21
+ end
22
+
23
+ let(:headers_hyphonised) do
24
+ Hash.new.tap do |hash|
25
+ headers_underscored.each do |key, value|
26
+ hash[key.gsub('_', '-')] = value
27
+ end
28
+ end
29
+ end
30
+
31
+ describe '#sanitise_headers' do
32
+
33
+ context 'port 80 present in url' do
34
+ it 'removes the port' do
35
+ headers_hyphonised['location'] = 'http://mysite.com:80/redirect_endpoint'
36
+ expect(subject.sanitise_headers(headers_hyphonised)['location']).to eq('http://mysite.com/redirect_endpoint')
37
+ end
38
+ end
39
+
40
+ context 'port 443 present in url' do
41
+ it 'removes the port' do
42
+ headers_hyphonised['location'] = 'http://mysite.com:443/redirect_endpoint'
43
+ expect(subject.sanitise_headers(headers_hyphonised)['location']).to eq('http://mysite.com/redirect_endpoint')
44
+ end
45
+ end
46
+
47
+ describe 'treatment of headers according to RFC2616: 13.5.1 and RFC2616: 14.10' do
48
+ context 'prohibitted headers hyphonised' do
49
+ it 'filters them out' do
50
+ sanatised_headers = subject.sanitise_headers(headers_hyphonised)
51
+ expect(sanatised_headers.empty?).to eq(true)
52
+ end
53
+ end
54
+
55
+ context 'prohibitted headers underscored' do
56
+ it 'filters them out' do
57
+ sanatised_headers = subject.sanitise_headers(headers_underscored)
58
+ expect(sanatised_headers.empty?).to eq(true)
59
+ end
60
+ end
61
+
62
+ it 'filters out connections' do
63
+ headers = subject.sanitise_headers('connection' => 'a, b', 'a' => 'value_a', 'b' => 'value_b', 'c' => 'value_c')
64
+ expect(headers).to eq('c' => 'value_c')
65
+ end
66
+ end
67
+ end
68
+
69
+
70
+ end
71
+ end
@@ -0,0 +1,21 @@
1
+ describe 'proxying calls' do
2
+ include_context :site_hub
3
+ include_context :async
4
+
5
+ describe 'supported HTTP verbs' do
6
+
7
+ before do
8
+ WebMock.enable!
9
+ end
10
+
11
+ let(:app){async_middleware.new(rack_application)}
12
+
13
+ %i(get post put delete).each do |verb|
14
+ it "forwards the downstream" do
15
+ stub_request(verb, downstream_url).to_return(:body => 'hello')
16
+ send(verb, '/endpoint')
17
+ expect(app.last_response.body).to eq(['hello'])
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,127 @@
1
+ require 'sitehub/logging/access_logger'
2
+ require 'stringio'
3
+
4
+ class SiteHub
5
+ module Logging
6
+ describe AccessLogger do
7
+
8
+ let(:logger) do
9
+ StringIO.new
10
+ end
11
+
12
+ let(:app) do
13
+ proc{[200, {}, []]}
14
+ end
15
+
16
+ subject do
17
+ described_class.new(app, logger)
18
+ end
19
+
20
+ describe '#initialize' do
21
+
22
+ context 'logger supplied' do
23
+ it 'sets logger to that logger' do
24
+ expect(described_class.new(:app, :custom_logger).logger).to eq(LogWrapper.new(:custom_logger))
25
+ end
26
+ end
27
+
28
+ context 'logger not supplied' do
29
+ it 'sets the logger to go to STDERR' do
30
+ expect(::Logger).to receive(:new).with(STDOUT).and_return(:logging)
31
+ expect(described_class.new(:app).logger).to eq(LogWrapper.new(:logging))
32
+ end
33
+ end
34
+ end
35
+
36
+ #describe '#before_call' do
37
+ # it 'saves the time' do
38
+ # now = Time.now
39
+ # allow(Time).to receive(:now).and_return(now)
40
+ # subject.before_call(:env)
41
+ # expect(subject.start_time).to eq(now)
42
+ # end
43
+ #end
44
+
45
+ describe '#call' do
46
+ let(:env) do
47
+ {
48
+ REQUEST_MAPPING => request_mapping,
49
+ described_class::QUERY_STRING => '',
50
+ described_class::PATH_INFO => 'path_info',
51
+ Constants::RackHttpHeaderKeys::TRANSACTION_ID => :transaction_id
52
+ }
53
+
54
+ end
55
+ let(:request_mapping) { RequestMapping.new(source_url: :source_url, mapped_url: :mapped_url.to_s, mapped_path: :mapped_path.to_s) }
56
+ before { subject.call(env) }
57
+
58
+ let(:args) { {request_mapping: request_mapping, downstream_response: Rack::Response.new} }
59
+ it 'logs the request / response details in the required format' do
60
+ expect(subject).to receive(:log_template).and_return(:template.to_s)
61
+ expect(logger).to receive(:write).with(:template.to_s)
62
+
63
+ subject.call(env)
64
+ end
65
+
66
+ context 'query string' do
67
+ let(:query_string) { described_class::QUERY_STRING }
68
+ context 'present' do
69
+ it 'logs it' do
70
+ env[query_string] = 'query'
71
+ subject.call(env)
72
+ expect(logger.string).to include("?query")
73
+ end
74
+ end
75
+
76
+ context 'not present' do
77
+ it 'is not logged' do
78
+ subject.call(env)
79
+ expect(logger.string).to_not include("?")
80
+ end
81
+ end
82
+ end
83
+
84
+ it 'logs the transaction id' do
85
+ subject.call(env)
86
+
87
+ expect(logger.string).to include("transaction_id:#{:transaction_id}")
88
+ end
89
+
90
+ it 'logs the response status' do
91
+ subject.call(env)
92
+
93
+ expect(logger.string).to include(args[:downstream_response].status.to_s)
94
+ end
95
+
96
+
97
+ it 'logs the downstream url that was proxied to' do
98
+ subject.call(env)
99
+
100
+ expect(logger.string).to include("#{env[described_class::PATH_INFO]} => #{:mapped_url}")
101
+ end
102
+
103
+ end
104
+ describe '#extract_content_length' do
105
+ context 'content length header not present' do
106
+ it 'returns -' do
107
+ expect(subject.extract_content_length({})).to eq('-')
108
+ end
109
+ end
110
+
111
+ context 'content length header not present' do
112
+ let(:content_length) { described_class::CONTENT_LENGTH }
113
+ context 'it is 0' do
114
+ it 'returns -' do
115
+ expect(subject.extract_content_length({content_length => 0})).to eq('-')
116
+ end
117
+ end
118
+ context 'it is set to a number other than 0'
119
+ it 'returns the number' do
120
+ expect(subject.extract_content_length({content_length => 5})).to eq(5)
121
+ end
122
+ end
123
+
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,80 @@
1
+ require 'sitehub/logging/error_logger'
2
+
3
+ class SiteHub
4
+ module Logging
5
+ describe ErrorLogger do
6
+
7
+ let(:app){ proc{[200, {}, []]}}
8
+ subject { described_class.new(app) }
9
+
10
+ describe '#initialize' do
11
+ context 'logger supplied' do
12
+ it 'sets logger to that logger' do
13
+ expect(described_class.new(:app, :custom_logger).logger).to eq(LogWrapper.new(:custom_logger))
14
+ end
15
+ end
16
+
17
+ context 'logger not supplied' do
18
+ it 'sets the logger to go to STDERR' do
19
+ expect(::Logger).to receive(:new).with(STDERR).and_return(:logging)
20
+ expect(described_class.new(:app).logger).to eq(LogWrapper.new(:logging))
21
+ end
22
+ end
23
+ end
24
+
25
+ describe '#before_call' do
26
+ it 'adds a collection hold errors' do
27
+ env = {}
28
+ subject.call(env)
29
+ expect(env[ERRORS]).to eq([])
30
+ end
31
+ end
32
+
33
+ describe '#call' do
34
+ let(:output) { StringIO.new }
35
+ let(:logger) { Logger.new(output) }
36
+ let(:error_message) { 'error' }
37
+ let(:errors) do
38
+ Logging::LogStash.new.tap do |stash|
39
+ stash << error_message
40
+ end
41
+ end
42
+ subject { described_class.new(app, logger) }
43
+
44
+ context 'errors have occurred' do
45
+ it 'logs errors' do
46
+ log_message = subject.log_message(error: error_message, transaction_id: :transaction_id)
47
+ subject.call({ERRORS => errors, Constants::RackHttpHeaderKeys::TRANSACTION_ID => :transaction_id})
48
+ expect(output.string).to eq(log_message)
49
+ end
50
+ end
51
+
52
+ context 'no errors have occured' do
53
+ it 'does not log anything' do
54
+ expect(logger).to_not receive(:write)
55
+ subject.call({ERRORS => []})
56
+ end
57
+ end
58
+ end
59
+
60
+ describe '#log_message' do
61
+ let(:error) { 'error' }
62
+ it 'contains the time and date' do
63
+ now = Time.now
64
+ expect(Time).to receive(:now).and_return(now)
65
+ expected_time = now.strftime("%d/%b/%Y:%H:%M:%S %z")
66
+ expect(subject.log_message(error: error, transaction_id: :transaction_id)).to start_with("[#{expected_time}]")
67
+ end
68
+
69
+ it 'logs statements made against blah' do
70
+
71
+ expect(subject.log_message(error: error, transaction_id: :transaction_id)).to match(/ERROR: .* - ?#{error}$/)
72
+ end
73
+
74
+ it 'contains the transation id of the request' do
75
+ expect(subject.log_message(error: error, transaction_id: :transaction_id)).to include("ERROR: #{:transaction_id}")
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,34 @@
1
+ require 'sitehub/logging/log_entry'
2
+ class SiteHub
3
+ module Logging
4
+ describe LogEntry do
5
+ describe '#initialize' do
6
+
7
+ let(:time){Time.now}
8
+ subject do
9
+ described_class.new(:message, time)
10
+ end
11
+
12
+ it 'sets the message' do
13
+ expect(subject.message).to be(:message)
14
+ end
15
+
16
+ it 'sets the time' do
17
+ expect(subject.time).to be(time)
18
+ end
19
+
20
+ context 'time not supplied' do
21
+ subject do
22
+ described_class.new(:message)
23
+ end
24
+ it 'defaults the time' do
25
+ expect(Time).to receive(:now).and_return(:current_time)
26
+ expect(subject.time).to be(:current_time)
27
+ end
28
+ end
29
+
30
+
31
+ end
32
+ end
33
+ end
34
+ end