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

allocate_port() click to toggle source

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
extract_host(lines) click to toggle source

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
extract_path(lines) click to toggle source

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
find_and_start_site(pattern) click to toggle source

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
headers_received?(data) click to toggle source

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
http_response_for(status, text) click to toggle source

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
port() click to toggle source

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
port_range() click to toggle source

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(data) click to toggle source

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
serve(bind: config[:proxy, :bind], port: config[:proxy, :port]) click to toggle source

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