mongrel2 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data.tar.gz.sig +0 -0
- data/.gemtest +0 -0
- data/History.rdoc +4 -0
- data/Manifest.txt +66 -0
- data/README.rdoc +169 -0
- data/Rakefile +77 -0
- data/bin/m2sh.rb +600 -0
- data/data/mongrel2/bootstrap.html +25 -0
- data/data/mongrel2/config.sql +84 -0
- data/data/mongrel2/mimetypes.sql +855 -0
- data/examples/README.txt +6 -0
- data/examples/config.rb +54 -0
- data/examples/helloworld-handler.rb +31 -0
- data/examples/request-dumper.rb +45 -0
- data/examples/request-dumper.tmpl +71 -0
- data/examples/run +17 -0
- data/lib/mongrel2.rb +62 -0
- data/lib/mongrel2/config.rb +212 -0
- data/lib/mongrel2/config/directory.rb +78 -0
- data/lib/mongrel2/config/dsl.rb +206 -0
- data/lib/mongrel2/config/handler.rb +124 -0
- data/lib/mongrel2/config/host.rb +88 -0
- data/lib/mongrel2/config/log.rb +48 -0
- data/lib/mongrel2/config/mimetype.rb +15 -0
- data/lib/mongrel2/config/proxy.rb +15 -0
- data/lib/mongrel2/config/route.rb +51 -0
- data/lib/mongrel2/config/server.rb +58 -0
- data/lib/mongrel2/config/setting.rb +15 -0
- data/lib/mongrel2/config/statistic.rb +23 -0
- data/lib/mongrel2/connection.rb +212 -0
- data/lib/mongrel2/constants.rb +159 -0
- data/lib/mongrel2/control.rb +165 -0
- data/lib/mongrel2/exceptions.rb +59 -0
- data/lib/mongrel2/handler.rb +309 -0
- data/lib/mongrel2/httprequest.rb +51 -0
- data/lib/mongrel2/httpresponse.rb +187 -0
- data/lib/mongrel2/jsonrequest.rb +43 -0
- data/lib/mongrel2/logging.rb +241 -0
- data/lib/mongrel2/mixins.rb +143 -0
- data/lib/mongrel2/request.rb +148 -0
- data/lib/mongrel2/response.rb +74 -0
- data/lib/mongrel2/table.rb +216 -0
- data/lib/mongrel2/xmlrequest.rb +36 -0
- data/spec/lib/constants.rb +237 -0
- data/spec/lib/helpers.rb +246 -0
- data/spec/lib/matchers.rb +50 -0
- data/spec/mongrel2/config/directory_spec.rb +91 -0
- data/spec/mongrel2/config/dsl_spec.rb +218 -0
- data/spec/mongrel2/config/handler_spec.rb +118 -0
- data/spec/mongrel2/config/host_spec.rb +30 -0
- data/spec/mongrel2/config/log_spec.rb +95 -0
- data/spec/mongrel2/config/proxy_spec.rb +30 -0
- data/spec/mongrel2/config/route_spec.rb +83 -0
- data/spec/mongrel2/config/server_spec.rb +84 -0
- data/spec/mongrel2/config/setting_spec.rb +30 -0
- data/spec/mongrel2/config/statistic_spec.rb +30 -0
- data/spec/mongrel2/config_spec.rb +111 -0
- data/spec/mongrel2/connection_spec.rb +172 -0
- data/spec/mongrel2/constants_spec.rb +32 -0
- data/spec/mongrel2/control_spec.rb +192 -0
- data/spec/mongrel2/handler_spec.rb +261 -0
- data/spec/mongrel2/httpresponse_spec.rb +232 -0
- data/spec/mongrel2/logging_spec.rb +76 -0
- data/spec/mongrel2/mixins_spec.rb +62 -0
- data/spec/mongrel2/request_spec.rb +157 -0
- data/spec/mongrel2/response_spec.rb +81 -0
- data/spec/mongrel2/table_spec.rb +176 -0
- data/spec/mongrel2_spec.rb +34 -0
- metadata +294 -0
- 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
|
+
|