m2r 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
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
+
@@ -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
@@ -0,0 +1,7 @@
1
+ require 'rack/lobster'
2
+ $: << ::File.dirname(__FILE__)
3
+ require 'rack_handler'
4
+
5
+ use Rack::ShowExceptions
6
+ puts "Lobster at http://localhost:6767/handlertest"
7
+ Rack::Handler::Mongrel2.run Rack::Lobster.new
@@ -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
@@ -0,0 +1,3 @@
1
+ require 'connection'
2
+ require 'request'
3
+ require 'handler'
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
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'm2r'
8
+
9
+ class Test::Unit::TestCase
10
+ end
data/test/test_m2r.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'helper'
2
+
3
+ class TestM2r < Test::Unit::TestCase
4
+ should "probably rename this file and start testing for real" do
5
+ flunk "hey buddy, you should probably rename this file and start testing for real"
6
+ end
7
+ end
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