mongrel2 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. data.tar.gz.sig +0 -0
  2. data/.gemtest +0 -0
  3. data/History.rdoc +4 -0
  4. data/Manifest.txt +66 -0
  5. data/README.rdoc +169 -0
  6. data/Rakefile +77 -0
  7. data/bin/m2sh.rb +600 -0
  8. data/data/mongrel2/bootstrap.html +25 -0
  9. data/data/mongrel2/config.sql +84 -0
  10. data/data/mongrel2/mimetypes.sql +855 -0
  11. data/examples/README.txt +6 -0
  12. data/examples/config.rb +54 -0
  13. data/examples/helloworld-handler.rb +31 -0
  14. data/examples/request-dumper.rb +45 -0
  15. data/examples/request-dumper.tmpl +71 -0
  16. data/examples/run +17 -0
  17. data/lib/mongrel2.rb +62 -0
  18. data/lib/mongrel2/config.rb +212 -0
  19. data/lib/mongrel2/config/directory.rb +78 -0
  20. data/lib/mongrel2/config/dsl.rb +206 -0
  21. data/lib/mongrel2/config/handler.rb +124 -0
  22. data/lib/mongrel2/config/host.rb +88 -0
  23. data/lib/mongrel2/config/log.rb +48 -0
  24. data/lib/mongrel2/config/mimetype.rb +15 -0
  25. data/lib/mongrel2/config/proxy.rb +15 -0
  26. data/lib/mongrel2/config/route.rb +51 -0
  27. data/lib/mongrel2/config/server.rb +58 -0
  28. data/lib/mongrel2/config/setting.rb +15 -0
  29. data/lib/mongrel2/config/statistic.rb +23 -0
  30. data/lib/mongrel2/connection.rb +212 -0
  31. data/lib/mongrel2/constants.rb +159 -0
  32. data/lib/mongrel2/control.rb +165 -0
  33. data/lib/mongrel2/exceptions.rb +59 -0
  34. data/lib/mongrel2/handler.rb +309 -0
  35. data/lib/mongrel2/httprequest.rb +51 -0
  36. data/lib/mongrel2/httpresponse.rb +187 -0
  37. data/lib/mongrel2/jsonrequest.rb +43 -0
  38. data/lib/mongrel2/logging.rb +241 -0
  39. data/lib/mongrel2/mixins.rb +143 -0
  40. data/lib/mongrel2/request.rb +148 -0
  41. data/lib/mongrel2/response.rb +74 -0
  42. data/lib/mongrel2/table.rb +216 -0
  43. data/lib/mongrel2/xmlrequest.rb +36 -0
  44. data/spec/lib/constants.rb +237 -0
  45. data/spec/lib/helpers.rb +246 -0
  46. data/spec/lib/matchers.rb +50 -0
  47. data/spec/mongrel2/config/directory_spec.rb +91 -0
  48. data/spec/mongrel2/config/dsl_spec.rb +218 -0
  49. data/spec/mongrel2/config/handler_spec.rb +118 -0
  50. data/spec/mongrel2/config/host_spec.rb +30 -0
  51. data/spec/mongrel2/config/log_spec.rb +95 -0
  52. data/spec/mongrel2/config/proxy_spec.rb +30 -0
  53. data/spec/mongrel2/config/route_spec.rb +83 -0
  54. data/spec/mongrel2/config/server_spec.rb +84 -0
  55. data/spec/mongrel2/config/setting_spec.rb +30 -0
  56. data/spec/mongrel2/config/statistic_spec.rb +30 -0
  57. data/spec/mongrel2/config_spec.rb +111 -0
  58. data/spec/mongrel2/connection_spec.rb +172 -0
  59. data/spec/mongrel2/constants_spec.rb +32 -0
  60. data/spec/mongrel2/control_spec.rb +192 -0
  61. data/spec/mongrel2/handler_spec.rb +261 -0
  62. data/spec/mongrel2/httpresponse_spec.rb +232 -0
  63. data/spec/mongrel2/logging_spec.rb +76 -0
  64. data/spec/mongrel2/mixins_spec.rb +62 -0
  65. data/spec/mongrel2/request_spec.rb +157 -0
  66. data/spec/mongrel2/response_spec.rb +81 -0
  67. data/spec/mongrel2/table_spec.rb +176 -0
  68. data/spec/mongrel2_spec.rb +34 -0
  69. metadata +294 -0
  70. metadata.gz.sig +0 -0
