m2r 0.0.2
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/.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
|