Module: Slinky::ProxyServer

Defined in:
lib/slinky/proxy_server.rb

Constant Summary collapse

HTTP_MATCHER =
/(GET|POST|PUT|DELETE|HEAD) (.+?)(?= HTTP)/
HOST_MATCHER =
/Host: (\S+)/

Class Method Summary collapse

Class Method Details

.find_matcher(proxies, path) ⇒ Object



21
22
23
# 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) ⇒ Object



6
7
8
9
10
11
12
13
14
15
# 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) ⇒ Object



17
18
19
# 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) ⇒ Object



42
43
44
# 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) ⇒ Object



36
37
38
39
40
# 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) ⇒ Object



25
26
27
28
29
30
31
32
33
34
# 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) ⇒ Object



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# 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