@@ -0,0 +1,172 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ BEGIN {
4
+ require 'pathname'
5
+ basedir = Pathname.new( __FILE__ ).dirname.parent.parent
6
+
7
+ libdir = basedir + "lib"
8
+
9
+ $LOAD_PATH.unshift( basedir ) unless $LOAD_PATH.include?( basedir )
10
+ $LOAD_PATH.unshift( libdir ) unless $LOAD_PATH.include?( libdir )
11
+ }
12
+
13
+ require 'rspec'
14
+
15
+ require 'spec/lib/helpers'
16
+
17
+ require 'mongrel2'
18
+ require 'mongrel2/connection'
19
+
20
+
21
+ #####################################################################
22
+ ### C O N T E X T S
23
+ #####################################################################
24
+
25
+ describe Mongrel2::Connection do
26
+ include Mongrel2::Config::DSL
27
+
28
+ before( :all ) do
29
+ setup_logging( :fatal )
30
+ end
31
+
32
+ # Ensure 0MQ never actually gets called
33
+ before( :each ) do
34
+ @ctx = double( "0mq context" )
35
+ Mongrel2.instance_variable_set( :@zmq_ctx, @ctx )
36
+
37
+ @conn = Mongrel2::Connection.new( TEST_UUID, TEST_SEND_SPEC, TEST_RECV_SPEC )
38
+ end
39
+
40
+ after( :each ) do
41
+ Mongrel2.instance_variable_set( :@zmq_ctx, nil )
42
+ end
43
+
44
+ after( :all ) do
45
+ reset_logging()
46
+ end
47
+
48
+
49
+ it "doesn't connect to the endpoints when it's created" do
50
+ @conn.instance_variable_get( :@request_sock ).should be_nil()
51
+ @conn.instance_variable_get( :@response_sock ).should be_nil()
52
+ end
53
+
54
+ it "connects to the endpoints specified on demand" do
55
+ request_sock = double( "request socket" )
56
+ response_sock = double( "response socket" )
57
+
58
+ @ctx.should_receive( :socket ).with( ZMQ::PULL ).and_return( request_sock )
59
+ request_sock.should_receive( :setsockopt ).with( ZMQ::LINGER, 0 )
60
+ request_sock.should_receive( :connect ).with( TEST_SEND_SPEC )
61
+
62
+ @ctx.should_receive( :socket ).with( ZMQ::PUB ).and_return( response_sock )
63
+ response_sock.should_receive( :setsockopt ).with( ZMQ::LINGER, 0 )
64
+ response_sock.should_receive( :setsockopt ).with( ZMQ::IDENTITY, TEST_UUID )
65
+ response_sock.should_receive( :connect ).with( TEST_RECV_SPEC )
66
+
67
+ @conn.request_sock.should == request_sock
68
+ @conn.response_sock.should == response_sock
69
+ end
70
+
71
+ context "after a connection has been established" do
72
+
73
+ before( :each ) do
74
+ @request_sock = double( "request socket", :setsockopt => nil, :connect => nil )
75
+ @response_sock = double( "response socket", :setsockopt => nil, :connect => nil )
76
+
77
+ @ctx.stub( :socket ).with( ZMQ::PULL ).and_return( @request_sock )
78
+ @ctx.stub( :socket ).with( ZMQ::PUB ).and_return( @response_sock )
79
+
80
+ @conn.connect
81
+ end
82
+
83
+
84
+ it "closes both of its sockets when closed" do
85
+ @request_sock.should_receive( :close )
86
+ @response_sock.should_receive( :close )
87
+
88
+ @conn.close
89
+ end
90
+
91
+ it "raises an exception if asked to fetch data after being closed" do
92
+ @request_sock.stub( :close )
93
+ @response_sock.stub( :close )
94
+
95
+ @conn.close
96
+
97
+ expect {
98
+ @conn.recv
99
+ }.to raise_error( Mongrel2::ConnectionError, /operation on closed connection/i )
100
+ end
101
+
102
+ it "doesn't keep its request and response sockets when duped" do
103
+ request_sock2 = double( "request socket", :setsockopt => nil, :connect => nil )
104
+ response_sock2 = double( "response socket", :setsockopt => nil, :connect => nil )
105
+ @ctx.stub( :socket ).with( ZMQ::PULL ).and_return( request_sock2 )
106
+ @ctx.stub( :socket ).with( ZMQ::PUB ).and_return( response_sock2 )
107
+
108
+ duplicate = @conn.dup
109
+
110
+ duplicate.request_sock.should == request_sock2
111
+ duplicate.response_sock.should == response_sock2
112
+ end
113
+
114
+ it "doesn't keep its closed state when duped" do
115
+ @request_sock.should_receive( :close )
116
+ @response_sock.should_receive( :close )
117
+
118
+ @conn.close
119
+
120
+ duplicate = @conn.dup
121
+ duplicate.should_not be_closed()
122
+ end
123
+
124
+ it "can read raw request messages off of the request_sock" do
125
+ @request_sock.should_receive( :recv ).and_return( "the data" )
126
+ @conn.recv.should == "the data"
127
+ end
128
+
129
+ it "can read request messages off of the request_sock as Mongrel2::Request objects" do
130
+ msg = make_request()
131
+ @request_sock.should_receive( :recv ).and_return( msg )
132
+ @conn.receive.should be_a( Mongrel2::Request )
133
+ end
134
+
135
+ it "can write raw response messages with a TNetString header onto the response_sock" do
136
+ @response_sock.should_receive( :send ).with( "#{TEST_UUID} 1:8, the data" )
137
+ @conn.send( TEST_UUID, 8, "the data" )
138
+ end
139
+
140
+ it "can write Mongrel2::Responses to the response_sock" do
141
+ @response_sock.should_receive( :send ).with( "#{TEST_UUID} 1:8, the data" )
142
+
143
+ response = Mongrel2::Response.new( TEST_UUID, 8, 'the data' )
144
+ @conn.reply( response )
145
+ end
146
+
147
+ it "can write raw response messages to more than one conn_id at the same time" do
148
+ @response_sock.should_receive( :send ).with( "#{TEST_UUID} 15:8 16 44 45 1833, the data" )
149
+ @conn.broadcast( TEST_UUID, [8, 16, 44, 45, 1833], 'the data' )
150
+ end
151
+
152
+ it "can write raw response messages to more than one conn_id at the same time" do
153
+ @response_sock.should_receive( :send ).with( "#{TEST_UUID} 15:8 16 44 45 1833, the data" )
154
+ @conn.broadcast( TEST_UUID, [8, 16, 44, 45, 1833], 'the data' )
155
+ end
156
+
157
+ it "can tell the connection a request or a response was from to close" do
158
+ @response_sock.should_receive( :send ).with( "#{TEST_UUID} 1:8, " )
159
+
160
+ response = Mongrel2::Response.new( TEST_UUID, 8 )
161
+ @conn.reply_close( response )
162
+ end
163
+
164
+ it "can broadcast a close to multiple connection IDs" do
165
+ @response_sock.should_receive( :send ).with( "#{TEST_UUID} 15:8 16 44 45 1833, " )
166
+ @conn.broadcast_close( TEST_UUID, [8, 16, 44, 45, 1833] )
167
+ end
168
+
169
+ end
170
+
171
+ end
172
+
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ BEGIN {
4
+ require 'pathname'
5
+ basedir = Pathname.new( __FILE__ ).dirname.parent.parent
6
+
7
+ libdir = basedir + "lib"
8
+
9
+ $LOAD_PATH.unshift( basedir ) unless $LOAD_PATH.include?( basedir )
10
+ $LOAD_PATH.unshift( libdir ) unless $LOAD_PATH.include?( libdir )
11
+ }
12
+
13
+ require 'rspec'
14
+
15
+ require 'spec/lib/helpers'
16
+
17
+ require 'mongrel2'
18
+ require 'mongrel2/connection'
19
+
20
+
21
+ #####################################################################
22
+ ### C O N T E X T S
23
+ #####################################################################
24
+
25
+ describe Mongrel2::Constants do
26
+
27
+ it "defines a default configuration URI" do
28
+ Mongrel2::Constants.constants.map( &:to_sym ).should include( :DEFAULT_CONFIG_URI )
29
+ end
30
+
31
+ end
32
+
@@ -0,0 +1,192 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ BEGIN {
4
+ require 'pathname'
5
+ basedir = Pathname.new( __FILE__ ).dirname.parent.parent
6
+
7
+ libdir = basedir + "lib"
8
+
9
+ $LOAD_PATH.unshift( basedir ) unless $LOAD_PATH.include?( basedir )
10
+ $LOAD_PATH.unshift( libdir ) unless $LOAD_PATH.include?( libdir )
11
+ }
12
+
13
+ require 'rspec'
14
+
15
+ require 'spec/lib/helpers'
16
+
17
+ require 'mongrel2'
18
+ require 'mongrel2/control'
19
+
20
+
21
+ #####################################################################
22
+ ### C O N T E X T S
23
+ #####################################################################
24
+
25
+ describe Mongrel2::Control do
26
+
27
+ before( :all ) do
28
+ setup_logging( :fatal )
29
+ end
30
+
31
+ before( :each ) do
32
+ @ctx = double( "ZMQ::Context" )
33
+ @socket = double( "ZMQ REQ socket", :connect => nil )
34
+ @ctx.stub( :socket ).with( ZMQ::REQ ).and_return( @socket )
35
+
36
+ Mongrel2.instance_variable_set( :@zmq_ctx, @ctx )
37
+
38
+ @control = Mongrel2::Control.new
39
+ end
40
+
41
+ after( :all ) do
42
+ Mongrel2.instance_variable_set( :@zmq_ctx, nil )
43
+ reset_logging()
44
+ end
45
+
46
+
47
+ it "sends a 'stop' command to the control port when #stop is called" do
48
+ @socket.should_receive( :send ).with( "10:4:stop,0:}]" )
49
+ @socket.should_receive( :recv ).
50
+ and_return( "59:7:headers,6:3:msg,]4:rows,29:25:21:signal sent to server,]]}" )
51
+ @control.stop.should == [{ :msg => "signal sent to server" }]
52
+ end
53
+
54
+ it "sends a 'reload' command to the control port when #reload is called" do
55
+ @socket.should_receive( :send ).with( "12:6:reload,0:}]" )
56
+ @socket.should_receive( :recv ).
57
+ and_return( "59:7:headers,6:3:msg,]4:rows,29:25:21:signal sent to server,]]}" )
58
+ @control.reload.should == [{ :msg => "signal sent to server" }]
59
+ end
60
+
61
+ it "sends a 'terminate' command to the control port when #terminate is called" do
62
+ @socket.should_receive( :send ).with( "15:9:terminate,0:}]" )
63
+ @socket.should_receive( :recv ).
64
+ and_return( "59:7:headers,6:3:msg,]4:rows,29:25:21:signal sent to server,]]}" )
65
+ @control.terminate.should == [{ :msg => "signal sent to server" }]
66
+ end
67
+
68
+ it "sends a 'help' command to the control port when #help is called" do
69
+ @socket.should_receive( :send ).with( "10:4:help,0:}]" )
70
+ @socket.should_receive( :recv ).
71
+ and_return( "416:7:headers,14:4:name,4:help,]4:rows,376:35:4:stop" +
72
+ ",24:stop the server (SIGINT),]30:6:reload,17:reload " +
73
+ "the server,]23:4:help,12:this command,]37:12:control" +
74
+ "_stop,17:stop control port,]28:4:kill,17:kill a conn" +
75
+ "ection,]41:6:status,28:status, what=['net'|'tasks']," +
76
+ "]46:9:terminate,30:terminate the server (SIGTERM),]2" +
77
+ "8:4:time,17:the server's time,]28:4:uuid,17:the serv" +
78
+ "er's uuid,]40:4:info,29:information about this serve" +
79
+ "r,]]}" )
80
+ @control.help.should == [
81
+ {:name => "stop", :help => "stop the server (SIGINT)"},
82
+ {:name => "reload", :help => "reload the server"},
83
+ {:name => "help", :help => "this command"},
84
+ {:name => "control_stop", :help => "stop control port"},
85
+ {:name => "kill", :help => "kill a connection"},
86
+ {:name => "status", :help => "status, what=['net'|'tasks']"},
87
+ {:name => "terminate", :help => "terminate the server (SIGTERM)"},
88
+ {:name => "time", :help => "the server's time"},
89
+ {:name => "uuid", :help => "the server's uuid"},
90
+ {:name => "info", :help => "information about this server"}
91
+ ]
92
+ end
93
+
94
+ it "sends a 'uuid' command to the control port when #uuid is called" do
95
+ @socket.should_receive( :send ).with( "10:4:uuid,0:}]" )
96
+ @socket.should_receive( :recv ).
97
+ and_return( "75:7:headers,7:4:uuid,]4:rows,44:40:36:34D8E57C-3E91" +
98
+ "-4F24-9BBE-0B53C1827CB4,]]}" )
99
+ @control.uuid.should == [{ :uuid => '34D8E57C-3E91-4F24-9BBE-0B53C1827CB4' }]
100
+ end
101
+
102
+ it "sends an 'info' command to the control port when #info is called" do
103
+ @socket.should_receive( :send ).with( "10:4:info,0:}]" )
104
+ @socket.should_receive( :recv ).
105
+ and_return( "260:7:headers,92:4:port,9:bind_addr,4:uuid,6:chroot," +
106
+ "10:access_log,9:error_log,8:pid_file,16:default_host" +
107
+ "name,]4:rows,142:137:4:8113#7:0.0.0.0,36:34D8E57C-3E" +
108
+ "91-4F24-9BBE-0B53C1827CB4,2:./,18:.//logs/access.log" +
109
+ ",15:/logs/error.log,18:./run/mongrel2.pid,9:localhos" +
110
+ "t,]]}" )
111
+ @control.info.should == [{
112
+ :port => 8113,
113
+ :bind_addr => "0.0.0.0",
114
+ :uuid => "34D8E57C-3E91-4F24-9BBE-0B53C1827CB4",
115
+ :chroot => "./",
116
+ :access_log => ".//logs/access.log",
117
+ :error_log => "/logs/error.log",
118
+ :pid_file => "./run/mongrel2.pid",
119
+ :default_hostname => "localhost"
120
+ }]
121
+ end
122
+
123
+ it "sends a 'status' command with a 'what' option set to 'tasks' to the control port " +
124
+ "when #tasklist is called" do
125
+
126
+ @socket.should_receive( :send ).with( "28:6:status,15:4:what,5:tasks,}]" )
127
+ @socket.should_receive( :recv ).
128
+ and_return( "343:7:headers,38:2:id,6:system,4:name,5:state,6:status," +
129
+ "]4:rows,279:38:1:1#5:false!6:SERVER,7:read fd,4:idle,]5" +
130
+ "1:1:2#5:false!12:Handler_task,12:read handler,4:idle,]5" +
131
+ "1:1:3#5:false!12:Handler_task,12:read handler,4:idle,]4" +
132
+ "8:1:4#5:false!7:control,12:read handler,7:running,]31:1" +
133
+ ":5#5:false!6:ticker,0:,4:idle,]36:1:6#4:true!6:fdtask,5" +
134
+ ":yield,5:ready,]]}" )
135
+ @control.tasklist.should == [
136
+ {:id=>1, :system=>false, :name=>"SERVER", :state=>"read fd", :status=>"idle"},
137
+ {:id=>2, :system=>false, :name=>"Handler_task", :state=>"read handler", :status=>"idle"},
138
+ {:id=>3, :system=>false, :name=>"Handler_task", :state=>"read handler", :status=>"idle"},
139
+ {:id=>4, :system=>false, :name=>"control", :state=>"read handler", :status=>"running"},
140
+ {:id=>5, :system=>false, :name=>"ticker", :state=>"", :status=>"idle"},
141
+ {:id=>6, :system=>true, :name=>"fdtask", :state=>"yield", :status=>"ready"}
142
+ ]
143
+ end
144
+
145
+ it "sends an 'status' command with a 'what' option set to 'net' to the control port " +
146
+ "when #conn_status is called" do
147
+
148
+ @socket.should_receive( :send ).with( "26:6:status,13:4:what,3:net,}]" )
149
+ @socket.should_receive( :recv ).
150
+ and_return( "150:7:headers,86:2:id,2:fd,4:type,9:last_ping,9:last_read," +
151
+ "10:last_write,10:bytes_read,13:bytes_written,]4:rows,39:35" +
152
+ ":1:2#2:38#1:1#1:0#1:0#1:0#3:405#1:0#]]}" )
153
+ @control.conn_status.should == [
154
+ {:id=>2, :fd=>38, :type=>1, :last_ping=>0, :last_read=>0, :last_write=>0,
155
+ :bytes_read=>405, :bytes_written=>0}
156
+ ]
157
+ end
158
+
159
+ it "sends a 'time' command to the control port when #time is called" do
160
+ @socket.should_receive( :send ).with( "10:4:time,0:}]" )
161
+ @socket.should_receive( :recv ).
162
+ and_return( "49:7:headers,7:4:time,]4:rows,18:14:10:1315532674,]]}" )
163
+ @control.time.should == [{ :time => Time.at(1315532674) }]
164
+ end
165
+
166
+ it "sends a 'kill' command with an ID equal to the argument to the control port when #kill " +
167
+ "is called" do
168
+ @socket.should_receive( :send ).with( "19:4:kill,9:2:id,1:0#}]" )
169
+ @socket.should_receive( :recv ).and_return( "40:7:headers,9:6:status,]4:rows,8:5:2:OK,]]}" )
170
+ @control.kill( 0 ).should == [{ :status => "OK" }]
171
+ end
172
+
173
+ it "sends a 'control_stop' command to the control port when #info is called" do
174
+ @socket.should_receive( :send ).with( "19:12:control_stop,0:}]" )
175
+ @socket.should_receive( :recv ).
176
+ and_return( "63:7:headers,6:3:msg,]4:rows,33:29:25:stopping the control port,]]}" )
177
+ @control.control_stop.should == [{:msg => "stopping the control port"}]
178
+ end
179
+
180
+
181
+ it "raises an exception if the server responds with an error" do
182
+ @socket.should_receive( :send ).with( "19:4:kill,9:2:id,1:0#}]" )
183
+ @socket.should_receive( :recv ).
184
+ and_return( "61:4:code,16:INVALID_ARGUMENT,5:error,22:Invalid argument type.,}" )
185
+
186
+ expect {
187
+ @control.kill( 0 )
188
+ }.to raise_error( Mongrel2::ControlError, /invalid argument type/i )
189
+ end
190
+
191
+ end
192
+
@@ -0,0 +1,261 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ BEGIN {
4
+ require 'pathname'
5
+ basedir = Pathname.new( __FILE__ ).dirname.parent.parent
6
+
7
+ libdir = basedir + "lib"
8
+
9
+ $LOAD_PATH.unshift( basedir ) unless $LOAD_PATH.include?( basedir )
10
+ $LOAD_PATH.unshift( libdir ) unless $LOAD_PATH.include?( libdir )
11
+ }
12
+
13
+ require 'rspec'
14
+
15
+ require 'spec/lib/helpers'
16
+
17
+ require 'mongrel2'
18
+ require 'mongrel2/config'
19
+ require 'mongrel2/handler'
20
+
21
+
22
+ #####################################################################
23
+ ### C O N T E X T S
24
+ #####################################################################
25
+
26
+ describe Mongrel2::Handler do
27
+
28
+ # Testing handler config
29
+ HANDLER_CONFIG = {
30
+ :send_spec => TEST_SEND_SPEC,
31
+ :send_ident => TEST_UUID,
32
+ :recv_spec => TEST_RECV_SPEC,
33
+ }
34
+
35
+
36
+ # Make a handler class for testing that only ever handles one request, and
37
+ # keeps track of any requests it handles and their responses.
38
+ class OneShotHandler < Mongrel2::Handler
39
+ def initialize( * )
40
+ @transactions = {}
41
+ super
42
+ end
43
+
44
+ attr_reader :transactions
45
+
46
+ # Overridden to accept one request and shut down
47
+ def dispatch_request( request )
48
+ response = super
49
+ self.transactions[ request ] = response
50
+ self.shutdown
51
+ return response
52
+ end
53
+
54
+ end # class OneShotHandler
55
+
56
+
57
+ before( :all ) do
58
+ setup_logging( :fatal )
59
+ setup_config_db()
60
+ end
61
+
62
+ after( :all ) do
63
+ reset_logging()
64
+ end
65
+
66
+
67
+ # Ensure 0MQ never actually gets called
68
+ before( :each ) do
69
+ @ctx = double( "0mq context" )
70
+ @request_sock = double( "request socket", :setsockopt => nil, :connect => nil, :close => nil )
71
+ @response_sock = double( "response socket", :setsockopt => nil, :connect => nil, :close => nil )
72
+
73
+ @ctx.stub( :socket ).with( ZMQ::PULL ).and_return( @request_sock )
74
+ @ctx.stub( :socket ).with( ZMQ::PUB ).and_return( @response_sock )
75
+
76
+ Mongrel2.instance_variable_set( :@zmq_ctx, @ctx )
77
+ end
78
+
79
+ after( :each ) do
80
+ Mongrel2.instance_variable_set( :@zmq_ctx, nil )
81
+ end
82
+
83
+
84
+
85
+ context "with a Handler entry in the config database" do
86
+
87
+ before( :each ) do
88
+ Mongrel2::Config::Handler.dataset.truncate
89
+ Mongrel2::Config::Handler.create( HANDLER_CONFIG )
90
+ end
91
+
92
+ it "can look up connection information given an application ID" do
93
+ Mongrel2::Handler.connection_info_for( TEST_UUID ).
94
+ should == [ TEST_SEND_SPEC, TEST_RECV_SPEC ]
95
+ end
96
+
97
+ it "has a convenience method for instantiating and running a Handler given an " +
98
+ "application ID" do
99
+
100
+ req = make_request()
101
+ @request_sock.should_receive( :recv ).and_return( req )
102
+
103
+ res = OneShotHandler.run( TEST_UUID )
104
+
105
+ # It should have pulled its connection info from the Handler entry in the database
106
+ res.conn.app_id.should == TEST_UUID
107
+ res.conn.sub_addr.should == TEST_SEND_SPEC
108
+ res.conn.pub_addr.should == TEST_RECV_SPEC
109
+ end
110
+
111
+ end
112
+
113
+
114
+ context "without a Handler entry for it in the config database" do
115
+
116
+ before( :each ) do
117
+ Mongrel2::Config::Handler.dataset.truncate
118
+ end
119
+
120
+ it "raises an exception if no handler with its appid exists in the config DB" do
121
+ Mongrel2::Config::Handler.dataset.truncate
122
+ expect {
123
+ Mongrel2::Handler.connection_info_for( TEST_UUID )
124
+ }.should raise_error()
125
+ end
126
+
127
+ end
128
+
129
+
130
+ it "dispatches HTTP requests to the #handle method" do
131
+ req = make_request()
132
+ @request_sock.should_receive( :recv ).and_return( req )
133
+
134
+ res = OneShotHandler.new( TEST_UUID, TEST_SEND_SPEC, TEST_RECV_SPEC ).run
135
+
136
+ res.transactions.should have( 1 ).member
137
+ request, response = res.transactions.first
138
+ request.should be_a( Mongrel2::HTTPRequest )
139
+ response.should be_a( Mongrel2::HTTPResponse )
140
+ response.status.should == 204
141
+ end
142
+
143
+
144
+ it "ignores JSON messages by default" do
145
+ req = make_json_request()
146
+ @request_sock.should_receive( :recv ).and_return( req )
147
+
148
+ res = OneShotHandler.new( TEST_UUID, TEST_SEND_SPEC, TEST_RECV_SPEC ).run
149
+
150
+ res.transactions.should have( 1 ).member
151
+ request, response = res.transactions.first
152
+ request.should be_a( Mongrel2::JSONRequest )
153
+ response.should be_nil()
154
+ end
155
+
156
+
157
+ it "dispatches JSON message to the #handle_json method" do
158
+ json_handler = Class.new( OneShotHandler ) do
159
+ def handle_json( request )
160
+ return request.response
161
+ end
162
+ end
163
+
164
+ req = make_json_request()
165
+ @request_sock.should_receive( :recv ).and_return( req )
166
+
167
+ res = json_handler.new( TEST_UUID, TEST_SEND_SPEC, TEST_RECV_SPEC ).run
168
+
169
+ res.transactions.should have( 1 ).member
170
+ request, response = res.transactions.first
171
+ request.should be_a( Mongrel2::JSONRequest )
172
+ response.should be_a( Mongrel2::Response )
173
+ end
174
+
175
+
176
+ it "ignores XML messages by default" do
177
+ req = make_xml_request()
178
+ @request_sock.should_receive( :recv ).and_return( req )
179
+
180
+ res = OneShotHandler.new( TEST_UUID, TEST_SEND_SPEC, TEST_RECV_SPEC ).run
181
+
182
+ res.transactions.should have( 1 ).member
183
+ request, response = res.transactions.first
184
+ request.should be_a( Mongrel2::XMLRequest )
185
+ response.should be_nil()
186
+ end
187
+
188
+
189
+ it "dispatches XML message to the #handle_xml method" do
190
+ xml_handler = Class.new( OneShotHandler ) do
191
+ def handle_xml( request )
192
+ return request.response
193
+ end
194
+ end
195
+
196
+ req = make_xml_request()
197
+ @request_sock.should_receive( :recv ).and_return( req )
198
+
199
+ res = xml_handler.new( TEST_UUID, TEST_SEND_SPEC, TEST_RECV_SPEC ).run
200
+
201
+ res.transactions.should have( 1 ).member
202
+ request, response = res.transactions.first
203
+ request.should be_a( Mongrel2::XMLRequest )
204
+ response.should be_a( Mongrel2::Response )
205
+ end
206
+
207
+
208
+ it "continues when a ZMQ::Error is received but the connection remains open" do
209
+ req = make_request()
210
+
211
+ @request_sock.should_receive( :recv ).and_raise( ZMQ::Error.new("Interrupted system call.") )
212
+ @request_sock.should_receive( :recv ).and_return( req )
213
+
214
+ res = OneShotHandler.new( TEST_UUID, TEST_SEND_SPEC, TEST_RECV_SPEC ).run
215
+
216
+ res.transactions.should have( 1 ).member
217
+ request, response = res.transactions.first
218
+ request.should be_a( Mongrel2::HTTPRequest )
219
+ response.should be_a( Mongrel2::HTTPResponse )
220
+ response.status.should == 204
221
+ end
222
+
223
+ it "ignores disconnect notices by default" do
224
+ req = make_json_request( :path => '@*', :body => {'type' => 'disconnect'} )
225
+ @request_sock.should_receive( :recv ).and_return( req )
226
+
227
+ res = OneShotHandler.new( TEST_UUID, TEST_SEND_SPEC, TEST_RECV_SPEC ).run
228
+
229
+ res.transactions.should have( 1 ).member
230
+ request, response = res.transactions.first
231
+ request.should be_a( Mongrel2::JSONRequest )
232
+ response.should be_nil()
233
+ end
234
+
235
+ it "dispatches disconnect notices to the #handle_disconnect method" do
236
+ disconnect_handler = Class.new( OneShotHandler ) do
237
+ def handle_disconnect( request )
238
+ self.log.debug "Doing stuff for disconnected connection %d" % [ request.conn_id ]
239
+ end
240
+ end
241
+
242
+ req = make_json_request( :path => '@*', :body => {'type' => 'disconnect'} )
243
+ @request_sock.should_receive( :recv ).and_return( req )
244
+
245
+ res = disconnect_handler.new( TEST_UUID, TEST_SEND_SPEC, TEST_RECV_SPEC ).run
246
+
247
+ res.transactions.should have( 1 ).member
248
+ request, response = res.transactions.first
249
+ request.should be_a( Mongrel2::JSONRequest )
250
+ response.should be_nil()
251
+ end
252
+
253
+ it "re-establishes its connection when told to restart" do
254
+ res = OneShotHandler.new( TEST_UUID, TEST_SEND_SPEC, TEST_RECV_SPEC )
255
+ original_conn = res.conn
256
+ res.restart
257
+ res.conn.should_not equal( original_conn )
258
+ end
259
+
260
+ end
261
+