From 9e97517f9675005f92cac7a68f4e03e068998df8 Mon Sep 17 00:00:00 2001 From: Joey Aghion Date: Wed, 23 Jan 2013 20:31:50 -0500 Subject: [PATCH 1/8] Minor updates to README (released version of em-proxy specified in .gemspec is adequate) --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 173f626..76d9e6e 100644 --- a/README.md +++ b/README.md @@ -13,11 +13,10 @@ 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`. @@ -108,7 +107,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 From 93e4105261ce23797d55f38a32583cab81ca5ae4 Mon Sep 17 00:00:00 2001 From: Joey Aghion Date: Thu, 24 Jan 2013 01:17:31 -0500 Subject: [PATCH 2/8] fix typo in directory name --- spec/lib/heroku/{bakends => backends}/thin_spec.rb | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename spec/lib/heroku/{bakends => backends}/thin_spec.rb (100%) diff --git a/spec/lib/heroku/bakends/thin_spec.rb b/spec/lib/heroku/backends/thin_spec.rb similarity index 100% rename from spec/lib/heroku/bakends/thin_spec.rb rename to spec/lib/heroku/backends/thin_spec.rb From b39fdf384ef48ecdacc9f4fab960a2ac72963ae3 Mon Sep 17 00:00:00 2001 From: Joey Aghion Date: Thu, 24 Jan 2013 01:45:12 -0500 Subject: [PATCH 3/8] add alternative backend for unicorn --- Gemfile | 1 + heroku-forward.gemspec | 6 ++- lib/heroku/forward/backends.rb | 7 ++- lib/heroku/forward/backends/unicorn.rb | 56 +++++++++++++++++++++++ spec/lib/heroku/backends/thin_spec.rb | 1 + spec/lib/heroku/backends/unicorn_spec.rb | 38 ++++++++++++++++ spec/lib/heroku/proxy/server_spec.rb | 57 ++++++++++++++---------- spec/spec_helper.rb | 1 - 8 files changed, 141 insertions(+), 26 deletions(-) create mode 100644 lib/heroku/forward/backends/unicorn.rb create mode 100644 spec/lib/heroku/backends/unicorn_spec.rb diff --git a/Gemfile b/Gemfile index 6f74e0f..beb0033 100644 --- a/Gemfile +++ b/Gemfile @@ -11,6 +11,7 @@ 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" gem "mocha" end diff --git a/heroku-forward.gemspec b/heroku-forward.gemspec index 67af60a..03c30b2 100644 --- a/heroku-forward.gemspec +++ b/heroku-forward.gemspec @@ -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", @@ -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", @@ -49,6 +50,7 @@ Gem::Specification.new do |s| s.add_development_dependency(%q, ["~> 2.6"]) s.add_development_dependency(%q, ["~> 1.6"]) s.add_development_dependency(%q, ["~> 1.5"]) + s.add_development_dependency(%q, ["~> 4.5"]) s.add_development_dependency(%q, ["~> 1.0"]) s.add_development_dependency(%q, [">= 0"]) else @@ -61,6 +63,7 @@ Gem::Specification.new do |s| s.add_dependency(%q, ["~> 2.6"]) s.add_dependency(%q, ["~> 1.6"]) s.add_dependency(%q, ["~> 1.5"]) + s.add_dependency(%q, ["~> 4.5"]) s.add_dependency(%q, ["~> 1.0"]) s.add_dependency(%q, [">= 0"]) end @@ -74,6 +77,7 @@ Gem::Specification.new do |s| s.add_dependency(%q, ["~> 2.6"]) s.add_dependency(%q, ["~> 1.6"]) s.add_dependency(%q, ["~> 1.5"]) + s.add_dependency(%q, ["~> 4.5"]) s.add_dependency(%q, ["~> 1.0"]) s.add_dependency(%q, [">= 0"]) end diff --git a/lib/heroku/forward/backends.rb b/lib/heroku/forward/backends.rb index dd1c983..6905067 100644 --- a/lib/heroku/forward/backends.rb +++ b/lib/heroku/forward/backends.rb @@ -1 +1,6 @@ -require 'heroku/forward/backends/thin' +module Heroku + module Forward + module Backends + end + end +end diff --git a/lib/heroku/forward/backends/unicorn.rb b/lib/heroku/forward/backends/unicorn.rb new file mode 100644 index 0000000..6047199 --- /dev/null +++ b/lib/heroku/forward/backends/unicorn.rb @@ -0,0 +1,56 @@ +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] || new_socket + @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 + + def new_socket + require 'tmpdir' + Dir::Tmpname.create(['unicorn', '.sock']) do |path| + return path + end + 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 diff --git a/spec/lib/heroku/backends/thin_spec.rb b/spec/lib/heroku/backends/thin_spec.rb index 97da3a6..0a3a67f 100644 --- a/spec/lib/heroku/backends/thin_spec.rb +++ b/spec/lib/heroku/backends/thin_spec.rb @@ -1,4 +1,5 @@ require 'spec_helper' +require 'heroku/forward/backends/thin' describe Heroku::Forward::Backends::Thin do diff --git a/spec/lib/heroku/backends/unicorn_spec.rb b/spec/lib/heroku/backends/unicorn_spec.rb new file mode 100644 index 0000000..bdef13d --- /dev/null +++ b/spec/lib/heroku/backends/unicorn_spec.rb @@ -0,0 +1,38 @@ +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 + +end + diff --git a/spec/lib/heroku/proxy/server_spec.rb b/spec/lib/heroku/proxy/server_spec.rb index 2a7a03d..57835ef 100644 --- a/spec/lib/heroku/proxy/server_spec.rb +++ b/spec/lib/heroku/proxy/server_spec.rb @@ -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 diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index cf3b694..207d477 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -2,7 +2,6 @@ $LOAD_PATH.unshift(File.dirname(__FILE__)) require 'heroku-forward' -require 'em-proxy' require 'em-http' require 'logger' require 'mocha/api' From ef6ec1964cd6b5dc9de4db228cc042cb0ca0e2d5 Mon Sep 17 00:00:00 2001 From: Joey Aghion Date: Thu, 24 Jan 2013 01:46:56 -0500 Subject: [PATCH 4/8] document new unicorn backend in README --- README.md | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 76d9e6e..dc6425a 100644 --- a/README.md +++ b/README.md @@ -19,14 +19,14 @@ Add `heroku-forward` to your `Gemfile`. gem "heroku-forward", "~> 0.2" ``` -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' @@ -41,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 ------- @@ -87,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 --- @@ -118,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 --------- From d70fccfbb851ffbe76983d5bdf75a32c53f23750 Mon Sep 17 00:00:00 2001 From: Joey Aghion Date: Thu, 24 Jan 2013 08:30:40 -0500 Subject: [PATCH 5/8] add spec for unicorn arguments --- spec/lib/heroku/backends/unicorn_spec.rb | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/spec/lib/heroku/backends/unicorn_spec.rb b/spec/lib/heroku/backends/unicorn_spec.rb index bdef13d..de64f85 100644 --- a/spec/lib/heroku/backends/unicorn_spec.rb +++ b/spec/lib/heroku/backends/unicorn_spec.rb @@ -34,5 +34,25 @@ backend.terminate!.should be_true end + context "constructs command" do + + let(:application) { "spec/support/app.ru" } + let(:socket) { "foobar" } + + let(:backend) do + Heroku::Forward::Backends::Unicorn.new( + :application => application, + :socket => socket, + :env => 'test', + :config_file => 'config/unicorn.rb' + ) + end + + it "forwards arguments to spawner" do + backend.expects(:spawn).with("unicorn --env test --config-file config/unicorn.rb --listen foobar spec/support/app.ru").returns(0) + backend.spawn! + end + + end end From bccbdb5e89a4a90b8339050055501e7a1ac324b9 Mon Sep 17 00:00:00 2001 From: Joey Aghion Date: Thu, 24 Jan 2013 11:31:39 -0500 Subject: [PATCH 6/8] Fix ruby 1.8.7 support by borrowing from ruby 1.9's Dir::Tmpname.make_tmpname --- lib/heroku/forward/backends/unicorn.rb | 10 ++++------ spec/lib/heroku/backends/unicorn_spec.rb | 10 +++------- spec/support/unicorn.rb | 3 +++ 3 files changed, 10 insertions(+), 13 deletions(-) create mode 100644 spec/support/unicorn.rb diff --git a/lib/heroku/forward/backends/unicorn.rb b/lib/heroku/forward/backends/unicorn.rb index 6047199..f0ebb30 100644 --- a/lib/heroku/forward/backends/unicorn.rb +++ b/lib/heroku/forward/backends/unicorn.rb @@ -6,7 +6,7 @@ class Unicorn def initialize(options = {}) @application = options[:application] - @socket = options[:socket] || new_socket + @socket = options[:socket] || tmp_filename('unicorn', '.sock') @env = options[:env] || 'development' @config_file = options[:config_file] end @@ -38,11 +38,9 @@ def spawned? private - def new_socket - require 'tmpdir' - Dir::Tmpname.create(['unicorn', '.sock']) do |path| - return path - end + # 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! diff --git a/spec/lib/heroku/backends/unicorn_spec.rb b/spec/lib/heroku/backends/unicorn_spec.rb index de64f85..0063d18 100644 --- a/spec/lib/heroku/backends/unicorn_spec.rb +++ b/spec/lib/heroku/backends/unicorn_spec.rb @@ -36,20 +36,16 @@ context "constructs command" do - let(:application) { "spec/support/app.ru" } - let(:socket) { "foobar" } - let(:backend) do Heroku::Forward::Backends::Unicorn.new( - :application => application, - :socket => socket, + :application => 'spec/support/app.ru', :env => 'test', - :config_file => 'config/unicorn.rb' + :config_file => 'spec/support/unicorn.rb' ) end it "forwards arguments to spawner" do - backend.expects(:spawn).with("unicorn --env test --config-file config/unicorn.rb --listen foobar spec/support/app.ru").returns(0) + backend.expects(:spawn).with("unicorn --env test --config-file spec/support/unicorn.rb --listen foobar spec/support/app.ru").returns(0) backend.spawn! end diff --git a/spec/support/unicorn.rb b/spec/support/unicorn.rb new file mode 100644 index 0000000..3cff6f5 --- /dev/null +++ b/spec/support/unicorn.rb @@ -0,0 +1,3 @@ +worker_processes 2 +timeout 30 +preload_app true From 7824feb64400d0f6b818d406bc03687a712b548b Mon Sep 17 00:00:00 2001 From: Joey Aghion Date: Thu, 24 Jan 2013 12:31:52 -0500 Subject: [PATCH 7/8] fix specs that were expecting 'expects' --- spec/lib/heroku/backends/unicorn_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/lib/heroku/backends/unicorn_spec.rb b/spec/lib/heroku/backends/unicorn_spec.rb index 0063d18..038f87d 100644 --- a/spec/lib/heroku/backends/unicorn_spec.rb +++ b/spec/lib/heroku/backends/unicorn_spec.rb @@ -40,12 +40,13 @@ 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 - backend.expects(:spawn).with("unicorn --env test --config-file spec/support/unicorn.rb --listen foobar spec/support/app.ru").returns(0) + 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 From 6275e450f9110636e1130f76e94c0494e99513ff Mon Sep 17 00:00:00 2001 From: Joey Aghion Date: Thu, 24 Jan 2013 12:32:07 -0500 Subject: [PATCH 8/8] update CHANGELOG with new unicorn back-end --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ae7be6..d128752 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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)