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.
@@ -0,0 +1,26 @@
1
+ require 'm2r/response'
2
+
3
+ module M2R
4
+ class Response
5
+ # Use to disable persisent connections even though
6
+ # your client would prefer otherwise.
7
+ #
8
+ # @api public
9
+ module AlwaysClose
10
+ def close?
11
+ true
12
+ end
13
+
14
+ def headers(value = GETTER)
15
+ if value == GETTER
16
+ h = super
17
+ h['Connection'] = 'close'
18
+ h
19
+ else
20
+ super
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+
@@ -10,8 +10,14 @@ module M2R
10
10
  #
11
11
  # @api public
12
12
  module ContentLength
13
- def headers
14
- super.merge('Content-Length' => body.bytesize)
13
+ def headers(value = GETTER)
14
+ if value == GETTER
15
+ h = super
16
+ h['Content-Length'] ||= body.bytesize
17
+ h
18
+ else
19
+ super
20
+ end
15
21
  end
16
22
  end
17
23
  end
@@ -0,0 +1,26 @@
1
+ require 'm2r/response'
2
+
3
+ module M2R
4
+ class Response
5
+
6
+ # Handles the logic of having response with proper
7
+ # http version and 'Connection' header in relation to
8
+ # given request
9
+ #
10
+ # @api public
11
+ module ToRequest
12
+ # params [Request] request Request that response handles
13
+ # params [true, false] identical Whether http version in response should be identical
14
+ # to the received one.
15
+ # @return [self] Response object
16
+ # @api public
17
+ def to(request, identical = false)
18
+ # http://www.ietf.org/rfc/rfc2145.txt
19
+ # 2.3 Which version number to send in a message
20
+ http_version(request.http_version) if identical
21
+ headers['Connection'] = 'close' if request.close?
22
+ self
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,5 +1,5 @@
1
1
  module M2R
2
2
  # m2r gem version
3
3
  # @api public
4
- VERSION = '1.0.0'
4
+ VERSION = '2.0.0'
5
5
  end
@@ -14,8 +14,8 @@ module Rack
14
14
 
15
15
  def self.run(app, options = {})
16
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)
17
+ parser = M2R::Request
18
+ adapter = M2R::RackHandler.new(app, connection_factory(options), parser)
19
19
  adapter.listen
20
20
  end
21
21
 
@@ -23,9 +23,23 @@ module Rack
23
23
  {
24
24
  'recv_addr=RECV_ADDR' => 'Receive address',
25
25
  'send_addr=SEND_ADDR' => 'Send address',
26
- 'sender_id=UUID' => 'Sender UUID'
26
+ 'sender_id=UUID' => 'Sender UUID'
27
27
  }
28
28
  end
29
+
30
+ def self.connection_factory(options)
31
+ klass = if custom = options.connection_factory
32
+ begin
33
+ M2R::ConnectionFactory.const_get(custom.classify)
34
+ rescue NameError
35
+ require "m2r/connection_factory/#{custom.underscore}"
36
+ M2R::ConnectionFactory.const_get(custom.classify)
37
+ end
38
+ else
39
+ M2R::ConnectionFactory
40
+ end
41
+ klass.new(options)
42
+ end
29
43
  end
30
44
 
31
45
  register :mongrel2, ::Rack::Handler::Mongrel2
@@ -2,7 +2,7 @@ require 'm2r/handler'
2
2
 
3
3
  class TestHandler < M2R::Handler
4
4
  attr_reader :called_methods
5
- def initialize(connection)
5
+ def initialize(connection, parser)
6
6
  super
7
7
  @called_methods = []
8
8
  end
@@ -48,4 +48,8 @@ class TestHandler < M2R::Handler
48
48
  def after_all(request, response)
49
49
  @called_methods << :all
50
50
  end
51
+
52
+ def on_error(request, response, error)
53
+ @called_methods << :error
54
+ end
51
55
  end
@@ -7,7 +7,6 @@ module M2R
7
7
  sender_id = "sid"
8
8
  request_addr = "req"
9
9
  response_addr = "req"
10
- request_parser = Object.new
11
10
 
12
11
  pull = stub(:pull)
13
12
  pub = stub(:pub)
@@ -21,9 +20,10 @@ module M2R
21
20
  pub.expects(:connect).with(response_addr)
22
21
  pub.expects(:setsockopt).with(ZMQ::IDENTITY, sender_id)
23
22
 
