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.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/.rspec +1 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +114 -0
- data/LICENSE +28 -0
- data/README.md +198 -0
- data/Rakefile +11 -0
- data/lib/sitehub.rb +9 -0
- data/lib/sitehub/builder.rb +82 -0
- data/lib/sitehub/collection.rb +35 -0
- data/lib/sitehub/collection/route_collection.rb +28 -0
- data/lib/sitehub/collection/split_route_collection.rb +50 -0
- data/lib/sitehub/collection/split_route_collection/split.rb +18 -0
- data/lib/sitehub/constants.rb +23 -0
- data/lib/sitehub/constants/http_header_keys.rb +25 -0
- data/lib/sitehub/constants/rack_http_header_keys.rb +17 -0
- data/lib/sitehub/cookie.rb +54 -0
- data/lib/sitehub/cookie/attribute.rb +22 -0
- data/lib/sitehub/cookie/flag.rb +22 -0
- data/lib/sitehub/cookie_rewriting.rb +35 -0
- data/lib/sitehub/forward_proxies.rb +50 -0
- data/lib/sitehub/forward_proxy.rb +67 -0
- data/lib/sitehub/forward_proxy_builder.rb +99 -0
- data/lib/sitehub/http_headers.rb +60 -0
- data/lib/sitehub/logging.rb +5 -0
- data/lib/sitehub/logging/access_logger.rb +74 -0
- data/lib/sitehub/logging/error_logger.rb +36 -0
- data/lib/sitehub/logging/log_entry.rb +14 -0
- data/lib/sitehub/logging/log_stash.rb +10 -0
- data/lib/sitehub/logging/log_wrapper.rb +23 -0
- data/lib/sitehub/middleware.rb +21 -0
- data/lib/sitehub/path_directive.rb +32 -0
- data/lib/sitehub/path_directives.rb +21 -0
- data/lib/sitehub/request_mapping.rb +43 -0
- data/lib/sitehub/resolver.rb +11 -0
- data/lib/sitehub/reverse_proxy.rb +53 -0
- data/lib/sitehub/rules.rb +13 -0
- data/lib/sitehub/string_sanitiser.rb +7 -0
- data/lib/sitehub/transaction_id.rb +16 -0
- data/lib/sitehub/version.rb +3 -0
- data/mem_usage.txt +1584 -0
- data/sitehub.gemspec +43 -0
- data/spec/basket_spec.rb +30 -0
- data/spec/sitehub/builder_spec.rb +203 -0
- data/spec/sitehub/collection/route_collection_spec.rb +91 -0
- data/spec/sitehub/collection/split_route_collection_spec.rb +111 -0
- data/spec/sitehub/collection_spec.rb +40 -0
- data/spec/sitehub/cookie/attribute_spec.rb +37 -0
- data/spec/sitehub/cookie/flag_spec.rb +27 -0
- data/spec/sitehub/cookie_rewriting_spec.rb +67 -0
- data/spec/sitehub/cookie_spec.rb +61 -0
- data/spec/sitehub/error_handling_spec.rb +21 -0
- data/spec/sitehub/forward_proxies_spec.rb +99 -0
- data/spec/sitehub/forward_proxy_builder_spec.rb +295 -0
- data/spec/sitehub/forward_proxy_spec.rb +138 -0
- data/spec/sitehub/http_headers_spec.rb +71 -0
- data/spec/sitehub/integration_spec.rb +21 -0
- data/spec/sitehub/logging/access_logger_spec.rb +127 -0
- data/spec/sitehub/logging/error_logger_spec.rb +80 -0
- data/spec/sitehub/logging/log_entry_spec.rb +34 -0
- data/spec/sitehub/logging/log_stash_spec.rb +21 -0
- data/spec/sitehub/logging/log_wrapper_spec.rb +33 -0
- data/spec/sitehub/middleware_spec.rb +69 -0
- data/spec/sitehub/path_directive_spec.rb +50 -0
- data/spec/sitehub/path_directives_spec.rb +45 -0
- data/spec/sitehub/request_mapping_spec.rb +71 -0
- data/spec/sitehub/resolver_spec.rb +15 -0
- data/spec/sitehub/reverse_proxy_spec.rb +105 -0
- data/spec/sitehub/transaction_id_spec.rb +28 -0
- data/spec/sitehub_spec.rb +19 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/support/patch/rack/response.rb +25 -0
- data/spec/support/shared_contexts/async_context.rb +69 -0
- data/spec/support/shared_contexts/middleware_context.rb +51 -0
- data/spec/support/shared_contexts/rack_test_context.rb +12 -0
- data/spec/support/shared_contexts/sitehub_context.rb +25 -0
- data/spec/support/silent_warnings.rb +5 -0
- 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,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,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
|