├── .document ├── .gitignore ├── .rspec ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── example ├── mongrel2.conf └── sinatra │ ├── .gitignore │ ├── app.rb │ ├── config.ru │ └── mongrel2.conf ├── lib ├── mongrel2.rb ├── mongrel2 │ ├── connection.rb │ ├── request.rb │ └── response.rb └── rack │ └── handler │ └── mongrel2.rb ├── rack-mongrel2.gemspec └── spec ├── request_spec.rb ├── response_spec.rb ├── spec.opts └── spec_helper.rb /.document: -------------------------------------------------------------------------------- 1 | README.rdoc 2 | lib/**/*.rb 3 | bin/* 4 | features/**/*.feature 5 | LICENSE 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 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 22 | .bundle/ 23 | *.rbc 24 | .rvmrc 25 | Gemfile.lock -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --colour 2 | --format Fuubar -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | 3 | gemspec 4 | 5 | # For development 6 | gem 'yajl-ruby', '~> 0.7.8', :require => 'yajl' 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 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. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Abandoned. Contact me if you want to take over this project. 2 | 3 | # rack-mongrel2 4 | 5 | The only Mongrel2 Rack handler you'll ever need. 6 | 7 | I wrote this because I wanted to learn Mongrel2, and I didn't like what was out there. I copy-pasted a lot of code from Colin Curtin's m2r project (http://github.com/perplexes/m2r), but I also changed and reorganized it into what I believe is a good setup for a proper rubygem. 8 | 9 | ## How to use 10 | 11 | 1. Get mongrel2 installed (http://mongrel2.org/wiki?name=GettingStarted) 12 | 1. Get your config for mongrel2 setup (see example directory) 13 | 1. Add it to your Gemfile 14 | 15 | gem 'rack-mongrel2', '~> 0.2.0', :require => nil 16 | 17 | 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 18 | 1. Run Mongrel2 19 | 1. Run your rails application 20 | 21 | RACK_MONGREL2_UUID= rails s Mongrel2 22 | 23 | 1. Profit! 24 | 25 | Check out the blog post too: http://blog.darkhax.com/2010/10/26/deploying-your-ruby-app-with-mongrel2 26 | 27 | ## Advanced setup 28 | 29 | ### Using custom send and receive socket values 30 | 31 | The Mongrel2 rack handler defaults the receive socket to `tcp://127.0.0.1:9997` and the send socket to `tcp://127.0.0.1:9996`. 32 | 33 | To use different values set the `RACK_MONGREL2_RECV` and `RACK_MONGREL2_SEND` environment variables. For example: 34 | 35 | export RACK_MONGREL2_RECV= 36 | export RACK_MONGREL2_SEND= 37 | export RACK_MONGREL2_UUID= 38 | rails server Mongrel2 39 | 40 | If for example your Mongrel2 handler configuration contains: 41 | 42 | ... 43 | recv_spec='tcp://127.0.0.1:7771' 44 | send_spec='tcp://127.0.0.1:7772', 45 | send_ident='42ffdda3-d151-41b1-923f-899ef6fc530a', 46 | ... 47 | 48 | Then you will need to start your Rails application using: 49 | 50 | export RACK_MONGREL2_RECV=tcp://127.0.0.1:7772 51 | export RACK_MONGREL2_SEND=tcp://127.0.0.1:7771 52 | export RACK_MONGREL2_UUID=42ffdda3-d151-41b1-923f-899ef6fc530a 53 | rails server Mongrel2 54 | 55 | ## Thanks! 56 | 57 | * [Kevin Williams](https://github.com/kevwil) for PULL, specs, and other things. 58 | 59 | ## Note on Patches/Pull Requests 60 | 61 | * Fork the project. 62 | * Make your feature addition or bug fix. 63 | * Add tests for it. This is important so I don't break it in a 64 | future version unintentionally. 65 | * Commit, do not mess with rakefile, version, or history. 66 | (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) 67 | * Send me a pull request. Bonus points for topic branches. 68 | 69 | ## Copyright 70 | 71 | Copyright (c) 2010, 2011 Daniel Huckstep. See LICENSE for details. 72 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 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 -------------------------------------------------------------------------------- /example/mongrel2.conf: -------------------------------------------------------------------------------- 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 | }) 7 | 8 | main = Server( 9 | uuid='94601E4B-A770-4B7D-930D-CE3A484B5280', 10 | chroot='.', 11 | access_log='/logs/mongrel2_access.log', 12 | error_log='/logs/mongrel2_error.log', 13 | pid_file='/tmp/pids/mongrel2.pid', 14 | default_host='localhost', 15 | name='main', 16 | port=8080, 17 | hosts=[darkblog2] 18 | ) 19 | 20 | 21 | settings = { 22 | 'zeromq.threads': 1, 23 | 'control_port': 'ipc://tmp/mongrel2_control' 24 | } 25 | 26 | servers = [main] 27 | -------------------------------------------------------------------------------- /example/sinatra/.gitignore: -------------------------------------------------------------------------------- 1 | run/ 2 | tmp/ 3 | config.sqlite -------------------------------------------------------------------------------- /example/sinatra/app.rb: -------------------------------------------------------------------------------- 1 | require 'sinatra' 2 | require 'yajl/json_gem' 3 | 4 | get '*' do 5 | sleep(5) 6 | request.env.to_json 7 | end -------------------------------------------------------------------------------- /example/sinatra/config.ru: -------------------------------------------------------------------------------- 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) -------------------------------------------------------------------------------- /example/sinatra/mongrel2.conf: -------------------------------------------------------------------------------- 1 | darkblog2 = Host(name='localhost', routes={ 2 | '/': Handler(send_spec='tcp://127.0.0.1:9997', 3 | send_ident='sinatra', 4 | recv_spec='tcp://127.0.0.1:9996', 5 | recv_ident='') 6 | }) 7 | 8 | main = Server( 9 | uuid='sinatra-example', 10 | chroot='.', 11 | access_log='/run/access.log', 12 | error_log='/run/error.log', 13 | pid_file='/run/mongrel2.pid', 14 | default_host='localhost', 15 | name='main', 16 | port=8080, 17 | hosts=[darkblog2] 18 | ) 19 | 20 | 21 | settings = { 22 | 'zeromq.threads': 1 23 | } 24 | 25 | servers = [main] 26 | -------------------------------------------------------------------------------- /lib/mongrel2.rb: -------------------------------------------------------------------------------- 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.2.4' 14 | end -------------------------------------------------------------------------------- /lib/mongrel2/connection.rb: -------------------------------------------------------------------------------- 1 | require 'ffi-rzmq' 2 | require 'mongrel2/request' 3 | require 'mongrel2/response' 4 | 5 | module Mongrel2 6 | class Connection 7 | @context = nil 8 | 9 | def self.context 10 | @context ||= ZMQ::Context.new(1) 11 | end 12 | 13 | def initialize(uuid, sub, pub) 14 | @uuid, @sub, @pub = uuid, sub, pub 15 | 16 | # Connect to receive requests 17 | @reqs = self.class.context.socket(ZMQ::PULL) 18 | @reqs.connect(sub) 19 | 20 | # Connect to send responses 21 | @resp = self.class.context.socket(ZMQ::PUB) 22 | @resp.connect(pub) 23 | @resp.setsockopt(ZMQ::IDENTITY, uuid) 24 | end 25 | 26 | def recv 27 | msg = @reqs.recv_string(0) 28 | msg.nil? ? nil : Request.parse(msg) 29 | end 30 | 31 | def reply(req, body, status = 200, headers = {}) 32 | resp = Response.new(@resp) 33 | resp.send_http(req, body, status, headers) 34 | resp.close(req) if req.close? 35 | end 36 | 37 | def close 38 | # I think I should be able to just close the context 39 | self.class.context.close rescue nil 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/mongrel2/request.rb: -------------------------------------------------------------------------------- 1 | require 'mongrel2' 2 | 3 | module Mongrel2 4 | class Request 5 | attr_reader :headers, :body, :uuid, :conn_id, :path 6 | 7 | class << self 8 | def parse(msg) 9 | # UUID CONN_ID PATH SIZE:HEADERS,SIZE:BODY, 10 | uuid, conn_id, path, rest = msg.split(' ', 4) 11 | headers, rest = parse_netstring(rest) 12 | body, _ = parse_netstring(rest) 13 | headers = Mongrel2::JSON.parse(headers) 14 | new(uuid, conn_id, path, headers, body) 15 | end 16 | 17 | def parse_netstring(ns) 18 | # SIZE:HEADERS, 19 | 20 | len, rest = ns.split(':', 2) 21 | len = len.to_i 22 | raise "Netstring did not end in ','" unless rest[len].chr == ',' 23 | [rest[0, len], rest[(len + 1)..-1]] 24 | end 25 | end 26 | 27 | def initialize(uuid, conn_id, path, headers, body) 28 | @uuid, @conn_id, @path, @headers, @body = uuid, conn_id, path, headers, body 29 | @data = headers['METHOD'] == 'JSON' ? Mongrel2::JSON.parse(body) : {} 30 | end 31 | 32 | def disconnect? 33 | headers['METHOD'] == 'JSON' && @data['type'] == 'disconnect' 34 | end 35 | 36 | def close? 37 | headers['connection'] == 'close' || headers['VERSION'] == 'HTTP/1.0' 38 | end 39 | end 40 | end -------------------------------------------------------------------------------- /lib/mongrel2/response.rb: -------------------------------------------------------------------------------- 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_string('%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 -------------------------------------------------------------------------------- /lib/rack/handler/mongrel2.rb: -------------------------------------------------------------------------------- 1 | require 'mongrel2/connection' 2 | require 'stringio' 3 | 4 | module Rack 5 | module Handler 6 | class Mongrel2 7 | class << self 8 | def run(app, options = {}) 9 | options = { 10 | :recv => ENV['RACK_MONGREL2_RECV'] || 'tcp://127.0.0.1:9997', 11 | :send => ENV['RACK_MONGREL2_SEND'] || 'tcp://127.0.0.1:9996', 12 | :uuid => ENV['RACK_MONGREL2_UUID'] 13 | }.merge(options) 14 | 15 | raise ArgumentError.new('Must specify an :uuid or set RACK_MONGREL2_UUID') if options[:uuid].nil? 16 | 17 | conn = ::Mongrel2::Connection.new(options[:uuid], options[:recv], options[:send]) 18 | 19 | running = true 20 | 21 | # This doesn't work at all until zmq fixes their shit (in 2.1.x I think), but trap it now anyway. 22 | %w(INT TERM KILL).each do |sig| 23 | trap(sig) do 24 | conn.close 25 | running = false 26 | end 27 | end 28 | 29 | while running 30 | req = conn.recv rescue nil 31 | next if req.nil? || req.disconnect? 32 | break if !running 33 | 34 | script_name = ENV['RACK_RELATIVE_URL_ROOT'] || req.headers['PATTERN'].split('(', 2).first.gsub(/\/$/, '') 35 | env = { 36 | 'rack.version' => Rack::VERSION, 37 | 'rack.url_scheme' => 'http', # Only HTTP for now 38 | 'rack.input' => StringIO.new(req.body), 39 | 'rack.errors' => $stderr, 40 | 'rack.multithread' => true, 41 | 'rack.multiprocess' => true, 42 | 'rack.run_once' => false, 43 | 'mongrel2.pattern' => req.headers['PATTERN'], 44 | 'REQUEST_METHOD' => req.headers['METHOD'], 45 | 'CONTENT_TYPE' => req.headers['content-type'], 46 | 'SCRIPT_NAME' => script_name, 47 | 'PATH_INFO' => req.headers['PATH'].gsub(script_name, ''), 48 | 'QUERY_STRING' => req.headers['QUERY'] || '' 49 | } 50 | 51 | env['SERVER_NAME'], env['SERVER_PORT'] = req.headers['host'].split(':', 2) 52 | req.headers.each do |key, val| 53 | unless key =~ /content_(type|length)/i 54 | key = "HTTP_#{key.upcase.gsub('-', '_')}" 55 | end 56 | env[key] = val 57 | end 58 | 59 | status, headers, rack_response = app.call(env) 60 | body = '' 61 | rack_response.each { |b| body << b } 62 | conn.reply(req, body, status, headers) 63 | end 64 | ensure 65 | conn.close if conn.respond_to?(:close) 66 | end 67 | end 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /rack-mongrel2.gemspec: -------------------------------------------------------------------------------- 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 = 'rack-mongrel2' 16 | s.version = '0.2.4' 17 | s.date = '2011-08-23' 18 | s.rubyforge_project = 'rack-mongrel2' 19 | 20 | ## Make sure your summary is short. The description may be as long 21 | ## as you like. 22 | s.summary = %Q{The only Mongrel2 Rack handler you'll ever need.} 23 | s.description = %Q{A Rack handler for the Mongrel2 web server, 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 = ['Daniel Huckstep'] 29 | s.email = 'darkhelmet@darkhelmetlive.com' 30 | s.homepage = 'http://github.com/darkhelmet/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('ffi-rzmq', ['~> 0.7.0']) 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 | example/mongrel2.conf 62 | example/sinatra/.gitignore 63 | example/sinatra/app.rb 64 | example/sinatra/config.ru 65 | example/sinatra/mongrel2.conf 66 | lib/mongrel2.rb 67 | lib/mongrel2/connection.rb 68 | lib/mongrel2/request.rb 69 | lib/mongrel2/response.rb 70 | lib/rack/handler/mongrel2.rb 71 | rack-mongrel2.gemspec 72 | spec/request_spec.rb 73 | spec/response_spec.rb 74 | spec/spec.opts 75 | spec/spec_helper.rb 76 | ] 77 | # = MANIFEST = 78 | 79 | ## Test files will be grabbed from the file list. Make sure the path glob 80 | ## matches what you actually use. 81 | s.test_files = s.files.select { |path| path =~ /^spec\/.*_spec\.rb/ } 82 | end -------------------------------------------------------------------------------- /spec/request_spec.rb: -------------------------------------------------------------------------------- 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) 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 | end -------------------------------------------------------------------------------- /spec/response_spec.rb: -------------------------------------------------------------------------------- 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_string).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_string).with(httpreq) 27 | 28 | @response.close(@req) 29 | end 30 | end -------------------------------------------------------------------------------- /spec/spec.opts: -------------------------------------------------------------------------------- 1 | --color 2 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift(File.dirname(__FILE__)) 2 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 3 | require 'rspec' --------------------------------------------------------------------------------