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