hookout 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.rdoc +98 -0
- data/Rakefile +70 -0
- data/VERSION.yml +5 -0
- data/bin/hookout +7 -0
- data/lib/hookout.rb +7 -0
- data/lib/hookout/rack_adapter.rb +25 -0
- data/lib/hookout/reversehttp_connector.rb +228 -0
- data/lib/hookout/runner.rb +92 -0
- data/lib/hookout/thin_backend.rb +68 -0
- data/lib/rack/handler/hookout.rb +32 -0
- metadata +72 -0
data/README.rdoc
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
= Hookout - Reverse HTTP Webhooks
|
2
|
+
Developing webhook based applications from behind a firewall or NAT provides many challenges. The
|
3
|
+
biggest is allowing external applications to see your hook. Hookout allows you to use a
|
4
|
+
ReverseHTTP (http://www.reversehttp.net) server to expose your application to the outside world, without needing
|
5
|
+
to configure any complicated firewall holes.
|
6
|
+
|
7
|
+
== Getting Started
|
8
|
+
=== Installing
|
9
|
+
Hookout can be installed via RubyGems with:
|
10
|
+
gem sources -a http://gems.github.com
|
11
|
+
gem install paulj-hookout
|
12
|
+
|
13
|
+
It can also be installed manually with:
|
14
|
+
git clone git://github.com/paulj/hookout.git
|
15
|
+
cd hookout
|
16
|
+
rake build install
|
17
|
+
|
18
|
+
=== Running an application via the hookout helper
|
19
|
+
Hookout provides a Rack (http://rack.rubyforge.org) adapter, allowing any standard Rack application to
|
20
|
+
be made available. It also bundles a script called hookout that will start up an instance of Thin using
|
21
|
+
ReverseHTTP as a backend. This section will cover running a simple Sinatra application with the hookout helper.
|
22
|
+
|
23
|
+
Firstly, say we have a simple Sinatra application (called simple-app.rb):
|
24
|
+
require 'rubygems'
|
25
|
+
require 'sinatra'
|
26
|
+
|
27
|
+
get '/' do
|
28
|
+
"Hello World"
|
29
|
+
end
|
30
|
+
|
31
|
+
To run with the hookout adapter, you'll need a simple rackup configuration file. Create a config.ru such as:
|
32
|
+
require 'simple-app'
|
33
|
+
|
34
|
+
set :run, false
|
35
|
+
|
36
|
+
run Sinatra::Application
|
37
|
+
|
38
|
+
From the command line, you can now start this application with a command line such as:
|
39
|
+
hookout -a http://www.reversehttp.net/reversehttp -n simple-ruby-app -R config.ru start
|
40
|
+
Thin should boot, and provide output such as:
|
41
|
+
>> Thin web server (v1.0.0 codename That's What She Said)
|
42
|
+
>> Maximum connections set to 1024
|
43
|
+
>> Listening on simple-ruby-app via http://www.reversehttp.net/reversehttp, CTRL+C to stop
|
44
|
+
Bound to location http://simple-ruby-app.www.reversehttp.net/
|
45
|
+
|
46
|
+
You can now visit http://simple-ruby-app.www.reversehttp.net to see you application.
|
47
|
+
|
48
|
+
=== Configuring Sinatra to use Hookout as the default server
|
49
|
+
To make a Sintatra application run Hookout instead of thin, a simple script such as the following can be used:
|
50
|
+
require 'rubygems'
|
51
|
+
require 'sinatra'
|
52
|
+
require 'hookout'
|
53
|
+
|
54
|
+
set :server, 'hookout'
|
55
|
+
set :host, 'http://www.reversehttp.net/reversehttp'
|
56
|
+
set :port, 'standalone-ruby-app'
|
57
|
+
|
58
|
+
get '/' do
|
59
|
+
"Hello World"
|
60
|
+
end
|
61
|
+
|
62
|
+
This instructs Sinatra to start Hookout as the Rack adapter; and informs hookout that it should use
|
63
|
+
the public http://www.reversehttp.net server; and informs hookout that it should request the
|
64
|
+
"standalone-ruby-app" space on that server for this application. (Note: To prevent problems, it is HIGHLY
|
65
|
+
recommended to change this to something more unique before trying this!).
|
66
|
+
|
67
|
+
You can now run the application with:
|
68
|
+
ruby test-app.rb
|
69
|
+
|
70
|
+
You should see it startup with something like:
|
71
|
+
== Sinatra/0.9.2 has taken the stage on test-ruby-app for development with backup from Hookout
|
72
|
+
Location changed to http://standalone-ruby-app.www.reversehttp.net/
|
73
|
+
|
74
|
+
You can now visit http://standalone-ruby-app.www.reversehttp.net, and see you own Sinatra app serving pages!
|
75
|
+
|
76
|
+
== Software License
|
77
|
+
Copyright (c) 2008, 2009 Paul Jones <paulj@lshift.net>
|
78
|
+
Copyright (c) 2008, 2009 LShift Ltd. <query@lshift.net>
|
79
|
+
|
80
|
+
Permission is hereby granted, free of charge, to any person
|
81
|
+
obtaining a copy of this software and associated documentation
|
82
|
+
files (the "Software"), to deal in the Software without
|
83
|
+
restriction, including without limitation the rights to use, copy,
|
84
|
+
modify, merge, publish, distribute, sublicense, and/or sell copies
|
85
|
+
of the Software, and to permit persons to whom the Software is
|
86
|
+
furnished to do so, subject to the following conditions:
|
87
|
+
|
88
|
+
The above copyright notice and this permission notice shall be
|
89
|
+
included in all copies or substantial portions of the Software.
|
90
|
+
|
91
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
92
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
93
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
94
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
95
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
96
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
97
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
98
|
+
DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'spec/rake/spectask'
|
3
|
+
require 'rake/clean'
|
4
|
+
require 'rake/rdoctask'
|
5
|
+
require 'rubygems/specification'
|
6
|
+
|
7
|
+
desc "Run all specs"
|
8
|
+
Spec::Rake::SpecTask.new('spec') do |t|
|
9
|
+
t.spec_opts = ["-cfs"]
|
10
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
11
|
+
t.libs = ['lib']
|
12
|
+
end
|
13
|
+
|
14
|
+
desc "Print specdocs"
|
15
|
+
Spec::Rake::SpecTask.new(:doc) do |t|
|
16
|
+
t.spec_opts = ["--format", "specdoc", "--dry-run"]
|
17
|
+
t.spec_files = FileList['spec/*_spec.rb']
|
18
|
+
end
|
19
|
+
|
20
|
+
desc "Generate RCov code coverage report"
|
21
|
+
Spec::Rake::SpecTask.new('rcov') do |t|
|
22
|
+
t.spec_files = FileList['spec/*_spec.rb']
|
23
|
+
t.rcov = true
|
24
|
+
t.rcov_opts = ['--exclude', 'examples']
|
25
|
+
end
|
26
|
+
|
27
|
+
def gemspec
|
28
|
+
@gemspec ||= begin
|
29
|
+
file = File.expand_path('../hookout.gemspec', __FILE__)
|
30
|
+
eval(File.read(file), binding, file)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
begin
|
35
|
+
require 'rake/gempackagetask'
|
36
|
+
rescue LoadError
|
37
|
+
task(:gem) { $stderr.puts '`gem install rake` to package gems' }
|
38
|
+
else
|
39
|
+
Rake::GemPackageTask.new(gemspec) do |pkg|
|
40
|
+
pkg.gem_spec = gemspec
|
41
|
+
end
|
42
|
+
task :gem => :gemspec
|
43
|
+
end
|
44
|
+
|
45
|
+
desc "install the gem locally"
|
46
|
+
task :install => :package do
|
47
|
+
sh %{gem install pkg/#{gemspec.name}-#{gemspec.version}}
|
48
|
+
end
|
49
|
+
|
50
|
+
desc "validate the gemspec"
|
51
|
+
task :gemspec do
|
52
|
+
gemspec.validate
|
53
|
+
end
|
54
|
+
|
55
|
+
task :package => :gemspec
|
56
|
+
|
57
|
+
|
58
|
+
Rake::RDocTask.new do |t|
|
59
|
+
t.rdoc_dir = 'rdoc'
|
60
|
+
t.title = "Hookout"
|
61
|
+
t.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
|
62
|
+
t.options << '--charset' << 'utf-8'
|
63
|
+
t.rdoc_files.include('README.rdoc')
|
64
|
+
t.rdoc_files.include('lib/hookout/*.rb')
|
65
|
+
end
|
66
|
+
|
67
|
+
CLEAN.include [ 'build/*', '**/*.o', '**/*.so', '**/*.a', 'lib/*-*', '**/*.log', 'pkg', 'lib/*.bundle', '*.gem', '.config' ]
|
68
|
+
|
69
|
+
task :test => [ :spec ]
|
70
|
+
task :default => :spec
|
data/bin/hookout
ADDED
data/lib/hookout.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'thin'
|
2
|
+
|
3
|
+
module Hookout
|
4
|
+
class RackAdapter
|
5
|
+
def initialize(app)
|
6
|
+
@app = app
|
7
|
+
end
|
8
|
+
|
9
|
+
def handle_request(request)
|
10
|
+
thin_request = Thin::Request.new
|
11
|
+
thin_request.parse request.body
|
12
|
+
|
13
|
+
status,headers,body = @app.call(thin_request.env)
|
14
|
+
|
15
|
+
thin_response = Thin::Response.new
|
16
|
+
thin_response.status = status
|
17
|
+
thin_response.headers = headers
|
18
|
+
thin_response.body = body
|
19
|
+
|
20
|
+
thin_response.each do |chunk|
|
21
|
+
request.write(chunk)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,228 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'cgi'
|
3
|
+
|
4
|
+
module Hookout
|
5
|
+
class ReverseHttpConnector
|
6
|
+
DEFAULT_SERVER = 'http://localhost:8000/reversehttp'
|
7
|
+
DEFAULT_LABEL = 'ruby'
|
8
|
+
|
9
|
+
attr_accessor :failure_delay, :lease_seconds, :report_poll_exceptions, :location_change_callback
|
10
|
+
|
11
|
+
def initialize(label, server_address, handler)
|
12
|
+
@label = label
|
13
|
+
@server_address = server_address
|
14
|
+
@handler = handler
|
15
|
+
|
16
|
+
@next_req = nil
|
17
|
+
@location = nil
|
18
|
+
@failure_delay = 2
|
19
|
+
@token = '-'
|
20
|
+
@lease_seconds = 30
|
21
|
+
@report_poll_exceptions = false
|
22
|
+
@location_change_callback = nil
|
23
|
+
@closed = false
|
24
|
+
@requestor = Hookout::Utils.new
|
25
|
+
end
|
26
|
+
|
27
|
+
def start
|
28
|
+
until @closed
|
29
|
+
begin
|
30
|
+
(request, client) = next_request()
|
31
|
+
unless request.nil?
|
32
|
+
@handler.handle_request(request)
|
33
|
+
request.close
|
34
|
+
end
|
35
|
+
rescue
|
36
|
+
puts $!
|
37
|
+
puts $@
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def stop
|
43
|
+
@closed = true
|
44
|
+
@requestor.abort
|
45
|
+
end
|
46
|
+
|
47
|
+
def next_request
|
48
|
+
until @closed
|
49
|
+
declare_mode = (@next_req == nil)
|
50
|
+
begin
|
51
|
+
response = nil
|
52
|
+
if declare_mode
|
53
|
+
response = @requestor.post_form @server_address, {:name => @label, :token => @token}
|
54
|
+
else
|
55
|
+
response = @requestor.get @next_req
|
56
|
+
end
|
57
|
+
|
58
|
+
return nil if response.nil?
|
59
|
+
|
60
|
+
@failure_delay = 2
|
61
|
+
if declare_mode
|
62
|
+
link_headers = parse_link_headers(response)
|
63
|
+
|
64
|
+
@next_req = link_headers['first']
|
65
|
+
location_text = link_headers['related']
|
66
|
+
if location_text
|
67
|
+
@location = location_text
|
68
|
+
on_location_changed()
|
69
|
+
end
|
70
|
+
elsif response['Requesting-Client']
|
71
|
+
client_addr = response['Requesting-Client'].split(":")
|
72
|
+
this_req = @next_req
|
73
|
+
@next_req = parse_link_headers(response)['next']
|
74
|
+
return [ReverseHttpRequest.new(this_req, @server_address, response.body), client_addr]
|
75
|
+
end
|
76
|
+
rescue
|
77
|
+
if @report_poll_exceptions
|
78
|
+
report_poll_exception
|
79
|
+
end
|
80
|
+
sleep(@failure_delay) unless @closed
|
81
|
+
|
82
|
+
if @failure_delay < 30
|
83
|
+
@failure_delay = @failure_delay * 2
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
def urlencode(hash)
|
91
|
+
hash.each { |k, v| "#{CGI.escape(k)}=#{CGI.escape(v)}" }.join("&")
|
92
|
+
end
|
93
|
+
|
94
|
+
def parse_link_headers(resp)
|
95
|
+
result = {}
|
96
|
+
resp['Link'].split(", ").each do |link_header|
|
97
|
+
url, rel = nil, nil
|
98
|
+
|
99
|
+
link_header.split(";").each do |piece|
|
100
|
+
piece = piece.strip
|
101
|
+
|
102
|
+
if piece[0..0] == '<':
|
103
|
+
url = piece[1..-2]
|
104
|
+
elsif piece[0..4].downcase == 'rel="':
|
105
|
+
rel = piece[5..-2]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
if url and rel
|
110
|
+
result[rel] = url
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
result
|
115
|
+
end
|
116
|
+
|
117
|
+
def handle_error(request, client_address)
|
118
|
+
if not request.closed:
|
119
|
+
begin
|
120
|
+
request.write("HTTP/1.0 500 Internal Server Error\r\n\r\n")
|
121
|
+
request.close()
|
122
|
+
rescue
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def report_poll_exception
|
128
|
+
puts $!
|
129
|
+
puts $@
|
130
|
+
end
|
131
|
+
|
132
|
+
def on_location_changed
|
133
|
+
if @location_change_callback
|
134
|
+
@location_change_callback.call(@location)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
class ReverseHttpRequest
|
140
|
+
attr_reader :body, :server_address
|
141
|
+
|
142
|
+
def initialize(reply_url, server_address, body)
|
143
|
+
@reply_url = reply_url
|
144
|
+
@server_address = server_address
|
145
|
+
@body = body
|
146
|
+
@response_buffer = StringIO.new
|
147
|
+
@closed = false
|
148
|
+
end
|
149
|
+
|
150
|
+
def makefile(mode, bufsize)
|
151
|
+
if mode[0] == 'r':
|
152
|
+
StringIO.new(@body)
|
153
|
+
elsif mode[0] == 'w':
|
154
|
+
self
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def write(x)
|
159
|
+
@response_buffer.write(x)
|
160
|
+
end
|
161
|
+
|
162
|
+
def flush
|
163
|
+
end
|
164
|
+
|
165
|
+
def close
|
166
|
+
if not @closed
|
167
|
+
@response_buffer.flush
|
168
|
+
resp_body = @response_buffer.string
|
169
|
+
|
170
|
+
Hookout::Utils.post_data @reply_url, resp_body
|
171
|
+
@closed = true
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
class Utils
|
177
|
+
def initialize
|
178
|
+
@current_http = nil
|
179
|
+
@current_thread = nil
|
180
|
+
end
|
181
|
+
|
182
|
+
def get(url)
|
183
|
+
parts = URI.parse(url)
|
184
|
+
req = Net::HTTP::Get.new(parts.path)
|
185
|
+
execute_request(parts, req)
|
186
|
+
end
|
187
|
+
|
188
|
+
def post_form(url, params)
|
189
|
+
parts = URI.parse(url)
|
190
|
+
req = Net::HTTP::Post.new(parts.path)
|
191
|
+
req.set_form_data(params)
|
192
|
+
execute_request(parts, req)
|
193
|
+
end
|
194
|
+
|
195
|
+
def abort
|
196
|
+
@current_http.finish if @current_http
|
197
|
+
@current_thread.raise ConnectorAbortException.new if @current_thread
|
198
|
+
end
|
199
|
+
|
200
|
+
def execute_request(parts, req)
|
201
|
+
begin
|
202
|
+
Net::HTTP.start(parts.host, parts.port) {|http|
|
203
|
+
@current_http = http
|
204
|
+
@current_thread = Thread.current
|
205
|
+
http.request(req)
|
206
|
+
}
|
207
|
+
rescue ConnectorAbortException
|
208
|
+
return nil
|
209
|
+
ensure
|
210
|
+
@current_http = nil
|
211
|
+
@current_thread = nil
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
def self.post_data(url, data)
|
216
|
+
parts = URI.parse(url)
|
217
|
+
req = Net::HTTP::Post.new(parts.path)
|
218
|
+
req.body = data
|
219
|
+
req.content_type = 'message/http'
|
220
|
+
Net::HTTP.start(parts.host, parts.port) {|http|
|
221
|
+
http.request(req)
|
222
|
+
}
|
223
|
+
end
|
224
|
+
|
225
|
+
class ConnectorAbortException < Exception
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module Hookout
|
5
|
+
# Hookout overrides of the Hookout runner
|
6
|
+
class Runner < Thin::Runner
|
7
|
+
def parser
|
8
|
+
# Load in Hookup specific defaults
|
9
|
+
# Default options values
|
10
|
+
@options[:address] = ReverseHttpConnector::DEFAULT_SERVER
|
11
|
+
@options[:label] = ReverseHttpConnector::DEFAULT_LABEL
|
12
|
+
@options[:log] = 'log/hookout.log'
|
13
|
+
@options[:pid] = 'tmp/pids/hookout.pid'
|
14
|
+
@options[:backend] =Hookout::ThinBackend.name
|
15
|
+
|
16
|
+
# NOTE: If you add an option here make sure the key in the +options+ hash is the
|
17
|
+
# same as the name of the command line option.
|
18
|
+
# +option+ keys are used to build the command line to launch other processes,
|
19
|
+
# see <tt>lib/thin/command.rb</tt>.
|
20
|
+
@parser ||= OptionParser.new do |opts|
|
21
|
+
opts.banner = "Usage: hookout [options] #{self.class.commands.join('|')}"
|
22
|
+
|
23
|
+
opts.separator ""
|
24
|
+
opts.separator "Server options:"
|
25
|
+
|
26
|
+
opts.on("-a", "--address SERVER", "use SERVER for Reverse HTTP bindings " +
|
27
|
+
"(default: #{@options[:address]})") { |host| @options[:address] = host }
|
28
|
+
opts.on("-n", "--label LABEL", "use LABEL (default: #{@options[:label]})") { |label| @options[:label] = label }
|
29
|
+
opts.on("-A", "--adapter NAME", "Rack adapter to use (default: autodetect)",
|
30
|
+
"(#{Rack::ADAPTERS.map{|(a,b)|a}.join(', ')})") { |name| @options[:adapter] = name }
|
31
|
+
opts.on("-R", "--rackup FILE", "Load a Rack config file instead of " +
|
32
|
+
"Rack adapter") { |file| @options[:rackup] = file }
|
33
|
+
opts.on("-c", "--chdir DIR", "Change to dir before starting") { |dir| @options[:chdir] = File.expand_path(dir) }
|
34
|
+
opts.on( "--stats PATH", "Mount the Stats adapter under PATH") { |path| @options[:stats] = path }
|
35
|
+
|
36
|
+
opts.separator ""
|
37
|
+
opts.separator "Adapter options:"
|
38
|
+
opts.on("-e", "--environment ENV", "Framework environment " +
|
39
|
+
"(default: #{@options[:environment]})") { |env| @options[:environment] = env }
|
40
|
+
opts.on( "--prefix PATH", "Mount the app under PATH (start with /)") { |path| @options[:prefix] = path }
|
41
|
+
|
42
|
+
unless Thin.win? # Daemonizing not supported on Windows
|
43
|
+
opts.separator ""
|
44
|
+
opts.separator "Daemon options:"
|
45
|
+
|
46
|
+
opts.on("-d", "--daemonize", "Run daemonized in the background") { @options[:daemonize] = true }
|
47
|
+
opts.on("-l", "--log FILE", "File to redirect output " +
|
48
|
+
"(default: #{@options[:log]})") { |file| @options[:log] = file }
|
49
|
+
opts.on("-P", "--pid FILE", "File to store PID " +
|
50
|
+
"(default: #{@options[:pid]})") { |file| @options[:pid] = file }
|
51
|
+
opts.on("-u", "--user NAME", "User to run daemon as (use with -g)") { |user| @options[:user] = user }
|
52
|
+
opts.on("-g", "--group NAME", "Group to run daemon as (use with -u)") { |group| @options[:group] = group }
|
53
|
+
|
54
|
+
opts.separator ""
|
55
|
+
opts.separator "Cluster options:"
|
56
|
+
|
57
|
+
opts.on("-s", "--servers NUM", "Number of servers to start") { |num| @options[:servers] = num.to_i }
|
58
|
+
opts.on("-o", "--only NUM", "Send command to only one server of the cluster") { |only| @options[:only] = only }
|
59
|
+
opts.on("-C", "--config FILE", "Load options from config file") { |file| @options[:config] = file }
|
60
|
+
opts.on( "--all [DIR]", "Send command to each config files in DIR") { |dir| @options[:all] = dir } if Thin.linux?
|
61
|
+
end
|
62
|
+
|
63
|
+
opts.separator ""
|
64
|
+
opts.separator "Tuning options:"
|
65
|
+
|
66
|
+
opts.on("-b", "--backend CLASS", "Backend to use (ignored)") { |name| }
|
67
|
+
opts.on("-p", "--port PORT", "Port to use (ignored)") { |name| }
|
68
|
+
opts.on("-t", "--timeout SEC", "Request or command timeout in sec " +
|
69
|
+
"(default: #{@options[:timeout]})") { |sec| @options[:timeout] = sec.to_i }
|
70
|
+
opts.on("-f", "--force", "Force the execution of the command") { @options[:force] = true }
|
71
|
+
opts.on( "--max-conns NUM", "Maximum number of connections " +
|
72
|
+
"(default: #{@options[:max_conns]})",
|
73
|
+
"Might require sudo to set higher then 1024") { |num| @options[:max_conns] = num.to_i } unless Thin.win?
|
74
|
+
opts.on( "--max-persistent-conns NUM",
|
75
|
+
"Maximum number of persistent connections",
|
76
|
+
"(default: #{@options[:max_persistent_conns]})") { |num| @options[:max_persistent_conns] = num.to_i }
|
77
|
+
opts.on( "--threaded", "Call the Rack application in threads " +
|
78
|
+
"[experimental]") { @options[:threaded] = true }
|
79
|
+
opts.on( "--no-epoll", "Disable the use of epoll") { @options[:no_epoll] = true } if Thin.linux?
|
80
|
+
|
81
|
+
opts.separator ""
|
82
|
+
opts.separator "Common options:"
|
83
|
+
|
84
|
+
opts.on_tail("-r", "--require FILE", "require the library") { |file| @options[:require] << file }
|
85
|
+
opts.on_tail("-D", "--debug", "Set debbuging on") { @options[:debug] = true }
|
86
|
+
opts.on_tail("-V", "--trace", "Set tracing on (log raw request/response)") { @options[:trace] = true }
|
87
|
+
opts.on_tail("-h", "--help", "Show this message") { puts opts; exit }
|
88
|
+
opts.on_tail('-v', '--version', "Show version") { puts Thin::SERVER; exit }
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Hookout
|
2
|
+
# Backend allowing Thin to act as a Reverse HTTP server
|
3
|
+
class ThinBackend < Thin::Backends::Base
|
4
|
+
# Address and port on which the server is listening for connections.
|
5
|
+
attr_accessor :server_address, :label
|
6
|
+
|
7
|
+
def initialize(server, label, options)
|
8
|
+
@server_address = options[:address]
|
9
|
+
@label = options[:label]
|
10
|
+
|
11
|
+
super()
|
12
|
+
end
|
13
|
+
|
14
|
+
# Connect the server
|
15
|
+
def connect
|
16
|
+
@connector = ReverseHttpConnector.new(@label, @server_address, self)
|
17
|
+
@connector.report_poll_exceptions = true
|
18
|
+
@connector.location_change_callback = lambda { |l| puts "Bound to location #{l}" }
|
19
|
+
|
20
|
+
EventMachine.defer do
|
21
|
+
@connector.start
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Stops the server
|
26
|
+
def disconnect
|
27
|
+
@connector.stop
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_s
|
31
|
+
"#{@label} via #{@server_address}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def handle_request(request)
|
35
|
+
connection = ThinConnection.new(request.to_s)
|
36
|
+
connection.rhttp_req = request
|
37
|
+
connection.backend = self
|
38
|
+
initialize_connection(connection)
|
39
|
+
|
40
|
+
connection.receive_data(request.body)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class ThinConnection < Thin::Connection
|
45
|
+
attr_accessor :rhttp_req
|
46
|
+
attr_accessor :backend
|
47
|
+
|
48
|
+
def persistent?
|
49
|
+
false
|
50
|
+
end
|
51
|
+
|
52
|
+
def comm_inactivity_timeout=(timeout)
|
53
|
+
# Ignore
|
54
|
+
end
|
55
|
+
|
56
|
+
def send_data(data)
|
57
|
+
@rhttp_req.write(data)
|
58
|
+
end
|
59
|
+
|
60
|
+
def close_connection_after_writing
|
61
|
+
begin
|
62
|
+
@rhttp_req.close
|
63
|
+
ensure
|
64
|
+
@backend.connection_finished(self)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Rack
|
2
|
+
module Handler
|
3
|
+
# Rack Handler stricly to be able to use Hookout through the rackup command.
|
4
|
+
# To do so, simply require 'hookout' in your Rack config file and run like this
|
5
|
+
#
|
6
|
+
# rackup --server hookout
|
7
|
+
#
|
8
|
+
class Hookout
|
9
|
+
def self.run(app, options={})
|
10
|
+
# Determine our host
|
11
|
+
host = options[:Host] || 'http://localhost:8000/reversehttp'
|
12
|
+
host = 'http://localhost:8000/reversehttp' if host[0..3].downcase != 'http'
|
13
|
+
|
14
|
+
# Determine our label
|
15
|
+
label = options[:Port] || 'ruby'
|
16
|
+
label = 'ruby' if label.to_i != 0
|
17
|
+
|
18
|
+
server = ::Hookout::ReverseHttpConnector.new(
|
19
|
+
label,
|
20
|
+
host,
|
21
|
+
::Hookout::RackAdapter.new(app))
|
22
|
+
server.report_poll_exceptions = options[:report_poll_exceptions] || true # false
|
23
|
+
server.location_change_callback = lambda {|l| puts "Location changed to #{l}"} # if options[:log_location_change]
|
24
|
+
|
25
|
+
yield server if block_given?
|
26
|
+
server.start
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
register 'hookout', 'Rack::Handler::Hookout'
|
31
|
+
end
|
32
|
+
end
|
metadata
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hookout
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 1
|
9
|
+
version: 0.1.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Paul Jones
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-04-10 00:00:00 +01:00
|
18
|
+
default_executable: hookout
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: Hookout allows you to expose your web hook applications to the web via Reverse HTTP.
|
22
|
+
email: pauljones23@gmail.com
|
23
|
+
executables:
|
24
|
+
- hookout
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files:
|
28
|
+
- README.rdoc
|
29
|
+
files:
|
30
|
+
- bin/hookout
|
31
|
+
- lib/hookout/rack_adapter.rb
|
32
|
+
- lib/hookout/reversehttp_connector.rb
|
33
|
+
- lib/hookout/runner.rb
|
34
|
+
- lib/hookout/thin_backend.rb
|
35
|
+
- lib/hookout.rb
|
36
|
+
- lib/rack/handler/hookout.rb
|
37
|
+
- Rakefile
|
38
|
+
- README.rdoc
|
39
|
+
- VERSION.yml
|
40
|
+
has_rdoc: true
|
41
|
+
homepage: http://github.com/paulj/hookout/
|
42
|
+
licenses: []
|
43
|
+
|
44
|
+
post_install_message:
|
45
|
+
rdoc_options:
|
46
|
+
- --inline-source
|
47
|
+
- --charset=UTF-8
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
segments:
|
55
|
+
- 0
|
56
|
+
version: "0"
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
segments:
|
62
|
+
- 0
|
63
|
+
version: "0"
|
64
|
+
requirements: []
|
65
|
+
|
66
|
+
rubyforge_project:
|
67
|
+
rubygems_version: 1.3.6
|
68
|
+
signing_key:
|
69
|
+
specification_version: 2
|
70
|
+
summary: Hookout Reverse HTTP Connector
|
71
|
+
test_files: []
|
72
|
+
|