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