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 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