m2r 0.0.3 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile +6 -0
- data/README.md +141 -35
- data/Rakefile +13 -45
- data/example/Procfile +4 -0
- data/example/config.sqlite +0 -0
- data/example/http_0mq.rb +37 -19
- data/example/lobster.ru +14 -6
- data/example/mongrel2.conf +47 -0
- data/example/tmp/access.log +505 -0
- data/example/uploading.ru +37 -0
- data/lib/m2r.rb +49 -3
- data/lib/m2r/connection.rb +66 -0
- data/lib/m2r/connection_factory.rb +41 -0
- data/lib/m2r/handler.rb +130 -0
- data/lib/m2r/headers.rb +72 -0
- data/lib/m2r/rack_handler.rb +47 -0
- data/lib/m2r/request.rb +129 -0
- data/lib/m2r/request/base.rb +33 -0
- data/lib/m2r/request/upload.rb +60 -0
- data/lib/m2r/response.rb +102 -0
- data/lib/m2r/response/content_length.rb +18 -0
- data/lib/m2r/version.rb +5 -0
- data/lib/rack/handler/mongrel2.rb +33 -0
- data/m2r.gemspec +30 -63
- data/test/acceptance/examples_test.rb +32 -0
- data/test/support/capybara.rb +4 -0
- data/test/support/mongrel_helper.rb +40 -0
- data/test/support/test_handler.rb +51 -0
- data/test/support/test_user.rb +37 -0
- data/test/test_helper.rb +5 -0
- data/test/unit/connection_factory_test.rb +29 -0
- data/test/unit/connection_test.rb +49 -0
- data/test/unit/handler_test.rb +41 -0
- data/test/unit/headers_test.rb +50 -0
- data/test/unit/m2r_test.rb +40 -0
- data/test/unit/rack_handler_test.rb +52 -0
- data/test/unit/request_test.rb +38 -0
- data/test/unit/response_test.rb +30 -0
- metadata +310 -105
- data/.document +0 -5
- data/.gitignore +0 -21
- data/ISSUES +0 -62
- data/VERSION +0 -1
- data/benchmarks/jruby +0 -60
- data/example/rack_handler.rb +0 -69
- data/lib/connection.rb +0 -158
- data/lib/fiber_handler.rb +0 -43
- data/lib/handler.rb +0 -66
- data/lib/request.rb +0 -44
- data/test/helper.rb +0 -10
- data/test/test_m2r.rb +0 -7
data/lib/m2r/request.rb
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'm2r'
|
2
|
+
require 'm2r/request/base'
|
3
|
+
require 'm2r/request/upload'
|
4
|
+
require 'm2r/headers'
|
5
|
+
|
6
|
+
module M2R
|
7
|
+
# Abstraction over Mongrel 2 request
|
8
|
+
# @api public
|
9
|
+
class Request
|
10
|
+
# @api private
|
11
|
+
TRUE_STRINGS = %w(true yes on 1).map(&:freeze).freeze
|
12
|
+
|
13
|
+
include Base
|
14
|
+
include Upload
|
15
|
+
|
16
|
+
# @return [String] UUID of mongrel2 origin instance
|
17
|
+
attr_reader :sender
|
18
|
+
|
19
|
+
# @return [String] Mongrel2 connection id sending this request
|
20
|
+
attr_reader :conn_id
|
21
|
+
|
22
|
+
# @return [String] HTTP Path of request
|
23
|
+
attr_reader :path
|
24
|
+
|
25
|
+
# @return [String] HTTP Body of request
|
26
|
+
attr_reader :body
|
27
|
+
|
28
|
+
# @param [String] sender UUID of mongrel2 origin instance
|
29
|
+
# @param [String] conn_id Mongrel2 connection id sending this request
|
30
|
+
# @param [String] path HTTP Path of request
|
31
|
+
# @param [M2R::Headers] headers HTTP headers of request
|
32
|
+
# @param [String] body HTTP Body of request
|
33
|
+
def initialize(sender, conn_id, path, headers, body)
|
34
|
+
@http_headers, @mongrel_headers = split_headers(headers)
|
35
|
+
@sender = sender
|
36
|
+
@conn_id = conn_id
|
37
|
+
@path = path
|
38
|
+
@body = body
|
39
|
+
@data = MultiJson.load(@body) if json?
|
40
|
+
end
|
41
|
+
|
42
|
+
# Parse Mongrel2 request received via ZMQ message
|
43
|
+
#
|
44
|
+
# @param [String] msg Monrel2 Request message formatted according to rules
|
45
|
+
# of creating it described it m2 manual.
|
46
|
+
# @return [Request]
|
47
|
+
#
|
48
|
+
# @api public
|
49
|
+
def self.parse(msg)
|
50
|
+
sender, conn_id, path, rest = msg.split(' ', 4)
|
51
|
+
|
52
|
+
headers, rest = TNetstring.parse(rest)
|
53
|
+
body, _ = TNetstring.parse(rest)
|
54
|
+
headers = Headers.new MultiJson.load(headers)
|
55
|
+
self.new(sender, conn_id, path, headers, body)
|
56
|
+
end
|
57
|
+
|
58
|
+
# @return [M2R::Headers] HTTP headers
|
59
|
+
def headers
|
60
|
+
@http_headers
|
61
|
+
end
|
62
|
+
|
63
|
+
# @return [String] Mongrel2 pattern used to match this request
|
64
|
+
def pattern
|
65
|
+
@mongrel_headers['pattern']
|
66
|
+
end
|
67
|
+
|
68
|
+
# @return [String] HTTP method
|
69
|
+
def method
|
70
|
+
@mongrel_headers['method']
|
71
|
+
end
|
72
|
+
|
73
|
+
# @return [String] Request query string
|
74
|
+
def query
|
75
|
+
@mongrel_headers['query']
|
76
|
+
end
|
77
|
+
|
78
|
+
# return [String] URL scheme
|
79
|
+
def scheme
|
80
|
+
@mongrel_headers['url_scheme'] || mongrel17_scheme
|
81
|
+
end
|
82
|
+
|
83
|
+
# @return [true, false] Internal mongrel2 message to handler issued when
|
84
|
+
# message delivery is not possible because the client already
|
85
|
+
# disconnected and there is no connection with such {#conn_id}
|
86
|
+
def disconnect?
|
87
|
+
json? and @data['type'] == 'disconnect'
|
88
|
+
end
|
89
|
+
|
90
|
+
# @return [true, false] Information whether HTTP Connection should
|
91
|
+
# be closed after processing the request. Happens when HTTP/1.0
|
92
|
+
# or request has Connection=close header.
|
93
|
+
def close?
|
94
|
+
unsupported_version? or connection_close?
|
95
|
+
end
|
96
|
+
|
97
|
+
protected
|
98
|
+
|
99
|
+
def mongrel17_scheme
|
100
|
+
return 'https' if TRUE_STRINGS.include? env_https
|
101
|
+
return 'http'
|
102
|
+
end
|
103
|
+
|
104
|
+
def env_https
|
105
|
+
(ENV['HTTPS'] || "").downcase
|
106
|
+
end
|
107
|
+
|
108
|
+
def unsupported_version?
|
109
|
+
@http_headers['version'] != 'HTTP/1.1'
|
110
|
+
end
|
111
|
+
|
112
|
+
def connection_close?
|
113
|
+
@http_headers['connection'] == 'close'
|
114
|
+
end
|
115
|
+
|
116
|
+
def json?
|
117
|
+
method == 'JSON'
|
118
|
+
end
|
119
|
+
|
120
|
+
def split_headers(headers)
|
121
|
+
mongrel = Headers.new
|
122
|
+
mongrel_headers.each do |header|
|
123
|
+
next unless headers[header]
|
124
|
+
mongrel[header] = headers.delete(header)
|
125
|
+
end
|
126
|
+
return headers, mongrel
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module M2R
|
2
|
+
# Logic for typical Mongrel2 request with no fancy features such as
|
3
|
+
# async upload
|
4
|
+
#
|
5
|
+
# @private
|
6
|
+
module Base
|
7
|
+
MONGREL2_BASE_HEADERS = %w(pattern method path query url_scheme).map(&:freeze).freeze
|
8
|
+
|
9
|
+
# @return [StringIO] Request body encapsulated in IO compatible object
|
10
|
+
# @api public
|
11
|
+
def body_io
|
12
|
+
@body_io ||= begin
|
13
|
+
b = StringIO.new(body)
|
14
|
+
b.set_encoding(Encoding::BINARY) if b.respond_to?(:set_encoding)
|
15
|
+
b
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [nil] Free external resources such as files or sockets
|
20
|
+
# @api public
|
21
|
+
def free!
|
22
|
+
body_io.close
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
|
27
|
+
def mongrel_headers
|
28
|
+
MONGREL2_BASE_HEADERS
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module M2R
|
2
|
+
# Logic for Mongrel2 request delivered using async-upload feature
|
3
|
+
# Contains methods for recognizing such requests and reading them.
|
4
|
+
# @private
|
5
|
+
module Upload
|
6
|
+
# Headers related to async-upload feature
|
7
|
+
# @private
|
8
|
+
MONGREL2_UPLOAD_HEADERS = %w(x-mongrel2-upload-start x-mongrel2-upload-done).map(&:freeze).freeze
|
9
|
+
|
10
|
+
# @return [true,false] True if this is async-upload related request
|
11
|
+
# @api public
|
12
|
+
def upload?
|
13
|
+
!!@mongrel_headers['x-mongrel2-upload-start']
|
14
|
+
end
|
15
|
+
|
16
|
+
# @return [true,false] True if this is async-upload start notification
|
17
|
+
# @api public
|
18
|
+
def upload_start?
|
19
|
+
upload? and not upload_path
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [true,false] True if this is final async-upload request
|
23
|
+
# @api public
|
24
|
+
def upload_done?
|
25
|
+
upload? and upload_path
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return [String] Relative path to file containing body of HTTP
|
29
|
+
# request.
|
30
|
+
# @api public
|
31
|
+
def upload_path
|
32
|
+
@mongrel_headers['x-mongrel2-upload-done']
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return [File] Request body encapsulated in IO compatible object
|
36
|
+
# @api public
|
37
|
+
def body_io
|
38
|
+
return super unless upload_done?
|
39
|
+
@body_io ||= begin
|
40
|
+
f = File.open(upload_path, "r+b")
|
41
|
+
f.set_encoding(Encoding::BINARY) if f.respond_to?(:set_encoding)
|
42
|
+
f
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# @return [nil] Free external resources such as files or sockets
|
47
|
+
# @api public
|
48
|
+
def free!
|
49
|
+
super
|
50
|
+
File.delete(body_io.path) if upload_done?
|
51
|
+
end
|
52
|
+
|
53
|
+
protected
|
54
|
+
|
55
|
+
def mongrel_headers
|
56
|
+
super + MONGREL2_UPLOAD_HEADERS
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
data/lib/m2r/response.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'm2r'
|
2
|
+
require 'm2r/response/content_length'
|
3
|
+
|
4
|
+
module M2R
|
5
|
+
# Simplest possible abstraction layer over HTTP request
|
6
|
+
#
|
7
|
+
# @api public
|
8
|
+
class Response
|
9
|
+
|
10
|
+
# @private
|
11
|
+
VERSION = "HTTP/1.1".freeze
|
12
|
+
|
13
|
+
# @private
|
14
|
+
CRLF = "\r\n".freeze
|
15
|
+
|
16
|
+
# @private
|
17
|
+
STATUS_CODES = {
|
18
|
+
100 => 'Continue',
|
19
|
+
101 => 'Switching Protocols',
|
20
|
+
102 => 'Processing',
|
21
|
+
200 => 'OK',
|
22
|
+
201 => 'Created',
|
23
|
+
202 => 'Accepted',
|
24
|
+
203 => 'Non-Authoritative Information',
|
25
|
+
204 => 'No Content',
|
26
|
+
205 => 'Reset Content',
|
27
|
+
206 => 'Partial Content',
|
28
|
+
207 => 'Multi-Status',
|
29
|
+
226 => 'IM Used',
|
30
|
+
300 => 'Multiple Choices',
|
31
|
+
301 => 'Moved Permanently',
|
32
|
+
302 => 'Found',
|
33
|
+
303 => 'See Other',
|
34
|
+
304 => 'Not Modified',
|
35
|
+
305 => 'Use Proxy',
|
36
|
+
306 => 'Reserved',
|
37
|
+
307 => 'Temporary Redirect',
|
38
|
+
400 => 'Bad Request',
|
39
|
+
401 => 'Unauthorized',
|
40
|
+
402 => 'Payment Required',
|
41
|
+
403 => 'Forbidden',
|
42
|
+
404 => 'Not Found',
|
43
|
+
405 => 'Method Not Allowed',
|
44
|
+
406 => 'Not Acceptable',
|
45
|
+
407 => 'Proxy Authentication Required',
|
46
|
+
408 => 'Request Timeout',
|
47
|
+
409 => 'Conflict',
|
48
|
+
410 => 'Gone',
|
49
|
+
411 => 'Length Required',
|
50
|
+
412 => 'Precondition Failed',
|
51
|
+
413 => 'Request Entity Too Large',
|
52
|
+
414 => 'Request-URI Too Long',
|
53
|
+
415 => 'Unsupported Media Type',
|
54
|
+
416 => 'Requested Range Not Satisfiable',
|
55
|
+
417 => 'Expectation Failed',
|
56
|
+
418 => "I'm a Teapot",
|
57
|
+
422 => 'Unprocessable Entity',
|
58
|
+
423 => 'Locked',
|
59
|
+
424 => 'Failed Dependency',
|
60
|
+
426 => 'Upgrade Required',
|
61
|
+
500 => 'Internal Server Error',
|
62
|
+
501 => 'Not Implemented',
|
63
|
+
502 => 'Bad Gateway',
|
64
|
+
503 => 'Service Unavailable',
|
65
|
+
504 => 'Gateway Timeout',
|
66
|
+
}
|
67
|
+
STATUS_CODES.freeze
|
68
|
+
|
69
|
+
# @return [Fixnum] HTTP Status code
|
70
|
+
attr_reader :status
|
71
|
+
|
72
|
+
# @return [Hash, Headers] Collection of response HTTP Headers
|
73
|
+
attr_reader :headers
|
74
|
+
|
75
|
+
# @return [String] HTTP Body
|
76
|
+
attr_reader :body
|
77
|
+
|
78
|
+
# @return [String] HTTP Status code description
|
79
|
+
attr_reader :reason
|
80
|
+
|
81
|
+
# @param [Fixnum, #to_i] status HTTP status code
|
82
|
+
# @param [Hash] headers HTTP headers
|
83
|
+
# @param [String, nil] body HTTP body
|
84
|
+
def initialize(status, headers, body = nil)
|
85
|
+
@status = status.to_i
|
86
|
+
@headers = headers
|
87
|
+
@body = body || ""
|
88
|
+
@reason = STATUS_CODES[status.to_i]
|
89
|
+
end
|
90
|
+
|
91
|
+
# @return [String] HTTP Response
|
92
|
+
def to_s
|
93
|
+
response = "#{VERSION} #{status} #{reason}#{CRLF}"
|
94
|
+
unless headers.empty?
|
95
|
+
response << headers.map { |h, v| "#{h}: #{v}" }.join(CRLF) << CRLF
|
96
|
+
end
|
97
|
+
response << CRLF
|
98
|
+
response << body
|
99
|
+
response
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'm2r/response'
|
2
|
+
|
3
|
+
module M2R
|
4
|
+
class Response
|
5
|
+
# Adds Content-Length header based on body size
|
6
|
+
# This is mostly required when you use bare
|
7
|
+
# Response class without any framework on top of it.
|
8
|
+
# HTTP clients require such header when there is
|
9
|
+
# body in response. Otherwise they hang out.
|
10
|
+
#
|
11
|
+
# @api public
|
12
|
+
module ContentLength
|
13
|
+
def headers
|
14
|
+
super.merge('Content-Length' => body.bytesize)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/m2r/version.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'rack/handler'
|
2
|
+
require 'm2r/rack_handler'
|
3
|
+
require 'securerandom'
|
4
|
+
require 'ostruct'
|
5
|
+
|
6
|
+
module Rack
|
7
|
+
module Handler
|
8
|
+
class Mongrel2
|
9
|
+
DEFAULT_OPTIONS = {
|
10
|
+
'recv_addr' => 'tcp://127.0.0.1:9997',
|
11
|
+
'send_addr' => 'tcp://127.0.0.1:9996',
|
12
|
+
'sender_id' => SecureRandom.uuid
|
13
|
+
}
|
14
|
+
|
15
|
+
def self.run(app, options = {})
|
16
|
+
options = OpenStruct.new( DEFAULT_OPTIONS.merge(options) )
|
17
|
+
factory = M2R::ConnectionFactory.new(options.sender_id, options.recv_addr, options.send_addr)
|
18
|
+
adapter = M2R::RackHandler.new(app, factory)
|
19
|
+
adapter.listen
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.valid_options
|
23
|
+
{
|
24
|
+
'recv_addr=RECV_ADDR' => 'Receive address',
|
25
|
+
'send_addr=SEND_ADDR' => 'Send address',
|
26
|
+
'sender_id=UUID' => 'Sender UUID'
|
27
|
+
}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
register :mongrel2, ::Rack::Handler::Mongrel2
|
32
|
+
end
|
33
|
+
end
|
data/m2r.gemspec
CHANGED
@@ -1,69 +1,36 @@
|
|
1
|
-
# Generated by jeweler
|
2
|
-
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
-
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
1
|
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/m2r/version', __FILE__)
|
5
3
|
|
6
|
-
Gem::Specification.new do |
|
7
|
-
|
8
|
-
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Colin Curtin", "Pradeep Elankumaran", "Pawel Pacana", "Robert Pankowecki"]
|
6
|
+
gem.email = ["colin.t.curtin+m2r@gmail.com", "pawel.pacana+m2r@gmail.com", "robert.pankowecki+m2r@gmail.com"]
|
7
|
+
gem.description = "A Mongrel2 interface and handler library for JRuby, and hopefully other Ruby implementations in the future. Works with Rack, so it works with Rails! (Rails installation guide forthcoming)."
|
8
|
+
gem.homepage = "http://github.com/perplexes/m2r"
|
9
|
+
gem.summary = "Mongrel2 interface and handler library for JRuby."
|
10
|
+
gem.license = "MIT"
|
9
11
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
s.description = %q{A Mongrel2 interface and handler library for JRuby, and hopefully other Ruby implementations in the future. Works with Rack, so it works with Rails! (Rails installation guide forthcoming.)}
|
14
|
-
s.email = %q{colin.t.curtin+m2r@gmail.com}
|
15
|
-
s.extra_rdoc_files = [
|
16
|
-
"LICENSE",
|
17
|
-
"README.md"
|
18
|
-
]
|
19
|
-
s.files = [
|
20
|
-
".document",
|
21
|
-
".gitignore",
|
22
|
-
"ISSUES",
|
23
|
-
"LICENSE",
|
24
|
-
"README.md",
|
25
|
-
"Rakefile",
|
26
|
-
"VERSION",
|
27
|
-
"benchmarks/jruby",
|
28
|
-
"example/http_0mq.rb",
|
29
|
-
"example/lobster.ru",
|
30
|
-
"example/rack_handler.rb",
|
31
|
-
"lib/connection.rb",
|
32
|
-
"lib/fiber_handler.rb",
|
33
|
-
"lib/handler.rb",
|
34
|
-
"lib/m2r.rb",
|
35
|
-
"lib/request.rb",
|
36
|
-
"m2r.gemspec",
|
37
|
-
"test/helper.rb",
|
38
|
-
"test/test_m2r.rb"
|
39
|
-
]
|
40
|
-
s.homepage = %q{http://github.com/perplexes/m2r}
|
41
|
-
s.rdoc_options = ["--charset=UTF-8"]
|
42
|
-
s.require_paths = ["lib"]
|
43
|
-
s.rubygems_version = %q{1.3.6}
|
44
|
-
s.summary = %q{Mongrel2 interface and handler library for JRuby}
|
45
|
-
s.test_files = [
|
46
|
-
"test/helper.rb",
|
47
|
-
"test/test_m2r.rb"
|
48
|
-
]
|
12
|
+
gem.files = Dir.glob("{lib,example,test}/**/*") + %w(LICENSE README.md Rakefile Gemfile m2r.gemspec)
|
13
|
+
gem.test_files = Dir.glob("test/**/*")
|
14
|
+
gem.extra_rdoc_files = ["LICENSE", "README.md" ]
|
49
15
|
|
50
|
-
|
51
|
-
|
52
|
-
|
16
|
+
gem.name = "m2r"
|
17
|
+
gem.date = "2010-10-23"
|
18
|
+
gem.require_path = "lib"
|
19
|
+
gem.version = M2R::VERSION
|
53
20
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
end
|
21
|
+
gem.add_dependency "ffi-rzmq", "~> 0.9.3"
|
22
|
+
gem.add_dependency "ffi", ">= 1.0.0"
|
23
|
+
gem.add_dependency "multi_json"
|
24
|
+
gem.add_dependency "tnetstring"
|
25
|
+
|
26
|
+
gem.add_development_dependency "rack"
|
27
|
+
gem.add_development_dependency "rake"
|
28
|
+
gem.add_development_dependency "minitest", "= 3.2.0"
|
29
|
+
gem.add_development_dependency "mocha", "~> 0.12.1"
|
30
|
+
gem.add_development_dependency "bbq", "= 0.0.4"
|
31
|
+
gem.add_development_dependency "capybara-mechanize", "= 0.3.0"
|
32
|
+
gem.add_development_dependency "activesupport", "~> 3.2.7"
|
33
|
+
gem.add_development_dependency "yard", "~> 0.8.2"
|
34
|
+
gem.add_development_dependency "kramdown", "~> 0.13.7"
|
69
35
|
|
36
|
+
end
|