-
Notifications
You must be signed in to change notification settings - Fork 59
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support subdomain and paths in process labels #123
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,22 +2,84 @@ module Invoker | |
class DNSCache | ||
attr_accessor :dns_data | ||
|
||
# INI: | ||
# [app] | ||
# port = 8000 | ||
# location = www2.app/blah | ||
# | ||
# [chat] | ||
# port = 8001 | ||
# location www.app/chat | ||
# | ||
# [api] | ||
# port = 8002 | ||
# location = www.app/api | ||
# | ||
# dns_data: | ||
# { | ||
# 'api' => [ | ||
# ['', 8002] | ||
# ], | ||
# 'app' => [ | ||
# ['', 8000] | ||
# ], | ||
# 'chat' => [ | ||
# ['', 8001] | ||
# ], | ||
# 'www.app' => [ | ||
# ['', 8000], | ||
# ['/api, 8002], | ||
# ['/chat, 8001] | ||
# ], | ||
# 'www2.app' => [ | ||
# ['/blah', 8000] | ||
# ] | ||
# } | ||
def initialize(config) | ||
self.dns_data = {} | ||
self.dns_data = Hash.new {|h,k| h[k] = []} | ||
@dns_mutex = Mutex.new | ||
Invoker.config.processes.each do |process| | ||
if process.port | ||
dns_data[process.label] = { 'port' => process.port } | ||
add(process.label, process.port) | ||
if process.location | ||
process.location.split(' ').each do |loc| | ||
add(loc, process.port) | ||
end | ||
end | ||
end | ||
end | ||
end | ||
|
||
def [](process_name) | ||
@dns_mutex.synchronize { dns_data[process_name] } | ||
def find_process(host, path) | ||
@dns_mutex.synchronize { | ||
until dns_data.include?(host) || host.nil? | ||
host = host.split('.', 2)[1] | ||
end | ||
|
||
dns_data[host].reverse_each {|prefix, port| | ||
if path_matches_prefix?(path, prefix) | ||
return {:port => port} | ||
end | ||
} | ||
|
||
nil | ||
} | ||
end | ||
|
||
def add(location, port) | ||
@dns_mutex.synchronize { | ||
host, path_prefix = split_host_path(location) | ||
(dns_data[host] << [path_prefix.to_s, port]).sort_by! &:length | ||
} | ||
end | ||
|
||
private | ||
def split_host_path(label) | ||
label.split(%r{(?=/)}, 2) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, so this seems to be supporting both old and new format. Do you think it is best to support one or another? I would rather just have separate There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Although we are not explicitly adding path support to process labels, it's actually easier to support path than not, since both process labels and locations go through the same location parser. It also makes sense, in my opinion, since there's no clear way to not support paths in labels. We could, perhaps, add to code to error out, or add code to ignore any process labels that have paths, but why not just let it be handled in the same way location is handled? |
||
end | ||
|
||
def add(name, port) | ||
@dns_mutex.synchronize { dns_data[name] = { 'port' => port } } | ||
def path_matches_prefix?(path, prefix) | ||
path.start_with?(prefix) && [nil, "/", "?"].member?(path[prefix.length]) | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,8 +22,8 @@ def post_init | |
|
||
class Balancer | ||
attr_accessor :connection, :http_parser, :session, :protocol | ||
DEV_MATCH_REGEX = /([\w-]+)\.dev(\:\d+)?$/ | ||
XIP_IO_MATCH_REGEX = /([\w-]+)\.\d+\.\d+\.\d+\.\d+\.xip\.io(\:\d+)?$/ | ||
DEV_MATCH_REGEX = /([\w.-]+)\.dev(\:\d+)?$/ | ||
XIP_IO_MATCH_REGEX = /([\w.-]+)\.\d+\.\d+\.\d+\.\d+\.xip\.io(\:\d+)?$/ | ||
|
||
def self.run(options = {}) | ||
start_http_proxy(InvokerHttpProxy, 'http', options) | ||
|
@@ -43,29 +43,47 @@ def initialize(connection, protocol) | |
@connection = connection | ||
@protocol = protocol | ||
@http_parser = HttpParser.new(protocol) | ||
@response_http_parser = HttpParser.new(protocol) | ||
@session = nil | ||
@buffer = [] | ||
end | ||
|
||
def install_callbacks | ||
http_parser.on_url { |url| url_received(url) } | ||
http_parser.on_headers_complete { |headers| headers_received(headers) } | ||
http_parser.on_message_complete { |full_message| complete_message_received(full_message) } | ||
|
||
@response_http_parser.on_message_complete { response_complete } | ||
|
||
connection.on_data { |data| upstream_data(data) } | ||
connection.on_response { |backend, data| backend_data(backend, data) } | ||
connection.on_finish { |backend, name| frontend_disconnect(backend, name) } | ||
end | ||
|
||
def response_complete | ||
# disconnect from backend after each response, since subsequent | ||
# requests from persistent HTTP connections might be proxied to a stale | ||
# backend | ||
EventMachine.next_tick do # still needs chance to send data | ||
connection.unbind_backend(@session) | ||
@session = nil | ||
@response_http_parser.reset | ||
http_parser.reset | ||
end | ||
end | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is sort of unrelated to this change isn't it? Do you think this should be a good idea in general or the path matching specifically made something worse, thereby highlighting a bug we haven't seen before? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is actually required since the previously, there was a one-to-one mapping between incoming domain and backend. Now, since a single incoming domain could have multiple backends depending on the path, we need to make sure that the backend is not persisted across the same request coming in from the same domain. |
||
|
||
def complete_message_received(full_message) | ||
connection.relay_to_servers(full_message) | ||
http_parser.reset | ||
end | ||
|
||
def url_received(url) | ||
@path = url | ||
end | ||
|
||
def headers_received(headers) | ||
if @session | ||
return | ||
end | ||
@session = UUID.generate() | ||
dns_check_response = select_backend_config(headers['Host']) | ||
dns_check_response = select_backend_config(headers['Host'], @path) | ||
if dns_check_response && dns_check_response.port | ||
connection.server(session, host: '0.0.0.0', port: dns_check_response.port) | ||
else | ||
|
@@ -88,6 +106,7 @@ def append_for_http_parsing(data) | |
end | ||
|
||
def backend_data(backend, data) | ||
@response_http_parser << data | ||
@backend_data = true | ||
data | ||
end | ||
|
@@ -107,11 +126,11 @@ def extract_host_from_domain(host) | |
|
||
private | ||
|
||
def select_backend_config(host) | ||
matching_string = extract_host_from_domain(host) | ||
def select_backend_config(domain, path) | ||
matching_string = extract_host_from_domain(domain) | ||
return nil unless matching_string | ||
if selected_app = matching_string[1] | ||
dns_check(process_name: selected_app) | ||
if host = matching_string[1] | ||
dns_check(host: host, path: path) | ||
else | ||
nil | ||
end | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment refers to still old proposed format isn't it? Lets update it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if you're referring to the example INI or the
dns_data
object, but the INI is referring to the new format withlocation
.The
dns_data
object is simply an internal object representation.