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