module Slinky::ProxyServer

Constants

HOST_MATCHER
HTTP_MATCHER

Public Class Methods

find_matcher(proxies, path) click to toggle source
# File lib/slinky/proxy_server.rb, line 21
def self.find_matcher proxies, path
  proxies.find{|p| path.start_with?(p[0])}
end
process_proxies(proxy_hash) click to toggle source
# File lib/slinky/proxy_server.rb, line 6
def self.process_proxies proxy_hash
  proxy_hash.map{|from, h|
    begin
      to, opt = h.is_a?(Hash) ? [h.delete("to"), h] : [h, {}]
      a = [from, URI::parse(to), opt]
    rescue
      $stderr.puts "Invalid proxy setting: #{from} => #{to}".foreground(:red)
    end
  }.compact
end
process_proxy_servers(proxies) click to toggle source
# File lib/slinky/proxy_server.rb, line 17
def self.process_proxy_servers proxies
  proxies.map{|p| [p[1].host, p[1].port]}
end
replace_host(http, host) click to toggle source
# File lib/slinky/proxy_server.rb, line 42
def self.replace_host http, host
  http.gsub(HOST_MATCHER, "Host: #{host}")
end
replace_path(http, old_path, new_path, addition) click to toggle source
# File lib/slinky/proxy_server.rb, line 36
def self.replace_path http, old_path, new_path, addition
  # TODO: This may fail in certain, rare cases
  addition = addition[0..-2] if addition[-1] == "/"
  http.gsub(old_path, addition + new_path)
end
rewrite_path(path, proxy) click to toggle source
# File lib/slinky/proxy_server.rb, line 25
def self.rewrite_path path, proxy
  if proxy[0] == "/"
    # If we're proxying everything, we just want to pass the path
    # through unmodified. Otherwise we end up stripping the
    # initial slash, which is the wrong behavior.
    path
  else
    path.gsub(/^#{proxy[0]}/, "")
  end
end
run(proxy_hash, port, slinky_port) click to toggle source
# File lib/slinky/proxy_server.rb, line 46
def self.run proxy_hash, port, slinky_port
  proxies = process_proxies proxy_hash
  proxy_servers = process_proxy_servers proxies

  Proxy.start(:host => "0.0.0.0", :port => port){|conn|
    proxy = nil
    start_time = nil
    conn.server :slinky, :host => "127.0.0.1", :port => slinky_port
    server = nil
    
    conn.on_data do |data|
      begin
        matches = data.match(ProxyServer::HTTP_MATCHER)
        if matches
          path = matches[2]
          proxy = ProxyServer.find_matcher(proxies, path)
          start_time = Time.now
          server = if proxy
                     new_path = ProxyServer.rewrite_path path, proxy
                     data = ProxyServer.replace_path(data, path, new_path, proxy[1].path)
                     new_host = proxy[1].select(:host, :port).join(":")
                     data = ProxyServer.replace_host(data, new_host)
                     conn.server [proxy[1].host, proxy[1].port],
                                 :host => proxy[1].host, :port => proxy[1].port
                     [proxy[1].host, proxy[1].port]
                   else :slinky
                   end
        end
        [data, [server]]
      rescue
        conn.send_data "HTTP/1.1 500 Ooops...something went wrong\r\n"
      end
    end

    conn.on_response do |server, resp|
      opt = proxy && proxy[2]
      if opt && opt["lag"]
        # we want to get as close as possible to opt["lag"], so we
        # take into account the lag from the backend server
        so_far = Time.now - start_time
        time = opt["lag"]/1000.0-so_far
        EM.add_timer(time > 0 ? time : 0) do
          conn.send_data resp
        end
      else
        resp
      end
    end

    conn.on_finish do |name|
      unbind
    end
  }
end