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