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,51 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'mongrel2/request' unless defined?( Mongrel2::Request )
4
+ require 'mongrel2/mixins'
5
+ require 'mongrel2/httpresponse'
6
+
7
+
8
+ # The Mongrel2 HTTP Request class. Instances of this class represent an HTTP request from
9
+ # a Mongrel2 server.
10
+ class Mongrel2::HTTPRequest < Mongrel2::Request
11
+ include Mongrel2::Loggable
12
+
13
+ # HTTP verbs from RFC2616
14
+ HANDLED_HTTP_METHODS = [ :OPTIONS, :GET, :HEAD, :POST, :PUT, :DELETE, :TRACE, :CONNECT ]
15
+
16
+ register_request_type( self, *HANDLED_HTTP_METHODS )
17
+
18
+
19
+ ### Create a Mongrel2::HTTPResponse that corresponds to the receiver.
20
+ def response
21
+ return Mongrel2::HTTPResponse.from_request( self )
22
+ end
23
+
24
+
25
+ ### Return +true+ if the request is an HTTP/1.1 request and its
26
+ ### 'Connection' header indicates that the connection should stay
27
+ ### open.
28
+ def keepalive?
29
+ unless self.headers[:version] == 'HTTP/1.1'
30
+ self.log.debug "Not an http/1.1 request: not persistent"
31
+ return false
32
+ end
33
+ conn_header = self.headers[:connection]
34
+ if !conn_header
35
+ self.log.debug "No Connection header: assume persistence"
36
+ return true
37
+ end
38
+
39
+ if conn_header.split( /\s*,\s*/ ).include?( 'close' )
40
+ self.log.debug "Connection: close header."
41
+ return false
42
+ else
43
+ self.log.debug "Connection header didn't contain 'close': assume persistence"
44
+ return true
45
+ end
46
+ end
47
+
48
+ end # class Mongrel2::HTTPRequest
49
+
50
+ # vim: set nosta noet ts=4 sw=4:
51
+
@@ -0,0 +1,187 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'time'
4
+
5
+ require 'mongrel2/response' unless defined?( Mongrel2::Response )
6
+ require 'mongrel2/mixins'
7
+ require 'mongrel2/constants'
8
+
9
+
10
+ # The Mongrel2 HTTP Response class.
11
+ class Mongrel2::HTTPResponse < Mongrel2::Response
12
+ include Mongrel2::Loggable,
13
+ Mongrel2::Constants
14
+
15
+ # The format for building valid HTTP responses
16
+ STATUS_LINE_FORMAT = "HTTP/1.1 %03d %s".freeze
17
+
18
+ # The default status
19
+ DEFAULT_HTTP_STATUS = 200
20
+
21
+ # A network End-Of-Line
22
+ EOL = "\r\n".freeze
23
+
24
+ # The default content type
25
+ DEFAULT_CONTENT_TYPE = 'application/octet-stream'.freeze
26
+
27
+
28
+ ### Set up a few things specific to HTTP responses
29
+ def initialize( sender_id, conn_id, body='', headers={} ) # :notnew:
30
+ if body.is_a?( Hash )
31
+ headers = body
32
+ body = ''
33
+ end
34
+
35
+ super( sender_id, conn_id, body )
36
+
37
+ @headers = Mongrel2::Table.new( headers )
38
+ @status = nil
39
+ self.reset
40
+ end
41
+
42
+
43
+ ######
44
+ public
45
+ ######
46
+
47
+ # The response headers (a Mongrel2::Table)
48
+ attr_reader :headers
49
+
50
+ # The HTTP status code
51
+ attr_accessor :status
52
+
53
+
54
+ ### Stringify the response
55
+ def to_s
56
+ return [
57
+ self.status_line,
58
+ self.header_data,
59
+ self.body
60
+ ].join( "\r\n" )
61
+ end
62
+
63
+
64
+ ### Send the response status to the client
65
+ def status_line
66
+ self.log.warn "Building status line for unset status" if self.status.nil?
67
+
68
+ st = self.status || DEFAULT_HTTP_STATUS
69
+ return STATUS_LINE_FORMAT % [ st, HTTP::STATUS_NAME[st] ]
70
+ end
71
+
72
+
73
+ ### Returns true if the response is ready to be sent to the client.
74
+ def handled?
75
+ return ! @status.nil?
76
+ end
77
+ alias_method :is_handled?, :handled?
78
+
79
+
80
+ ### Return the numeric category of the response's status code (1-5)
81
+ def status_category
82
+ return 0 if self.status.nil?
83
+ return (self.status / 100).ceil
84
+ end
85
+
86
+
87
+ ### Return true if response is in the 1XX range
88
+ def status_is_informational?
89
+ return self.status_category == 1
90
+ end
91
+
92
+ ### Return true if response is in the 2XX range
93
+ def status_is_successful?
94
+ return self.status_category == 2
95
+ end
96
+
97
+
98
+ ### Return true if response is in the 3XX range
99
+ def status_is_redirect?
100
+ return self.status_category == 3
101
+ end
102
+
103
+
104
+ ### Return true if response is in the 4XX range
105
+ def status_is_clienterror?
106
+ return self.status_category == 4
107
+ end
108
+
109
+
110
+ ### Return true if response is in the 5XX range
111
+ def status_is_servererror?
112
+ return self.status_category == 5
113
+ end
114
+
115
+
116
+ ### Return the current response Content-Type.
117
+ def content_type
118
+ return self.headers[ :content_type ]
119
+ end
120
+
121
+
122
+ ### Set the current response Content-Type.
123
+ def content_type=( type )
124
+ return self.headers[ :content_type ] = type
125
+ end
126
+
127
+
128
+ ### Clear any existing headers and body and restore them to their defaults
129
+ def reset
130
+ @headers.clear
131
+ @headers[:server] = Mongrel2.version_string( true )
132
+ @status = nil
133
+ @body = ''
134
+
135
+ return true
136
+ end
137
+
138
+
139
+ ### Return the current response header as a valid HTTP string.
140
+ def header_data
141
+ self.headers[:date] ||= Time.now.httpdate
142
+ self.headers[:content_length] ||= self.get_content_length
143
+
144
+ return self.headers.to_s
145
+ end
146
+
147
+
148
+ ### Get the length of the body, either by calling its #length method if it has
149
+ ### one, or using #seek and #tell if it implements those. If neither of those are
150
+ ### possible, an exception is raised.
151
+ def get_content_length
152
+ if @body.respond_to?( :length )
153
+ return @body.length
154
+ elsif @body.respond_to?( :seek ) && @body.respond_to?( :tell )
155
+ starting_pos = @body.tell
156
+ @body.seek( 0, IO::SEEK_END )
157
+ length = @body.tell - starting_pos
158
+ @body.seek( starting_pos, IO::SEEK_SET )
159
+
160
+ return length
161
+ else
162
+ raise Mongrel2::ResponseError,
163
+ "No way to calculate the content length of the response (a %s)." %
164
+ [ @body.class.name ]
165
+ end
166
+ end
167
+
168
+
169
+ ### Set the Connection header to allow pipelined HTTP.
170
+ def keepalive=( value )
171
+ self.headers[:connection] = value ? 'keep-alive' : 'close'
172
+ end
173
+ alias_method :pipelining_enabled=, :keepalive=
174
+
175
+
176
+ ### Returns +true+ if the response has pipelining enabled.
177
+ def keepalive?
178
+ ka_header = self.headers[:connection]
179
+ return !ka_header.nil? && ka_header =~ /keep-alive/i
180
+ return false
181
+ end
182
+ alias_method :pipelining_enabled?, :keepalive?
183
+
184
+ end # class Mongrel2::Response
185
+
186
+ # vim: set nosta noet ts=4 sw=4:
187
+
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'yajl'
4
+
5
+ require 'mongrel2/request' unless defined?( Mongrel2::Request )
6
+ require 'mongrel2/mixins'
7
+
8
+
9
+ # The Mongrel2 JSON Request class. Instances of this class represent a JSSocket request from
10
+ # a Mongrel2 server.
11
+ class Mongrel2::JSONRequest < Mongrel2::Request
12
+ include Mongrel2::Loggable
13
+
14
+ register_request_type( self, :JSON )
15
+
16
+
17
+ ### Parse the body as JSON.
18
+ def initialize( sender_id, conn_id, path, headers, body, raw=nil )
19
+ super
20
+ self.log.debug "Parsing JSON request body"
21
+ @data = Yajl.load( body )
22
+ self.log.debug " body is: %p" % [ @data ]
23
+ end
24
+
25
+
26
+ ######
27
+ public
28
+ ######
29
+
30
+ # The parsed request data
31
+ attr_reader :data
32
+
33
+
34
+ ### Returns +true+ if the request is a special Mongrel2 'disconnect'
35
+ ### notification.
36
+ def is_disconnect?
37
+ return true if self.data['type'] == 'disconnect'
38
+ end
39
+
40
+ end # class Mongrel2::JSONRequest
41
+
42
+ # vim: set nosta noet ts=4 sw=4:
43
+
@@ -0,0 +1,241 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'logger'
4
+ require 'date'
5
+
6
+ require 'mongrel2' unless defined?( Mongrel2 )
7
+ require 'mongrel2/mixins'
8
+
9
+
10
+ # A mixin that adds a logging subsystem to the extended object.
11
+ module Mongrel2::Logging
12
+
13
+ ### Logging
14
+ # Log levels
15
+ LOG_LEVELS = {
16
+ 'debug' => Logger::DEBUG,
17
+ 'info' => Logger::INFO,
18
+ 'warn' => Logger::WARN,
19
+ 'error' => Logger::ERROR,
20
+ 'fatal' => Logger::FATAL,
21
+ }.freeze
22
+ LOG_LEVEL_NAMES = LOG_LEVELS.invert.freeze
23
+
24
+
25
+ ### Inclusion hook
26
+ def self::extended( mod )
27
+ super
28
+
29
+ class << mod
30
+ # the log formatter that will be used when the logging subsystem is reset
31
+ attr_accessor :default_log_formatter
32
+
33
+ # the logger that will be used when the logging subsystem is reset
34
+ attr_accessor :default_logger
35
+
36
+ # the logger that's currently in effect
37
+ attr_accessor :logger
38
+ alias_method :log, :logger
39
+ alias_method :log=, :logger=
40
+ end
41
+
42
+ mod.default_logger = mod.logger = Logger.new( $stderr )
43
+ mod.default_logger.level = $DEBUG ? Logger::DEBUG : Logger::WARN
44
+ mod.default_log_formatter = Mongrel2::Logging::Formatter.new( mod.default_logger )
45
+ end
46
+
47
+
48
+ ### Reset the global logger object to the default
49
+ def reset_logger
50
+ self.logger = self.default_logger
51
+ self.logger.level = $DEBUG ? Logger::DEBUG : Logger::WARN
52
+ self.logger.formatter = self.default_log_formatter
53
+ end
54
+
55
+
56
+ ### Returns +true+ if the global logger has not been set to something other than
57
+ ### the default one.
58
+ def using_default_logger?
59
+ return self.logger == self.default_logger
60
+ end
61
+
62
+
63
+ # A alternate formatter for Logger instances.
64
+ class Formatter < Logger::Formatter
65
+
66
+ # The format to output unless debugging is turned on
67
+ DEFAULT_FORMAT = "[%1$s.%2$06d %3$d/%4$s] %5$5s -- %7$s\n"
68
+
69
+ # The format to output if debugging is turned on
70
+ DEFAULT_DEBUG_FORMAT = "[%1$s.%2$06d %3$d/%4$s] %5$5s {%6$s} -- %7$s\n"
71
+
72
+
73
+ ### Initialize the formatter with a reference to the logger so it can check for log level.
74
+ def initialize( logger, format=DEFAULT_FORMAT, debug=DEFAULT_DEBUG_FORMAT ) # :notnew:
75
+ @logger = logger
76
+ @format = format
77
+ @debug_format = debug
78
+
79
+ super()
80
+ end
81
+
82
+ ######
83
+ public
84
+ ######
85
+
86
+ # The Logger object associated with the formatter
87
+ attr_accessor :logger
88
+
89
+ # The logging format string
90
+ attr_accessor :format
91
+
92
+ # The logging format string that's used when outputting in debug mode
93
+ attr_accessor :debug_format
94
+
95
+
96
+ ### Log using either the DEBUG_FORMAT if the associated logger is at ::DEBUG level or
97
+ ### using FORMAT if it's anything less verbose.
98
+ def call( severity, time, progname, msg )
99
+ args = [
100
+ time.strftime( '%Y-%m-%d %H:%M:%S' ), # %1$s
101
+ time.usec, # %2$d
102
+ Process.pid, # %3$d
103
+ Thread.current == Thread.main ? 'main' : Thread.object_id, # %4$s
104
+ severity, # %5$s
105
+ progname, # %6$s
106
+ msg # %7$s
107
+ ]
108
+
109
+ if @logger.level == Logger::DEBUG
110
+ return self.debug_format % args
111
+ else
112
+ return self.format % args
113
+ end
114
+ end
115
+ end # class LogFormatter
116
+
117
+
118
+ # A ANSI-colorized formatter for Logger instances.
119
+ class ColorFormatter < Logger::Formatter
120
+ extend Mongrel2::ANSIColorUtilities
121
+
122
+ # Color settings
123
+ LEVEL_FORMATS = {
124
+ :debug => colorize( :bold, :black ) {"[%1$s.%2$06d %3$d/%4$s] %5$5s {%6$s} -- %7$s\n"},
125
+ :info => colorize( :normal ) {"[%1$s.%2$06d %3$d/%4$s] %5$5s -- %7$s\n"},
126
+ :warn => colorize( :bold, :yellow ) {"[%1$s.%2$06d %3$d/%4$s] %5$5s -- %7$s\n"},
127
+ :error => colorize( :red ) {"[%1$s.%2$06d %3$d/%4$s] %5$5s -- %7$s\n"},
128
+ :fatal => colorize( :bold, :red ) {"[%1$s.%2$06d %3$d/%4$s] %5$5s -- %7$s\n"},
129
+ }
130
+
131
+
132
+ ### Initialize the formatter with a reference to the logger so it can check for log level.
133
+ def initialize( logger, settings={} ) # :notnew:
134
+ settings = LEVEL_FORMATS.merge( settings )
135
+
136
+ @logger = logger
137
+ @settings = settings
138
+
139
+ super()
140
+ end
141
+
142
+ ######
143
+ public
144
+ ######
145
+
146
+ # The Logger object associated with the formatter
147
+ attr_accessor :logger
148
+
149
+ # The formats, by level
150
+ attr_accessor :settings
151
+
152
+
153
+ ### Log using the format associated with the severity
154
+ def call( severity, time, progname, msg )
155
+ args = [
156
+ time.strftime( '%Y-%m-%d %H:%M:%S' ), # %1$s
157
+ time.usec, # %2$d
158
+ Process.pid, # %3$d
159
+ Thread.current == Thread.main ? 'main' : Thread.object_id, # %4$s
160
+ severity, # %5$s
161
+ progname, # %6$s
162
+ msg # %7$s
163
+ ]
164
+
165
+ return self.settings[ severity.downcase.to_sym ] % args
166
+ end
167
+ end # class LogFormatter
168
+
169
+
170
+ # An alternate formatter for Logger instances that outputs +div+ HTML
171
+ # fragments.
172
+ class HtmlFormatter < Logger::Formatter
173
+
174
+ # The default HTML fragment that'll be used as the template for each log message.
175
+ HTML_LOG_FORMAT = %q{
176
+ <div class="log-message %5$s">
177
+ <span class="log-time">%1$s.%2$06d</span>
178
+ [
179
+ <span class="log-pid">%3$d</span>
180
+ /
181
+ <span class="log-tid">%4$s</span>
182
+ ]
183
+ <span class="log-level">%5$s</span>
184
+ :
185
+ <span class="log-name">%6$s</span>
186
+ <span class="log-message-text">%7$s</span>
187
+ </div>
188
+ }
189
+
190
+ ### Override the logging formats with ones that generate HTML fragments
191
+ def initialize( logger, format=HTML_LOG_FORMAT ) # :notnew:
192
+ @logger = logger
193
+ @format = format
194
+ super()
195
+ end
196
+
197
+
198
+ ######
199
+ public
200
+ ######
201
+
202
+ # The HTML fragment that will be used as a format() string for the log
203
+ attr_accessor :format
204
+
205
+
206
+ ### Return a log message composed out of the arguments formatted using the
207
+ ### formatter's format string
208
+ def call( severity, time, progname, msg )
209
+ args = [
210
+ time.strftime( '%Y-%m-%d %H:%M:%S' ), # %1$s
211
+ time.usec, # %2$d
212
+ Process.pid, # %3$d
213
+ Thread.current == Thread.main ? 'main' : Thread.object_id, # %4$s
214
+ severity.downcase, # %5$s
215
+ progname, # %6$s
216
+ html_escape( msg ).gsub(/\n/, '<br />') # %7$s
217
+ ]
218
+
219
+ return self.format % args
220
+ end
221
+
222
+
223
+ #######
224
+ private
225
+ #######
226
+
227
+ ### Return a copy of the specified +string+ with HTML special characters escaped as
228
+ ### HTML entities.
229
+ def html_escape( string )
230
+ return string.
231
+ gsub( /&/, '&amp;' ).
232
+ gsub( /</, '&lt;' ).
233
+ gsub( />/, '&gt;' )
234
+ end
235
+
236
+ end # class HtmlLogFormatter
237
+
238
+ end # module Mongrel2
239
+
240
+ # vim: set nosta noet ts=4 sw=4:
241
+