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,36 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'nokogiri'
4
+
5
+ require 'mongrel2/request' unless defined?( Mongrel2::Request )
6
+ require 'mongrel2/mixins'
7
+
8
+
9
+ # The Mongrel2 XML Request class. Instances of this class represent a request for an XML route from
10
+ # a Mongrel2 server.
11
+ class Mongrel2::XMLRequest < Mongrel2::Request
12
+ include Mongrel2::Loggable
13
+
14
+ register_request_type( self, :XML )
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 XML request body"
21
+ @data = Nokogiri::XML( body )
22
+ end
23
+
24
+
25
+ ######
26
+ public
27
+ ######
28
+
29
+ # The parsed request data (a Nokogiri::XML document)
30
+ attr_reader :data
31
+
32
+
33
+ end # class Mongrel2::XMLRequest
34
+
35
+ # vim: set nosta noet ts=4 sw=4:
36
+
@@ -0,0 +1,237 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'uri'
4
+ require 'yajl'
5
+ require 'tnetstring'
6
+
7
+ require 'mongrel2' unless defined?( Mongrel2 )
8
+
9
+
10
+ ### A collection of constants used in testing
11
+ module Mongrel2::TestConstants # :nodoc:all
12
+
13
+ include Mongrel2::Constants
14
+
15
+ unless defined?( TEST_HOST )
16
+
17
+ TEST_HOST = 'localhost'
18
+ TEST_PORT = 8118
19
+
20
+ # Rule 2: Every message to and from Mongrel2 has that Mongrel2 instances
21
+ # UUID as the very first thing.
22
+ TEST_UUID = 'BD17D85C-4730-4BF2-999D-9D2B2E0FCCF9'
23
+
24
+ # 0mq socket specifications for Handlers
25
+ TEST_SEND_SPEC = 'tcp://127.0.0.1:9998'
26
+ TEST_RECV_SPEC = 'tcp://127.0.0.1:9997'
27
+
28
+ # Rule 3: Mongrel2 sends requests with one number right after the
29
+ # servers UUID separated by a space. Handlers return a netstring with
30
+ # a list of numbers separated by spaces. The numbers indicate the
31
+ # connected browser the message is to/from.
32
+ TEST_ID = 8
33
+
34
+ #
35
+ # HTTP request constants
36
+ #
37
+
38
+ TEST_ROUTE = '/handler'
39
+ TEST_PATH = TEST_ROUTE
40
+ TEST_QUERY = 'thing=foom'
41
+
42
+ TEST_HEADERS = {
43
+ "x-forwarded-for" => "127.0.0.1",
44
+ "accept-language" => "en-US,en;q=0.8",
45
+ "accept-encoding" => "gzip,deflate,sdch",
46
+ "connection" => "keep-alive",
47
+ "accept-charset" => "UTF-8,*;q=0.5",
48
+ "accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
49
+ "user-agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_1) " +
50
+ "AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.112 " +
51
+ "Safari/535.1",
52
+ "host" => "localhost:3667",
53
+ "METHOD" => "GET",
54
+ "VERSION" => "HTTP/1.1",
55
+ }
56
+
57
+ TEST_BODY = ''
58
+
59
+ TEST_REQUEST_OPTS = {
60
+ :uuid => TEST_UUID,
61
+ :id => TEST_ID,
62
+ :path => TEST_PATH,
63
+ :body => TEST_BODY,
64
+ }
65
+
66
+
67
+ #
68
+ # JSON (JSSocket, etc.) request constants
69
+ #
70
+
71
+ TEST_JSON_PATH = '@directory'
72
+
73
+ TEST_JSON_HEADERS = {
74
+ 'PATH' => TEST_JSON_PATH,
75
+ 'x-forwarded-for' => "127.0.0.1",
76
+ 'METHOD' => "JSON",
77
+ 'PATTERN' => TEST_JSON_PATH,
78
+ }
79
+ TEST_JSON_BODY = { 'type' => 'msg', 'msg' => 'connect' }
80
+
81
+ TEST_JSON_REQUEST_OPTS = {
82
+ :uuid => TEST_UUID,
83
+ :id => TEST_ID,
84
+ :path => TEST_JSON_PATH,
85
+ :body => TEST_JSON_BODY,
86
+ }
87
+
88
+
89
+ #
90
+ # XML message request constants
91
+ #
92
+
93
+ TEST_XML_PATH = '<directory'
94
+
95
+ TEST_XML_HEADERS = {
96
+ 'PATH' => TEST_XML_PATH,
97
+ 'x-forwarded-for' => "127.0.0.1",
98
+ 'METHOD' => "XML",
99
+ 'PATTERN' => TEST_XML_PATH,
100
+ }
101
+ TEST_XML_BODY = '<directory><file name="foom.txt" /><file name="foom2.md" /></directory>'
102
+
103
+ TEST_XML_REQUEST_OPTS = {
104
+ :uuid => TEST_UUID,
105
+ :id => TEST_ID,
106
+ :path => TEST_XML_PATH,
107
+ :body => TEST_XML_BODY,
108
+ }
109
+
110
+
111
+ #
112
+ # HTTP constants
113
+ #
114
+
115
+ # Space
116
+ SP = '\\x20'
117
+
118
+ # Network EOL
119
+ CRLF = '\\r\\n'
120
+
121
+ # Pattern to match the contents of ETag and If-None-Match headers
122
+ ENTITY_TAG_PATTERN = %r{
123
+ (w/)? # Weak flag
124
+ " # Opaque-tag
125
+ ([^"]+) # Quoted-string
126
+ " # Closing quote
127
+ }ix
128
+
129
+ # Separators = "(" | ")" | "<" | ">" | "@"
130
+ # | "," | ";" | ":" | "\" | <">
131
+ # | "/" | "[" | "]" | "?" | "="
132
+ # | "{" | "}" | SP | HT
133
+ SEPARATORS = Regexp.quote("(\")<>@,;:\\/[]?={} \t")
134
+
135
+ # token = 1*<any CHAR except CTLs or separators>
136
+ TOKEN = /[^#{SEPARATORS}[:cntrl:]]+/
137
+
138
+ # Borrow URI's pattern for matching absolute URIs
139
+ REQUEST_URI = URI::REL_URI_REF
140
+
141
+ # Canonical HTTP methods
142
+ REQUEST_METHOD = /OPTIONS|GET|HEAD|POST|PUT|DELETE|TRACE|CONNECT/
143
+
144
+ # Extension HTTP methods
145
+ # extension-method = token
146
+ EXTENSION_METHOD = TOKEN
147
+
148
+ # HTTP-Version = "HTTP" "/" 1*DIGIT "." 1*DIGIT
149
+ HTTP_VERSION = %r{HTTP/(\d+\.\d+)}
150
+
151
+ # LWS = [CRLF] 1*( SP | HT )
152
+ LWS = /#{CRLF}[ \t]+/
153
+
154
+ # TEX = <any OCTET except CTLs, but including LWS>
155
+ TEXT = /[^[:cntrl:]]|#{LWS}/
156
+
157
+ # Reason-Phrase = *<TEXT, excluding CR, LF>
158
+ REASON_PHRASE = %r{[^[:cntrl:]]+}
159
+
160
+ # Pattern to match HTTP response lines
161
+ # Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
162
+ HTTP_RESPONSE_LINE = %r{
163
+ (?<http_version>#{HTTP_VERSION})
164
+ #{SP}
165
+ (?<status_code>\d{3})
166
+ #{SP}
167
+ (?<reason_phrase>#{REASON_PHRASE})
168
+ #{CRLF}
169
+ }x
170
+
171
+ # message-header = field-name ":" [ field-value ]
172
+ # field-name = token
173
+ # field-value = *( field-content | LWS )
174
+ # field-content = <the OCTETs making up the field-value
175
+ # and consisting of either *TEXT or combinations
176
+ # of token, separators, and quoted-string>
177
+
178
+ # Pattern to match a single header tuple, possibly split over multiple lines
179
+ HEADER_LINE = %r{
180
+ ^
181
+ #{TOKEN}
182
+ :
183
+ (?:#{LWS}|#{TEXT})*
184
+ #{CRLF}
185
+ }mx
186
+
187
+ # entity-body = *OCTET
188
+ MESSAGE_BODY = /.*/
189
+
190
+ # Pattern to match an entire HTTP response
191
+ # Response = Status-Line ; Section 6.1
192
+ # *(( general-header ; Section 4.5
193
+ # | response-header ; Section 6.2
194
+ # | entity-header ) CRLF) ; Section 7.1
195
+ # CRLF
196
+ # [ message-body ] ; Section 7.2
197
+ HTTP_RESPONSE = %r{
198
+ ^
199
+ (?<response_line>#{HTTP_RESPONSE_LINE})
200
+ (?<headers>#{HEADER_LINE}*)
201
+ #{CRLF}
202
+ (?<message_body>#{MESSAGE_BODY})
203
+ }x
204
+
205
+ # wkday = "Mon" | "Tue" | "Wed"
206
+ # | "Thu" | "Fri" | "Sat" | "Sun"
207
+ WKDAY = Regexp.union( %w[Mon Tue Wed Thu Fri Sat Sun] )
208
+
209
+ # month = "Jan" | "Feb" | "Mar" | "Apr"
210
+ # | "May" | "Jun" | "Jul" | "Aug"
211
+ # | "Sep" | "Oct" | "Nov" | "Dec"
212
+ MONTH = Regexp.union( %w[Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec] )
213
+
214
+ # Match an RFC1123 "HTTP date"
215
+ # rfc1123-date = wkday "," SP date1 SP time SP "GMT"
216
+ # date1 = 2DIGIT SP month SP 4DIGIT
217
+ # ; day month year (e.g., 02 Jun 1982)
218
+ # time = 2DIGIT ":" 2DIGIT ":" 2DIGIT
219
+ # ; 00:00:00 - 23:59:59
220
+ HTTP_DATE = %r{
221
+ #{WKDAY} , #{SP}
222
+ \d{2} #{SP}
223
+ #{MONTH} #{SP}
224
+ \d{4} #{SP}
225
+ \d{2} : \d{2} : \d{2} #{SP} GMT
226
+ }x
227
+
228
+
229
+ # Freeze all testing constants
230
+ constants.each do |cname|
231
+ const_get(cname).freeze
232
+ end
233
+ end
234
+
235
+ end
236
+
237
+
@@ -0,0 +1,246 @@
1
+ #!/usr/bin/ruby
2
+ # coding: utf-8
3
+
4
+ BEGIN {
5
+ require 'pathname'
6
+ basedir = Pathname.new( __FILE__ ).dirname.parent
7
+
8
+ libdir = basedir + "lib"
9
+
10
+ $LOAD_PATH.unshift( basedir.to_s ) unless $LOAD_PATH.include?( basedir.to_s )
11
+ $LOAD_PATH.unshift( libdir.to_s ) unless $LOAD_PATH.include?( libdir.to_s )
12
+ }
13
+
14
+ # SimpleCov test coverage reporting; enable this using the :coverage rake task
15
+ if ENV['COVERAGE']
16
+ $stderr.puts "\n\n>>> Enabling coverage report.\n\n"
17
+ require 'simplecov'
18
+ SimpleCov.start do
19
+ add_filter 'spec'
20
+ add_group "Config Classes" do |file|
21
+ file.filename =~ %r{lib/mongrel2/config(\.rb|/.*)$}
22
+ end
23
+ add_group "Needing tests" do |file|
24
+ file.covered_percent < 90
25
+ end
26
+ end
27
+ end
28
+
29
+ begin
30
+ require 'configurability'
31
+ rescue LoadError => err
32
+ end
33
+
34
+ require 'pathname'
35
+ require 'tmpdir'
36
+
37
+ require 'rspec'
38
+ require 'mongrel2'
39
+ require 'mongrel2/config'
40
+
41
+ require 'sequel'
42
+ require 'sequel/model'
43
+
44
+ require 'spec/lib/constants'
45
+ require 'spec/lib/matchers'
46
+
47
+
48
+ ### RSpec helper functions.
49
+ module Mongrel2::SpecHelpers
50
+ include Mongrel2::TestConstants
51
+
52
+ class ArrayLogger
53
+ ### Create a new ArrayLogger that will append content to +array+.
54
+ def initialize( array )
55
+ @array = array
56
+ end
57
+
58
+ ### Write the specified +message+ to the array.
59
+ def write( message )
60
+ @array << message
61
+ end
62
+
63
+ ### No-op -- this is here just so Logger doesn't complain
64
+ def close; end
65
+
66
+ end # class ArrayLogger
67
+
68
+
69
+ unless defined?( LEVEL )
70
+ LEVEL = {
71
+ :debug => Logger::DEBUG,
72
+ :info => Logger::INFO,
73
+ :warn => Logger::WARN,
74
+ :error => Logger::ERROR,
75
+ :fatal => Logger::FATAL,
76
+ }
77
+ end
78
+
79
+ ###############
80
+ module_function
81
+ ###############
82
+
83
+ ### Make an easily-comparable version vector out of +ver+ and return it.
84
+ def vvec( ver )
85
+ return ver.split('.').collect {|char| char.to_i }.pack('N*')
86
+ end
87
+
88
+
89
+ ### Reset the logging subsystem to its default state.
90
+ def reset_logging
91
+ Mongrel2.reset_logger
92
+ end
93
+
94
+
95
+ ### Alter the output of the default log formatter to be pretty in SpecMate output
96
+ def setup_logging( level=Logger::FATAL )
97
+
98
+ # Turn symbol-style level config into Logger's expected Fixnum level
99
+ if Mongrel2::Logging::LOG_LEVELS.key?( level.to_s )
100
+ level = Mongrel2::Logging::LOG_LEVELS[ level.to_s ]
101
+ end
102
+
103
+ logger = Logger.new( $stderr )
104
+ Mongrel2.logger = logger
105
+ Mongrel2.logger.level = level
106
+
107
+ # Only do this when executing from a spec in TextMate
108
+ if ENV['HTML_LOGGING'] || (ENV['TM_FILENAME'] && ENV['TM_FILENAME'] =~ /_spec\.rb/)
109
+ Thread.current['logger-output'] = []
110
+ logdevice = ArrayLogger.new( Thread.current['logger-output'] )
111
+ Mongrel2.logger = Logger.new( logdevice )
112
+ # Mongrel2.logger.level = level
113
+ Mongrel2.logger.formatter = Mongrel2::Logging::HtmlFormatter.new( logger )
114
+ end
115
+ end
116
+
117
+
118
+ ### Set up a Mongrel2 configuration database in memory.
119
+ def setup_config_db( dbspec=':memory:' )
120
+ Mongrel2::Config.configure( :configdb => dbspec ) unless
121
+ Mongrel2::Config.db.uri[ %r{sqlite:/(.*)}, 1 ] == dbspec
122
+ Mongrel2::Config.init_database
123
+ Mongrel2::Config.db.tables.collect {|t| Mongrel2::Config.db[t] }.each( &:truncate )
124
+ end
125
+
126
+
127
+ ### Normalize and fill in missing members for the given +opts+.
128
+ def normalize_headers( opts, defaults=TEST_HEADERS )
129
+ headers = defaults.merge( opts[:headers] || {} )
130
+
131
+ headers["PATH"] = opts[:path]
132
+ headers["URI"] = "#{opts[:path]}?#{opts[:query]}"
133
+ headers["QUERY"] = opts[:query]
134
+ headers["PATTERN"] = opts[:pattern] || opts[:path]
135
+
136
+ return headers
137
+ end
138
+
139
+
140
+ ### Make a raw Mongrel2 request from the specified +opts+ and return it as a String.
141
+ def make_request( opts={} )
142
+ opts = TEST_REQUEST_OPTS.merge( opts )
143
+ headers = normalize_headers( opts )
144
+
145
+ headerstring = TNetstring.dump( Yajl::Encoder.encode(headers) )
146
+ bodystring = TNetstring.dump( opts[:body] || '' )
147
+
148
+ # UUID ID PATH SIZE:HEADERS,SIZE:BODY,
149
+ return "%s %d %s %s%s" % [
150
+ opts[:uuid],
151
+ opts[:id],
152
+ opts[:path],
153
+ headerstring,
154
+ bodystring,
155
+ ]
156
+ end
157
+
158
+
159
+ ### Make a new-style (TNetstring headers) raw Mongrel2 request from the specified +opts+
160
+ ### and return it as a String.
161
+ def make_tn_request( opts={} )
162
+ opts = TEST_REQUEST_OPTS.merge( opts )
163
+ headers = normalize_headers( opts )
164
+
165
+ headerstring = TNetstring.dump( headers )
166
+ bodystring = TNetstring.dump( opts[:body] || '' )
167
+
168
+ # UUID ID PATH SIZE:HEADERS,SIZE:BODY,
169
+ return "%s %d %s %s%s" % [
170
+ opts[:uuid],
171
+ opts[:id],
172
+ opts[:path],
173
+ headerstring,
174
+ bodystring,
175
+ ]
176
+ end
177
+
178
+
179
+ ### Make a Mongrel2 request for a JSON route.
180
+ def make_json_request( opts={} )
181
+ opts = TEST_JSON_REQUEST_OPTS.merge( opts )
182
+ headers = normalize_headers( opts, TEST_JSON_HEADERS )
183
+ headers.delete( 'URI' ) # JSON requests don't have one
184
+
185
+ Mongrel2.log.debug "JSON request, headers = %p, opts = %p" % [ headers, opts ]
186
+
187
+ headerstring = TNetstring.dump( Yajl::Encoder.encode(headers) )
188
+ bodystring = TNetstring.dump( Yajl::Encoder.encode(opts[:body] || []) )
189
+
190
+ # UUID ID PATH SIZE:HEADERS,SIZE:BODY,
191
+ return "%s %d %s %s%s" % [
192
+ opts[:uuid],
193
+ opts[:id],
194
+ opts[:path],
195
+ headerstring,
196
+ bodystring,
197
+ ]
198
+ end
199
+
200
+
201
+ ### Make a Mongrel2 request for an XML route.
202
+ def make_xml_request( opts={} )
203
+ opts = TEST_XML_REQUEST_OPTS.merge( opts )
204
+ headers = normalize_headers( opts, TEST_XML_HEADERS )
205
+ headers.delete( 'URI' ) # XML requests don't have one
206
+
207
+ Mongrel2.log.debug "XML request, headers = %p, opts = %p" % [ headers, opts ]
208
+
209
+ headerstring = TNetstring.dump( Yajl::Encoder.encode(headers) )
210
+ bodystring = TNetstring.dump( opts[:body] || "#{TEST_XML_PATH} />" )
211
+
212
+ # UUID ID PATH SIZE:HEADERS,SIZE:BODY,
213
+ return "%s %d %s %s%s" % [
214
+ opts[:uuid],
215
+ opts[:id],
216
+ opts[:path],
217
+ headerstring,
218
+ bodystring,
219
+ ]
220
+ end
221
+
222
+ end
223
+
224
+
225
+ abort "You need a version of RSpec >= 2.6.0" unless defined?( RSpec )
226
+
227
+ if defined?( ::Amalgalite )
228
+ $stderr.puts ">>> Using Amalgalite #{Amalgalite::VERSION} for DB access."
229
+ else
230
+ $stderr.puts ">>> Using SQLite3 #{SQLite3::VERSION} for DB access."
231
+ end
232
+
233
+ ### Mock with RSpec
234
+ RSpec.configure do |c|
235
+ include Mongrel2::TestConstants
236
+
237
+ c.mock_with :rspec
238
+
239
+ c.extend( Mongrel2::TestConstants )
240
+ c.include( Mongrel2::TestConstants )
241
+ c.include( Mongrel2::SpecHelpers )
242
+ c.include( Mongrel2::Matchers )
243
+ end
244
+
245
+ # vim: set nosta noet ts=4 sw=4:
246
+