heroku-forward 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE.md +22 -0
- data/README.md +117 -0
- data/lib/heroku-forward.rb +10 -0
- data/lib/heroku/forward.rb +4 -0
- data/lib/heroku/forward/backends.rb +1 -0
- data/lib/heroku/forward/backends/thin.rb +56 -0
- data/lib/heroku/forward/config/locales/en.yml +17 -0
- data/lib/heroku/forward/errors.rb +4 -0
- data/lib/heroku/forward/errors/backend_failed_to_start_error.rb +11 -0
- data/lib/heroku/forward/errors/heroku_forward_error.rb +88 -0
- data/lib/heroku/forward/errors/missing_backend_application_error.rb +13 -0
- data/lib/heroku/forward/errors/missing_backend_option_error.rb +13 -0
- data/lib/heroku/forward/proxy.rb +1 -0
- data/lib/heroku/forward/proxy/server.rb +96 -0
- data/lib/heroku/forward/version.rb +5 -0
- metadata +209 -0
data/LICENSE.md
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2012 Daniel Doubrovkine, Art.sy Inc.
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
Heroku::Forward [![Build Status](https://travis-ci.org/dblock/heroku-forward.png?branch=master)](https://travis-ci.org/dblock/heroku-forward)
|
2
|
+
===============
|
3
|
+
|
4
|
+
Beat Heroku's 60 seconds timeout with a forward proxy.
|
5
|
+
|
6
|
+
What's this?
|
7
|
+
------------
|
8
|
+
|
9
|
+
[Heroku](http://www.heroku.com/) will report an application crashing and yield an `R10 Boot Timeout` error when a web process took longer than 60 seconds to bind to its assigned `$PORT`. This error is often caused by a process being unable to reach an external resource, such as a database or because Heroku is pretty slow and you have a lot of gems in your `Gemfile`.
|
10
|
+
|
11
|
+
This gem implements a forward proxy using [em-proxy](https://github.com/igrigorik/em-proxy). This proxy is booted almost immediately, binding to the port assigned by Heroku. Heroku now reports the dyno up. The proxy then spawns your application's web server and establishes a connection over a unix domain socket (a file) between the proxy and the application. Once the application is ready, it will be able to serve HTTP requests normally. Until then requests may queue and some may timeout depending on how long it actually takes to start.
|
12
|
+
|
13
|
+
Usage
|
14
|
+
-----
|
15
|
+
|
16
|
+
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).
|
17
|
+
|
18
|
+
``` ruby
|
19
|
+
gem "heroku-forward", :git => "https://github.com/dblock/heroku-forward.git"
|
20
|
+
gem "em-proxy", :git => "https://github.com/igrigorik/em-proxy.git"
|
21
|
+
```
|
22
|
+
|
23
|
+
Create an application rackup file, eg. `my_app.ru` that boots your application. Under Rails, this is the file that calls `run`.
|
24
|
+
|
25
|
+
``` ruby
|
26
|
+
require ::File.expand_path('../config/environment', __FILE__)
|
27
|
+
run MyApp::Application
|
28
|
+
```
|
29
|
+
|
30
|
+
Modify your rackup file as follows. Under Rails this file is called `config.ru`.
|
31
|
+
|
32
|
+
``` ruby
|
33
|
+
require 'rubygems'
|
34
|
+
require 'bundler'
|
35
|
+
|
36
|
+
$stdout.sync = true
|
37
|
+
Bundler.require(:rack)
|
38
|
+
|
39
|
+
port = (ARGV.first || ENV['PORT'] || 3000).to_i
|
40
|
+
env = ENV['RACK_ENV'] || 'development'
|
41
|
+
|
42
|
+
require 'em-proxy'
|
43
|
+
require 'logger'
|
44
|
+
require 'heroku-forward'
|
45
|
+
|
46
|
+
application = File.expand_path('../my_app.ru', __FILE__)
|
47
|
+
backend = Heroku::Forward::Backends::Thin.new(application: application, env: env)
|
48
|
+
proxy = Heroku::Forward::Proxy::Server.new(backend, { host: '0.0.0.0', port: port })
|
49
|
+
proxy.logger = Logger.new(STDOUT)
|
50
|
+
proxy.forward!
|
51
|
+
```
|
52
|
+
|
53
|
+
This sets up a proxy on the port requested by Heroku and runs your application with Thin.
|
54
|
+
|
55
|
+
Foreman
|
56
|
+
-------
|
57
|
+
|
58
|
+
Heroku Cedar expects a `Procfile` that defines your application processes.
|
59
|
+
|
60
|
+
```
|
61
|
+
web: bundle exec ruby config.ru
|
62
|
+
worker: bundle exec rake jobs:work
|
63
|
+
```
|
64
|
+
|
65
|
+
You can use `foreman` to test the proxy locally with `foreman start web`.
|
66
|
+
|
67
|
+
Heroku Log
|
68
|
+
----------
|
69
|
+
|
70
|
+
Here's the log output from an application that uses this gem. Notice that Heroku reports the state of `web.1` up after just 4 seconds, while the application takes 67 seconds to boot.
|
71
|
+
|
72
|
+
```
|
73
|
+
2012-12-11T23:33:42+00:00 heroku[web.1]: Starting process with command `bundle exec ruby config.ru`
|
74
|
+
2012-12-11T23:33:46+00:00 app[web.1]: INFO -- : Launching Backend ...
|
75
|
+
2012-12-11T23:33:46+00:00 app[web.1]: INFO -- : Launching Proxy Server at 0.0.0.0:42017 ...
|
76
|
+
2012-12-11T23:33:46+00:00 app[web.1]: DEBUG -- : Attempting to connect to /tmp/thin20121211-2-1bfazzx.
|
77
|
+
2012-12-11T23:33:46+00:00 app[web.1]: WARN -- : no connection, 10 retries left.
|
78
|
+
2012-12-11T23:33:46+00:00 heroku[web.1]: State changed from starting to up
|
79
|
+
2012-12-11T23:34:32+00:00 app[web.1]: >> Thin web server (v1.5.0 codename Knife)
|
80
|
+
2012-12-11T23:34:32+00:00 app[web.1]: >> Maximum connections set to 1024
|
81
|
+
2012-12-11T23:34:32+00:00 app[web.1]: >> Listening on /tmp/thin20121211-2-1bfazzx, CTRL+C to stop
|
82
|
+
2012-12-11T23:34:53+00:00 app[web.1]: DEBUG -- : Attempting to connect to /tmp/thin20121211-2-1bfazzx.
|
83
|
+
2012-12-11T23:34:53+00:00 app[web.1]: DEBUG -- : Proxy Server ready at 0.0.0.0:42017 (67s).
|
84
|
+
```
|
85
|
+
|
86
|
+
Proxy Forwarding Options
|
87
|
+
------------------------
|
88
|
+
|
89
|
+
`Heroku::Forward::Proxy::Server.forward!` accepts the following options:
|
90
|
+
|
91
|
+
* `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.
|
92
|
+
|
93
|
+
Fail-Safe
|
94
|
+
---------
|
95
|
+
|
96
|
+
If you're worried about this implementation, consider building a fail-safe. Modify your `config.ru` to run without a proxy if `DISABLE_FORWARD_PROXY` is set as demonstrated in [this gist](https://gist.github.com/4263488). Add `DISABLE_FORWARD_PROXY` via `heroku config:add DISABLE_FORWARD_PROXY=1`.
|
97
|
+
|
98
|
+
Reading Materials
|
99
|
+
-----------------
|
100
|
+
|
101
|
+
* [Heroku R10 Boot Timeout](https://devcenter.heroku.com/articles/error-codes#r10-boot-timeout)
|
102
|
+
* [Beating Heroku's 60s Boot Times with the Cedar Stack and a Reverse Proxy](http://noverloop.be/beating-herokus-60s-boot-times-with-the-cedar-stack-and-a-reverse-proxy/) by Nicolas Overloop
|
103
|
+
* [Fighting the Unicorns: Becoming a Thin Wizard on Heroku](http://jgwmaxwell.com/fighting-the-unicorns-becoming-a-thin-wizard-on-heroku/) by JGW Maxwell
|
104
|
+
* [eventmachine](https://github.com/eventmachine/eventmachine)
|
105
|
+
* [em-proxy](https://github.com/igrigorik/em-proxy)
|
106
|
+
|
107
|
+
Contributing
|
108
|
+
------------
|
109
|
+
|
110
|
+
Fork the project. Make your feature addition or bug fix with tests. Send a pull request. Bonus points for topic branches.
|
111
|
+
|
112
|
+
Copyright and License
|
113
|
+
---------------------
|
114
|
+
|
115
|
+
MIT License, see [LICENSE](http://github.com/dblock/heroku-forward/raw/master/LICENSE.md) for details.
|
116
|
+
|
117
|
+
(c) 2012 [Daniel Doubrovkine](http://github.com/dblock), [Art.sy](http://artsy.github.com)
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'heroku/forward/backends/thin'
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Heroku
|
2
|
+
module Forward
|
3
|
+
module Backends
|
4
|
+
class Thin
|
5
|
+
include POSIX::Spawn
|
6
|
+
|
7
|
+
attr_accessor :application
|
8
|
+
attr_accessor :socket
|
9
|
+
attr_accessor :environment
|
10
|
+
attr_accessor :pid
|
11
|
+
|
12
|
+
# options:
|
13
|
+
# application: passed with -R, eg. app.ru
|
14
|
+
# socket: passed with --socket, eg. /tmp/thin.sock
|
15
|
+
# env: passed with -e, defaults to 'development'
|
16
|
+
def initialize(options = {})
|
17
|
+
@application = options[:application]
|
18
|
+
@socket = options[:socket] || new_socket
|
19
|
+
@env = options[:env] || :development
|
20
|
+
end
|
21
|
+
|
22
|
+
def spawn!
|
23
|
+
return false if spawned?
|
24
|
+
check!
|
25
|
+
@pid = spawn("thin start -R #{@application} --socket #{@socket} -e #{@env}")
|
26
|
+
@spawned = true
|
27
|
+
end
|
28
|
+
|
29
|
+
def terminate!
|
30
|
+
return false unless spawned?
|
31
|
+
Process.kill 'QUIT', @pid
|
32
|
+
@spawned = false
|
33
|
+
true
|
34
|
+
end
|
35
|
+
|
36
|
+
def spawned?
|
37
|
+
!! @spawned
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def new_socket
|
43
|
+
Tempfile.open 'thin' do |file|
|
44
|
+
return file.path
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def check!
|
49
|
+
raise Heroku::Forward::Errors::MissingBackendOptionError.new('application') unless @application && @application.length > 0
|
50
|
+
raise Heroku::Forward::Errors::MissingBackendApplicationError.new(@application) unless File.exists?(@application)
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
en:
|
2
|
+
heroku:
|
3
|
+
forward:
|
4
|
+
errors:
|
5
|
+
messages:
|
6
|
+
missing_backend_option:
|
7
|
+
message: "Missing backend option: %{name}."
|
8
|
+
summary: "A required option was not provided to the back-end."
|
9
|
+
resolution: "Review the documentation for the back-end that you're trying to proxy to."
|
10
|
+
missing_backend_application:
|
11
|
+
message: "Missing backend application in '%{path}'."
|
12
|
+
summary: "The file supplied to the back-end in the 'application' option is invalid."
|
13
|
+
resolution: "This is typically a file called 'myapp.ru', for Rails applications it will contain a call to 'MyApp::Application.initialize!'."
|
14
|
+
backend_failed_to_start:
|
15
|
+
message: "The back-end failed to start in a timely fashion."
|
16
|
+
summary: "The proxy started, but failed to connect to the back-end after retrying multiple times."
|
17
|
+
resolution: "Check that your application can start without the proxy and serve HTTP requests."
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Heroku #:nodoc:
|
3
|
+
module Forward
|
4
|
+
module Errors #:nodoc:
|
5
|
+
|
6
|
+
# Default parent error for all custom errors. This handles the base
|
7
|
+
# key for the translations and provides the convenience method for
|
8
|
+
# translating the messages.
|
9
|
+
#
|
10
|
+
# Generously borrowed from Mongoid[https://github.com/mongoid/mongoid/blob/master/lib/mongoid/errors/mongoid_error.rb].
|
11
|
+
class HerokuForwardError < StandardError
|
12
|
+
|
13
|
+
# Problem occurred.
|
14
|
+
attr_reader :problem
|
15
|
+
|
16
|
+
# Summary of the problem.
|
17
|
+
attr_reader :summary
|
18
|
+
|
19
|
+
# Suggested problem resolution.
|
20
|
+
attr_reader :resolution
|
21
|
+
|
22
|
+
# Compose the message.
|
23
|
+
# === Parameters
|
24
|
+
# [key] Lookup key in the translation table.
|
25
|
+
# [attributes] The objects to pass to create the message.
|
26
|
+
def compose_message(key, attributes = {})
|
27
|
+
@problem = create_problem(key, attributes)
|
28
|
+
@summary = create_summary(key, attributes)
|
29
|
+
@resolution = create_resolution(key, attributes)
|
30
|
+
|
31
|
+
"\nProblem:\n #{@problem}"+
|
32
|
+
"\nSummary:\n #{@summary}"+
|
33
|
+
"\nResolution:\n #{@resolution}"
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
BASE_KEY = "heroku.forward.errors.messages" #:nodoc:
|
39
|
+
|
40
|
+
# Given the key of the specific error and the options hash, translate the
|
41
|
+
# message.
|
42
|
+
#
|
43
|
+
# === Parameters
|
44
|
+
# [key] The key of the error in the locales.
|
45
|
+
# [options] The objects to pass to create the message.
|
46
|
+
#
|
47
|
+
# Returns a localized error message string.
|
48
|
+
def translate(key, options)
|
49
|
+
::I18n.translate("#{BASE_KEY}.#{key}", { :locale => :en }.merge(options)).strip
|
50
|
+
end
|
51
|
+
|
52
|
+
# Create the problem.
|
53
|
+
#
|
54
|
+
# === Parameters
|
55
|
+
# [key] The error key.
|
56
|
+
# [attributes] The attributes to interpolate.
|
57
|
+
#
|
58
|
+
# Returns the problem.
|
59
|
+
def create_problem(key, attributes)
|
60
|
+
translate("#{key}.message", attributes)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Create the summary.
|
64
|
+
#
|
65
|
+
# === Parameters
|
66
|
+
# [key] The error key.
|
67
|
+
# [attributes] The attributes to interpolate.
|
68
|
+
#
|
69
|
+
# Returns the summary.
|
70
|
+
def create_summary(key, attributes)
|
71
|
+
translate("#{key}.summary", attributes)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Create the resolution.
|
75
|
+
#
|
76
|
+
# === Parameters
|
77
|
+
# [key] The error key.
|
78
|
+
# [attributes] The attributes to interpolate.
|
79
|
+
#
|
80
|
+
# Returns the resolution.
|
81
|
+
def create_resolution(key, attributes)
|
82
|
+
translate("#{key}.resolution", attributes)
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'heroku/forward/proxy/server'
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module Heroku
|
2
|
+
module Forward
|
3
|
+
module Proxy
|
4
|
+
|
5
|
+
class Server
|
6
|
+
attr_reader :host, :port, :backend, :retries, :start
|
7
|
+
attr_accessor :logger
|
8
|
+
|
9
|
+
def initialize(backend, options = {})
|
10
|
+
@host = options[:host] || '0.0.0.0'
|
11
|
+
@port = options[:port] || 3000
|
12
|
+
@retries = options[:retries] || 10
|
13
|
+
@backend = backend
|
14
|
+
end
|
15
|
+
|
16
|
+
def on_connect(&callback)
|
17
|
+
if block_given?
|
18
|
+
@on_connect = callback
|
19
|
+
elsif @on_connect
|
20
|
+
@on_connect.call
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def forward!(options = {})
|
25
|
+
|
26
|
+
@start = Time.now
|
27
|
+
|
28
|
+
logger.info "Launching Backend ..." if logger
|
29
|
+
|
30
|
+
backend.spawn!
|
31
|
+
|
32
|
+
if options[:delay] && (delay = options[:delay].to_i) > 0
|
33
|
+
logger.info "Waiting #{delay}s to Launch Proxy Server ..." if logger
|
34
|
+
sleep delay
|
35
|
+
end
|
36
|
+
|
37
|
+
logger.info "Launching Proxy Server at #{host}:#{port} ..." if logger
|
38
|
+
|
39
|
+
s = self
|
40
|
+
::Proxy.start({ :host => host, :port => port, :debug => false }) do |conn|
|
41
|
+
if @start
|
42
|
+
EM.next_tick do
|
43
|
+
s.send(:connect, conn)
|
44
|
+
end
|
45
|
+
else
|
46
|
+
s.send(:connect, conn)
|
47
|
+
end
|
48
|
+
|
49
|
+
conn.on_connect do
|
50
|
+
s.on_connect
|
51
|
+
end
|
52
|
+
|
53
|
+
conn.on_data do |data|
|
54
|
+
data
|
55
|
+
end
|
56
|
+
|
57
|
+
conn.on_response do |backend, resp|
|
58
|
+
resp
|
59
|
+
end
|
60
|
+
|
61
|
+
conn.on_finish do
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
def stop!
|
68
|
+
logger.info "Terminating Proxy Server" if logger
|
69
|
+
EventMachine.stop
|
70
|
+
logger.info "Terminating Web Server" if logger
|
71
|
+
backend.terminate!
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def connect(conn)
|
77
|
+
begin
|
78
|
+
if start
|
79
|
+
logger.debug "Attempting to connect to #{backend.socket}." if logger
|
80
|
+
end
|
81
|
+
conn.server backend, :socket => backend.socket
|
82
|
+
if @start
|
83
|
+
logger.debug "Proxy Server ready at #{host}:#{port} (#{(Time.now - start).to_i}s)." if logger
|
84
|
+
@start = nil
|
85
|
+
end
|
86
|
+
rescue RuntimeError => e
|
87
|
+
raise BackendFailedToStartError.new if @retries <= 0
|
88
|
+
logger.warn "#{e.message}, #{retries} #{retries == 1 ? 'retry' : 'retries'} left." if logger
|
89
|
+
@retries -= 1
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
metadata
ADDED
@@ -0,0 +1,209 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: heroku-forward
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Daniel Doubrovkine
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-12-13 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: em-proxy
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.1.8
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.1.8
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: i18n
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0.6'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0.6'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: posix-spawn
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0.3'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.3'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rake
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '10.0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '10.0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: bundler
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ~>
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '1.0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ~>
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '1.0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: rspec
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ~>
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '2.6'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ~>
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '2.6'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: jeweler
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ~>
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '1.6'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ~>
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '1.6'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: thin
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ~>
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '1.5'
|
134
|
+
type: :development
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ~>
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '1.5'
|
142
|
+
- !ruby/object:Gem::Dependency
|
143
|
+
name: em-http-request
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
145
|
+
none: false
|
146
|
+
requirements:
|
147
|
+
- - ~>
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '1.0'
|
150
|
+
type: :development
|
151
|
+
prerelease: false
|
152
|
+
version_requirements: !ruby/object:Gem::Requirement
|
153
|
+
none: false
|
154
|
+
requirements:
|
155
|
+
- - ~>
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: '1.0'
|
158
|
+
description:
|
159
|
+
email: dblock@dblock.org
|
160
|
+
executables: []
|
161
|
+
extensions: []
|
162
|
+
extra_rdoc_files:
|
163
|
+
- LICENSE.md
|
164
|
+
- README.md
|
165
|
+
files:
|
166
|
+
- lib/heroku-forward.rb
|
167
|
+
- lib/heroku/forward.rb
|
168
|
+
- lib/heroku/forward/backends.rb
|
169
|
+
- lib/heroku/forward/backends/thin.rb
|
170
|
+
- lib/heroku/forward/config/locales/en.yml
|
171
|
+
- lib/heroku/forward/errors.rb
|
172
|
+
- lib/heroku/forward/errors/backend_failed_to_start_error.rb
|
173
|
+
- lib/heroku/forward/errors/heroku_forward_error.rb
|
174
|
+
- lib/heroku/forward/errors/missing_backend_application_error.rb
|
175
|
+
- lib/heroku/forward/errors/missing_backend_option_error.rb
|
176
|
+
- lib/heroku/forward/proxy.rb
|
177
|
+
- lib/heroku/forward/proxy/server.rb
|
178
|
+
- lib/heroku/forward/version.rb
|
179
|
+
- LICENSE.md
|
180
|
+
- README.md
|
181
|
+
homepage: http://github.com/dblock/heroku-forward
|
182
|
+
licenses:
|
183
|
+
- MIT
|
184
|
+
post_install_message:
|
185
|
+
rdoc_options: []
|
186
|
+
require_paths:
|
187
|
+
- lib
|
188
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
189
|
+
none: false
|
190
|
+
requirements:
|
191
|
+
- - ! '>='
|
192
|
+
- !ruby/object:Gem::Version
|
193
|
+
version: '0'
|
194
|
+
segments:
|
195
|
+
- 0
|
196
|
+
hash: 394519437
|
197
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
198
|
+
none: false
|
199
|
+
requirements:
|
200
|
+
- - ! '>='
|
201
|
+
- !ruby/object:Gem::Version
|
202
|
+
version: '0'
|
203
|
+
requirements: []
|
204
|
+
rubyforge_project:
|
205
|
+
rubygems_version: 1.8.24
|
206
|
+
signing_key:
|
207
|
+
specification_version: 3
|
208
|
+
summary: Beat Heroku's 60s boot timeout with a forward proxy.
|
209
|
+
test_files: []
|