module Locd::Proxy
Stuff for running the proxy server, which does “vhost”-style routing of HTTP requests it receives to user-defined sites.
It does this by matching the HTTP `Host` header against site labels.
Built off [proxymachine][], which is itself built on [eventmachine][].
[proxymachine]: rubygems.org/gems/proxymachine [eventmachine]: rubygems.org/gems/eventmachine
Constants
- HOST_RE
{Regexp} to match HTTP “Host” header line.
@return [Regexp]
Public Class Methods
Find a port in {.port_range} that is not already used by a {Locd::Agent::Site} to give to a new site.
@return [Fixnum]
Port number.
@raise
If a port can not be found.
# File lib/locd/proxy.rb, line 196 def self.allocate_port allocated_ports = Locd::Agent::Site.ports port = port_range.find { |port| ! allocated_ports.include? port } if port.nil? raise "Could not allocate port for #{ remote_key }" end port end
Get the request host from HTTP header lines.
@param [Array<String>] lines @return [String]
# File lib/locd/proxy.rb, line 96 def self.extract_host lines lines. find { |line| line =~ HOST_RE }. chomp. split( ' ', 2 )[1] end
Get the request path from HTTP header lines.
@param [Array<String>] lines @return [String]
# File lib/locd/proxy.rb, line 109 def self.extract_path lines lines[0].split( ' ' )[1] end
Um, find and start a {Locd::Agent::Site} from a pattern.
@param pattern (see Locd::Agent.find_only!) @return [Locd::Agent::Site] @raise (see Locd::Agent.find_only!)
# File lib/locd/proxy.rb, line 214 def self.find_and_start_site pattern logger.debug "Finding and starting site...", pattern: pattern site = Locd::Agent::Site.find_only! pattern logger.debug "Found site!", site: site if site.running? logger.debug "Site is RUNNING" else logger.debug "Site STOPPED, starting..." site.start logger.debug "Site started." end site end
See if the lines include complete HTTP headers.
Looks for the `'rnrn'` string that separates the headers from the body.
@param [String] data
Data received so far from {ProxyMachine}.
@return [Boolean]
`true` if `data` contains complete headers.
# File lib/locd/proxy.rb, line 64 def self.headers_received? data data.include? "\r\n\r\n" end
Generate an HTTP text response string.
@param [String] status
The HTTP status header.
@param [String] text
Text response body.
@return [String]
Full HTTP response.
# File lib/locd/proxy.rb, line 80 def self.http_response_for status, text [ "HTTP/1.1 #{ status }", "Content-Type: text/plain; charset=utf-8", "Status: #{ status }", "", text ].join( "\r\n" ) end
Get the proxy's port from it's `.plist` if it exists, otherwise from the config setting.
@return [Fixnum]
Port number.
@raise [TypeError]
If we can't find a suitable config setting when looking for one.
# File lib/locd/proxy.rb, line 242 def self.port if proxy = Locd::Agent::Proxy.get proxy.port else Locd.config[:proxy, :port, type: t.pos_int] end end
Range of ports to allocate to {Locd::Agent::Site} when one is not provided by the user.
Start (inclusive) and end (exclusive) values come from `site.ports.start` and `site.ports.end` config values, which default to
55000...56000
@return [Range<Fixnum, Fixnum>]
# File lib/locd/proxy.rb, line 182 def self.port_range Locd.config[:site, :ports, :start]...Locd.config[:site, :ports, :end] end
Route request based on data, see {ProxyMachine} docs for details.
@todo
This finds the agent using the host as a pattern, so it will match with unique partial label. I think in the case that the host is not the full label it should probably return a HTTP redirect to the full label so that the user URL is bookmark-abel, etc...?
@param [String] data
Data received so far.
@return [Hash<Symbol, (Hash | Boolean)]
Command for ProxyMachine.
# File lib/locd/proxy.rb, line 128 def self.route data lines = data.lines logger.debug "Received data:\n#{ lines.pretty_inspect }" unless headers_received? data logger.debug "Have not yet received HTTP headers, waiting..." logger.debug lines: lines return {noop: true} end logger.debug "HTTP headers received, processing...\n#{ }" logger.debug lines: lines host = extract_host lines logger.debug host: host path = extract_path lines logger.debug path: path # Label is the domain without the port label = if host.include? ':' host.split( ':', 2 )[0] else host end site = find_and_start_site label remote_host = "#{ Locd.config[:site, :bind] }:#{ site.port }" pm_cmd = {remote: remote_host} logger.debug "Routing to remote", cmd: pm_cmd return pm_cmd rescue Locd::RequestError => error logger.error error error.to_proxy_machine_cmd rescue Exception => error logger.error error {close: http_response_for( '500 Server Error', error.message )} end
Run the proxy server.
@param [String] bind
Address to bind to.
@param [Fixnum] port
Port to listen on.
@return [void]
Not sure if/when this method ever returns.
# File lib/locd/proxy.rb, line 262 def self.serve bind: config[:proxy, :bind], port: config[:proxy, :port] logger.info "Loc'd is starting ProxyMachine, hang on a sec...", bind: bind, port: port require 'locd/proxymachine' ProxyMachine.set_router method( :route ) ProxyMachine.run 'locd', bind, port end