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,159 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
require 'pathname'
|
5
|
+
require 'mongrel2' unless defined?( Mongrel2 )
|
6
|
+
|
7
|
+
|
8
|
+
# A collection of constants that are shared across the library
|
9
|
+
module Mongrel2::Constants
|
10
|
+
|
11
|
+
# The path to the default Sqlite configuration database
|
12
|
+
DEFAULT_CONFIG_URI = 'config.sqlite'
|
13
|
+
|
14
|
+
# Maximum number of identifiers that can be included in a broadcast response
|
15
|
+
MAX_BROADCAST_IDENTS = 100
|
16
|
+
|
17
|
+
|
18
|
+
# HTTP status and result constants
|
19
|
+
module HTTP
|
20
|
+
SWITCHING_PROTOCOLS = 101
|
21
|
+
PROCESSING = 102
|
22
|
+
|
23
|
+
OK = 200
|
24
|
+
CREATED = 201
|
25
|
+
ACCEPTED = 202
|
26
|
+
NON_AUTHORITATIVE = 203
|
27
|
+
NO_CONTENT = 204
|
28
|
+
RESET_CONTENT = 205
|
29
|
+
PARTIAL_CONTENT = 206
|
30
|
+
MULTI_STATUS = 207
|
31
|
+
|
32
|
+
MULTIPLE_CHOICES = 300
|
33
|
+
MOVED_PERMANENTLY = 301
|
34
|
+
MOVED = 301
|
35
|
+
MOVED_TEMPORARILY = 302
|
36
|
+
REDIRECT = 302
|
37
|
+
SEE_OTHER = 303
|
38
|
+
NOT_MODIFIED = 304
|
39
|
+
USE_PROXY = 305
|
40
|
+
TEMPORARY_REDIRECT = 307
|
41
|
+
|
42
|
+
BAD_REQUEST = 400
|
43
|
+
AUTH_REQUIRED = 401
|
44
|
+
UNAUTHORIZED = 401
|
45
|
+
PAYMENT_REQUIRED = 402
|
46
|
+
FORBIDDEN = 403
|
47
|
+
NOT_FOUND = 404
|
48
|
+
METHOD_NOT_ALLOWED = 405
|
49
|
+
NOT_ACCEPTABLE = 406
|
50
|
+
PROXY_AUTHENTICATION_REQUIRED = 407
|
51
|
+
REQUEST_TIME_OUT = 408
|
52
|
+
CONFLICT = 409
|
53
|
+
GONE = 410
|
54
|
+
LENGTH_REQUIRED = 411
|
55
|
+
PRECONDITION_FAILED = 412
|
56
|
+
REQUEST_ENTITY_TOO_LARGE = 413
|
57
|
+
REQUEST_URI_TOO_LARGE = 414
|
58
|
+
UNSUPPORTED_MEDIA_TYPE = 415
|
59
|
+
RANGE_NOT_SATISFIABLE = 416
|
60
|
+
EXPECTATION_FAILED = 417
|
61
|
+
UNPROCESSABLE_ENTITY = 422
|
62
|
+
LOCKED = 423
|
63
|
+
FAILED_DEPENDENCY = 424
|
64
|
+
|
65
|
+
SERVER_ERROR = 500
|
66
|
+
NOT_IMPLEMENTED = 501
|
67
|
+
BAD_GATEWAY = 502
|
68
|
+
SERVICE_UNAVAILABLE = 503
|
69
|
+
GATEWAY_TIME_OUT = 504
|
70
|
+
VERSION_NOT_SUPPORTED = 505
|
71
|
+
VARIANT_ALSO_VARIES = 506
|
72
|
+
INSUFFICIENT_STORAGE = 507
|
73
|
+
NOT_EXTENDED = 510
|
74
|
+
|
75
|
+
# Stolen from Apache 2.2.6's modules/http/http_protocol.c
|
76
|
+
STATUS_NAME = {
|
77
|
+
100 => "Continue",
|
78
|
+
101 => "Switching Protocols",
|
79
|
+
102 => "Processing",
|
80
|
+
200 => "OK",
|
81
|
+
201 => "Created",
|
82
|
+
202 => "Accepted",
|
83
|
+
203 => "Non-Authoritative Information",
|
84
|
+
204 => "No Content",
|
85
|
+
205 => "Reset Content",
|
86
|
+
206 => "Partial Content",
|
87
|
+
207 => "Multi-Status",
|
88
|
+
300 => "Multiple Choices",
|
89
|
+
301 => "Moved Permanently",
|
90
|
+
302 => "Found",
|
91
|
+
303 => "See Other",
|
92
|
+
304 => "Not Modified",
|
93
|
+
305 => "Use Proxy",
|
94
|
+
306 => "Undefined HTTP Status",
|
95
|
+
307 => "Temporary Redirect",
|
96
|
+
400 => "Bad Request",
|
97
|
+
401 => "Authorization Required",
|
98
|
+
402 => "Payment Required",
|
99
|
+
403 => "Forbidden",
|
100
|
+
404 => "Not Found",
|
101
|
+
405 => "Method Not Allowed",
|
102
|
+
406 => "Not Acceptable",
|
103
|
+
407 => "Proxy Authentication Required",
|
104
|
+
408 => "Request Time-out",
|
105
|
+
409 => "Conflict",
|
106
|
+
410 => "Gone",
|
107
|
+
411 => "Length Required",
|
108
|
+
412 => "Precondition Failed",
|
109
|
+
413 => "Request Entity Too Large",
|
110
|
+
414 => "Request-URI Too Large",
|
111
|
+
415 => "Unsupported Media Type",
|
112
|
+
416 => "Requested Range Not Satisfiable",
|
113
|
+
417 => "Expectation Failed",
|
114
|
+
418 => "Undefined HTTP Status",
|
115
|
+
419 => "Undefined HTTP Status",
|
116
|
+
420 => "Undefined HTTP Status",
|
117
|
+
421 => "Undefined HTTP Status",
|
118
|
+
406 => "Not Acceptable",
|
119
|
+
407 => "Proxy Authentication Required",
|
120
|
+
408 => "Request Time-out",
|
121
|
+
409 => "Conflict",
|
122
|
+
410 => "Gone",
|
123
|
+
411 => "Length Required",
|
124
|
+
412 => "Precondition Failed",
|
125
|
+
413 => "Request Entity Too Large",
|
126
|
+
414 => "Request-URI Too Large",
|
127
|
+
415 => "Unsupported Media Type",
|
128
|
+
416 => "Requested Range Not Satisfiable",
|
129
|
+
417 => "Expectation Failed",
|
130
|
+
418 => "Undefined HTTP Status",
|
131
|
+
419 => "Undefined HTTP Status",
|
132
|
+
420 => "Undefined HTTP Status",
|
133
|
+
421 => "Undefined HTTP Status",
|
134
|
+
422 => "Unprocessable Entity",
|
135
|
+
423 => "Locked",
|
136
|
+
424 => "Failed Dependency",
|
137
|
+
425 => "No code",
|
138
|
+
426 => "Upgrade Required",
|
139
|
+
500 => "Internal Server Error",
|
140
|
+
501 => "Method Not Implemented",
|
141
|
+
502 => "Bad Gateway",
|
142
|
+
503 => "Service Temporarily Unavailable",
|
143
|
+
504 => "Gateway Time-out",
|
144
|
+
505 => "HTTP Version Not Supported",
|
145
|
+
506 => "Variant Also Negotiates",
|
146
|
+
507 => "Insufficient Storage",
|
147
|
+
508 => "Undefined HTTP Status",
|
148
|
+
509 => "Undefined HTTP Status",
|
149
|
+
510 => "Not Extended"
|
150
|
+
}
|
151
|
+
|
152
|
+
|
153
|
+
# Methods which aren't allowed to have an entity-body
|
154
|
+
SHALLOW_METHODS = [ :GET, :HEAD, :DELETE ]
|
155
|
+
end
|
156
|
+
|
157
|
+
|
158
|
+
end # module Mongrel2::Constants
|
159
|
+
|
@@ -0,0 +1,165 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require 'zmq'
|
4
|
+
require 'yajl'
|
5
|
+
require 'tnetstring'
|
6
|
+
|
7
|
+
require 'mongrel2' unless defined?( Mongrel2 )
|
8
|
+
require 'mongrel2/mixins'
|
9
|
+
|
10
|
+
|
11
|
+
# An interface to the Mongrel2 control port.
|
12
|
+
#
|
13
|
+
# == References
|
14
|
+
# (http://mongrel2.org/static/mongrel2-manual.html#x1-380003.8)
|
15
|
+
class Mongrel2::Control
|
16
|
+
include Mongrel2::Loggable
|
17
|
+
|
18
|
+
# The default zmq connection spec to use when talking to a mongrel2 server
|
19
|
+
DEFAULT_PORT = 'ipc://run/control'
|
20
|
+
|
21
|
+
|
22
|
+
### Create a new control port object using the current configuration.
|
23
|
+
def initialize( port=DEFAULT_PORT )
|
24
|
+
@ctx = Mongrel2.zmq_context
|
25
|
+
@socket = @ctx.socket( ZMQ::REQ )
|
26
|
+
@socket.connect( port.to_s )
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
######
|
31
|
+
public
|
32
|
+
######
|
33
|
+
|
34
|
+
# The control port ZMQ::REQ socket
|
35
|
+
attr_reader :socket
|
36
|
+
|
37
|
+
|
38
|
+
### Stops the server using a SIGINT.
|
39
|
+
def stop
|
40
|
+
self.request( :stop )
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
### Reloads the server using a SIGHUP.
|
45
|
+
def reload
|
46
|
+
self.request( :reload )
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
### Terminates the server with SIGTERM.
|
51
|
+
def terminate
|
52
|
+
self.request( :terminate )
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
### Prints out a simple help message.
|
57
|
+
def help
|
58
|
+
self.request( :help )
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
### Returns the server’s UUID as a String.
|
63
|
+
def uuid
|
64
|
+
self.request( :uuid )
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
### More information about the server.
|
69
|
+
def info
|
70
|
+
self.request( :info )
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
### Returns a Hash of all the currently running tasks and what they’re doing, keyed
|
75
|
+
### by conn_id.
|
76
|
+
def tasklist
|
77
|
+
self.request( :status, :what => 'tasks' )
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
### Dumps a JSON dict that matches connections IDs (same ones your handlers
|
82
|
+
### get) to the seconds since their last ping. In the case of an HTTP
|
83
|
+
### connection this is how long they’ve been connected. In the case of a
|
84
|
+
### JSON socket this is the last time a ping message was received.
|
85
|
+
def conn_status
|
86
|
+
self.request( :status, :what => 'net' )
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
### Prints the unix time the server thinks it’s using. Useful for synching.
|
91
|
+
def time
|
92
|
+
response = self.request( :time )
|
93
|
+
response.each do |row|
|
94
|
+
row[ :time ] = Time.at( row.delete(:time).to_i ) if row[:time]
|
95
|
+
end
|
96
|
+
|
97
|
+
return response
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
### Does a forced close on the socket that is at the specified +conn_id+.
|
102
|
+
def kill( conn_id )
|
103
|
+
self.request( :kill, :id => conn_id )
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
### Shuts down the control port permanently in case you want to keep it from
|
108
|
+
### being accessed for some reason.
|
109
|
+
def control_stop
|
110
|
+
self.request( :control_stop )
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
### Send a raw request to the server, asking it to perform the +command+ with the specified
|
115
|
+
### +options+ hash and return the results.
|
116
|
+
def request( command, options={} )
|
117
|
+
msg = TNetstring.dump([ command, options ])
|
118
|
+
self.log.debug "Request: %p" % [ msg ]
|
119
|
+
self.socket.send( msg )
|
120
|
+
|
121
|
+
response = self.socket.recv
|
122
|
+
self.log.debug "Response: %p" % [ response ]
|
123
|
+
return unpack_response( response )
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
### Close the control port connection.
|
128
|
+
def close
|
129
|
+
self.socket.close
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
#######
|
134
|
+
private
|
135
|
+
#######
|
136
|
+
|
137
|
+
### Unpack a Mongrel2 Control Port response (TNetString encoded table) as an
|
138
|
+
### Array of Hashes.
|
139
|
+
def unpack_response( response )
|
140
|
+
table = TNetstring.parse( response ).first
|
141
|
+
self.log.debug "Unpacking response: %p" % [ table ]
|
142
|
+
|
143
|
+
# Success
|
144
|
+
if table.key?( 'headers' )
|
145
|
+
headers, rows = table.values_at( 'headers', 'rows' )
|
146
|
+
headers.map!( &:to_sym )
|
147
|
+
|
148
|
+
return rows.collect do |row|
|
149
|
+
Hash[ [headers, row].transpose ]
|
150
|
+
end
|
151
|
+
|
152
|
+
# Error
|
153
|
+
elsif table.key?( 'code' )
|
154
|
+
# {"code"=>"INVALID_ARGUMENT", "error"=>"Invalid argument type."}
|
155
|
+
raise Mongrel2::ControlError.new( table['code'], table['error'] )
|
156
|
+
|
157
|
+
else
|
158
|
+
raise ScriptError, "Don't know how to handle response: %p" % [ table ]
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
end # class Mongrel2::Control
|
163
|
+
|
164
|
+
# vim: set nosta noet ts=4 sw=4:
|
165
|
+
|
@@ -0,0 +1,59 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
#--
|
4
|
+
module Mongrel2
|
5
|
+
|
6
|
+
# A base exception class.
|
7
|
+
class Exception < ::RuntimeError; end
|
8
|
+
|
9
|
+
# An exception class raised from a Mongrel2::Request when
|
10
|
+
# a problem is encountered while parsing raw request data.
|
11
|
+
class ParseError < Mongrel2::Exception; end
|
12
|
+
|
13
|
+
# An exception class raised from a Mongrel2::Request when
|
14
|
+
# the raw request headers contain an unhandled METHOD.
|
15
|
+
class UnhandledMethodError < Mongrel2::Exception
|
16
|
+
|
17
|
+
### Set the +method_name+ that was unhandled.
|
18
|
+
def initialize( method_name, *args )
|
19
|
+
@method_name = method_name
|
20
|
+
super( "Unhandled method %p" % [ method_name ], *args )
|
21
|
+
end
|
22
|
+
|
23
|
+
# The METHOD that was unhandled
|
24
|
+
attr_reader :method_name
|
25
|
+
|
26
|
+
end # class UnhandledMethodError
|
27
|
+
|
28
|
+
# An exception class raised when an attempt is made to use a
|
29
|
+
# Mongrel2::Connection after it has been closed.
|
30
|
+
class ConnectionError < Mongrel2::Exception; end
|
31
|
+
|
32
|
+
# An exception type raised when an operation requires that a configuration
|
33
|
+
# database be configured but none was; if it's configured but doesn't
|
34
|
+
# exist; or if it doesn't contain the information requested.
|
35
|
+
class ConfigError < Mongrel2::Exception; end
|
36
|
+
|
37
|
+
# An exception type raised by a response if it can't generate a valid response
|
38
|
+
# document
|
39
|
+
class ResponseError < Mongrel2::Exception; end
|
40
|
+
|
41
|
+
# An exception type raised by Mongrel2::Control requests if they result in
|
42
|
+
# an error response.
|
43
|
+
class ControlError < Mongrel2::Exception
|
44
|
+
|
45
|
+
### Set the error +code+ in addition to the +message+.
|
46
|
+
def initialize( code, message )
|
47
|
+
@code = code.to_sym
|
48
|
+
super( message )
|
49
|
+
end
|
50
|
+
|
51
|
+
# The code of the particular kind of control error (a Symbol)
|
52
|
+
attr_reader :code
|
53
|
+
|
54
|
+
end # class ControlError
|
55
|
+
|
56
|
+
end # module Mongrel2
|
57
|
+
|
58
|
+
|
59
|
+
# vim: set noet nosta sw=4 ts=4 :
|
@@ -0,0 +1,309 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'mongrel2' unless defined?( Mongrel2 )
|
4
|
+
require 'mongrel2/config'
|
5
|
+
|
6
|
+
require 'mongrel2/request'
|
7
|
+
require 'mongrel2/httprequest'
|
8
|
+
require 'mongrel2/jsonrequest'
|
9
|
+
|
10
|
+
# Mongrel2 Handler application class. Instances of this class are the applications
|
11
|
+
# which connection to one or more Mongrel2 routes and respond to requests.
|
12
|
+
#
|
13
|
+
# == Example
|
14
|
+
#
|
15
|
+
# A dumb, dead-simple example that just returns a plaintext 'Hello'
|
16
|
+
# document with a timestamp.
|
17
|
+
#
|
18
|
+
# #!/usr/bin/env ruby
|
19
|
+
#
|
20
|
+
# require 'mongrel2/handler'
|
21
|
+
#
|
22
|
+
# class HelloWorldHandler < Mongrel2::Handler
|
23
|
+
#
|
24
|
+
# ### The main method to override -- accepts requests and
|
25
|
+
# ### returns responses.
|
26
|
+
# def handle( request )
|
27
|
+
# response = request.response
|
28
|
+
#
|
29
|
+
# response.status = 200
|
30
|
+
# response.headers.content_type = 'text/plain'
|
31
|
+
# response.puts "Hello, world, it's #{Time.now}!"
|
32
|
+
#
|
33
|
+
# return response
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# end # class HelloWorldHandler
|
37
|
+
#
|
38
|
+
# HelloWorldHandler.run( 'helloworld-handler' )
|
39
|
+
#
|
40
|
+
# This assumes the Mongrel2 SQLite config database is in the current
|
41
|
+
# directory, and is named 'config.sqlite' (the Mongrel2 default), but
|
42
|
+
# if it's somewhere else, you can point the Mongrel2::Config class
|
43
|
+
# to it:
|
44
|
+
#
|
45
|
+
# require 'mongrel2/config'
|
46
|
+
# Mongrel2::Config.configure( :configdb => 'mongrel2.db' )
|
47
|
+
#
|
48
|
+
# Mongrel2 also includes support for Configurability, so you can
|
49
|
+
# configure it along with your database connection, etc. Just add a
|
50
|
+
# 'mongrel2' section to the config with a 'configdb' key that points
|
51
|
+
# to where the Mongrel2 SQLite config database lives:
|
52
|
+
#
|
53
|
+
# # config.yaml
|
54
|
+
# db:
|
55
|
+
# uri: postgres://www@localhost/db01
|
56
|
+
#
|
57
|
+
# mongrel2:
|
58
|
+
# configdb: mongrel2.db
|
59
|
+
#
|
60
|
+
# whatever_else:
|
61
|
+
# ...
|
62
|
+
#
|
63
|
+
# Now just loading and installing the config configures Mongrel2 as
|
64
|
+
# well:
|
65
|
+
#
|
66
|
+
# require 'configurability/config'
|
67
|
+
#
|
68
|
+
# config = Configurability::Config.load( 'config.yml' )
|
69
|
+
# config.install
|
70
|
+
#
|
71
|
+
# If the Mongrel2 config database isn't accessible, or you need to
|
72
|
+
# configure the Handler's two 0mq connections yourself for some
|
73
|
+
# reason, you can do that, too:
|
74
|
+
#
|
75
|
+
# app = HelloWorldHandler.new( 'helloworld-handler',
|
76
|
+
# 'tcp://otherhost:9999', 'tcp://otherhost:9998' )
|
77
|
+
# app.run
|
78
|
+
#
|
79
|
+
class Mongrel2::Handler
|
80
|
+
include Mongrel2::Loggable
|
81
|
+
|
82
|
+
|
83
|
+
### Create an instance of the handler using the config from the database with
|
84
|
+
### the given +appid+ and run it.
|
85
|
+
def self::run( appid )
|
86
|
+
Mongrel2.log.info "Running application %p" % [ appid ]
|
87
|
+
send_spec, recv_spec = self.connection_info_for( appid )
|
88
|
+
Mongrel2.log.info " config specs: %s <-> %s" % [ send_spec, recv_spec ]
|
89
|
+
new( appid, send_spec, recv_spec ).run
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
### Return the send_spec and recv_spec for the given +appid+ from the current configuration
|
94
|
+
### database. Returns +nil+ if no Handler is configured with +appid+ as its +sender_id+.
|
95
|
+
def self::connection_info_for( appid )
|
96
|
+
Mongrel2.log.debug "Looking up handler spec for appid %p" % [ appid ]
|
97
|
+
hconfig = Mongrel2::Config::Handler.by_send_ident( appid ).first or
|
98
|
+
raise ArgumentError, "no handler with a send_ident of %p configured" % [ appid ]
|
99
|
+
|
100
|
+
Mongrel2.log.debug " found: %s" % [ hconfig.values ]
|
101
|
+
return hconfig.send_spec, hconfig.recv_spec
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
#################################################################
|
106
|
+
### I N S T A N C E M E T H O D S
|
107
|
+
#################################################################
|
108
|
+
|
109
|
+
### Create a new instance of the handler with the specified +app_id+, +send_spec+,
|
110
|
+
### and +recv_spec+.
|
111
|
+
def initialize( app_id, send_spec, recv_spec ) # :notnew:
|
112
|
+
@conn = Mongrel2::Connection.new( app_id, send_spec, recv_spec )
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
######
|
117
|
+
public
|
118
|
+
######
|
119
|
+
|
120
|
+
# The handler's Mongrel2::Connection object.
|
121
|
+
attr_reader :conn
|
122
|
+
|
123
|
+
|
124
|
+
### Run the handler.
|
125
|
+
def run
|
126
|
+
self.log.info "Starting up %p" % [ self ]
|
127
|
+
self.set_signal_handlers
|
128
|
+
self.start_accepting_requests
|
129
|
+
|
130
|
+
return self # For chaining
|
131
|
+
ensure
|
132
|
+
self.restore_signal_handlers
|
133
|
+
self.log.info "Done: %p" % [ self ]
|
134
|
+
end
|
135
|
+
|
136
|
+
|
137
|
+
### Shut down the handler.
|
138
|
+
def shutdown
|
139
|
+
self.log.info "Shutting down."
|
140
|
+
@conn.close
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
### Restart the handler. You should override this if you want to re-establish
|
145
|
+
### database connections, flush caches, or other restart-ey stuff.
|
146
|
+
def restart
|
147
|
+
self.log.info "Restarting"
|
148
|
+
old_conn = @conn
|
149
|
+
@conn = @conn.dup
|
150
|
+
self.log.debug " conn %p -> %p" % [ old_conn, @conn ]
|
151
|
+
old_conn.close
|
152
|
+
end
|
153
|
+
|
154
|
+
|
155
|
+
### Start a loop, accepting a request and handling it.
|
156
|
+
def start_accepting_requests
|
157
|
+
until @conn.closed?
|
158
|
+
req = @conn.receive
|
159
|
+
self.log.info "%p via %s:%d" % [ req.class, req.sender_id, req.conn_id ]
|
160
|
+
|
161
|
+
res = self.dispatch_request( req )
|
162
|
+
|
163
|
+
if res
|
164
|
+
self.log.debug " responding with a %p" % [ res.class ]
|
165
|
+
@conn.reply( res ) unless @conn.closed?
|
166
|
+
else
|
167
|
+
self.log.debug " no response; ignoring."
|
168
|
+
end
|
169
|
+
end
|
170
|
+
rescue ZMQ::Error => err
|
171
|
+
self.log.error "%p while accepting requests: %s" % [ err.class, err.message ]
|
172
|
+
self.log.debug { err.backtrace.join("\n ") }
|
173
|
+
|
174
|
+
retry unless @conn.closed?
|
175
|
+
end
|
176
|
+
|
177
|
+
|
178
|
+
### Invoke a handler method appropriate for the given +request+.
|
179
|
+
def dispatch_request( request )
|
180
|
+
if request.is_disconnect?
|
181
|
+
self.log.debug "disconnect!"
|
182
|
+
self.handle_disconnect( request )
|
183
|
+
return nil
|
184
|
+
|
185
|
+
else
|
186
|
+
case request
|
187
|
+
when Mongrel2::HTTPRequest
|
188
|
+
self.log.debug "HTTP request."
|
189
|
+
return self.handle( request )
|
190
|
+
when Mongrel2::JSONRequest
|
191
|
+
self.log.debug "JSON message request."
|
192
|
+
return self.handle_json( request )
|
193
|
+
when Mongrel2::XMLRequest
|
194
|
+
self.log.debug "XML message request."
|
195
|
+
return self.handle_xml( request )
|
196
|
+
else
|
197
|
+
self.log.error "Unhandled request type %p" % [ request.class ]
|
198
|
+
return nil
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
|
204
|
+
### Returns a string containing a human-readable representation of the Handler suitable
|
205
|
+
### for debugging.
|
206
|
+
def inspect
|
207
|
+
return "#<%p:0x%08x conn: %p>" % [
|
208
|
+
self.class,
|
209
|
+
self.object_id * 2,
|
210
|
+
self.conn,
|
211
|
+
]
|
212
|
+
end
|
213
|
+
|
214
|
+
|
215
|
+
#
|
216
|
+
# :section: Handler Methods
|
217
|
+
# These methods are the principle mechanism for defining the functionality of
|
218
|
+
# your handler. The logic that dispatches to these methods is all contained in
|
219
|
+
# the #dispatch_request method, so if you want to do something completely different,
|
220
|
+
# you should override that instead.
|
221
|
+
#
|
222
|
+
|
223
|
+
### The main handler function: handle the specified HTTP +request+ (a Mongrel2::Request) and
|
224
|
+
### return a response (Mongrel2::Response). If not overridden, this method returns a
|
225
|
+
### '204 No Content' response.
|
226
|
+
def handle( request )
|
227
|
+
self.log.warn "No default handler; responding with '204 No Content'"
|
228
|
+
response = request.response
|
229
|
+
response.status = HTTP::NO_CONTENT
|
230
|
+
|
231
|
+
return response
|
232
|
+
end
|
233
|
+
|
234
|
+
|
235
|
+
### Handle a JSON message +request+. If not overridden, JSON message ('@route')
|
236
|
+
### requests are ignored.
|
237
|
+
def handle_json( request )
|
238
|
+
self.log.warn "Unhandled JSON message request (%p)" % [ request.headers[:path] ]
|
239
|
+
return nil
|
240
|
+
end
|
241
|
+
|
242
|
+
|
243
|
+
### Handle an XML message +request+. If not overridden, XML message ('<route')
|
244
|
+
### requests are ignored.
|
245
|
+
def handle_xml( request )
|
246
|
+
self.log.warn "Unhandled XML message request (%p)" % [ request.headers[:path] ]
|
247
|
+
return nil
|
248
|
+
end
|
249
|
+
|
250
|
+
|
251
|
+
### Handle a disconnect notice from Mongrel2 via the given +request+. Its return value
|
252
|
+
### is ignored.
|
253
|
+
def handle_disconnect( request )
|
254
|
+
self.log.warn "Unhandled disconnect notice."
|
255
|
+
return nil
|
256
|
+
end
|
257
|
+
|
258
|
+
|
259
|
+
#
|
260
|
+
# :section: Signal Handling
|
261
|
+
# These methods set up some behavior for starting, restarting, and stopping
|
262
|
+
# your application when a signal is received. If you don't want signals to
|
263
|
+
# be handled, override #set_signal_handlers with an empty method.
|
264
|
+
#
|
265
|
+
|
266
|
+
### Set up signal handlers for SIGINT, SIGTERM, SIGINT, and SIGUSR1 that will call the
|
267
|
+
### #on_hangup_signal, #on_termination_signal, #on_interrupt_signal, and
|
268
|
+
### #on_user1_signal methods, respectively.
|
269
|
+
def set_signal_handlers
|
270
|
+
Signal.trap( :HUP, &self.method(:on_hangup_signal) )
|
271
|
+
Signal.trap( :TERM, &self.method(:on_termination_signal) )
|
272
|
+
Signal.trap( :INT, &self.method(:on_interrupt_signal) )
|
273
|
+
Signal.trap( :USR1, &self.method(:on_user1_signal) )
|
274
|
+
end
|
275
|
+
|
276
|
+
|
277
|
+
### Restore the default signal handlers
|
278
|
+
def restore_signal_handlers
|
279
|
+
Signal.trap( :HUP, :DEFAULT )
|
280
|
+
Signal.trap( :TERM, :DEFAULT )
|
281
|
+
Signal.trap( :INT, :DEFAULT )
|
282
|
+
Signal.trap( :USR1, :DEFAULT )
|
283
|
+
end
|
284
|
+
|
285
|
+
|
286
|
+
### Handle a HUP signal. The default is to restart the handler.
|
287
|
+
def on_hangup_signal( signo )
|
288
|
+
self.log.warn "Hangup: reconnecting."
|
289
|
+
self.restart
|
290
|
+
end
|
291
|
+
|
292
|
+
|
293
|
+
### Handle a TERM signal. Shuts the handler down after handling any current request/s. Also
|
294
|
+
### aliased to #on_interrupt_signal.
|
295
|
+
def on_termination_signal( signo )
|
296
|
+
self.log.warn "Halting on signal #{signo} (Thread: %p vs. main: %p)" %
|
297
|
+
[ Thread.current, Thread.main ]
|
298
|
+
self.shutdown
|
299
|
+
end
|
300
|
+
alias_method :on_interrupt_signal, :on_termination_signal
|
301
|
+
|
302
|
+
|
303
|
+
### Handle a USR1 signal. Writes a message to the log by default.
|
304
|
+
def on_user1_signal( signo )
|
305
|
+
self.log.info "Checkpoint: User signal."
|
306
|
+
end
|
307
|
+
|
308
|
+
end # class Mongrel2::Handler
|
309
|
+
|