24
- Connection.expects(:new).with(pull, pub, request_parser)
25
- cf = ConnectionFactory.new sender_id, request_addr, response_addr, request_parser, context
23
+ Connection.expects(:new).with(pull, pub)
24
+ cf = ConnectionFactory.new ConnectionFactory::Options.new(sender_id, request_addr, response_addr), context
26
25
  cf.connection
27
26
  end
27
+
28
28
  end
29
29
  end
@@ -13,6 +13,7 @@ module M2R
13
13
 
14
14
  @sub = M2R.zmq_context.socket(ZMQ::SUB)
15
15
  assert_equal 0, @sub.bind(@response_addr), "Could not bind sub socket in tests"
16
+ @sub.setsockopt(ZMQ::SUBSCRIBE, "")
16
17
 
17
18
 
18
19
  @request_socket = M2R.zmq_context.socket(ZMQ::PULL)
@@ -32,17 +33,67 @@ module M2R
32
33
 
33
34
  def test_receive_message
34
35
  connection = Connection.new(@request_socket, @response_socket)
35
- @push.send_string("1c5fd481-1121-49d8-a706-69127975db1a ebb407b2-49aa-48a5-9f96-9db121051484 / 2:{},0:,", ZMQ::NOBLOCK)
36
- assert_instance_of Request, connection.receive
36
+ @push.send_string(msg = "1c5fd481-1121-49d8-a706-69127975db1a ebb407b2-49aa-48a5-9f96-9db121051484 / 2:{},0:,", ZMQ::NOBLOCK)
37
+ assert_equal msg, connection.receive
37
38
  end
38
39
 
39
- def test_different_parser
40
- msg = "1c5fd481-1121-49d8-a706-69127975db1a ebb407b2-49aa-48a5-9f96-9db121051484 / 2:{},0:,"
41
- parser = stub(:parser)
42
- parser.expects(:parse).with(msg).returns(request = Object.new)
43
- connection = Connection.new(@request_socket, @response_socket, parser)
44
- @push.send_string(msg = "1c5fd481-1121-49d8-a706-69127975db1a ebb407b2-49aa-48a5-9f96-9db121051484 / 2:{},0:,", ZMQ::NOBLOCK)
45
- assert_equal request, connection.receive
40
+ def test_deliver_message
41
+ connection = Connection.new(@request_socket, @response_socket)
42
+ connection.deliver('uuid', ['conn1', 'conn2'], 'ddaattaa')
43
+ assert_equal 0, @sub.recv_string(msg = "")
44
+ assert_equal "uuid 11:conn1 conn2, ddaattaa", msg
45
+ end
46
+
47
+ def test_string_replay_non_close
48
+ connection = Connection.new(@request_socket, @response_socket)
49
+ connection.reply( stub(sender: 'uuid', conn_id: 'conn1', close?: false), 'ddaattaa')
50
+ assert_equal 0, @sub.recv_string(msg = "")
51
+ assert_equal "uuid 5:conn1, ddaattaa", msg
52
+ assert_equal -1, @sub.recv_string(msg = "", ZMQ::NOBLOCK)
53
+ end
54
+
55
+ def test_string_replay_close
56
+ connection = Connection.new(@request_socket, @response_socket)
57
+ connection.reply( stub(sender: 'uuid', conn_id: 'conn1', close?: true), 'ddaattaa')
58
+ assert_equal 0, @sub.recv_string(msg = "")
59
+ assert_equal "uuid 5:conn1, ddaattaa", msg
60
+ assert_equal 0, @sub.recv_string(msg = "")
61
+ assert_equal "uuid 5:conn1, ", msg
62
+ end
63
+
64
+ def test_response_replay_non_close
65
+ connection = Connection.new(@request_socket, @response_socket)
66
+ connection.reply( stub(sender: 'uuid', conn_id: 'conn1'), mock(to_s: 'ddaattaa', close?: false))
67
+ assert_equal 0, @sub.recv_string(msg = "")
68
+ assert_equal "uuid 5:conn1, ddaattaa", msg
69
+ assert_equal -1, @sub.recv_string(msg = "", ZMQ::NOBLOCK)
70
+ end
71
+
72
+ def test_response_replay_close
73
+ connection = Connection.new(@request_socket, @response_socket)
74
+ connection.reply( stub(sender: 'uuid', conn_id: 'conn1'), mock(to_s: 'ddaattaa', close?: true))
75
+ assert_equal 0, @sub.recv_string(msg = "")
76
+ assert_equal "uuid 5:conn1, ddaattaa", msg
77
+ assert_equal 0, @sub.recv_string(msg = "")
78
+ assert_equal "uuid 5:conn1, ", msg
79
+ end
80
+
81
+ def test_exception_when_receiving
82
+ request_socket = mock(:recv_string => -1)
83
+ connection = Connection.new request_socket, nil
84
+ assert_raises(Connection::Error) { connection.receive }
85
+ end
86
+
87
+ def test_exception_when_deliverying
88
+ response_socket = mock(:send_string => -1)
89
+ connection = Connection.new nil, response_socket
90
+ assert_raises(Connection::Error) { connection.deliver('uuid', ['connection_ids'], 'data') }
91
+ end
92
+
93
+ def test_exception_when_replying
94
+ response_socket = mock(:send_string => -1)
95
+ connection = Connection.new nil, response_socket
96
+ assert_raises(Connection::Error) { connection.reply( Struct.new(:sender, :conn_id).new('sender', 'conn_id') , 'data' ) }
46
97
  end
