Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an alternative back-end for Unicorn #5

Merged
merged 9 commits into from
Jan 24, 2013
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
Next Release
============


* [#5](https://github.com/dblock/heroku-forward/pull/5) - Add an alternative back-end for Unicorn. - [@joeyAghion](https://github.com/joeyAghion)
* Fix: `--ssl-key-file` option causes a `thin/controllers/controller.rb:37:in 'start': wrong number of arguments (2 for 0)` error - [@dblock](https://github.com/dblock).

0.2.0 (01/15/2013)
Expand Down
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ group :development, :test do
gem "rspec", "~> 2.6"
gem "jeweler", "~> 1.6"
gem "thin", "~> 1.5"
gem "unicorn", "~> 4.5"
gem "em-http-request", "~> 1.0"
end
38 changes: 30 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,20 @@ This gem implements a proxy using [em-proxy](https://github.com/igrigorik/em-pro
Usage
-----

Add `heroku-forward` and `em-proxy` to your `Gemfile`. Curently requires HEAD of `em-proxy` because of [this pull request](https://github.com/igrigorik/em-proxy/pull/31).
Add `heroku-forward` to your `Gemfile`.

``` ruby
gem "heroku-forward", "~> 0.2"
gem "em-proxy", ">= 0.1.8"
```

Create an application rackup file, eg. `my_app.ru` that boots your application. Under Rails, this is the file that calls `run`.
Create a new application rackup file, eg. `my_app.ru` that boots your application. Under Rails, this is the file that calls `run`.

``` ruby
require ::File.expand_path('../config/environment', __FILE__)
run MyApp::Application
```

Modify your rackup file as follows. Under Rails this file is called `config.ru`.
Modify your default rackup file as follows. Under Rails this file is called `config.ru`.

``` ruby
require 'rubygems'
Expand All @@ -42,15 +41,16 @@ env = ENV['RACK_ENV'] || 'development'
require 'em-proxy'
require 'logger'
require 'heroku-forward'
require 'heroku/forward/backends/thin'

application = File.expand_path('../my_app.ru', __FILE__)
backend = Heroku::Forward::Backends::Thin.new(application: application, env: env)
proxy = Heroku::Forward::Proxy::Server.new(backend, { host: '0.0.0.0', port: port })
proxy = Heroku::Forward::Proxy::Server.new(backend, host: '0.0.0.0', port: port)
proxy.logger = Logger.new(STDOUT)
proxy.forward!
```

This sets up a proxy on the port requested by Heroku and runs your application with Thin.
This sets up a proxy on the port requested by Heroku and runs your application with [Thin](http://code.macournoyer.com/thin).

Foreman
-------
Expand Down Expand Up @@ -88,7 +88,7 @@ Proxy Forwarding Options

`Heroku::Forward::Proxy::Server.forward!` accepts the following options:

* `delay`: number of seconds to sleep before launching the proxy, eg. `proxy.forward!(delay: 15)`. This prevents queuing of requests or reporting invalid `up` status to Heroku. It's recommended to set this value to as close as possible to the boot time of your application and less than the Heroku's 60s boot limit.
* **delay**: number of seconds to sleep before launching the proxy, eg. `proxy.forward!(delay: 15)`. This prevents queuing of requests or reporting invalid `up` status to Heroku. It's recommended to set this value to as close as possible to the boot time of your application and less than the Heroku's 60s boot limit.

SSL
---
Expand All @@ -108,7 +108,7 @@ In order to forward SSL arguments to thin update your config.ru
```ruby
options = { application: File.expand_path('../my_app.ru', __FILE__) }

# branch to desable SSL depending of your environment
# branch to disable SSL depending on your environment
if ENV['THIN_SSL_ENABLED']
options[:ssl] = true
options[:ssl_verify] = true
Expand All @@ -119,6 +119,28 @@ end
backend = Heroku::Forward::Backends::Thin.new(options)
```

Alternative Backends
--------------------

Backend implementations for both [Thin](http://code.macournoyer.com/thin) and [Unicorn](http://unicorn.bogomips.org/) are provided. Your rackup file must `require` the chosen backend in addition to `heroku-forward` and invoke as follows:

```ruby

# ...

require 'heroku-forward'
require 'heroku/forward/backends/unicorn'

application = File.expand_path('../my_app.ru', __FILE__)
config_file = File.expand_path('../config/unicorn.rb', __FILE__)
backend = Heroku::Forward::Backends::Unicorn.new(application: application, env: env, config_file: config_file)
proxy = Heroku::Forward::Proxy::Server.new(backend, host: '0.0.0.0', port: port)
proxy.logger = Logger.new(STDOUT)
proxy.forward!
```



Fail-Safe
---------

Expand Down
6 changes: 5 additions & 1 deletion heroku-forward.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Gem::Specification.new do |s|

s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Daniel Doubrovkine"]
s.date = "2013-01-15"
s.date = "2013-01-24"
s.email = "dblock@dblock.org"
s.extra_rdoc_files = [
"LICENSE.md",
Expand All @@ -20,6 +20,7 @@ Gem::Specification.new do |s|
"lib/heroku/forward.rb",
"lib/heroku/forward/backends.rb",
"lib/heroku/forward/backends/thin.rb",
"lib/heroku/forward/backends/unicorn.rb",
"lib/heroku/forward/config/locales/en.yml",
"lib/heroku/forward/errors.rb",
"lib/heroku/forward/errors/backend_failed_to_start_error.rb",
Expand Down Expand Up @@ -49,6 +50,7 @@ Gem::Specification.new do |s|
s.add_development_dependency(%q<rspec>, ["~> 2.6"])
s.add_development_dependency(%q<jeweler>, ["~> 1.6"])
s.add_development_dependency(%q<thin>, ["~> 1.5"])
s.add_development_dependency(%q<unicorn>, ["~> 4.5"])
s.add_development_dependency(%q<em-http-request>, ["~> 1.0"])
s.add_development_dependency(%q<mocha>, [">= 0"])
else
Expand All @@ -61,6 +63,7 @@ Gem::Specification.new do |s|
s.add_dependency(%q<rspec>, ["~> 2.6"])
s.add_dependency(%q<jeweler>, ["~> 1.6"])
s.add_dependency(%q<thin>, ["~> 1.5"])
s.add_dependency(%q<unicorn>, ["~> 4.5"])
s.add_dependency(%q<em-http-request>, ["~> 1.0"])
s.add_dependency(%q<mocha>, [">= 0"])
end
Expand All @@ -74,6 +77,7 @@ Gem::Specification.new do |s|
s.add_dependency(%q<rspec>, ["~> 2.6"])
s.add_dependency(%q<jeweler>, ["~> 1.6"])
s.add_dependency(%q<thin>, ["~> 1.5"])
s.add_dependency(%q<unicorn>, ["~> 4.5"])
s.add_dependency(%q<em-http-request>, ["~> 1.0"])
s.add_dependency(%q<mocha>, [">= 0"])
end
Expand Down
7 changes: 6 additions & 1 deletion lib/heroku/forward/backends.rb
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
require 'heroku/forward/backends/thin'
module Heroku
module Forward
module Backends
end
end
end
54 changes: 54 additions & 0 deletions lib/heroku/forward/backends/unicorn.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
module Heroku
module Forward
module Backends
class Unicorn
attr_accessor :application, :socket, :environment, :pid, :config_file

def initialize(options = {})
@application = options[:application]
@socket = options[:socket] || tmp_filename('unicorn', '.sock')
@env = options[:env] || 'development'
@config_file = options[:config_file]
end

def spawn!
return false if spawned?
check!

args = ['unicorn']
args.push '--env', @env
args.push '--config-file', @config_file if @config_file
args.push '--listen', @socket
args.push @application

@pid = Spoon.spawnp(*args)
@spawned = true
end

def terminate!
return false unless spawned?
Process.kill 'QUIT', @pid
@spawned = false
true
end

def spawned?
!!@spawned
end

private

# Borrowed from ruby 1.9's Dir::Tmpname.make_tmpname for 1.8.7-compatibility.
def tmp_filename(prefix, suffix)
File.join Dir.tmpdir, "#{prefix}#{Time.now.strftime("%Y%m%d")}-#{$$}-#{rand(0x100000000).to_s(36)}#{suffix}"
end

def check!
raise Heroku::Forward::Errors::MissingBackendOptionError.new('application') unless @application && @application.length > 0
raise Heroku::Forward::Errors::MissingBackendApplicationError.new(@application) unless File.exists?(@application)
end

end
end
end
end
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require 'spec_helper'
require 'heroku/forward/backends/thin'

describe Heroku::Forward::Backends::Thin do

Expand Down
55 changes: 55 additions & 0 deletions spec/lib/heroku/backends/unicorn_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
require 'spec_helper'
require 'heroku/forward/backends/unicorn'

describe Heroku::Forward::Backends::Unicorn do

let(:backend) do
Heroku::Forward::Backends::Unicorn.new
end

it "#spawned?" do
backend.spawned?.should be_false
end

context "checks" do
it "checks for application" do
expect {
backend.spawn!
}.to raise_error Heroku::Forward::Errors::MissingBackendOptionError
end

it "checks that the application file exists" do
expect {
backend.application = 'spec/foobar'
backend.spawn!
}.to raise_error Heroku::Forward::Errors::MissingBackendApplicationError
end

end

it "#spawn!" do
backend.application = "spec/support/app.ru"
backend.spawn!.should_not == 0
sleep 2
backend.terminate!.should be_true
end

context "constructs command" do

let(:backend) do
Heroku::Forward::Backends::Unicorn.new(
:application => 'spec/support/app.ru',
:env => 'test',
:socket => '/tmp/unicorn.sock',
:config_file => 'spec/support/unicorn.rb'
)
end

it "forwards arguments to spawner" do
Spoon.should_receive(:spawnp).with(*%w{unicorn --env test --config-file spec/support/unicorn.rb --listen /tmp/unicorn.sock spec/support/app.ru}).and_return(0)
backend.spawn!
end

end
end

57 changes: 34 additions & 23 deletions spec/lib/heroku/proxy/server_spec.rb
Original file line number Diff line number Diff line change
@@ -1,38 +1,49 @@
require 'spec_helper'
require 'heroku/forward/backends/thin'
require 'heroku/forward/backends/unicorn'

describe Heroku::Forward::Proxy::Server do

let(:backend) do
Heroku::Forward::Backends::Thin.new({ :application => "spec/support/app.ru" })
end
[
Heroku::Forward::Backends::Thin,
Heroku::Forward::Backends::Unicorn
].each do |backend_type|

context "with #{backend_type.name} backend" do

let(:backend) do
backend_type.new(:application => 'spec/support/app.ru')
end

let(:server) do
Heroku::Forward::Proxy::Server.new(backend, { :host => '127.0.0.1', :port => 4242 })
end
let(:server) do
Heroku::Forward::Proxy::Server.new(backend, { :host => '127.0.0.1', :port => 4242 })
end

context "spawned backend" do
context "spawned backend" do

before :each do
server.logger = Logger.new(STDOUT)
end
before :each do
server.logger = Logger.new(STDOUT)
end

after :each do
backend.terminate!
end
after :each do
backend.terminate!
end

it "proxy!" do
EM::Server.run(server) do
server.forward!
end
end
it "proxy!" do
EM::Server.run(server) do
server.forward!
end
end

it "waits for delay seconds" do
EM::Server.run(server) do
server.should_receive(:sleep).with(2)
server.forward!(:delay => 2)
it "waits for delay seconds" do
EM::Server.run(server) do
server.should_receive(:sleep).with(2)
server.forward!(:delay => 2)
end
end
end

end

end

end
Expand Down
1 change: 0 additions & 1 deletion spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
$LOAD_PATH.unshift(File.dirname(__FILE__))

require 'heroku-forward'
require 'em-proxy'
require 'em-http'
require 'logger'

Expand Down
3 changes: 3 additions & 0 deletions spec/support/unicorn.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
worker_processes 2
timeout 30
preload_app true