mongrel2 0.0.1

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.
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
+