47
98
 
48
99
  end
@@ -2,40 +2,77 @@ require 'test_helper'
2
2
 
3
3
  module M2R
4
4
  class HandlerTest < MiniTest::Unit::TestCase
5
+
5
6
  def test_lifecycle_for_disconnect
6
- connection = stub(:receive => disconnect_request)
7
+ connection = stub(:receive => "")
7
8
  connection.stubs(:connection).returns(connection)
8
- h = TestHandler.new(connection)
9
+ parser = stub(:parse => disconnect_request)
10
+ h = TestHandler.new(connection, parser)
9
11
  h.listen
10
12
  assert_equal [:wait, :request, :disconnect, :all], h.called_methods
11
13
  end
12
14
 
13
15
  def test_lifecycle_for_upload_start
14
- connection = stub(:receive => upload_start_request)
16
+ connection = stub(:receive => "")
15
17
  connection.stubs(:connection).returns(connection)
16
- h = TestHandler.new(connection)
18
+ parser = stub(:parse => upload_start_request)
19
+ h = TestHandler.new(connection, parser)
17
20
  h.listen
18
21
  assert_equal [:wait, :request, :start, :all], h.called_methods
19
22
  end
20
23
 
21
24
  def test_lifecycle_for_upload_done
22
- connection = stub(:receive => upload_done_request, :reply => nil)
25
+ connection = stub(:receive => "", :reply => nil)
23
26
  connection.stubs(:connection).returns(connection)
24
- h = TestHandler.new(connection)
27
+ parser = stub(:parse => upload_done_request)
28
+ h = TestHandler.new(connection, parser)
25
29
  h.listen
26
30
  assert_equal [:wait, :request, :done, :process, :after, :reply, :all], h.called_methods
27
31
  end
28
32
 
33
+ def test_lifecycle_for_exception_when_getting_request
34
+ connection = stub()
35
+ connection.stubs(:receive).raises(StandardError)
36
+ connection.stubs(:connection).returns(connection)
37
+ h = TestHandler.new(connection, nil)
38
+ h.listen
39
+ assert_equal [:wait, :error], h.called_methods
40
+ end
41
+
42
+ def test_lifecycle_for_exception_when_processing
43
+ connection = stub(:receive => "", :reply => nil)
44
+ connection.stubs(:connection).returns(connection)
45
+ parser = stub(:parse => request)
46
+ h = TestHandler.new(connection, parser)
47
+ h.extend(Module.new(){
48
+ def process(request)
49
+ super
50
+ raise StandardError
51
+ end
52
+ })
53
+ h.listen
54
+ assert_equal [:wait, :request, :process, :all, :error], h.called_methods
55
+ end
56
+
57
+
58
+ private
59
+
60
+
29
61
  def disconnect_request
30
- Request.new("sender", "conn_id", "/path", Headers.new({"METHOD" => "JSON"}), '{"type":"disconnect"}')
62
+ Request.new("sender", "conn_id", "/path", Headers.new({}), Headers.new({"METHOD" => "JSON"}), '{"type":"disconnect"}')
31
63
  end
32
64
 
33
65
  def upload_start_request
34
- Request.new("sender", "conn_id", "/path", Headers.new({"x-mongrel2-upload-start" => "/tmp/file"}), '')
66
+ Request.new("sender", "conn_id", "/path", Headers.new({}), Headers.new({"x-mongrel2-upload-start" => "/tmp/file"}), '')
35
67
  end
36
68
 
