Skip to content

Instantly share code, notes, and snippets.

@dblock
Created December 11, 2012 15:14
Show Gist options
  • Save dblock/4259205 to your computer and use it in GitHub Desktop.
Save dblock/4259205 to your computer and use it in GitHub Desktop.
Reverse-proxy setup to beat the Heroku 60 second boot timeout.
$stdout.sync = true
require ::File.expand_path('../config/environment', __FILE__)
run MyApp::Application
#
# Setup a reverse proxy for faster Heroku boot and future load-balancing.
# http://noverloop.be/beating-herokus-60s-boot-times-with-the-cedar-stack-and-a-reverse-proxy
# http://jgwmaxwell.com/fighting-the-unicorns-becoming-a-thin-wizard-on-heroku
# https://github.com/jgwmaxwell/heroku-thin-cluster
#
require 'rubygems'
require 'bundler'
$stdout.sync = true
Bundler.require(:rack)
PORT = (ARGV.first || 3000).to_i
RACK_ENV = ENV['RACK_ENV'] || 'development'
ENV['PORT'] = PORT.to_s
ENV['RACK_ENV'] = RACK_ENV
require 'tmpdir'
socket = File.join(Dir.tmpdir, "thin.sock")
puts "Launching Thin on #{socket}"
pid = spawn("thin start -R app.ru --socket #{socket} -e #{RACK_ENV}")
sleep 5 # wait till thin binds to port, otherwise the proxy will die on first request
require File.expand_path('../lib/forward_proxy', __FILE__)
ForwardProxy::Server.run '0.0.0.0', PORT, pid, socket # start proxy
# inspired by https://github.com/jgwmaxwell/heroku-thin-cluster
# also see https://github.com/igrigorik/em-proxy/issues/30
require 'em-proxy'
require 'uri'
require 'socket'
module ForwardProxy
extend self
# Represents a "backend", ie. the endpoint for the proxy.
class Backend
attr_reader :socket
def initialize(options = {})
@socket = options[:socket]
end
end
# Callbacks for em-proxy events
module Callbacks
extend self
def on_connect
end
def on_finish
end
def on_data
lambda do |data|
data
end
end
def on_response
lambda do |backend, resp|
resp
end
end
end
# Wrapping the proxy server
module Server
RETRIES = 10
def run(host = '0.0.0.0', port = 3000, pid, socket)
@pid = pid
@host = host
@port = port
@pid = pid
@start = Time.now
@retries = 0
puts "Launching Proxy Server at #{host}:#{port} ..."
@backend = Backend.new({ socket: socket })
Proxy.start(host: host, port: port, debug: false) do |conn|
if @start
EM.next_tick do
Server.connect(conn)
end
else
Server.connect(conn)
end
conn.on_connect &Callbacks.on_connect
conn.on_data &Callbacks.on_data
conn.on_response &Callbacks.on_response
conn.on_finish &Callbacks.on_finish
end
end
def stop
puts "Terminating Proxy Server"
EventMachine.stop
puts "Terminating Web Server #{pid}"
Process.kill('QUIT', @pid)
end
module_function :run
module_function :stop
private
def connect(conn)
begin
if @start
$stdout.puts "Attempting to connect to #{@backend.socket}."
end
conn.server @backend, socket: @backend.socket
if @start
$stdout.puts "Proxy Server ready at #{@host}:#{@port} (#{(Time.now - @start).to_i}s)."
@start = nil
end
rescue RuntimeError => e
$stderr.puts "ERROR: #{e.message} after #{@retries} #{@retries == 1 ? 'retry' : 'retries'}"
@retries += 1
end
end
module_function :connect
end
end
@dblock
Copy link
Author

dblock commented Dec 11, 2012

You will also need em-proxy from https://github.com/dblock/em-proxy or with igrigorik/em-proxy#31 merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment