m2r 1.0.0 → 2.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/README.md +64 -1
- data/example/config.sqlite +0 -0
- data/example/http_0mq.rb +10 -3
- data/example/tmp/access.log +215 -0
- data/lib/m2r.rb +1 -0
- data/lib/m2r/connection.rb +21 -10
- data/lib/m2r/connection_factory.rb +20 -17
- data/lib/m2r/handler.rb +40 -3
- data/lib/m2r/headers.rb +9 -1
- data/lib/m2r/http/close.rb +30 -0
- data/lib/m2r/rack_handler.rb +3 -3
- data/lib/m2r/reply.rb +15 -0
- data/lib/m2r/request.rb +38 -31
- data/lib/m2r/request/base.rb +0 -8
- data/lib/m2r/request/upload.rb +0 -10
- data/lib/m2r/response.rb +64 -18
- data/lib/m2r/response/always_close.rb +26 -0
- data/lib/m2r/response/content_length.rb +8 -2
- data/lib/m2r/response/to_request.rb +26 -0
- data/lib/m2r/version.rb +1 -1
- data/lib/rack/handler/mongrel2.rb +17 -3
- data/test/support/test_handler.rb +5 -1
- data/test/unit/connection_factory_test.rb +3 -3
- data/test/unit/connection_test.rb +60 -9
- data/test/unit/handler_test.rb +46 -9
- data/test/unit/headers_test.rb +13 -0
- data/test/unit/rack_handler_test.rb +26 -2
- data/test/unit/request_test.rb +1 -0
- data/test/unit/response_test.rb +76 -5
- metadata +7 -3
data/lib/m2r/handler.rb
CHANGED
@@ -17,8 +17,12 @@ module M2R
|
|
17
17
|
|
18
18
|
# @param [ConnectionFactory, Connection, #connection] connection_factory
|
19
19
|
# Factory for generating connections
|
20
|
-
|
20
|
+
#
|
21
|
+
# @param [#parse] parser
|
22
|
+
# Parser of M2 requests
|
23
|
+
def initialize(connection_factory, parser)
|
21
24
|
@connection = connection_factory.connection
|
25
|
+
@parser = parser
|
22
26
|
end
|
23
27
|
|
24
28
|
# Start processing request
|
@@ -44,12 +48,14 @@ module M2R
|
|
44
48
|
# Callback when a request is received
|
45
49
|
# @api public
|
46
50
|
# @!visibility public
|
51
|
+
# @param [Request] request Request object
|
47
52
|
def on_request(request)
|
48
53
|
end
|
49
54
|
|
50
55
|
# Override to return a response
|
51
56
|
# @api public
|
52
57
|
# @!visibility public
|
58
|
+
# @param [Request] request Request object
|
53
59
|
# @return [Response, String, #to_s] Response that should be sent to
|
54
60
|
# Mongrel2 instance
|
55
61
|
def process(request)
|
@@ -60,24 +66,30 @@ module M2R
|
|
60
66
|
# because client already disconnected.
|
61
67
|
# @api public
|
62
68
|
# @!visibility public
|
69
|
+
# @param [Request] request Request object
|
63
70
|
def on_disconnect(request)
|
64
71
|
end
|
65
72
|
|
66
73
|
# Callback when async-upload started
|
67
74
|
# @api public
|
68
75
|
# @!visibility public
|
76
|
+
# @param [Request] request Request object
|
69
77
|
def on_upload_start(request)
|
70
78
|
end
|
71
79
|
|
72
80
|
# Callback when async-upload finished
|
73
81
|
# @api public
|
74
82
|
# @!visibility public
|
83
|
+
# @param [Request] request Request object
|
75
84
|
def on_upload_done(request)
|
76
85
|
end
|
77
86
|
|
78
87
|
# Callback after process_request is done
|
79
88
|
# @api public
|
80
89
|
# @!visibility public
|
90
|
+
# @param [Request] request Request object
|
91
|
+
# @param [Response, String, #to_s] response Response that should be sent to
|
92
|
+
# Mongrel2 instance
|
81
93
|
def after_process(request, response)
|
82
94
|
return response
|
83
95
|
end
|
@@ -85,6 +97,9 @@ module M2R
|
|
85
97
|
# Callback after sending the response back
|
86
98
|
# @api public
|
87
99
|
# @!visibility public
|
100
|
+
# @param [Request] request Request object
|
101
|
+
# @param [Response, String, #to_s] response Response that was sent to
|
102
|
+
# Mongrel2 instance
|
88
103
|
def after_reply(request, response)
|
89
104
|
end
|
90
105
|
|
@@ -93,18 +108,39 @@ module M2R
|
|
93
108
|
# resources (closing files etc)
|
94
109
|
# @api public
|
95
110
|
# @!visibility public
|
111
|
+
# @note `response` might be nil depending on when exception occured.
|
112
|
+
# @note In case of error this callback is called before on_error
|
113
|
+
# @param [Request] request Request object
|
114
|
+
# @param [Response, String, #to_s, nil] response Response that was sent to
|
115
|
+
# Mongrel2 instance
|
96
116
|
def after_all(request, response)
|
97
117
|
end
|
98
118
|
|
119
|
+
# Callback when exception occured
|
120
|
+
# @api public
|
121
|
+
# @!visibility public
|
122
|
+
# @note `request` and/or `response` might be nil depending on when error occured
|
123
|
+
# @param [Request, nil] request Request object
|
124
|
+
# @param [Response, String, #to_s, nil] response Response that might have been sent to
|
125
|
+
# Mongrel2 instance
|
126
|
+
# @param [StandardError] error
|
127
|
+
def on_error(request, response, error)
|
128
|
+
end
|
129
|
+
|
99
130
|
private
|
100
131
|
|
132
|
+
def next_request
|
133
|
+
@parser.parse @connection.receive
|
134
|
+
end
|
135
|
+
|
101
136
|
def one_loop
|
102
137
|
on_wait
|
103
138
|
throw :stop if stop?
|
104
|
-
request_lifecycle(
|
139
|
+
response = request_lifecycle(request = next_request)
|
140
|
+
rescue => error
|
141
|
+
on_error(request, response, error)
|
105
142
|
end
|
106
143
|
|
107
|
-
|
108
144
|
def request_lifecycle(request)
|
109
145
|
on_request(request)
|
110
146
|
|
@@ -118,6 +154,7 @@ module M2R
|
|
118
154
|
@connection.reply(request, response)
|
119
155
|
|
120
156
|
after_reply(request, response)
|
157
|
+
return response
|
121
158
|
ensure
|
122
159
|
after_all(request, response)
|
123
160
|
end
|
data/lib/m2r/headers.rb
CHANGED
@@ -7,7 +7,11 @@ module M2R
|
|
7
7
|
include Enumerable
|
8
8
|
|
9
9
|
# @param [Hash, #inject] hash Collection of headers
|
10
|
-
|
10
|
+
# @param [true, false] compatible Whether the hash already contains
|
11
|
+
# downcased strings only. If so it is going to be directly as
|
12
|
+
# container for the headers.
|
13
|
+
def initialize(hash = {}, compatible = false)
|
14
|
+
@headers = hash and return if compatible
|
11
15
|
@headers = hash.inject({}) do |headers,(header,value)|
|
12
16
|
headers[transform_key(header)] = value
|
13
17
|
headers
|
@@ -63,6 +67,10 @@ module M2R
|
|
63
67
|
env
|
64
68
|
end
|
65
69
|
|
70
|
+
def empty?
|
71
|
+
@headers.empty?
|
72
|
+
end
|
73
|
+
|
66
74
|
protected
|
67
75
|
|
68
76
|
def transform_key(key)
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module M2R
|
2
|
+
module HTTP
|
3
|
+
|
4
|
+
# Detect that whether connection should be closed
|
5
|
+
# based on http protocol version and `Connection' header
|
6
|
+
# We do not support persistent connections for HTTP 1.0
|
7
|
+
module Close
|
8
|
+
|
9
|
+
# @return [true, false] Information whether HTTP Connection should
|
10
|
+
# be closed after processing the request. Happens when HTTP/1.0
|
11
|
+
# or request has Connection=close header.
|
12
|
+
def close?
|
13
|
+
unsupported_version? || connection_close?
|
14
|
+
end
|
15
|
+
|
16
|
+
protected
|
17
|
+
|
18
|
+
# http://en.wikipedia.org/wiki/HTTP_persistent_connection
|
19
|
+
def unsupported_version?
|
20
|
+
http_version != 'HTTP/1.1'
|
21
|
+
end
|
22
|
+
|
23
|
+
def connection_close?
|
24
|
+
headers['Connection'] == 'close'
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
data/lib/m2r/rack_handler.rb
CHANGED
@@ -7,9 +7,9 @@ module M2R
|
|
7
7
|
class RackHandler < Handler
|
8
8
|
attr_accessor :app
|
9
9
|
|
10
|
-
def initialize(app, connection_factory)
|
10
|
+
def initialize(app, connection_factory, parser)
|
11
11
|
@app = app
|
12
|
-
super(connection_factory)
|
12
|
+
super(connection_factory, parser)
|
13
13
|
|
14
14
|
trap('INT') { stop }
|
15
15
|
end
|
@@ -36,7 +36,7 @@ module M2R
|
|
36
36
|
status, headers, body = @app.call(env)
|
37
37
|
buffer = ""
|
38
38
|
body.each { |part| buffer << part }
|
39
|
-
return Response.new(status
|
39
|
+
return Response.new.status(status).headers(headers).body(buffer)
|
40
40
|
end
|
41
41
|
|
42
42
|
def after_all(request, response)
|
data/lib/m2r/reply.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'm2r/response'
|
2
|
+
require 'm2r/response/content_length'
|
3
|
+
require 'm2r/response/to_request'
|
4
|
+
|
5
|
+
module M2R
|
6
|
+
# Response object to be used without any other framework
|
7
|
+
# doing the job of handling content lenght and dealing with
|
8
|
+
# 'Connection' header.
|
9
|
+
#
|
10
|
+
# @api public
|
11
|
+
class Reply < Response
|
12
|
+
include Response::ContentLength
|
13
|
+
include Response::ToRequest
|
14
|
+
end
|
15
|
+
end
|
data/lib/m2r/request.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
+
require 'set'
|
1
2
|
require 'm2r'
|
3
|
+
require 'm2r/http/close'
|
2
4
|
require 'm2r/request/base'
|
3
5
|
require 'm2r/request/upload'
|
4
6
|
require 'm2r/headers'
|
@@ -8,10 +10,17 @@ module M2R
|
|
8
10
|
# @api public
|
9
11
|
class Request
|
10
12
|
# @api private
|
11
|
-
TRUE_STRINGS
|
13
|
+
TRUE_STRINGS = Set.new(%w(true yes on 1).map(&:freeze)).freeze
|
14
|
+
# @api private
|
15
|
+
MONGREL2_BASE_HEADERS = Set.new(%w(pattern method path query url_scheme version).map(&:upcase).map(&:freeze)).freeze
|
16
|
+
# @api private
|
17
|
+
MONGREL2_UPLOAD_HEADERS = Set.new(%w(x-mongrel2-upload-start x-mongrel2-upload-done).map(&:downcase).map(&:freeze)).freeze
|
18
|
+
# @api private
|
19
|
+
MONGREL2_HEADERS = (MONGREL2_BASE_HEADERS + MONGREL2_UPLOAD_HEADERS).freeze
|
12
20
|
|
13
21
|
include Base
|
14
22
|
include Upload
|
23
|
+
include HTTP::Close
|
15
24
|
|
16
25
|
# @return [String] UUID of mongrel2 origin instance
|
17
26
|
attr_reader :sender
|
@@ -29,14 +38,16 @@ module M2R
|
|
29
38
|
# @param [String] conn_id Mongrel2 connection id sending this request
|
30
39
|
# @param [String] path HTTP Path of request
|
31
40
|
# @param [M2R::Headers] headers HTTP headers of request
|
41
|
+
# @param [M2R::Headers] headers Additional mongrel2 headers
|
32
42
|
# @param [String] body HTTP Body of request
|
33
|
-
def initialize(sender, conn_id, path,
|
34
|
-
@
|
35
|
-
@
|
36
|
-
@
|
37
|
-
@
|
38
|
-
@
|
39
|
-
@
|
43
|
+
def initialize(sender, conn_id, path, http_headers, mongrel_headers, body)
|
44
|
+
@sender = sender
|
45
|
+
@conn_id = conn_id
|
46
|
+
@path = path
|
47
|
+
@http_headers = http_headers
|
48
|
+
@mongrel_headers = mongrel_headers
|
49
|
+
@body = body
|
50
|
+
@data = MultiJson.load(@body) if json?
|
40
51
|
end
|
41
52
|
|
42
53
|
# Parse Mongrel2 request received via ZMQ message
|
@@ -51,8 +62,11 @@ module M2R
|
|
51
62
|
|
52
63
|
headers, rest = TNetstring.parse(rest)
|
53
64
|
body, _ = TNetstring.parse(rest)
|
54
|
-
headers =
|
55
|
-
|
65
|
+
headers = MultiJson.load(headers)
|
66
|
+
headers, mong = split_headers(headers)
|
67
|
+
headers = Headers.new headers, true
|
68
|
+
mong = Headers.new mong, true
|
69
|
+
self.new(sender, conn_id, path, headers, mong, body)
|
56
70
|
end
|
57
71
|
|
58
72
|
# @return [M2R::Headers] HTTP headers
|
@@ -80,6 +94,10 @@ module M2R
|
|
80
94
|
@mongrel_headers['url_scheme'] || mongrel17_scheme
|
81
95
|
end
|
82
96
|
|
97
|
+
def http_version
|
98
|
+
@mongrel_headers['version']
|
99
|
+
end
|
100
|
+
|
83
101
|
# @return [true, false] Internal mongrel2 message to handler issued when
|
84
102
|
# message delivery is not possible because the client already
|
85
103
|
# disconnected and there is no connection with such {#conn_id}
|
@@ -87,13 +105,6 @@ module M2R
|
|
87
105
|
json? and @data['type'] == 'disconnect'
|
88
106
|
end
|
89
107
|
|
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
108
|
protected
|
98
109
|
|
99
110
|
def mongrel17_scheme
|
@@ -105,25 +116,21 @@ module M2R
|
|
105
116
|
(ENV['HTTPS'] || "").downcase
|
106
117
|
end
|
107
118
|
|
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
119
|
def json?
|
117
120
|
method == 'JSON'
|
118
121
|
end
|
119
122
|
|
120
|
-
def split_headers(headers)
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
123
|
+
def self.split_headers(headers)
|
124
|
+
http = {}
|
125
|
+
mongrel = {}
|
126
|
+
headers.each do |header, value|
|
127
|
+
if MONGREL2_HEADERS.include?(header)
|
128
|
+
mongrel[header.downcase] = value
|
129
|
+
else
|
130
|
+
http[header] = value
|
131
|
+
end
|
125
132
|
end
|
126
|
-
return
|
133
|
+
return http, mongrel
|
127
134
|
end
|
128
135
|
end
|
129
136
|
end
|
data/lib/m2r/request/base.rb
CHANGED
@@ -4,8 +4,6 @@ module M2R
|
|
4
4
|
#
|
5
5
|
# @private
|
6
6
|
module Base
|
7
|
-
MONGREL2_BASE_HEADERS = %w(pattern method path query url_scheme).map(&:freeze).freeze
|
8
|
-
|
9
7
|
# @return [StringIO] Request body encapsulated in IO compatible object
|
10
8
|
# @api public
|
11
9
|
def body_io
|
@@ -22,12 +20,6 @@ module M2R
|
|
22
20
|
body_io.close
|
23
21
|
end
|
24
22
|
|
25
|
-
protected
|
26
|
-
|
27
|
-
def mongrel_headers
|
28
|
-
MONGREL2_BASE_HEADERS
|
29
|
-
end
|
30
|
-
|
31
23
|
end
|
32
24
|
end
|
33
25
|
|
data/lib/m2r/request/upload.rb
CHANGED
@@ -3,10 +3,6 @@ module M2R
|
|
3
3
|
# Contains methods for recognizing such requests and reading them.
|
4
4
|
# @private
|
5
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
6
|
# @return [true,false] True if this is async-upload related request
|
11
7
|
# @api public
|
12
8
|
def upload?
|
@@ -49,12 +45,6 @@ module M2R
|
|
49
45
|
super
|
50
46
|
File.delete(body_io.path) if upload_done?
|
51
47
|
end
|
52
|
-
|
53
|
-
protected
|
54
|
-
|
55
|
-
def mongrel_headers
|
56
|
-
super + MONGREL2_UPLOAD_HEADERS
|
57
|
-
end
|
58
48
|
end
|
59
49
|
end
|
60
50
|
|
data/lib/m2r/response.rb
CHANGED
@@ -1,11 +1,14 @@
|
|
1
1
|
require 'm2r'
|
2
|
+
require 'm2r/http/close'
|
2
3
|
require 'm2r/response/content_length'
|
4
|
+
require 'm2r/response/to_request'
|
3
5
|
|
4
6
|
module M2R
|
5
7
|
# Simplest possible abstraction layer over HTTP request
|
6
8
|
#
|
7
9
|
# @api public
|
8
10
|
class Response
|
11
|
+
include HTTP::Close
|
9
12
|
|
10
13
|
# @private
|
11
14
|
VERSION = "HTTP/1.1".freeze
|
@@ -66,37 +69,80 @@ module M2R
|
|
66
69
|
}
|
67
70
|
STATUS_CODES.freeze
|
68
71
|
|
69
|
-
|
70
|
-
|
72
|
+
attr_reader :reason
|
73
|
+
|
74
|
+
def initialize
|
75
|
+
status(200)
|
76
|
+
headers(Headers.new)
|
77
|
+
body("")
|
78
|
+
http_version(VERSION)
|
79
|
+
end
|
71
80
|
|
72
|
-
# @
|
73
|
-
|
81
|
+
# @param [Fixnum, #to_i] value HTTP status code
|
82
|
+
def status(value = GETTER)
|
83
|
+
if value == GETTER
|
84
|
+
@status
|
85
|
+
else
|
86
|
+
@status = value.to_i
|
87
|
+
@reason = STATUS_CODES[@status]
|
88
|
+
self
|
89
|
+
end
|
90
|
+
end
|
74
91
|
|
75
|
-
# @
|
76
|
-
|
92
|
+
# @param [Hash] value HTTP headers
|
93
|
+
def headers(value = GETTER)
|
94
|
+
if value == GETTER
|
95
|
+
@headers
|
96
|
+
else
|
97
|
+
@headers = value
|
98
|
+
self
|
99
|
+
end
|
100
|
+
end
|
77
101
|
|
78
|
-
# @
|
79
|
-
|
102
|
+
# @param [Hash] header HTTP header key
|
103
|
+
# @param [Hash] value HTTP header value
|
104
|
+
def header(header, value = GETTER)
|
105
|
+
if value == GETTER
|
106
|
+
@headers[header]
|
107
|
+
else
|
108
|
+
@headers[header] = value
|
109
|
+
self
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# @param [String, nil] value HTTP body
|
114
|
+
def body(value = GETTER)
|
115
|
+
if value == GETTER
|
116
|
+
@body
|
117
|
+
else
|
118
|
+
@body = value
|
119
|
+
self
|
120
|
+
end
|
121
|
+
end
|
80
122
|
|
81
|
-
# @param [
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
123
|
+
# @param [String, nil] version HTTP body
|
124
|
+
def http_version(value = GETTER)
|
125
|
+
if value == GETTER
|
126
|
+
@version
|
127
|
+
else
|
128
|
+
@version = value
|
129
|
+
self
|
130
|
+
end
|
89
131
|
end
|
90
132
|
|
91
133
|
# @return [String] HTTP Response
|
92
134
|
def to_s
|
93
|
-
response = "#{
|
135
|
+
response = "#{http_version} #{status} #{reason}#{CRLF}"
|
94
136
|
unless headers.empty?
|
95
137
|
response << headers.map { |h, v| "#{h}: #{v}" }.join(CRLF) << CRLF
|
96
138
|
end
|
97
139
|
response << CRLF
|
98
|
-
response << body
|
140
|
+
response << body.to_s
|
99
141
|
response
|
100
142
|
end
|
143
|
+
|
144
|
+
protected
|
145
|
+
|
146
|
+
GETTER = Object.new
|
101
147
|
end
|
102
148
|
end
|