37
69
  def upload_done_request
38
- Request.new("sender", "conn_id", "/path", Headers.new({"x-mongrel2-upload-start" => "/tmp/file", "x-mongrel2-upload-done" => "/tmp/file"}), '')
70
+ Request.new("sender", "conn_id", "/path", Headers.new({}), Headers.new({"x-mongrel2-upload-start" => "/tmp/file", "x-mongrel2-upload-done" => "/tmp/file"}), '')
39
71
  end
72
+
73
+ def request
74
+ Request.new("sender", "conn_id", "/path", Headers.new({}), Headers.new({}), '')
75
+ end
76
+
40
77
  end
41
78
  end
@@ -46,5 +46,18 @@ module M2R
46
46
  }, env)
47
47
  end
48
48
 
49
+ def test_compatibility_trust
50
+ headers = Headers.new({"Content-Type" => "CT"}, true)
51
+ assert_equal nil, headers['content-type']
52
+ end
53
+
54
+ def test_compatibility_direct_access
55
+ headers = Headers.new(source = {"content-type" => "CT"}, true)
56
+ assert_equal "CT", headers['content-type']
57
+ headers['Content-type'] = "NEW"
58
+ assert_equal "NEW", headers['content-Type']
59
+ assert_equal "NEW", source['content-type']
60
+ end
61
+
49
62
  end
50
63
  end
@@ -1,5 +1,6 @@
1
1
  require 'test_helper'
2
2
  require 'm2r/rack_handler'
3
+ require 'm2r/connection_factory'
3
4
 
4
5
  class HelloWorld
5
6
  def call(env)
@@ -8,6 +9,13 @@ class HelloWorld
8
9
  end
9
10
 
10
11
  module M2R
12
+ class ConnectionFactory
13
+ class Custom
14
+ def initialize(*)
15
+ end
16
+ end
17
+ end
18
+
11
19
  class RackHandlerTest < MiniTest::Unit::TestCase
12
20
  def test_discoverability
13
21
  handler = ::Rack::Handler.get(:mongrel2)
@@ -26,20 +34,36 @@ module M2R
26
34
  'sender_id' => id = SecureRandom.uuid
27
35
  }
28
36
  cf = mock(:connection)
29
- ConnectionFactory.expects(:new).with(id, recv, send).returns(cf)
37
+ ConnectionFactory.expects(:new).with(responds_with(:sender_id, id)).returns(cf)
30
38
  RackHandler.any_instance.stubs(:stop? => true)
31
39
  handler.run(HelloWorld.new, options)
32
40
  end
33
41
 
34
42
  def test_lint_rack_adapter
35
43
  factory = stub(:connection)
36
- handler = RackHandler.new(app, factory)
44
+ handler = RackHandler.new(app, factory, Request)
37
45
  response = handler.process(root_request)
38
46
 
39
47
  assert_equal "Hello world!", response.body
40
48
  assert_equal 200, response.status
41
49
  end
42
50
 
51
+ def test_custom_connection_factory
52
+ require 'rack/handler/mongrel2'
53
+ handler = ::Rack::Handler::Mongrel2
54
+ options = {
55
+ 'connection_factory' => 'custom'
56
+ }
57
+ cf = mock(:connection)
58
+ ConnectionFactory::Custom.expects(:new).with(responds_with(:connection_factory, 'custom')).returns(cf)
59
+ RackHandler.any_instance.stubs(:stop? => true)
60
+ handler.run(HelloWorld.new, options)
61
+ end
62
+
63
+
64
+ private
65
+
66
+
43
67
  def root_request
