Class: ProxyRole

Inherits:
Role
  • Object
show all
Defined in:
lib/proxy_role.rb

Constant Summary collapse

PORT =
9400
ESI_MATCH =
/<esi:try>.*?<esi:attempt>.*?<esi:include src="([^"]+)" ?\/>.*?<\/esi:attempt>.*?<esi:except>(.*?)<\/esi:except>.*?<\/esi:try>/m

Constants inherited from Role

Role::INDEX

Instance Method Summary collapse

Methods inherited from Role

#disable_link, #enable_link, get_role, #init_links, role, #state

Constructor Details

#initialize(manifest) ⇒ ProxyRole

Returns a new instance of ProxyRole.



9
10
11
12
13
# File 'lib/proxy_role.rb', line 9

def initialize(manifest)
  super
  init_links 'backends', @manifest['backends']
  init_links 'stats_backends', @manifest['stats_backends']
end

Instance Method Details

#choose_backend(request) ⇒ Object



15
16
17
18
19
20
21
22
23
24
25
# File 'lib/proxy_role.rb', line 15

def choose_backend(request)
  url = request.match(/^[A-Z]+ (.*?) HTTP/)[1]
  case url
  when /^\/stats/
    addr = @active_links['stats_backends'].sort_by { rand }.pop
    [addr, StatsRole::PORT] if addr
  else
    addr = @active_links['backends'].sort_by { rand }.pop
    [addr, RailsRole::PORT] if addr
  end
end

#handle_esi(body) ⇒ Object



29
30
31
32
33
34
35
36
37
# File 'lib/proxy_role.rb', line 29

def handle_esi(body)
  body.gsub(ESI_MATCH) do |match|
    url = $1
    otherwise = $2

    html = simulate_request($1) || ''
    html.strip.size > 0 ? html : otherwise
  end
end

#simulate_request(url) ⇒ Object



39
40
41
42
# File 'lib/proxy_role.rb', line 39

def simulate_request(url)
  puts "Simulating request to #{url}"
  Net::HTTP.get(URI.parse("http://127.0.0.1:#{PORT}#{url}"))
end

#startObject Also known as: enable



44
45
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
# File 'lib/proxy_role.rb', line 44

def start
  @server_thread = Thread.new {
    @proxy_server = TCPServer.new('0.0.0.0', PORT)
    loop do
      Thread.new(@proxy_server.accept) { |client_sock|
        begin
          request = client_sock.recv(16384)

          backend_addr = choose_backend(request)
          if backend_addr
            backend_sock = TCPSocket.new(*backend_addr)

            backend_sock.send(request, 0)

            first_blob = backend_sock.recv(16384)
            headers, body = first_blob.split("\r\n\r\n")
            body ||= ''

            match = headers.match(/Content-Length: (\d+)/)
            content_length = match ? match[1].to_i : 0

            if content_length > 0
              if content_length > body.size
                body << backend_sock.recv(content_length - body.size)
              end

              processed_body = handle_esi(body)

              real_length = processed_body.size
              processed_headers = headers.gsub(/Content-Length: (\d+)/, "Content-Length: #{real_length}")

              client_sock.send(processed_headers, 0)
              client_sock.send("\r\n\r\n", 0)
              client_sock.send(processed_body, 0)
            else
              client_sock.send(first_blob,0)
            end
          else
            # no available backend ; send a 503
            client_sock.send "HTTP/1.1 503 Service Unavailable\r\n\r\n", 0
          end
        rescue Errno::ECONNREFUSED, Errno::ECONNABORTED
          # no available backend ; send a 503
          client_sock.send "HTTP/1.1 503 Service Unavailable\r\n\r\n", 0
        ensure
          backend_sock.close if backend_sock
          client_sock.close if client_sock
        end
      }
    end
  }

  #health_check
end

#stopObject Also known as: disable



99
100
101
102
103
104
105
106
# File 'lib/proxy_role.rb', line 99

def stop
  begin
    @proxy_server.shutdown if @proxy_server
  rescue Errno::ENOTCONN
  end
  @server_thread.kill if @server_thread
  @health_thread.kill if @health_thread
end