Skip to content

Commit

Permalink
It works! Add a first-pass rackup adapter which also works.
Browse files Browse the repository at this point in the history
  • Loading branch information
perplexes committed Jul 15, 2010
1 parent 8ecdf19 commit edc424b
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 21 deletions.
25 changes: 23 additions & 2 deletions README.md
@@ -1,3 +1,24 @@
This is almost verbatim from Zed Shaw's python connector for mongrel2, which you can see here: http://mongrel2.org/dir?ci=1bdfff8f050b97df&name=examples/python/mongrel2
m2r
===

You'll need a recent 1.9 ruby and FFI and http://github.com/chuckremes/ffi-rzmq, oh, and Zero MQ. Run: `ruby example/http_0mq.rb`
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).

Usage/Examples
-----

* `examples/http_0mq.rb` is a test little servlet thing (based on what comes with mongrel2)and there is also a rack handler at
* `examples/rack_handler.rb` is a Mongrel2 ruby handler rack handler mouthful, whose variables are probably a little off
* `examples/lobster.ru` is a rackup file using the Rack handler that'll serve Rack's funny little lobster app.

Run:
* `ruby examples/http_0mq.rb`, which with Mongrel2's test config will serve up at http://localhost:6767/handlertest
* `rackup examples/lobster.ru`, ditto, http://localhost:6767/handlertest

Installation
------------

* Ruby 1.9ish (RVM saved my life here)
* [FFI](http://github.com/ffi/ffi), `gem install ffi` should be fine
* [Zero MQ](http://www.zeromq.org/area:download)
* [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!
* Rack (gem install rack) if you want to run the rack example.
71 changes: 52 additions & 19 deletions connection.rb
Expand Up @@ -7,21 +7,11 @@
gem 'json'
require 'ffi-rzmq'
require 'json'

$: << File.dirname(__FILE__)
require 'request'

CTX = ZMQ::Context.new(1)
HTTP_FORMAT = "HTTP/1.1 %(code)s %(status)s\r\n%(headers)s\r\n\r\n%(body)s"

class String
# Match python's % function
def |(args)
args.inject(self.dup) do |copy, (key, val)|
copy.gsub!(/%\(#{key.to_s}\)/, val.to_s)
copy
end
end
end

module Mongrel2
# A Connection object manages the connection between your handler
Expand Down Expand Up @@ -86,8 +76,8 @@ def reply_json(req, data)
# Basic HTTP response mechanism which will take your body,
# any headers you've made, and encode them so that the
# browser gets them.
def reply_http(req, body, code=200, status="OK", headers={})
self.reply(req, http_response(body, code, status, headers))
def reply_http(req, body, code=200, headers={})
self.reply(req, http_response(body, code, headers))
end

# This lets you send a single message to many currently
Expand All @@ -108,17 +98,60 @@ def deliver_json(idents, data)
# Same as deliver, but builds an HTTP response, which means, yes,
# you can reply to multiple connected clients waiting for an HTTP
# response from one handler. Kinda cool.
def deliver_http(idents, body, code=200, status="OK", headers={})
self.deliver(idents, http_response(body, code, status, headers))
def deliver_http(idents, body, code=200, headers={})
self.deliver(idents, http_response(body, code, headers))
end

private
def http_response(body, code, status, headers)
payload = {'code' => code, 'status' => status, 'body' => body}
def http_response(body, code, headers)
headers['Content-Length'] = body.size
payload['headers'] = headers.map{|k, v| "%s: %s" % [k,v]}.join("\r\n")
headers_s = headers.map{|k, v| "%s: %s" % [k,v]}.join("\r\n")

HTTP_FORMAT | payload
"HTTP/1.1 #{code} #{StatusMessage[code.to_i]}\r\n#{headers_s}\r\n\r\n#{body}"
end

# From WEBrick: thanks dawg.
StatusMessage = {
100 => 'Continue',
101 => 'Switching Protocols',
200 => 'OK',
201 => 'Created',
202 => 'Accepted',
203 => 'Non-Authoritative Information',
204 => 'No Content',
205 => 'Reset Content',
206 => 'Partial Content',
300 => 'Multiple Choices',
301 => 'Moved Permanently',
302 => 'Found',
303 => 'See Other',
304 => 'Not Modified',
305 => 'Use Proxy',
307 => 'Temporary Redirect',
400 => 'Bad Request',
401 => 'Unauthorized',
402 => 'Payment Required',
403 => 'Forbidden',
404 => 'Not Found',
405 => 'Method Not Allowed',
406 => 'Not Acceptable',
407 => 'Proxy Authentication Required',
408 => 'Request Timeout',
409 => 'Conflict',
410 => 'Gone',
411 => 'Length Required',
412 => 'Precondition Failed',
413 => 'Request Entity Too Large',
414 => 'Request-URI Too Large',
415 => 'Unsupported Media Type',
416 => 'Request Range Not Satisfiable',
417 => 'Expectation Failed',
500 => 'Internal Server Error',
501 => 'Not Implemented',
502 => 'Bad Gateway',
503 => 'Service Unavailable',
504 => 'Gateway Timeout',
505 => 'HTTP Version Not Supported'
}
end # class Connection
end # mod Mongrel2
7 changes: 7 additions & 0 deletions example/lobster.ru
@@ -0,0 +1,7 @@
require 'rack/lobster'
$: << File.dirname(__FILE__)
require 'rack_handler'

use Rack::ShowExceptions
puts "Lobster at http://localhost:6767/handlertest"
Rack::Handler::Mongrel2Handler.run Rack::Lobster.new
57 changes: 57 additions & 0 deletions example/rack_handler.rb
@@ -0,0 +1,57 @@
require 'rubygems'
require 'rack'
require 'stringio'
# require 'ruby-debug'
# Debugger.start
# gem install ruby-debug19 -- --with-ruby-include=$HOME/.rvm/src/ruby-1.9.2-head

$: << File.expand_path(File.dirname(__FILE__) + '/..')
require 'connection'

$sender_id = "70D107AB-19F5-44AE-A2D0-2326A167D8D7"

module Rack
module Handler
class Mongrel2Handler
def self.run(app, receive = "tcp://127.0.0.1:9997", send = "tcp://127.0.0.1:9996")
conn = Mongrel2::Connection.new($sender_id, receive, send)
while true
puts "WAITING FOR REQUEST"

req = conn.recv

if req.is_disconnect
puts "DICONNECT"
next
end

env = {
"rack.version" => Rack::VERSION,
"rack.url_scheme" => "http",
"rack.input" => StringIO.new(req.body),
"rack.errors" => $stderr,
"rack.multithread" => true,
"rack.multiprocess" => true,
"rack.run_once" => false,
"REQUEST_METHOD" => req.headers["METHOD"],
"SCRIPT_NAME" => "",
"PATH_INFO" => env["PATH"],
"QUERY_STRING" => env["QUERY"]
}

env["SERVER_NAME"], env["SERVER_PORT"] = req.headers["Host"].split(':', 2)
req.headers.each do |key, val|
unless key =~ /content_(type|length)/i
key = "HTTP_#{key.upcase}"
end
env[key] = val
end

status, headers, rack_response = app.call(env)
conn.reply_http(req, rack_response.body.join, status, headers)
end
end
end
end
end

0 comments on commit edc424b

Please sign in to comment.