44
68
  data = %q("1c5fd481-1121-49d8-a706-69127975db1a ebb407b2-49aa-48a5-9f96-9db121051484 / 96:{"PATH":"/","host":"127.0.0.1:6767","PATTERN":"/","METHOD":"GET","VERSION":"HTTP/1.1","URI":"/"},0:,)
45
69
  Request.parse(data)
@@ -12,6 +12,7 @@ module M2R
12
12
  assert_equal nil, request.query
13
13
  assert_equal nil, request.pattern
14
14
  assert_equal "https", request.scheme
15
+ assert_equal false, request.close?
15
16
  end
16
17
 
17
18
  def test_scheme
@@ -3,28 +3,99 @@ require 'test_helper'
3
3
  module M2R
4
4
  class ResponseTest < MiniTest::Unit::TestCase
5
5
  def test_response_with_nil_body
6
- ok = Response.new(200, {"Transfer-Encoding" => "chunked"}, nil)
6
+ ok = Response.new.status(200).headers({"Transfer-Encoding" => "chunked"}).body(nil)
7
7
  http = "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n"
8
8
  assert_equal http, ok.to_s
9
9
  end
10
10
 
11
11
  def test_response_with_empty_body
12
- ok = Response.new(200, {"Transfer-Encoding" => "chunked"}, "")
12
+ ok = Response.new.status(200).headers({"Transfer-Encoding" => "chunked"}).body("")
13
13
  http = "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n"
14
14
  assert_equal http, ok.to_s
15
15
  end
16
16
 
17
17
  def test_response_with_no_body
18
- ok = Response.new(200, {"Transfer-Encoding" => "chunked"})
18
+ ok = Response.new.status(200).headers({"Transfer-Encoding" => "chunked"})
19
19
  http = "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n"
20
20
  assert_equal http, ok.to_s
21
21
  end
22
22
 
23
23
  def test_response_with_content_length
24
- ok = Response.new(200, {}, 'data')
24
+ ok = Response.new.body('data')
25
25
  ok.extend Response::ContentLength
26
- http = "HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ndata"
26
+ http = "HTTP/1.1 200 OK\r\ncontent-length: 4\r\n\r\ndata"
27
27
  assert_equal http, ok.to_s
28
28
  end
29
+
30
+ def test_response_with_header
31
+ ok = Response.new.body('data').header('X-Man', 'Wolverine')
32
+ ok.extend Response::ContentLength
33
+ http = "HTTP/1.1 200 OK\r\nx-man: Wolverine\r\ncontent-length: 4\r\n\r\ndata"
34
+ assert_equal http, ok.to_s
35
+ end
36
+
37
+ def test_response_with_old_version
38
+ ok = Response.new.http_version('HTTP/1.0').body('data')
39
+ ok.extend Response::ContentLength
40
+ http = "HTTP/1.0 200 OK\r\ncontent-length: 4\r\n\r\ndata"
41
+ assert_equal http, ok.to_s
42
+ end
43
+
44
+ def test_getters
45
+ ok = Response.new.http_version(v = 'HTTP/1.0').status(s = 300).headers({'X-Man' => xman = 'Summers'}).header("X-Angel", angel = "Warren").body(data = 'data')
46
+ assert_equal v, ok.http_version
47
+ assert_equal s, ok.status
48
+ assert_equal xman, ok.header("X-Man")
49
+ assert_equal angel, ok.header("X-Angel")
50
+ assert_equal data , ok.body
51
+ assert_equal 2, ok.headers.size
52
+ end
53
+
54
+ def test_default_close
55
+ ok = Response.new
56
+ refute ok.close?
57
+ end
58
+
59
+ def test_http10_close
60
+ ok = Response.new.http_version('HTTP/1.0')
61
+ assert ok.close?
62
+ end
63
+
64
+ def test_header_close
65
+ ok = Response.new.header('Connection', 'close')
66
+ assert ok.close?
67
+ end
68
+
69
+ def test_response_to_http10_identical
70
+ ok = Response.new
71
+ ok.extend(Response::ToRequest)
72
+ ok.to(stub(http_version: 'HTTP/1.0', close?:true), true)
73
+ assert_equal 'HTTP/1.0', ok.http_version
74
+ assert_equal 'close', ok.header('Connection')
75
+ end
76
+
77
+ def test_response_to_http10_rfc2145
78
+ ok = Response.new
79
+ ok.extend(Response::ToRequest)
80
+ ok.to(stub(http_version: 'HTTP/1.0', close?:true))
81
+ assert_equal 'HTTP/1.1', ok.http_version
82
+ assert_equal 'close', ok.header('Connection')
83
+ end
84
+
85
+ def test_response_to_http11
86
+ ok = Response.new
87
+ ok.extend(Response::ToRequest)
88
+ ok.to(stub(http_version: 'HTTP/1.1', close?:false))
89
+ assert_equal 'HTTP/1.1', ok.http_version
90
+ assert_equal nil, ok.header('Connection')
91
+ end
92
+
93
+ def test_response_to_http11_with_close
94
+ ok = Response.new
95
+ ok.extend(Response::ToRequest)
96
+ ok.to(stub(http_version: 'HTTP/1.1', close?:true))
97
+ assert_equal 'HTTP/1.1', ok.http_version
98
+ assert_equal 'close', ok.header('Connection')
99
+ end
29
100
  end
30
101
  end