em-rack-mongrel2 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ # For development
6
+ gem 'yajl-ruby', '~> 0.7.8', :require => 'yajl'
7
+ gem "async_sinatra", "~> 0.5.0"
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010, 2011 Daniel Huckstep
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,67 @@
1
+ # em-rack-mongrel2
2
+
3
+ This is a Mongrel2 Rack handler that supports EventMachine and async response.
4
+
5
+ I folked this from darkhelmet's rack-mongrel2 so that I can rack up async_sinatra apps.
6
+ Tested on async_sinatra but will be able to run any Rack apps that requires an async web server such as Thin.
7
+
8
+ This is my very first contribution to Ruby ecosystem. Let me know if I've done wrong.
9
+ Pull requests and feature requests are very welcome!
10
+
11
+ ## How to run examples
12
+
13
+ Clone the repository.
14
+
15
+ git clone https://github.com/ichiban/em-rack-mongrel2.git
16
+
17
+ Download all dependencies.
18
+
19
+ cd em-rack-mongrel2
20
+ bundle install
21
+
22
+ Run Mongrel2.
23
+
24
+ cd example
25
+ mkdir -p tmp/pids logs
26
+ m2sh load
27
+ m2sh start -name main
28
+
29
+ Run a simple sinatra example or
30
+
31
+ cd sinatra
32
+ export RACK_MONGREL2_SEND=tcp://127.0.0.1:9996
33
+ export RACK_MONGREL2_RECV=tcp://127.0.0.1:9997
34
+ export RACK_MONGREL2_UUID=9539ED88-1B33-4D19-A9F9-283E5BF11AC7
35
+ rackup -s Mongrel2
36
+
37
+ Run an async sinatra example.
38
+
39
+ cd async_sinatra
40
+ export RACK_MONGREL2_SEND=tcp://127.0.0.1:9998
41
+ export RACK_MONGREL2_RECV=tcp://127.0.0.1:9999
42
+ export RACK_MONGREL2_UUID=AEE66029-E420-42E7-A7C8-6C37BBFC7B9F
43
+ rackup -s Mongrel2
44
+
45
+ ## How to use in your projects (work in progress)
46
+
47
+ 1. Get mongrel2 installed (http://mongrel2.org/wiki/quick_start.html)
48
+ 1. Get your config for mongrel2 setup (see example directory)
49
+ 1. Add it to your Gemfile (gem 'em-rack-mongrel2', '~> 0.0.0' # This is not available yet.)
50
+ 1. You also need some sort of JSON parsing library installed, like Yajl or JSON (gem i yajl-ruby or gem i json). json-jruby will work too
51
+ 1. Run Mongrel2
52
+ 1. Run your rack application
53
+
54
+ ## Note on Patches/Pull Requests
55
+
56
+ * Fork the project.
57
+ * Make your feature addition or bug fix.
58
+ * Add tests for it. This is important so I don't break it in a
59
+ future version unintentionally.
60
+ * Commit, do not mess with rakefile, version, or history.
61
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
62
+ * Send me a pull request. Bonus points for topic branches.
63
+
64
+ ## Copyright
65
+
66
+ * Original project Copyright (c) 2010, 2011 Daniel Huckstep. See LICENSE for details.
67
+ * This derivative project Copyright (c) 2011 ICHIBANGASE, Yutaka.
data/Rakefile ADDED
@@ -0,0 +1,127 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'date'
4
+
5
+ #############################################################################
6
+ #
7
+ # Helper functions
8
+ #
9
+ #############################################################################
10
+
11
+ def name
12
+ @name ||= Dir['*.gemspec'].first.split('.').first
13
+ end
14
+
15
+ def version
16
+ line = File.read("lib/mongrel2.rb")[/^\s*VERSION\s*=\s*.*/]
17
+ line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
18
+ end
19
+
20
+ def date
21
+ Date.today.to_s
22
+ end
23
+
24
+ def rubyforge_project
25
+ name
26
+ end
27
+
28
+ def gemspec_file
29
+ "#{name}.gemspec"
30
+ end
31
+
32
+ def gem_file
33
+ "#{name}-#{version}.gem"
34
+ end
35
+
36
+ def replace_header(head, header_name)
37
+ head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
38
+ end
39
+
40
+ #############################################################################
41
+ #
42
+ # Standard tasks
43
+ #
44
+ #############################################################################
45
+
46
+ task :default => :spec
47
+
48
+ require 'rspec/core/rake_task'
49
+ RSpec::Core::RakeTask.new(:spec) do |t|
50
+ t.ruby_opts = ['-Ilib', '-Ispec']
51
+ t.pattern = 'spec/**/*_spec.rb'
52
+ end
53
+
54
+ desc "Open an irb session preloaded with this library"
55
+ task :console do
56
+ sh "irb -rubygems -r ./lib/#{name}.rb"
57
+ end
58
+
59
+ #############################################################################
60
+ #
61
+ # Custom tasks (add your own tasks here)
62
+ #
63
+ #############################################################################
64
+
65
+ begin
66
+ require 'yard'
67
+ YARD::Rake::YardocTask.new
68
+ rescue LoadError
69
+ task :yardoc do
70
+ abort 'YARD is not available. In order to run yardoc, you must: `gem i yard`'
71
+ end
72
+ end
73
+
74
+ #############################################################################
75
+ #
76
+ # Packaging tasks
77
+ #
78
+ #############################################################################
79
+
80
+ desc "Create tag v#{version} and build and push #{gem_file} to Rubygems"
81
+ task :release => :build do
82
+ unless `git branch` =~ /^\* master$/
83
+ puts "You must be on the master branch to release!"
84
+ exit!
85
+ end
86
+ sh "git commit --allow-empty -a -m 'Release #{version}'"
87
+ sh "git tag v#{version}"
88
+ sh "git push origin master"
89
+ sh "git push origin v#{version}"
90
+ sh "gem push pkg/#{name}-#{version}.gem"
91
+ end
92
+
93
+ desc "Build #{gem_file} into the pkg directory"
94
+ task :build => :gemspec do
95
+ sh "mkdir -p pkg"
96
+ sh "gem build #{gemspec_file}"
97
+ sh "mv #{gem_file} pkg"
98
+ end
99
+
100
+ desc "Generate #{gemspec_file}"
101
+ task :gemspec do
102
+ # read spec file and split out manifest section
103
+ spec = File.read(gemspec_file)
104
+ head, manifest, tail = spec.split(" # = MANIFEST =\n")
105
+
106
+ # replace name version and date
107
+ replace_header(head, :name)
108
+ replace_header(head, :version)
109
+ replace_header(head, :date)
110
+ #comment this out if your rubyforge_project has a different name
111
+ replace_header(head, :rubyforge_project)
112
+
113
+ # determine file list from git ls-files
114
+ files = `git ls-files`.
115
+ split("\n").
116
+ sort.
117
+ reject { |file| file =~ /^\./ }.
118
+ reject { |file| file =~ /^(rdoc|pkg)/ }.
119
+ map { |file| " #{file}" }.
120
+ join("\n")
121
+
122
+ # piece file back together and write
123
+ manifest = " s.files = %w[\n#{files}\n ]\n"
124
+ spec = [head, manifest, tail].join(" # = MANIFEST =\n")
125
+ File.open(gemspec_file, 'w') { |io| io.write(spec) }
126
+ puts "Updated #{gemspec_file}"
127
+ end
@@ -0,0 +1,84 @@
1
+ ## This is the rakegem gemspec template. Make sure you read and understand
2
+ ## all of the comments. Some sections require modification, and others can
3
+ ## be deleted if you don't need them. Once you understand the contents of
4
+ ## this file, feel free to delete any comments that begin with two hash marks.
5
+ ## You can find comprehensive Gem::Specification documentation, at
6
+ ## http://docs.rubygems.org/read/chapter/20
7
+ Gem::Specification.new do |s|
8
+ s.specification_version = 2 if s.respond_to? :specification_version=
9
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
10
+ s.rubygems_version = '1.3.5'
11
+
12
+ ## Leave these as is they will be modified for you by the rake gemspec task.
13
+ ## If your rubyforge_project name is different, then edit it and comment out
14
+ ## the sub! line in the Rakefile
15
+ s.name = 'em-rack-mongrel2'
16
+ s.version = '0.1.0'
17
+ s.date = '2012-01-13'
18
+ s.rubyforge_project = 'em-rack-mongrel2'
19
+
20
+ ## Make sure your summary is short. The description may be as long
21
+ ## as you like.
22
+ s.summary = %Q{A Mongrel2 Rack handler with EventMachine.}
23
+ s.description = %Q{A Rack handler for the Mongrel2 web server with EventMachine support, by Zed Shaw. http://mongrel2.org/}
24
+
25
+ ## List the primary authors. If there are a bunch of authors, it's probably
26
+ ## better to set the email to an email list or something. If you don't have
27
+ ## a custom homepage, consider using your GitHub URL or the like.
28
+ s.authors = ['ICHIBANGASE, Yutaka']
29
+ s.email = 'yichiban@gmail.com'
30
+ s.homepage = 'http://github.com/ichiban/em-rack-mongrel2'
31
+
32
+ ## This gets added to the $LOAD_PATH so that 'lib/NAME.rb' can be required as
33
+ ## require 'NAME.rb' or'/lib/NAME/file.rb' can be as require 'NAME/file.rb'
34
+ s.require_paths = %w[lib]
35
+
36
+ ## Specify any RDoc options here. You'll want to add your README and
37
+ ## LICENSE files to the extra_rdoc_files list.
38
+ s.rdoc_options = ["--charset=UTF-8"]
39
+ s.extra_rdoc_files = %w[README.md LICENSE]
40
+
41
+ ## List your runtime dependencies here. Runtime dependencies are those
42
+ ## that are needed for an end user to actually USE your code.
43
+ s.add_dependency('ffi', ['~> 1.0.0'])
44
+ s.add_dependency('em-zeromq', ['~> 0.2.2'])
45
+
46
+ ## List your development dependencies here. Development dependencies are
47
+ ## those that are only needed during development
48
+ s.add_development_dependency('rspec', ['~> 2.3.0'])
49
+ s.add_development_dependency('fuubar', ['~> 0.0.3'])
50
+ s.add_development_dependency('yard', ['~> 0.6.4'])
51
+
52
+ ## Leave this section as-is. It will be automatically generated from the
53
+ ## contents of your Git repository via the gemspec task. DO NOT REMOVE
54
+ ## THE MANIFEST COMMENTS, they are used as delimiters by the task.
55
+ # = MANIFEST =
56
+ s.files = %w[
57
+ Gemfile
58
+ LICENSE
59
+ README.md
60
+ Rakefile
61
+ em-rack-mongrel2.gemspec
62
+ example/.gitignore
63
+ example/async_sinatra/app.rb
64
+ example/async_sinatra/config.ru
65
+ example/mongrel2.conf
66
+ example/sinatra/.gitignore
67
+ example/sinatra/app.rb
68
+ example/sinatra/config.ru
69
+ lib/mongrel2.rb
70
+ lib/mongrel2/connection.rb
71
+ lib/mongrel2/request.rb
72
+ lib/mongrel2/response.rb
73
+ lib/rack/handler/mongrel2.rb
74
+ spec/request_spec.rb
75
+ spec/response_spec.rb
76
+ spec/spec.opts
77
+ spec/spec_helper.rb
78
+ ]
79
+ # = MANIFEST =
80
+
81
+ ## Test files will be grabbed from the file list. Make sure the path glob
82
+ ## matches what you actually use.
83
+ s.test_files = s.files.select { |path| path =~ /^spec\/.*_spec\.rb/ }
84
+ end
@@ -0,0 +1,4 @@
1
+ *.sqlite
2
+ logs/
3
+ tmp/
4
+
@@ -0,0 +1,14 @@
1
+ require 'sinatra'
2
+ require 'sinatra/async'
3
+ require 'yajl/json_gem'
4
+
5
+ class AsyncExample < Sinatra::Base
6
+ register Sinatra::Async
7
+
8
+ aget '*' do
9
+ EM.add_timer(5) do
10
+ body request.env.to_json
11
+ end
12
+ end
13
+ end
14
+
@@ -0,0 +1,8 @@
1
+ $: << File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'lib'))
2
+ $:.unshift(File.expand_path('.')) # Ruby 1.9 doesn't have . in the load path...
3
+
4
+ require 'rack/handler/mongrel2'
5
+ require 'app'
6
+
7
+ Rack::Handler::Mongrel2.run AsyncExample, :uuid => 'sinatra', :block => true
8
+ exit(0)
@@ -0,0 +1,30 @@
1
+ darkblog2 = Host(name='localhost', routes={
2
+ '/': Handler(send_spec='tcp://127.0.0.1:9997',
3
+ send_ident='9539ED88-1B33-4D19-A9F9-283E5BF11AC7',
4
+ recv_spec='tcp://127.0.0.1:9996',
5
+ recv_ident='')
6
+ '/async/': Handler(send_spec='tcp://127.0.0.1:9999',
7
+ send_ident='AEE66029-E420-42E7-A7C8-6C37BBFC7B9F',
8
+ recv_spec='tcp://127.0.0.1:9998',
9
+ recv_ident='')
10
+ })
11
+
12
+ main = Server(
13
+ uuid='94601E4B-A770-4B7D-930D-CE3A484B5280',
14
+ chroot='.',
15
+ access_log='/logs/mongrel2_access.log',
16
+ error_log='/logs/mongrel2_error.log',
17
+ pid_file='/tmp/pids/mongrel2.pid',
18
+ default_host='localhost',
19
+ name='main',
20
+ port=8080,
21
+ hosts=[darkblog2]
22
+ )
23
+
24
+
25
+ settings = {
26
+ 'zeromq.threads': 1,
27
+ 'control_port': 'ipc://tmp/mongrel2_control'
28
+ }
29
+
30
+ servers = [main]
@@ -0,0 +1,3 @@
1
+ run/
2
+ tmp/
3
+ config.sqlite
@@ -0,0 +1,7 @@
1
+ require 'sinatra'
2
+ require 'yajl/json_gem'
3
+
4
+ get '*' do
5
+ sleep(5)
6
+ request.env.to_json
7
+ end
@@ -0,0 +1,8 @@
1
+ $: << File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'lib'))
2
+ $:.unshift(File.expand_path('.')) # Ruby 1.9 doesn't have . in the load path...
3
+
4
+ require 'rack/handler/mongrel2'
5
+ require 'app'
6
+
7
+ Rack::Handler::Mongrel2.run Sinatra::Application, :uuid => 'sinatra', :block => true
8
+ exit(0)
@@ -0,0 +1,75 @@
1
+ require 'em-zeromq'
2
+ require 'mongrel2/request'
3
+ require 'mongrel2/response'
4
+
5
+ module Mongrel2
6
+ class Connection
7
+ attr_reader :received
8
+ @context = nil
9
+
10
+ def self.context
11
+ @context ||= EM::ZeroMQ::Context.new(1)
12
+ end
13
+
14
+ def initialize(uuid, sub, pub, app)
15
+ @uuid, @sub, @pub, @app = uuid, sub, pub, app
16
+
17
+ # Connect to receive requests
18
+ @reqs = self.class.context.connect(ZMQ::PULL, sub, self)
19
+
20
+ # Connect to send responses
21
+ @resp = self.class.context.connect(ZMQ::PUB, pub, nil, :identity => uuid)
22
+ end
23
+
24
+ def on_readable(socket, messages)
25
+ messages.each do |msg|
26
+ req = msg.nil? ? nil : Request.parse(msg.copy_out_string, self)
27
+ next if req.nil? || req.disconnect?
28
+ process req
29
+ end
30
+ end
31
+
32
+ def process(req)
33
+ pre = Proc.new do
34
+ method(:pre_process).call(req)
35
+ end
36
+
37
+ post = Proc.new do |resp|
38
+ method(:post_process).call(resp, req)
39
+ end
40
+
41
+ EM.defer pre, post
42
+ end
43
+
44
+ def pre_process(req)
45
+ status, headers, rack_response = -1, {}, []
46
+
47
+ catch(:async) do
48
+ status, headers, rack_response = @app.call(req.env)
49
+ end
50
+
51
+ [status, headers, rack_response]
52
+ end
53
+
54
+ def post_process(response, req)
55
+ status, headers, rack_response = *response
56
+ # Status code -1 indicates that we're going to respond later (async).
57
+ return if -1 == status
58
+
59
+ body = ''
60
+ rack_response.each { |b| body << b }
61
+ reply req, body, status, headers
62
+ end
63
+
64
+ def reply(req, body, status = 200, headers = {})
65
+ resp = Response.new(@resp)
66
+ resp.send_http req, body, status, headers
67
+ resp.close req if req.close?
68
+ end
69
+
70
+ def close
71
+ # I think I should be able to just close the context
72
+ self.class.context.close rescue nil
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,76 @@
1
+ require 'mongrel2'
2
+ require 'rack'
3
+ require 'eventmachine'
4
+
5
+ module Mongrel2
6
+ class Request
7
+ attr_reader :headers, :body, :uuid, :conn_id, :path, :connection
8
+
9
+ class << self
10
+ def parse(msg, connection)
11
+ # UUID CONN_ID PATH SIZE:HEADERS,SIZE:BODY,
12
+ uuid, conn_id, path, rest = msg.split(' ', 4)
13
+ headers, rest = parse_netstring(rest)
14
+ body, _ = parse_netstring(rest)
15
+ headers = Mongrel2::JSON.parse(headers)
16
+ new(uuid, conn_id, path, headers, body, connection)
17
+ end
18
+
19
+ def parse_netstring(ns)
20
+ # SIZE:HEADERS,
21
+
22
+ len, rest = ns.split(':', 2)
23
+ len = len.to_i
24
+ raise "Netstring did not end in ','" unless rest[len].chr == ','
25
+ [rest[0, len], rest[(len + 1)..-1]]
26
+ end
27
+ end
28
+
29
+ def initialize(uuid, conn_id, path, headers, body, connection)
30
+ @uuid, @conn_id, @path, @headers, @body = uuid, conn_id, path, headers, body
31
+ @data = headers['METHOD'] == 'JSON' ? Mongrel2::JSON.parse(body) : {}
32
+ @connection = connection
33
+ end
34
+
35
+ def disconnect?
36
+ headers['METHOD'] == 'JSON' && @data['type'] == 'disconnect'
37
+ end
38
+
39
+ def close?
40
+ headers['connection'] == 'close' || headers['VERSION'] == 'HTTP/1.0'
41
+ end
42
+
43
+ def env
44
+ script_name = ENV['RACK_RELATIVE_URL_ROOT'] || headers['PATTERN'].split('(', 2).first.gsub(/\/$/, '')
45
+ env = {
46
+ 'rack.version' => Rack::VERSION,
47
+ 'rack.url_scheme' => 'http', # Only HTTP for now
48
+ 'rack.input' => StringIO.new(body),
49
+ 'rack.errors' => $stderr,
50
+ 'rack.multithread' => true,
51
+ 'rack.multiprocess' => true,
52
+ 'rack.run_once' => false,
53
+ 'mongrel2.pattern' => headers['PATTERN'],
54
+ 'REQUEST_METHOD' => headers['METHOD'],
55
+ 'CONTENT_TYPE' => headers['content-type'],
56
+ 'SCRIPT_NAME' => script_name,
57
+ 'PATH_INFO' => headers['PATH'].gsub(script_name, ''),
58
+ 'QUERY_STRING' => headers['QUERY'] || '',
59
+ 'async.callback' => Proc.new { |resp|
60
+ connection.method(:post_process).call(resp, self)
61
+ },
62
+ 'async.close' => EM::DefaultDeferrable.new
63
+ }
64
+
65
+ env['SERVER_NAME'], env['SERVER_PORT'] = headers['host'].split(':', 2)
66
+ headers.each do |key, val|
67
+ unless key =~ /content_(type|length)/i
68
+ key = "HTTP_#{key.upcase.gsub('-', '_')}"
69
+ end
70
+ env[key] = val
71
+ end
72
+
73
+ env
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,70 @@
1
+ module Mongrel2
2
+ class Response
3
+ StatusMessage = {
4
+ 100 => 'Continue',
5
+ 101 => 'Switching Protocols',
6
+ 200 => 'OK',
7
+ 201 => 'Created',
8
+ 202 => 'Accepted',
9
+ 203 => 'Non-Authoritative Information',
10
+ 204 => 'No Content',
11
+ 205 => 'Reset Content',
12
+ 206 => 'Partial Content',
13
+ 300 => 'Multiple Choices',
14
+ 301 => 'Moved Permanently',
15
+ 302 => 'Found',
16
+ 303 => 'See Other',
17
+ 304 => 'Not Modified',
18
+ 305 => 'Use Proxy',
19
+ 307 => 'Temporary Redirect',
20
+ 400 => 'Bad Request',
21
+ 401 => 'Unauthorized',
22
+ 402 => 'Payment Required',
23
+ 403 => 'Forbidden',
24
+ 404 => 'Not Found',
25
+ 405 => 'Method Not Allowed',
26
+ 406 => 'Not Acceptable',
27
+ 407 => 'Proxy Authentication Required',
28
+ 408 => 'Request Timeout',
29
+ 409 => 'Conflict',
30
+ 410 => 'Gone',
31
+ 411 => 'Length Required',
32
+ 412 => 'Precondition Failed',
33
+ 413 => 'Request Entity Too Large',
34
+ 414 => 'Request-URI Too Large',
35
+ 415 => 'Unsupported Media Type',
36
+ 416 => 'Request Range Not Satisfiable',
37
+ 417 => 'Expectation Failed',
38
+ 500 => 'Internal Server Error',
39
+ 501 => 'Not Implemented',
40
+ 502 => 'Bad Gateway',
41
+ 503 => 'Service Unavailable',
42
+ 504 => 'Gateway Timeout',
43
+ 505 => 'HTTP Version Not Supported'
44
+ }
45
+
46
+ def initialize(resp)
47
+ @resp = resp
48
+ end
49
+
50
+ def send_http(req, body, status, headers)
51
+ send_resp(req.uuid, req.conn_id, build_http_response(body, status, headers))
52
+ end
53
+
54
+ def close(req)
55
+ send_resp(req.uuid, req.conn_id, '')
56
+ end
57
+
58
+ private
59
+
60
+ def send_resp(uuid, conn_id, data)
61
+ @resp.send_msg('%s %d:%s, %s' % [uuid, conn_id.size, conn_id, data])
62
+ end
63
+
64
+ def build_http_response(body, status, headers)
65
+ headers['Content-Length'] = body.size.to_s
66
+ headers = headers.map{ |k, v| '%s: %s' % [k,v] }.join("\r\n")
67
+ "HTTP/1.1 #{status} #{StatusMessage[status.to_i]}\r\n#{headers}\r\n\r\n#{body}"
68
+ end
69
+ end
70
+ end
data/lib/mongrel2.rb ADDED
@@ -0,0 +1,14 @@
1
+ begin
2
+ require 'yajl'
3
+ rescue LoadError
4
+ begin
5
+ require 'json'
6
+ rescue LoadError
7
+ raise "You need either the yajl-ruby or json gems present in order to parse JSON!"
8
+ end
9
+ end
10
+
11
+ module Mongrel2
12
+ JSON = Object.const_defined?('Yajl') ? ::Yajl::Parser : ::JSON
13
+ VERSION = '0.1.0'
14
+ end
@@ -0,0 +1,37 @@
1
+ require 'mongrel2/connection'
2
+ require 'stringio'
3
+ require 'eventmachine'
4
+
5
+ module Rack
6
+ module Handler
7
+ class Mongrel2
8
+ class << self
9
+ def run(app, options = {})
10
+ options = {
11
+ :recv => ENV['RACK_MONGREL2_RECV'] || 'tcp://127.0.0.1:9997',
12
+ :send => ENV['RACK_MONGREL2_SEND'] || 'tcp://127.0.0.1:9996',
13
+ :uuid => ENV['RACK_MONGREL2_UUID']
14
+ }.merge(options)
15
+
16
+ raise ArgumentError.new('Must specify an :uuid or set RACK_MONGREL2_UUID') if options[:uuid].nil?
17
+
18
+ conn = nil
19
+
20
+ EM.run do
21
+ conn = ::Mongrel2::Connection.new(options[:uuid], options[:recv], options[:send], app)
22
+
23
+ # This doesn't work at all until zmq fixes their shit (in 2.1.x I think), but trap it now anyway.
24
+ %w(INT TERM KILL).each do |sig|
25
+ trap(sig) do
26
+ conn.close
27
+ EM.stop
28
+ end
29
+ end
30
+ end
31
+ ensure
32
+ conn.close if conn.respond_to?(:close)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,58 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+ require 'mongrel2/request'
3
+
4
+ describe Mongrel2::Request do
5
+ it 'should parse a netstring and ignore the contents of the netstring as well as the trailing comma' do
6
+ netstring = '9:aoeu:snth,'
7
+ result = Mongrel2::Request.parse_netstring(netstring)
8
+ result.length.should == 2
9
+ result[0].length.should == 9
10
+ result[0].should eql('aoeu:snth')
11
+ end
12
+
13
+ it 'should parse a netstring made up of multiple netstrings' do
14
+ netstring = '9:aoeu:snth,16:aeou snth qwerty,'
15
+ result = Mongrel2::Request.parse_netstring(netstring)
16
+ result.length.should == 2
17
+ result[0].length.should == 9
18
+ result[0].should eql('aoeu:snth')
19
+ result[1].length.should == 20
20
+ result[1].should eql('16:aeou snth qwerty,')
21
+ end
22
+
23
+ it 'should fail if the netstring does not end in a comma' do
24
+ expect { Mongrel2::Request.parse_netstring('3:foo') }.to raise_error(NameError)
25
+ end
26
+
27
+ it "should parse a Mongrel2 message and have all parts populated" do
28
+ netstring = "UUID CON PATH 253:{\"PATH\":\"/\",\"user-agent\":\"curl/7.19.7 (universal-apple-darwin10.0) libcurl/7.19.7 OpenSSL/0.9.8l zlib/1.2.3\",\"host\":\"localhost:6767\",\"accept\":\"*/*\",\"connection\":\"close\",\"x-forwarded-for\":\"::1\",\"METHOD\":\"GET\",\"VERSION\":\"HTTP/1.1\",\"URI\":\"/\",\"PATTERN\":\"/\"},0:,"
29
+ r = Mongrel2::Request.parse(netstring, double())
30
+ r.should_not be_nil
31
+ r.uuid.should eql('UUID')
32
+ r.conn_id.should eql('CON')
33
+ r.path.should eql('PATH')
34
+ r.body.length.should == 0
35
+ r.headers.length.should == 10
36
+ r.headers['PATH'].should eql('/')
37
+ r.headers['user-agent'].should eql('curl/7.19.7 (universal-apple-darwin10.0) libcurl/7.19.7 OpenSSL/0.9.8l zlib/1.2.3')
38
+ r.headers['host'].should eql('localhost:6767')
39
+ r.headers['accept'].should eql('*/*')
40
+ r.headers['x-forwarded-for'].should eql('::1')
41
+ r.headers['METHOD'].should eql('GET')
42
+ r.headers['VERSION'].should eql('HTTP/1.1')
43
+ r.headers['URI'].should eql('/')
44
+ r.headers['PATTERN'].should eql('/')
45
+ r.close?.should be_true
46
+ end
47
+
48
+ it "should return rack env with async callbacks" do
49
+ netstring = "UUID CON PATH 253:{\"PATH\":\"/\",\"user-agent\":\"curl/7.19.7 (universal-apple-darwin10.0) libcurl/7.19.7 OpenSSL/0.9.8l zlib/1.2.3\",\"host\":\"localhost:6767\",\"accept\":\"*/*\",\"connection\":\"close\",\"x-forwarded-for\":\"::1\",\"METHOD\":\"GET\",\"VERSION\":\"HTTP/1.1\",\"URI\":\"/\",\"PATTERN\":\"/\"},0:,"
50
+ response = double("response")
51
+ connection = double("connection")
52
+ r = Mongrel2::Request.parse(netstring, connection)
53
+ connection.should_receive(:post_process).with(response, r)
54
+ env = r.env
55
+ env['async.callback'].call(response)
56
+ env['async.close'].should_not be_nil
57
+ end
58
+ end
@@ -0,0 +1,30 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+ require 'mongrel2/response'
3
+
4
+ describe Mongrel2::Response do
5
+ before(:each) do
6
+ @req = double()
7
+ @resp = double()
8
+ @response = Mongrel2::Response.new(@resp)
9
+ end
10
+
11
+ it 'should build the HTTP request format' do
12
+ @req.should_receive(:uuid) { 'UUID' }
13
+ @req.should_receive(:conn_id) { 'CONN_ID' }
14
+
15
+ httpreq = "UUID 7:CONN_ID, HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\nBoo!"
16
+ @resp.should_receive(:send_msg).with(httpreq)
17
+
18
+ @response.send_http(@req, 'Boo!', 200, {})
19
+ end
20
+
21
+ it 'should send a blank response to close the response' do
22
+ @req.should_receive(:uuid) { 'UUID' }
23
+ @req.should_receive(:conn_id) { 'CONN_ID' }
24
+
25
+ httpreq = 'UUID 7:CONN_ID, '
26
+ @resp.should_receive(:send_msg).with(httpreq)
27
+
28
+ @response.close(@req)
29
+ end
30
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,3 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'rspec'
metadata ADDED
@@ -0,0 +1,166 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: em-rack-mongrel2
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - ICHIBANGASE, Yutaka
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-01-13 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: ffi
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ hash: 23
29
+ segments:
30
+ - 1
31
+ - 0
32
+ - 0
33
+ version: 1.0.0
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: em-zeromq
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ hash: 19
45
+ segments:
46
+ - 0
47
+ - 2
48
+ - 2
49
+ version: 0.2.2
50
+ type: :runtime
51
+ version_requirements: *id002
52
+ - !ruby/object:Gem::Dependency
53
+ name: rspec
54
+ prerelease: false
55
+ requirement: &id003 !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ~>
59
+ - !ruby/object:Gem::Version
60
+ hash: 3
61
+ segments:
62
+ - 2
63
+ - 3
64
+ - 0
65
+ version: 2.3.0
66
+ type: :development
67
+ version_requirements: *id003
68
+ - !ruby/object:Gem::Dependency
69
+ name: fuubar
70
+ prerelease: false
71
+ requirement: &id004 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ~>
75
+ - !ruby/object:Gem::Version
76
+ hash: 25
77
+ segments:
78
+ - 0
79
+ - 0
80
+ - 3
81
+ version: 0.0.3
82
+ type: :development
83
+ version_requirements: *id004
84
+ - !ruby/object:Gem::Dependency
85
+ name: yard
86
+ prerelease: false
87
+ requirement: &id005 !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ~>
91
+ - !ruby/object:Gem::Version
92
+ hash: 15
93
+ segments:
94
+ - 0
95
+ - 6
96
+ - 4
97
+ version: 0.6.4
98
+ type: :development
99
+ version_requirements: *id005
100
+ description: A Rack handler for the Mongrel2 web server with EventMachine support, by Zed Shaw. http://mongrel2.org/
101
+ email: yichiban@gmail.com
102
+ executables: []
103
+
104
+ extensions: []
105
+
106
+ extra_rdoc_files:
107
+ - README.md
108
+ - LICENSE
109
+ files:
110
+ - Gemfile
111
+ - LICENSE
112
+ - README.md
113
+ - Rakefile
114
+ - em-rack-mongrel2.gemspec
115
+ - example/.gitignore
116
+ - example/async_sinatra/app.rb
117
+ - example/async_sinatra/config.ru
118
+ - example/mongrel2.conf
119
+ - example/sinatra/.gitignore
120
+ - example/sinatra/app.rb
121
+ - example/sinatra/config.ru
122
+ - lib/mongrel2.rb
123
+ - lib/mongrel2/connection.rb
124
+ - lib/mongrel2/request.rb
125
+ - lib/mongrel2/response.rb
126
+ - lib/rack/handler/mongrel2.rb
127
+ - spec/request_spec.rb
128
+ - spec/response_spec.rb
129
+ - spec/spec.opts
130
+ - spec/spec_helper.rb
131
+ homepage: http://github.com/ichiban/em-rack-mongrel2
132
+ licenses: []
133
+
134
+ post_install_message:
135
+ rdoc_options:
136
+ - --charset=UTF-8
137
+ require_paths:
138
+ - lib
139
+ required_ruby_version: !ruby/object:Gem::Requirement
140
+ none: false
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ hash: 3
145
+ segments:
146
+ - 0
147
+ version: "0"
148
+ required_rubygems_version: !ruby/object:Gem::Requirement
149
+ none: false
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ hash: 3
154
+ segments:
155
+ - 0
156
+ version: "0"
157
+ requirements: []
158
+
159
+ rubyforge_project: em-rack-mongrel2
160
+ rubygems_version: 1.8.11
161
+ signing_key:
162
+ specification_version: 2
163
+ summary: A Mongrel2 Rack handler with EventMachine.
164
+ test_files:
165
+ - spec/request_spec.rb
166
+ - spec/response_spec.rb