m2r 1.0.0 → 2.0.0

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