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:

  • (Regexp)
/^Host\:\ /i
ROTATE_LOGS_LABEL =

Label for the Loc'd proxy agent itself

Returns:

  • (String)
'com.nrser.locd.rotate-logs'

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:

  • (Fixnum)

    Port number.

Raises:

  • If a port can not be found.



190
191
192
193
194
195
196
197
198
199
200
# File 'lib/locd/proxy.rb', line 190

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:

  • lines (Array<String>)

Returns:

  • (String)


90
91
92
93
94
95
# File 'lib/locd/proxy.rb', line 90

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:

  • lines (Array<String>)

Returns:

  • (String)


103
104
105
# File 'lib/locd/proxy.rb', line 103

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 (String | Pattern)

    Pattern to match against agent.

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

Returns:



208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/locd/proxy.rb', line 208

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:

  • (Boolean)

    true if data contains complete headers.



58
59
60
# File 'lib/locd/proxy.rb', line 58

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:

  • status (String)

    The HTTP status header.

  • text (String)

    Text response body.

Returns:

  • (String)

    Full HTTP response.



74
75
76
77
78
79
80
81
82
# File 'lib/locd/proxy.rb', line 74

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:

  • (Fixnum)

    Port number.

Raises:

  • (TypeError)

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



236
237
238
239
240
241
242
# File 'lib/locd/proxy.rb', line 236

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:

  • (Range<Fixnum, Fixnum>)


176
177
178
# File 'lib/locd/proxy.rb', line 176

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 (String)

    Data received so far.

Returns:

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

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



122
123
124
125
126
127
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
# File 'lib/locd/proxy.rb', line 122

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:

  • bind (String) (defaults to: )

    Address to bind to.

  • port (Fixnum) (defaults to: )

    Port to listen on.



256
257
258
259
260
261
262
263
264
265
# File 'lib/locd/proxy.rb', line 256

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