m2r 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +21 -0
- data/ISSUES +62 -0
- data/LICENSE +20 -0
- data/README.md +55 -0
- data/Rakefile +54 -0
- data/VERSION +1 -0
- data/benchmarks/jruby +60 -0
- data/example/http_0mq.rb +33 -0
- data/example/lobster.ru +7 -0
- data/example/rack_handler.rb +69 -0
- data/lib/connection.rb +158 -0
- data/lib/fiber_handler.rb +43 -0
- data/lib/handler.rb +66 -0
- data/lib/m2r.rb +3 -0
- data/lib/request.rb +44 -0
- data/m2r.gemspec +62 -0
- data/test/helper.rb +10 -0
- data/test/test_m2r.rb +7 -0
- metadata +129 -0
data/.document
ADDED
data/.gitignore
ADDED
data/ISSUES
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
On Ruby 1.9.2, ffi-rzmq crashes on even moderately high loads with the following stack. At this point, the only fix is to switch over to JRuby. I suspect that this crash has a lot to do with MRI's global interpreter lock (GIL). There's also a note about weird behavior on the ffi-rzmq github page, which counsels people to use JRuby, which doesn't have a GIL.
|
2
|
+
|
3
|
+
.rvm/gems/ruby-1.9.2-p0/gems/ffi-rzmq-0.5.0/lib/ffi-rzmq/message.rb:80: [BUG] cfp consistency error - call0
|
4
|
+
ruby 1.9.2p0 (2010-08-18 revision 29036) [x86_64-darwin10.4.0]
|
5
|
+
|
6
|
+
-- control frame ----------
|
7
|
+
c:0014 p:---- s:0048 b:0048 l:000047 d:000047 CFUNC :(null)
|
8
|
+
c:0013 p:---- s:0046 b:0046 l:000045 d:000045 CFUNC :new
|
9
|
+
c:0012 p:0044 s:0040 b:0040 l:000039 d:000039 METHOD .rvm/gems/ruby-1.9.2-p0/gems/ffi-rzmq-0.5.0/lib/ffi-rzmq/message.rb:80
|
10
|
+
c:0011 p:---- s:0035 b:0035 l:000034 d:000034 FINISH
|
11
|
+
c:0010 p:---- s:0033 b:0033 l:000032 d:000032 CFUNC :new
|
12
|
+
c:0009 p:0017 s:0030 b:0030 l:000029 d:000029 METHOD .rvm/gems/ruby-1.9.2-p0/gems/ffi-rzmq-0.5.0/lib/ffi-rzmq/socket.rb:293
|
13
|
+
c:0008 p:0022 s:0024 b:0023 l:000022 d:000022 METHOD /m2r/lib/connection.rb:42
|
14
|
+
c:0007 p:0021 s:0020 b:0020 l:000010 d:000019 BLOCK /m2r/lib/handler.rb:43
|
15
|
+
c:0006 p:---- s:0016 b:0016 l:000015 d:000015 FINISH
|
16
|
+
c:0005 p:---- s:0014 b:0014 l:000013 d:000013 CFUNC :loop
|
17
|
+
c:0004 p:0011 s:0011 b:0011 l:000010 d:000010 METHOD /m2r/lib/handler.rb:39
|
18
|
+
c:0003 p:0112 s:0008 b:0008 l:001598 d:000a68 EVAL example/http_0mq.rb:33
|
19
|
+
c:0002 p:---- s:0004 b:0004 l:000003 d:000003 FINISH
|
20
|
+
c:0001 p:0000 s:0002 b:0002 l:001598 d:001598 TOP
|
21
|
+
---------------------------
|
22
|
+
-- Ruby level backtrace information ----------------------------------------
|
23
|
+
example/http_0mq.rb:33:in `<main>'
|
24
|
+
/m2r/lib/handler.rb:39:in `listen'
|
25
|
+
/m2r/lib/handler.rb:39:in `loop'
|
26
|
+
/m2r/lib/handler.rb:43:in `block in listen'
|
27
|
+
/m2r/lib/connection.rb:42:in `recv'
|
28
|
+
.rvm/gems/ruby-1.9.2-p0/gems/ffi-rzmq-0.5.0/lib/ffi-rzmq/socket.rb:293:in `recv_string'
|
29
|
+
.rvm/gems/ruby-1.9.2-p0/gems/ffi-rzmq-0.5.0/lib/ffi-rzmq/socket.rb:293:in `new'
|
30
|
+
.rvm/gems/ruby-1.9.2-p0/gems/ffi-rzmq-0.5.0/lib/ffi-rzmq/message.rb:80:in `initialize'
|
31
|
+
.rvm/gems/ruby-1.9.2-p0/gems/ffi-rzmq-0.5.0/lib/ffi-rzmq/message.rb:80:in `new'
|
32
|
+
.rvm/gems/ruby-1.9.2-p0/gems/ffi-rzmq-0.5.0/lib/ffi-rzmq/message.rb:80: [BUG] Segmentation fault
|
33
|
+
ruby 1.9.2p0 (2010-08-18 revision 29036) [x86_64-darwin10.4.0]
|
34
|
+
|
35
|
+
-- control frame ----------
|
36
|
+
c:0014 p:---- s:0048 b:0048 l:000047 d:000047 CFUNC :(null)
|
37
|
+
c:0013 p:---- s:0046 b:0046 l:000045 d:000045 CFUNC :new
|
38
|
+
c:0012 p:0044 s:0040 b:0040 l:000039 d:000039 METHOD .rvm/gems/ruby-1.9.2-p0/gems/ffi-rzmq-0.5.0/lib/ffi-rzmq/message.rb:80
|
39
|
+
c:0011 p:---- s:0035 b:0035 l:000034 d:000034 FINISH
|
40
|
+
c:0010 p:---- s:0033 b:0033 l:000032 d:000032 CFUNC :new
|
41
|
+
c:0009 p:0017 s:0030 b:0030 l:000029 d:000029 METHOD .rvm/gems/ruby-1.9.2-p0/gems/ffi-rzmq-0.5.0/lib/ffi-rzmq/socket.rb:293
|
42
|
+
c:0008 p:0022 s:0024 b:0023 l:000022 d:000022 METHOD /m2r/lib/connection.rb:42
|
43
|
+
c:0007 p:0021 s:0020 b:0020 l:000010 d:000019 BLOCK /m2r/lib/handler.rb:43
|
44
|
+
c:0006 p:---- s:0016 b:0016 l:000015 d:000015 FINISH
|
45
|
+
c:0005 p:---- s:0014 b:0014 l:000013 d:000013 CFUNC :loop
|
46
|
+
c:0004 p:0011 s:0011 b:0011 l:000010 d:000010 METHOD /m2r/lib/handler.rb:39
|
47
|
+
c:0003 p:0112 s:0008 b:0008 l:001598 d:000a68 EVAL example/http_0mq.rb:33
|
48
|
+
c:0002 p:---- s:0004 b:0004 l:000003 d:000003 FINISH
|
49
|
+
c:0001 p:0000 s:0002 b:0002 l:001598 d:001598 TOP
|
50
|
+
---------------------------
|
51
|
+
-- Ruby level backtrace information ----------------------------------------
|
52
|
+
example/http_0mq.rb:33:in `<main>'
|
53
|
+
/m2r/lib/handler.rb:39:in `listen'
|
54
|
+
/m2r/lib/handler.rb:39:in `loop'
|
55
|
+
/m2r/lib/handler.rb:43:in `block in listen'
|
56
|
+
/m2r/lib/connection.rb:42:in `recv'
|
57
|
+
.rvm/gems/ruby-1.9.2-p0/gems/ffi-rzmq-0.5.0/lib/ffi-rzmq/socket.rb:293:in `recv_string'
|
58
|
+
.rvm/gems/ruby-1.9.2-p0/gems/ffi-rzmq-0.5.0/lib/ffi-rzmq/socket.rb:293:in `new'
|
59
|
+
.rvm/gems/ruby-1.9.2-p0/gems/ffi-rzmq-0.5.0/lib/ffi-rzmq/message.rb:80:in `initialize'
|
60
|
+
.rvm/gems/ruby-1.9.2-p0/gems/ffi-rzmq-0.5.0/lib/ffi-rzmq/message.rb:80:in `new'
|
61
|
+
|
62
|
+
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Pradeep Elankumaran
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
m2r
|
2
|
+
===
|
3
|
+
|
4
|
+
A [mongrel2](http://mongrel2.org/index) backend handler written in Ruby, based on [Zed's Python backend handler](http://mongrel2.org/dir?ci=1bdfff8f050b97df&name=examples/python/mongrel2).
|
5
|
+
|
6
|
+
Usage/Examples
|
7
|
+
-----
|
8
|
+
|
9
|
+
* `examples/http_0mq.rb` is a test little servlet thing (based on what comes with mongrel2).
|
10
|
+
* `examples/rack_handler.rb` is a Mongrel2 ruby handler rack handler mouthful, whose variables are probably a little off.
|
11
|
+
* `examples/lobster.ru` is a rackup file using the Rack handler that'll serve Rack's funny little lobster app.
|
12
|
+
|
13
|
+
Running Examples
|
14
|
+
----------------
|
15
|
+
|
16
|
+
* `ruby examples/http_0mq.rb`, which with Mongrel2's test config will serve up at http://localhost:6767/handlertest
|
17
|
+
* `rackup examples/lobster.ru`, ditto, http://localhost:6767/handlertest
|
18
|
+
|
19
|
+
With rails3:
|
20
|
+
Add this to your Gemfile:
|
21
|
+
|
22
|
+
gem 'ffi'
|
23
|
+
gem 'ffi-rzmq'
|
24
|
+
gem 'json'
|
25
|
+
|
26
|
+
And this to your config.ru:
|
27
|
+
|
28
|
+
$: << location_to_m2r + '/example'
|
29
|
+
require 'rack_handler'
|
30
|
+
|
31
|
+
Rack::Handler::Mongrel2.run YourRailsAppName::Application
|
32
|
+
|
33
|
+
Then do all like `bundle exec rackup`
|
34
|
+
|
35
|
+
Installation
|
36
|
+
------------
|
37
|
+
|
38
|
+
* JRuby 1.5+ (don't use MRI - it will crash horribly, check the ISSUES file)
|
39
|
+
* [FFI](http://github.com/ffi/ffi), `gem install ffi` should be fine.
|
40
|
+
* [Zero MQ](http://www.zeromq.org/area:download), you'll need to compile and install to get the headers and such for:
|
41
|
+
* [ffi-rzmq](http://github.com/chuckremes/ffi-rzmq), which you'll have to build. The native zmq didn't work for me, but if you want to fix it, please do!
|
42
|
+
* [json](http://github.com/genki/json), since the headers are returned in JSON, which is RAD. (Really Awesome, Dude)
|
43
|
+
* Rack `gem install rack` if you want to run the rack example.
|
44
|
+
|
45
|
+
Contributing
|
46
|
+
------------
|
47
|
+
|
48
|
+
Once you've made your great commits:
|
49
|
+
|
50
|
+
1. Fork m2r
|
51
|
+
2. Create a topic branch - `git checkout -b my_branch`
|
52
|
+
3. Push to your branch - `git push origin my_branch`
|
53
|
+
4. Send a pull request
|
54
|
+
|
55
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "m2r"
|
8
|
+
gem.summary = "Mongrel2 interface and handler library for JRuby"
|
9
|
+
gem.description = "A Mongrel2 interface and handler library for JRuby, and hopefully other Ruby implementations in the future. Works with Rack, so it works with Rails! (Rails installation guide forthcoming.)"
|
10
|
+
gem.homepage = "http://github.com/perplexes/m2r"
|
11
|
+
gem.add_dependency "ffi", ">= 0"
|
12
|
+
gem.add_dependency "ffi-rzmq", ">= 0"
|
13
|
+
gem.add_dependency "json", ">= 0"
|
14
|
+
gem.authors = ["Colin Curtin", "Pradeep Elankumaran"]
|
15
|
+
gem.email = "colin.t.curtin+m2r@gmail.com"
|
16
|
+
end
|
17
|
+
Jeweler::GemcutterTasks.new
|
18
|
+
rescue LoadError
|
19
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
20
|
+
end
|
21
|
+
|
22
|
+
require 'rake/testtask'
|
23
|
+
Rake::TestTask.new(:test) do |test|
|
24
|
+
test.libs << 'lib' << 'test'
|
25
|
+
test.pattern = 'test/**/test_*.rb'
|
26
|
+
test.verbose = true
|
27
|
+
end
|
28
|
+
|
29
|
+
begin
|
30
|
+
require 'rcov/rcovtask'
|
31
|
+
Rcov::RcovTask.new do |test|
|
32
|
+
test.libs << 'test'
|
33
|
+
test.pattern = 'test/**/test_*.rb'
|
34
|
+
test.verbose = true
|
35
|
+
end
|
36
|
+
rescue LoadError
|
37
|
+
task :rcov do
|
38
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
task :test => :check_dependencies
|
43
|
+
|
44
|
+
task :default => :test
|
45
|
+
|
46
|
+
require 'rake/rdoctask'
|
47
|
+
Rake::RDocTask.new do |rdoc|
|
48
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
49
|
+
|
50
|
+
rdoc.rdoc_dir = 'rdoc'
|
51
|
+
rdoc.title = "m2r #{version}"
|
52
|
+
rdoc.rdoc_files.include('README*')
|
53
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
54
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.2
|
data/benchmarks/jruby
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
Machine: 2.66GHZ Core 2 Duo
|
2
|
+
RAM: 4GB
|
3
|
+
|
4
|
+
w/ 1 worker running on JRuby 1.5.1 64-bit hotspot
|
5
|
+
--------------------------------------------------------------------------------------------------
|
6
|
+
httperf --server=localhost --num-conns 10 --num-calls 10000 --port 6767 --uri "/handlertest"
|
7
|
+
httperf --client=0/1 --server=localhost --port=6767 --uri=/handlertest --send-buffer=4096 --recv-buffer=16384 --num-conns=10 --num-calls=10000
|
8
|
+
httperf: warning: open file limit > FD_SETSIZE; limiting max. # of open files to FD_SETSIZE
|
9
|
+
Maximum connect burst length: 1
|
10
|
+
|
11
|
+
Total: connections 10 requests 100000 replies 100000 test-duration 278.142 s
|
12
|
+
|
13
|
+
Connection rate: 0.0 conn/s (27814.2 ms/conn, <=1 concurrent connections)
|
14
|
+
Connection time [ms]: min 23196.2 avg 27814.2 max 36479.8 median 25946.5 stddev 3871.1
|
15
|
+
Connection time [ms]: connect 1.1
|
16
|
+
Connection length [replies/conn]: 10000.000
|
17
|
+
|
18
|
+
Request rate: 359.5 req/s (2.8 ms/req)
|
19
|
+
Request size [B]: 73.0
|
20
|
+
|
21
|
+
Reply rate [replies/s]: min 165.0 avg 359.1 max 473.6 stddev 61.9 (55 samples)
|
22
|
+
Reply time [ms]: response 2.8 transfer 0.0
|
23
|
+
Reply size [B]: header 40.0 content 290.0 footer 0.0 (total 330.0)
|
24
|
+
Reply status: 1xx=0 2xx=100000 3xx=0 4xx=0 5xx=0
|
25
|
+
|
26
|
+
CPU time [s]: user 25.18 system 109.49 (user 9.1% system 39.4% total 48.4%)
|
27
|
+
Net I/O: 141.5 KB/s (1.2*10^6 bps)
|
28
|
+
|
29
|
+
Errors: total 0 client-timo 0 socket-timo 0 connrefused 0 connreset 0
|
30
|
+
Errors: fd-unavail 0 addrunavail 0 ftab-full 0 other 0
|
31
|
+
|
32
|
+
|
33
|
+
w/ 3 workers running on Jruby 1.5.1 64-bit hotspot
|
34
|
+
----------------------------------------------------------------------------------------------------
|
35
|
+
httperf --server=localhost --num-conns 10 --num-calls 10000 --port 6767 --uri "/handlertest"
|
36
|
+
httperf --client=0/1 --server=localhost --port=6767 --uri=/handlertest --send-buffer=4096 --recv-buffer=16384 --num-conns=10 --num-calls=10000
|
37
|
+
httperf: warning: open file limit > FD_SETSIZE; limiting max. # of open files to FD_SETSIZE
|
38
|
+
Maximum connect burst length: 1
|
39
|
+
|
40
|
+
Total: connections 10 requests 100000 replies 100000 test-duration 311.645 s
|
41
|
+
|
42
|
+
Connection rate: 0.0 conn/s (31164.5 ms/conn, <=1 concurrent connections)
|
43
|
+
Connection time [ms]: min 26030.3 avg 31164.5 max 40239.7 median 28590.5 stddev 4521.9
|
44
|
+
Connection time [ms]: connect 0.5
|
45
|
+
Connection length [replies/conn]: 10000.000
|
46
|
+
|
47
|
+
Request rate: 320.9 req/s (3.1 ms/req)
|
48
|
+
Request size [B]: 73.0
|
49
|
+
|
50
|
+
Reply rate [replies/s]: min 152.2 avg 320.6 max 409.4 stddev 56.1 (62 samples)
|
51
|
+
Reply time [ms]: response 3.1 transfer 0.0
|
52
|
+
Reply size [B]: header 40.0 content 290.0 footer 0.0 (total 330.0)
|
53
|
+
Reply status: 1xx=0 2xx=100000 3xx=0 4xx=0 5xx=0
|
54
|
+
|
55
|
+
CPU time [s]: user 30.40 system 134.12 (user 9.8% system 43.0% total 52.8%)
|
56
|
+
Net I/O: 126.3 KB/s (1.0*10^6 bps)
|
57
|
+
|
58
|
+
Errors: total 0 client-timo 0 socket-timo 0 connrefused 0 connreset 0
|
59
|
+
Errors: fd-unavail 0 addrunavail 0 ftab-full 0 other 0
|
60
|
+
|
data/example/http_0mq.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# http_0mq.rb - an example handler from the Mongrel2 book
|
2
|
+
# You can spin up many of these - Mongrel2 will then round-robin requests to each one.
|
3
|
+
|
4
|
+
# require 'rubygems'
|
5
|
+
# require 'ruby-debug'
|
6
|
+
# Debugger.start
|
7
|
+
$: << File.expand_path(File.dirname(__FILE__) + '/../lib')
|
8
|
+
require 'm2r'
|
9
|
+
|
10
|
+
class Http0MQHandler < Mongrel2::Handler
|
11
|
+
# There are more hooks you can override - check out lib/handler.rb
|
12
|
+
|
13
|
+
def on_wait
|
14
|
+
puts "WAITING FOR REQUEST"
|
15
|
+
end
|
16
|
+
|
17
|
+
def on_disconnect
|
18
|
+
puts "DISCONNECT"
|
19
|
+
end
|
20
|
+
|
21
|
+
def process(req)
|
22
|
+
response = "<pre>\nSENDER: %s\nIDENT:%s\nPATH: %s\nHEADERS:%s\nBODY:%s</pre>" % [
|
23
|
+
req.sender.inspect, req.conn_id.inspect, req.path.inspect,
|
24
|
+
JSON.generate(req.headers).inspect, req.body.inspect]
|
25
|
+
puts response
|
26
|
+
response
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
sender_id = "C2256F34-14A1-45DD-BB73-97CAE25E25B4"
|
31
|
+
handler = Http0MQHandler.new(
|
32
|
+
sender_id, "tcp://127.0.0.1:9997", "tcp://127.0.0.1:9996")
|
33
|
+
handler.listen
|
data/example/lobster.ru
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rack'
|
3
|
+
require 'stringio'
|
4
|
+
# require 'ruby-debug'
|
5
|
+
# Debugger.start
|
6
|
+
# gem install ruby-debug19 -- --with-ruby-include=$HOME/.rvm/src/ruby-1.9.2-head
|
7
|
+
|
8
|
+
$: << ::File.expand_path(::File.dirname(__FILE__) + '/../lib')
|
9
|
+
require 'connection'
|
10
|
+
|
11
|
+
$sender_id = "70D107AB-19F5-44AE-A2D0-2326A167D8D7"
|
12
|
+
|
13
|
+
module Rack
|
14
|
+
module Handler
|
15
|
+
class Mongrel2
|
16
|
+
def self.run(app, receive = "tcp://127.0.0.1:9997", send = "tcp://127.0.0.1:9996")
|
17
|
+
conn = ::Mongrel2::Connection.new($sender_id, receive, send)
|
18
|
+
@running = true
|
19
|
+
trap("SIGINT") do
|
20
|
+
@running = false
|
21
|
+
end
|
22
|
+
|
23
|
+
while @running
|
24
|
+
puts "WAITING FOR REQUEST"
|
25
|
+
|
26
|
+
req = conn.recv # Caution: Abort traps on SIGINT :/
|
27
|
+
if req.disconnect?
|
28
|
+
puts "DICONNECT"
|
29
|
+
next
|
30
|
+
end
|
31
|
+
|
32
|
+
script_name = ENV["RACK_RELATIVE_URL_ROOT"] ||
|
33
|
+
# PATTERN is like: /test/(.*.json) or /handlertest
|
34
|
+
req.headers["PATTERN"].split('(', 2).first.gsub(/\/$/, '')
|
35
|
+
|
36
|
+
env = {
|
37
|
+
"rack.version" => Rack::VERSION,
|
38
|
+
"rack.url_scheme" => "http",
|
39
|
+
"rack.input" => StringIO.new(req.body),
|
40
|
+
"rack.errors" => $stderr,
|
41
|
+
"rack.multithread" => true,
|
42
|
+
"rack.multiprocess" => true,
|
43
|
+
"rack.run_once" => false,
|
44
|
+
|
45
|
+
"mongrel2.pattern" => req.headers["PATTERN"],
|
46
|
+
|
47
|
+
"REQUEST_METHOD" => req.headers["METHOD"],
|
48
|
+
"SCRIPT_NAME" => script_name,
|
49
|
+
"PATH_INFO" => req.headers["PATH"].gsub(script_name, ''),
|
50
|
+
"QUERY_STRING" => req.headers["QUERY"]
|
51
|
+
}
|
52
|
+
|
53
|
+
env["SERVER_NAME"], env["SERVER_PORT"] = req.headers["Host"].split(':', 2)
|
54
|
+
req.headers.each do |key, val|
|
55
|
+
unless key =~ /content_(type|length)/i
|
56
|
+
key = "HTTP_#{key.upcase}"
|
57
|
+
end
|
58
|
+
env[key] = val
|
59
|
+
end
|
60
|
+
|
61
|
+
status, headers, rack_response = app.call(env)
|
62
|
+
body = ""
|
63
|
+
rack_response.each{|b| body << b}
|
64
|
+
conn.reply_http(req, body, status, headers)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/connection.rb
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
# On OSX:
|
2
|
+
# sudo port install zmq
|
3
|
+
# sudo gem install zmq
|
4
|
+
# RUBY_ENGINE = 'ruby'
|
5
|
+
require 'rubygems'
|
6
|
+
gem 'ffi-rzmq'
|
7
|
+
gem 'json'
|
8
|
+
require 'ffi-rzmq'
|
9
|
+
require 'json'
|
10
|
+
|
11
|
+
$: << File.dirname(__FILE__)
|
12
|
+
require 'request'
|
13
|
+
|
14
|
+
CTX = ZMQ::Context.new(1)
|
15
|
+
|
16
|
+
module Mongrel2
|
17
|
+
# A Connection object manages the connection between your handler
|
18
|
+
# and a Mongrel2 server (or servers). It can receive raw requests
|
19
|
+
# or JSON encoded requests whether from HTTP or MSG request types,
|
20
|
+
# and it can send individual responses or batch responses either
|
21
|
+
# raw or as JSON. It also has a way to encode HTTP responses
|
22
|
+
# for simplicity since that'll be fairly common.
|
23
|
+
class Connection
|
24
|
+
|
25
|
+
def initialize(sender_id, sub_addr, pub_addr)
|
26
|
+
@sender_id = sender_id
|
27
|
+
|
28
|
+
@reqs = CTX.socket(ZMQ::UPSTREAM)
|
29
|
+
@reqs.connect(sub_addr)
|
30
|
+
|
31
|
+
@resp = CTX.socket(ZMQ::PUB)
|
32
|
+
@resp.connect(pub_addr)
|
33
|
+
@resp.setsockopt(ZMQ::IDENTITY, sender_id)
|
34
|
+
|
35
|
+
@sub_addr = sub_addr
|
36
|
+
@pub_addr = pub_addr
|
37
|
+
end
|
38
|
+
|
39
|
+
# Receives a raw Request object that you
|
40
|
+
# can then work with.
|
41
|
+
def recv
|
42
|
+
Request.parse(@reqs.recv_string(0))
|
43
|
+
end
|
44
|
+
|
45
|
+
# Same as regular recv, but assumes the body is JSON and
|
46
|
+
# creates a new attribute named req.data with the decoded
|
47
|
+
# payload. This will throw an error if it is not JSON.
|
48
|
+
#
|
49
|
+
# Normally Request just does this if the METHOD is 'JSON'
|
50
|
+
# but you can use this to force it for say HTTP requests.
|
51
|
+
def recv_json
|
52
|
+
self.recv.tap do |req|
|
53
|
+
req.data ||= JSON.parse(req.body)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Raw send to the given connection ID, mostly used
|
58
|
+
# internally.
|
59
|
+
def send_resp(uuid, conn_id, msg)
|
60
|
+
header = "%s %d:%s," % [uuid, conn_id.size, conn_id]
|
61
|
+
string = header + ' ' + msg
|
62
|
+
#puts "DEBUG: #{string.inspect}"
|
63
|
+
@resp.send_string(string, 0)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Does a reply based on the given Request object and message.
|
67
|
+
# This is easier since the req object contains all the info
|
68
|
+
# needed to do the proper reply addressing.
|
69
|
+
def reply(req, msg)
|
70
|
+
self.send_resp(req.sender, req.conn_id, msg)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Same as reply, but tries to convert data to JSON first.
|
74
|
+
def reply_json(req, data)
|
75
|
+
self.send_resp(req.sender, req.conn_id, JSON.generate(data))
|
76
|
+
end
|
77
|
+
|
78
|
+
# Basic HTTP response mechanism which will take your body,
|
79
|
+
# any headers you've made, and encode them so that the
|
80
|
+
# browser gets them.
|
81
|
+
def reply_http(req, body, code=200, headers={})
|
82
|
+
self.reply(req, http_response(body, code, headers))
|
83
|
+
end
|
84
|
+
|
85
|
+
# This lets you send a single message to many currently
|
86
|
+
# connected clients. There's a MAX_IDENTS that you should
|
87
|
+
# not exceed, so chunk your targets as needed. Each target
|
88
|
+
# will receive the message once by Mongrel2, but you don't have
|
89
|
+
# to loop which cuts down on reply volume.
|
90
|
+
def deliver(uuid, idents, data)
|
91
|
+
self.send_resp(uuid, idents.join(' '), data)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Same as deliver, but converts to JSON first.
|
95
|
+
def deliver_json(uuid, idents, data)
|
96
|
+
self.deliver(uuid, idents, JSON.generate(data))
|
97
|
+
end
|
98
|
+
|
99
|
+
# Same as deliver, but builds an HTTP response, which means, yes,
|
100
|
+
# you can reply to multiple connected clients waiting for an HTTP
|
101
|
+
# response from one handler. Kinda cool.
|
102
|
+
def deliver_http(uuid, idents, body, code=200, headers={})
|
103
|
+
self.deliver(uuid, idents, http_response(body, code, headers))
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
def http_response(body, code, headers)
|
108
|
+
headers['Content-Length'] = body.size
|
109
|
+
headers_s = headers.map{|k, v| "%s: %s" % [k,v]}.join("\r\n")
|
110
|
+
|
111
|
+
"HTTP/1.1 #{code} #{StatusMessage[code.to_i]}\r\n#{headers_s}\r\n\r\n#{body}"
|
112
|
+
end
|
113
|
+
|
114
|
+
# From WEBrick: thanks dawg.
|
115
|
+
StatusMessage = {
|
116
|
+
100 => 'Continue',
|
117
|
+
101 => 'Switching Protocols',
|
118
|
+
200 => 'OK',
|
119
|
+
201 => 'Created',
|
120
|
+
202 => 'Accepted',
|
121
|
+
203 => 'Non-Authoritative Information',
|
122
|
+
204 => 'No Content',
|
123
|
+
205 => 'Reset Content',
|
124
|
+
206 => 'Partial Content',
|
125
|
+
300 => 'Multiple Choices',
|
126
|
+
301 => 'Moved Permanently',
|
127
|
+
302 => 'Found',
|
128
|
+
303 => 'See Other',
|
129
|
+
304 => 'Not Modified',
|
130
|
+
305 => 'Use Proxy',
|
131
|
+
307 => 'Temporary Redirect',
|
132
|
+
400 => 'Bad Request',
|
133
|
+
401 => 'Unauthorized',
|
134
|
+
402 => 'Payment Required',
|
135
|
+
403 => 'Forbidden',
|
136
|
+
404 => 'Not Found',
|
137
|
+
405 => 'Method Not Allowed',
|
138
|
+
406 => 'Not Acceptable',
|
139
|
+
407 => 'Proxy Authentication Required',
|
140
|
+
408 => 'Request Timeout',
|
141
|
+
409 => 'Conflict',
|
142
|
+
410 => 'Gone',
|
143
|
+
411 => 'Length Required',
|
144
|
+
412 => 'Precondition Failed',
|
145
|
+
413 => 'Request Entity Too Large',
|
146
|
+
414 => 'Request-URI Too Large',
|
147
|
+
415 => 'Unsupported Media Type',
|
148
|
+
416 => 'Request Range Not Satisfiable',
|
149
|
+
417 => 'Expectation Failed',
|
150
|
+
500 => 'Internal Server Error',
|
151
|
+
501 => 'Not Implemented',
|
152
|
+
502 => 'Bad Gateway',
|
153
|
+
503 => 'Service Unavailable',
|
154
|
+
504 => 'Gateway Timeout',
|
155
|
+
505 => 'HTTP Version Not Supported'
|
156
|
+
}
|
157
|
+
end # class Connection
|
158
|
+
end # mod Mongrel2
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Mongrel2
|
2
|
+
class FiberHandler < Handler
|
3
|
+
def initialize(*args)
|
4
|
+
raise "This handler is just around for testing. don't use it, it'll suck."
|
5
|
+
end
|
6
|
+
|
7
|
+
def fiber_handle
|
8
|
+
@fiber ||= Fiber.new do |request|
|
9
|
+
loop do
|
10
|
+
on_request(request)
|
11
|
+
|
12
|
+
# run on_disconnect if the server disconnects
|
13
|
+
if request.disconnect?
|
14
|
+
on_disconnect
|
15
|
+
request = Fiber.yield
|
16
|
+
next
|
17
|
+
end
|
18
|
+
|
19
|
+
# get the response from on_request
|
20
|
+
response = process(request)
|
21
|
+
|
22
|
+
# run the response through a filter
|
23
|
+
response = after_process(response, request)
|
24
|
+
|
25
|
+
# send it back to the server on the PUB socket
|
26
|
+
@connection.reply_http(request, response)
|
27
|
+
|
28
|
+
after_reply(request, response)
|
29
|
+
request = Fiber.yield
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def listen
|
35
|
+
loop do
|
36
|
+
on_wait
|
37
|
+
request = @connection.recv
|
38
|
+
fiber_handle.resume(request)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
data/lib/handler.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
module Mongrel2
|
2
|
+
class Handler
|
3
|
+
attr_accessor :connection
|
4
|
+
def initialize(sender_uuid, subscribe_address, publish_address)
|
5
|
+
@connection = Mongrel2::Connection.new(sender_uuid,
|
6
|
+
subscribe_address, publish_address)
|
7
|
+
end
|
8
|
+
|
9
|
+
# Callback for when the handler is waiting for a request
|
10
|
+
def on_wait(*args)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Callback when a request is received (for debug)
|
14
|
+
def on_request(request, *args)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Override this to return a custom response
|
18
|
+
def process(request, *args)
|
19
|
+
puts "PROCES REQUEST: #{request}"
|
20
|
+
#raise NoHandlerDefined, "define process_request in your subclass"
|
21
|
+
return request.inspect
|
22
|
+
end
|
23
|
+
|
24
|
+
# Callback for when the server disconnects
|
25
|
+
def on_disconnect(request, *args)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Callback after process_request is done
|
29
|
+
def after_process(response, request, *args)
|
30
|
+
return response
|
31
|
+
end
|
32
|
+
|
33
|
+
# Callback after the server gets the response
|
34
|
+
def after_reply(request, response, *args)
|
35
|
+
end
|
36
|
+
|
37
|
+
# the body of the main recv loop
|
38
|
+
def listen
|
39
|
+
loop do
|
40
|
+
on_wait
|
41
|
+
|
42
|
+
# get the request from Mongrel2 on the UPSTREAM socket
|
43
|
+
request = @connection.recv
|
44
|
+
# run the on_request hook
|
45
|
+
on_request(request)
|
46
|
+
|
47
|
+
# run on_disconnect if the server disconnects
|
48
|
+
if request.disconnect?
|
49
|
+
on_disconnect
|
50
|
+
next
|
51
|
+
end
|
52
|
+
|
53
|
+
# get the response from on_request
|
54
|
+
response = process(request)
|
55
|
+
|
56
|
+
# run the response through a filter
|
57
|
+
response = after_process(response, request)
|
58
|
+
|
59
|
+
# send it back to the server on the PUB socket
|
60
|
+
@connection.reply_http(request, response)
|
61
|
+
|
62
|
+
after_reply(request, response)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/m2r.rb
ADDED
data/lib/request.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
module Mongrel2
|
2
|
+
class Request
|
3
|
+
attr_reader :sender, :conn_id, :path, :headers, :body
|
4
|
+
def initialize(sender, conn_id, path, headers, body)
|
5
|
+
@sender = sender
|
6
|
+
@conn_id = conn_id
|
7
|
+
@path = path
|
8
|
+
@headers = headers
|
9
|
+
@body = body
|
10
|
+
|
11
|
+
if headers['METHOD'] == 'JSON'
|
12
|
+
@data = JSON.parse(@body)
|
13
|
+
else
|
14
|
+
@data = {}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.parse_netstring(ns)
|
19
|
+
len, rest = ns.split(':', 2)
|
20
|
+
len = len.to_i
|
21
|
+
raise "Netstring did not end in ','" unless rest[len].chr == ','
|
22
|
+
[ rest[0...len], rest[(len+1)..-1] ]
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.parse(msg)
|
26
|
+
sender, conn_id, path, rest = msg.split(' ', 4)
|
27
|
+
headers, head_rest = parse_netstring(rest)
|
28
|
+
body, _ = parse_netstring(head_rest)
|
29
|
+
|
30
|
+
headers = JSON.parse(headers)
|
31
|
+
|
32
|
+
self.new(sender, conn_id, path, headers, body)
|
33
|
+
end
|
34
|
+
|
35
|
+
def disconnect?
|
36
|
+
if self.headers['METHOD'] == 'JSON'
|
37
|
+
@data['type'] == 'disconnect'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
alias :is_disconnect :disconnect?
|
41
|
+
|
42
|
+
end # class Request
|
43
|
+
end # mod Mongrel2
|
44
|
+
|
data/m2r.gemspec
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{m2r}
|
8
|
+
s.version = "0.0.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.date = %q{2010-08-19}
|
12
|
+
s.extra_rdoc_files = [
|
13
|
+
"LICENSE",
|
14
|
+
"README.md"
|
15
|
+
]
|
16
|
+
s.files = [
|
17
|
+
".document",
|
18
|
+
".gitignore",
|
19
|
+
"LICENSE",
|
20
|
+
"README.md",
|
21
|
+
"Rakefile",
|
22
|
+
"VERSION",
|
23
|
+
"example/http_0mq.rb",
|
24
|
+
"example/lobster.ru",
|
25
|
+
"example/rack_handler.rb",
|
26
|
+
"lib/connection.rb",
|
27
|
+
"lib/m2r.rb",
|
28
|
+
"lib/request.rb",
|
29
|
+
"m2r.gemspec",
|
30
|
+
"test/helper.rb",
|
31
|
+
"test/test_m2r.rb"
|
32
|
+
]
|
33
|
+
s.homepage = %q{http://github.com/skyfallsin/m2r}
|
34
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
35
|
+
s.require_paths = ["lib"]
|
36
|
+
s.rubygems_version = %q{1.3.7}
|
37
|
+
s.summary = %q{Mongrel2 interface and handler2 library for JRuby}
|
38
|
+
s.test_files = [
|
39
|
+
"test/helper.rb",
|
40
|
+
"test/test_m2r.rb"
|
41
|
+
]
|
42
|
+
|
43
|
+
if s.respond_to? :specification_version then
|
44
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
45
|
+
s.specification_version = 3
|
46
|
+
|
47
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
48
|
+
s.add_runtime_dependency(%q<ffi>, [">= 0"])
|
49
|
+
s.add_runtime_dependency(%q<ffi-rzmq>, [">= 0"])
|
50
|
+
s.add_runtime_dependency(%q<json>, [">= 0"])
|
51
|
+
else
|
52
|
+
s.add_dependency(%q<ffi>, [">= 0"])
|
53
|
+
s.add_dependency(%q<ffi-rzmq>, [">= 0"])
|
54
|
+
s.add_dependency(%q<json>, [">= 0"])
|
55
|
+
end
|
56
|
+
else
|
57
|
+
s.add_dependency(%q<ffi>, [">= 0"])
|
58
|
+
s.add_dependency(%q<ffi-rzmq>, [">= 0"])
|
59
|
+
s.add_dependency(%q<json>, [">= 0"])
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
data/test/helper.rb
ADDED
data/test/test_m2r.rb
ADDED
metadata
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: m2r
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 2
|
10
|
+
version: 0.0.2
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Colin Curtin
|
14
|
+
- Pradeep Elankumaran
|
15
|
+
autorequire:
|
16
|
+
bindir: bin
|
17
|
+
cert_chain: []
|
18
|
+
|
19
|
+
date: 2010-10-03 00:00:00 -07:00
|
20
|
+
default_executable:
|
21
|
+
dependencies:
|
22
|
+
- !ruby/object:Gem::Dependency
|
23
|
+
name: ffi
|
24
|
+
prerelease: false
|
25
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ">="
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
hash: 3
|
31
|
+
segments:
|
32
|
+
- 0
|
33
|
+
version: "0"
|
34
|
+
type: :runtime
|
35
|
+
version_requirements: *id001
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: ffi-rzmq
|
38
|
+
prerelease: false
|
39
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 3
|
45
|
+
segments:
|
46
|
+
- 0
|
47
|
+
version: "0"
|
48
|
+
type: :runtime
|
49
|
+
version_requirements: *id002
|
50
|
+
- !ruby/object:Gem::Dependency
|
51
|
+
name: json
|
52
|
+
prerelease: false
|
53
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
hash: 3
|
59
|
+
segments:
|
60
|
+
- 0
|
61
|
+
version: "0"
|
62
|
+
type: :runtime
|
63
|
+
version_requirements: *id003
|
64
|
+
description: A Mongrel2 interface and handler library for JRuby, and hopefully other Ruby implementations in the future. Works with Rack, so it works with Rails! (Rails installation guide forthcoming.)
|
65
|
+
email: colin.t.curtin+m2r@gmail.com
|
66
|
+
executables: []
|
67
|
+
|
68
|
+
extensions: []
|
69
|
+
|
70
|
+
extra_rdoc_files:
|
71
|
+
- LICENSE
|
72
|
+
- README.md
|
73
|
+
files:
|
74
|
+
- .document
|
75
|
+
- .gitignore
|
76
|
+
- ISSUES
|
77
|
+
- LICENSE
|
78
|
+
- README.md
|
79
|
+
- Rakefile
|
80
|
+
- VERSION
|
81
|
+
- benchmarks/jruby
|
82
|
+
- example/http_0mq.rb
|
83
|
+
- example/lobster.ru
|
84
|
+
- example/rack_handler.rb
|
85
|
+
- lib/connection.rb
|
86
|
+
- lib/fiber_handler.rb
|
87
|
+
- lib/handler.rb
|
88
|
+
- lib/m2r.rb
|
89
|
+
- lib/request.rb
|
90
|
+
- m2r.gemspec
|
91
|
+
- test/helper.rb
|
92
|
+
- test/test_m2r.rb
|
93
|
+
has_rdoc: true
|
94
|
+
homepage: http://github.com/perplexes/m2r
|
95
|
+
licenses: []
|
96
|
+
|
97
|
+
post_install_message:
|
98
|
+
rdoc_options:
|
99
|
+
- --charset=UTF-8
|
100
|
+
require_paths:
|
101
|
+
- lib
|
102
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
103
|
+
none: false
|
104
|
+
requirements:
|
105
|
+
- - ">="
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
hash: 3
|
108
|
+
segments:
|
109
|
+
- 0
|
110
|
+
version: "0"
|
111
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
112
|
+
none: false
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
hash: 3
|
117
|
+
segments:
|
118
|
+
- 0
|
119
|
+
version: "0"
|
120
|
+
requirements: []
|
121
|
+
|
122
|
+
rubyforge_project:
|
123
|
+
rubygems_version: 1.3.7
|
124
|
+
signing_key:
|
125
|
+
specification_version: 3
|
126
|
+
summary: Mongrel2 interface and handler library for JRuby
|
127
|
+
test_files:
|
128
|
+
- test/helper.rb
|
129
|
+
- test/test_m2r.rb
|