mongrel2 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. data.tar.gz.sig +0 -0
  2. data/.gemtest +0 -0
  3. data/History.rdoc +4 -0
  4. data/Manifest.txt +66 -0
  5. data/README.rdoc +169 -0
  6. data/Rakefile +77 -0
  7. data/bin/m2sh.rb +600 -0
  8. data/data/mongrel2/bootstrap.html +25 -0
  9. data/data/mongrel2/config.sql +84 -0
  10. data/data/mongrel2/mimetypes.sql +855 -0
  11. data/examples/README.txt +6 -0
  12. data/examples/config.rb +54 -0
  13. data/examples/helloworld-handler.rb +31 -0
  14. data/examples/request-dumper.rb +45 -0
  15. data/examples/request-dumper.tmpl +71 -0
  16. data/examples/run +17 -0
  17. data/lib/mongrel2.rb +62 -0
  18. data/lib/mongrel2/config.rb +212 -0
  19. data/lib/mongrel2/config/directory.rb +78 -0
  20. data/lib/mongrel2/config/dsl.rb +206 -0
  21. data/lib/mongrel2/config/handler.rb +124 -0
  22. data/lib/mongrel2/config/host.rb +88 -0
  23. data/lib/mongrel2/config/log.rb +48 -0
  24. data/lib/mongrel2/config/mimetype.rb +15 -0
  25. data/lib/mongrel2/config/proxy.rb +15 -0
  26. data/lib/mongrel2/config/route.rb +51 -0
  27. data/lib/mongrel2/config/server.rb +58 -0
  28. data/lib/mongrel2/config/setting.rb +15 -0
  29. data/lib/mongrel2/config/statistic.rb +23 -0
  30. data/lib/mongrel2/connection.rb +212 -0
  31. data/lib/mongrel2/constants.rb +159 -0
  32. data/lib/mongrel2/control.rb +165 -0
  33. data/lib/mongrel2/exceptions.rb +59 -0
  34. data/lib/mongrel2/handler.rb +309 -0
  35. data/lib/mongrel2/httprequest.rb +51 -0
  36. data/lib/mongrel2/httpresponse.rb +187 -0
  37. data/lib/mongrel2/jsonrequest.rb +43 -0
  38. data/lib/mongrel2/logging.rb +241 -0
  39. data/lib/mongrel2/mixins.rb +143 -0
  40. data/lib/mongrel2/request.rb +148 -0
  41. data/lib/mongrel2/response.rb +74 -0
  42. data/lib/mongrel2/table.rb +216 -0
  43. data/lib/mongrel2/xmlrequest.rb +36 -0
  44. data/spec/lib/constants.rb +237 -0
  45. data/spec/lib/helpers.rb +246 -0
  46. data/spec/lib/matchers.rb +50 -0
  47. data/spec/mongrel2/config/directory_spec.rb +91 -0
  48. data/spec/mongrel2/config/dsl_spec.rb +218 -0
  49. data/spec/mongrel2/config/handler_spec.rb +118 -0
  50. data/spec/mongrel2/config/host_spec.rb +30 -0
  51. data/spec/mongrel2/config/log_spec.rb +95 -0
  52. data/spec/mongrel2/config/proxy_spec.rb +30 -0
  53. data/spec/mongrel2/config/route_spec.rb +83 -0
  54. data/spec/mongrel2/config/server_spec.rb +84 -0
  55. data/spec/mongrel2/config/setting_spec.rb +30 -0
  56. data/spec/mongrel2/config/statistic_spec.rb +30 -0
  57. data/spec/mongrel2/config_spec.rb +111 -0
  58. data/spec/mongrel2/connection_spec.rb +172 -0
  59. data/spec/mongrel2/constants_spec.rb +32 -0
  60. data/spec/mongrel2/control_spec.rb +192 -0
  61. data/spec/mongrel2/handler_spec.rb +261 -0
  62. data/spec/mongrel2/httpresponse_spec.rb +232 -0
  63. data/spec/mongrel2/logging_spec.rb +76 -0
  64. data/spec/mongrel2/mixins_spec.rb +62 -0
  65. data/spec/mongrel2/request_spec.rb +157 -0
  66. data/spec/mongrel2/response_spec.rb +81 -0
  67. data/spec/mongrel2/table_spec.rb +176 -0
  68. data/spec/mongrel2_spec.rb +34 -0
  69. metadata +294 -0
  70. metadata.gz.sig +0 -0
@@ -0,0 +1,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
+