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 +7 -0
- data/LICENSE +20 -0
- data/README.md +67 -0
- data/Rakefile +127 -0
- data/em-rack-mongrel2.gemspec +84 -0
- data/example/.gitignore +4 -0
- data/example/async_sinatra/app.rb +14 -0
- data/example/async_sinatra/config.ru +8 -0
- data/example/mongrel2.conf +30 -0
- data/example/sinatra/.gitignore +3 -0
- data/example/sinatra/app.rb +7 -0
- data/example/sinatra/config.ru +8 -0
- data/lib/mongrel2/connection.rb +75 -0
- data/lib/mongrel2/request.rb +76 -0
- data/lib/mongrel2/response.rb +70 -0
- data/lib/mongrel2.rb +14 -0
- data/lib/rack/handler/mongrel2.rb +37 -0
- data/spec/request_spec.rb +58 -0
- data/spec/response_spec.rb +30 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +3 -0
- metadata +166 -0
data/Gemfile
ADDED
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
|
data/example/.gitignore
ADDED
@@ -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,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
|
data/spec/spec_helper.rb
ADDED
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
|