Module: Locd::Proxy

Includes:
NRSER::Log::Mixin
Defined in:
lib/locd/proxy.rb

Overview

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.

Constant Summary collapse

HOST_RE =

Regexp to match HTTP "Host" header line.

Returns:

/^Host\:\ /i

Class Method Summary collapse

Class Method Details

.allocate_portFixnum

Find a port in port_range that is not already used by a Agent::Site to give to a new site.

Returns:

  • Port number.

Raises:

  • If a port can not be found.



196
197
198
199
200
201
202
203
204
205
206
# 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) ⇒ String

Get the request host from HTTP header lines.

Parameters:

Returns:



96
97
98
99
100
101
# 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) ⇒ String

Get the request path from HTTP header lines.

Parameters:

Returns:



109
110
111
# File 'lib/locd/proxy.rb', line 109

def self.extract_path lines
  lines[0].split( ' ' )[1]
end

.find_and_start_site(pattern) ⇒ Locd::Agent::Site

Um, find and start a Agent::Site from a pattern.

Parameters:

  • Pattern to match against agent.

    When it's a String, passed to Locd::Pattern.from to get the pattern.

Returns:



214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# 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) ⇒ Boolean

See if the lines include complete HTTP headers.

Looks for the '\r\n\r\n' string that separates the headers from the body.

Parameters:

Returns:

  • true if data contains complete headers.



64
65
66
# File 'lib/locd/proxy.rb', line 64

def self.headers_received? data
  data.include? "\r\n\r\n"
end

.http_response_for(status, text) ⇒ String

Generate an HTTP text response string.

Parameters:

  • The HTTP status header.

  • Text response body.

Returns:

  • Full HTTP response.



80
81
82
83
84
85
86
87
88
# 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

.portFixnum

Get the proxy's port from it's .plist if it exists, otherwise from the config setting.

Returns:

  • Port number.

Raises:

  • If we can't find a suitable config setting when looking for one.



242
243
244
245
246
247
248
# 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_rangeRange<Fixnum, Fixnum>

Range of ports to allocate to 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

Returns:



182
183
184
# File 'lib/locd/proxy.rb', line 182

def self.port_range
  Locd.config[:site, :ports, :start]...Locd.config[:site, :ports, :end]
end

.route(data) ⇒ Hash<Symbol, (Hash | Boolean)] Command for ProxyMachine.

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...?

Route request based on data, see ProxyMachine docs for details.

Parameters:

  • Data received so far.

Returns:

  • Hash<Symbol, (Hash | Boolean)] Command for ProxyMachine.



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# 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: , port: ) ⇒ void

This method returns an undefined value.

Run the proxy server.

Parameters:

  • (defaults to: )

    Address to bind to.

  • (defaults to: )

    Port to listen on.



262
263
264
265
266
267
268
269
270
271
# 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