m2r 1.0.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|