freels-mongrel 1.1.2

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 (72) hide show
  1. data/CHANGELOG +12 -0
  2. data/COPYING +55 -0
  3. data/LICENSE +55 -0
  4. data/Manifest +70 -0
  5. data/README +74 -0
  6. data/Rakefile +237 -0
  7. data/TODO +4 -0
  8. data/bin/mongrel_rails +284 -0
  9. data/examples/builder.rb +29 -0
  10. data/examples/camping/README +3 -0
  11. data/examples/camping/blog.rb +294 -0
  12. data/examples/camping/tepee.rb +149 -0
  13. data/examples/httpd.conf +474 -0
  14. data/examples/mime.yaml +3 -0
  15. data/examples/mongrel.conf +9 -0
  16. data/examples/mongrel_simple_ctrl.rb +92 -0
  17. data/examples/mongrel_simple_service.rb +116 -0
  18. data/examples/monitrc +57 -0
  19. data/examples/random_thrash.rb +19 -0
  20. data/examples/simpletest.rb +52 -0
  21. data/examples/webrick_compare.rb +20 -0
  22. data/ext/http11/ext_help.h +15 -0
  23. data/ext/http11/extconf.rb +6 -0
  24. data/ext/http11/http11.c +527 -0
  25. data/ext/http11/http11_parser.c +1216 -0
  26. data/ext/http11/http11_parser.h +49 -0
  27. data/ext/http11/http11_parser.java.rl +171 -0
  28. data/ext/http11/http11_parser.rl +165 -0
  29. data/ext/http11/http11_parser_common.rl +55 -0
  30. data/ext/http11_java/Http11Service.java +13 -0
  31. data/ext/http11_java/org/jruby/mongrel/Http11.java +266 -0
  32. data/ext/http11_java/org/jruby/mongrel/Http11Parser.java +572 -0
  33. data/lib/mongrel.rb +359 -0
  34. data/lib/mongrel/camping.rb +107 -0
  35. data/lib/mongrel/cgi.rb +182 -0
  36. data/lib/mongrel/command.rb +220 -0
  37. data/lib/mongrel/configurator.rb +389 -0
  38. data/lib/mongrel/const.rb +114 -0
  39. data/lib/mongrel/debug.rb +203 -0
  40. data/lib/mongrel/gems.rb +22 -0
  41. data/lib/mongrel/handlers.rb +472 -0
  42. data/lib/mongrel/header_out.rb +28 -0
  43. data/lib/mongrel/http_request.rb +155 -0
  44. data/lib/mongrel/http_response.rb +163 -0
  45. data/lib/mongrel/init.rb +10 -0
  46. data/lib/mongrel/logger.rb +74 -0
  47. data/lib/mongrel/mime_types.yml +616 -0
  48. data/lib/mongrel/rails.rb +185 -0
  49. data/lib/mongrel/stats.rb +89 -0
  50. data/lib/mongrel/tcphack.rb +18 -0
  51. data/lib/mongrel/uri_classifier.rb +76 -0
  52. data/mongrel-public_cert.pem +20 -0
  53. data/mongrel.gemspec +47 -0
  54. data/setup.rb +1585 -0
  55. data/test/mime.yaml +3 -0
  56. data/test/mongrel.conf +1 -0
  57. data/test/test_cgi_wrapper.rb +26 -0
  58. data/test/test_command.rb +86 -0
  59. data/test/test_conditional.rb +107 -0
  60. data/test/test_configurator.rb +88 -0
  61. data/test/test_debug.rb +25 -0
  62. data/test/test_handlers.rb +104 -0
  63. data/test/test_http11.rb +272 -0
  64. data/test/test_redirect_handler.rb +44 -0
  65. data/test/test_request_progress.rb +100 -0
  66. data/test/test_response.rb +127 -0
  67. data/test/test_stats.rb +35 -0
  68. data/test/test_uriclassifier.rb +261 -0
  69. data/test/test_ws.rb +116 -0
  70. data/test/testhelp.rb +74 -0
  71. data/tools/trickletest.rb +45 -0
  72. metadata +202 -0
@@ -0,0 +1,359 @@
1
+ # Ruby
2
+ require 'socket'
3
+ require 'tempfile'
4
+ require 'yaml'
5
+ require 'time'
6
+ require 'etc'
7
+ require 'uri'
8
+ require 'stringio'
9
+
10
+ # Ensure working require
11
+ require 'mongrel/gems'
12
+
13
+ # TODO: Only require these for RUBY_VERSION <= 1.8.6
14
+ # and only for platforms that require it, exclusive matching
15
+ if !RUBY_PLATFORM.match(/java|mswin/) && !RUBY_VERSION.match(/1\.8\.\d/)
16
+ Mongrel::Gems.require 'cgi_multipart_eof_fix'
17
+ Mongrel::Gems.require 'fastthread'
18
+ end
19
+ require 'thread'
20
+
21
+ require 'http11'
22
+ require 'mongrel/logger'
23
+ require 'mongrel/cgi'
24
+ require 'mongrel/handlers'
25
+ require 'mongrel/command'
26
+ require 'mongrel/tcphack'
27
+ require 'mongrel/configurator'
28
+ require 'mongrel/uri_classifier'
29
+ require 'mongrel/const'
30
+ require 'mongrel/http_request'
31
+ require 'mongrel/header_out'
32
+ require 'mongrel/http_response'
33
+
34
+ # Mongrel module containing all of the classes (include C extensions) for running
35
+ # a Mongrel web server. It contains a minimalist HTTP server with just enough
36
+ # functionality to service web application requests fast as possible.
37
+ module Mongrel
38
+
39
+ # Used to stop the HttpServer via Thread.raise.
40
+ class StopServer < Exception; end
41
+
42
+ # Thrown at a thread when it is timed out.
43
+ class TimeoutError < Exception; end
44
+
45
+ # A Hash with one extra parameter for the HTTP body, used internally.
46
+ class HttpParams < Hash
47
+ attr_accessor :http_body
48
+ end
49
+
50
+ # This is the main driver of Mongrel, while the Mongrel::HttpParser and Mongrel::URIClassifier
51
+ # make up the majority of how the server functions. It's a very simple class that just
52
+ # has a thread accepting connections and a simple HttpServer.process_client function
53
+ # to do the heavy lifting with the IO and Ruby.
54
+ #
55
+ # You use it by doing the following:
56
+ #
57
+ # server = 2("0.0.0.0", 3000)
58
+ # server.register("/stuff", MyNiftyHandler.new)
59
+ # server.run.join
60
+ #
61
+ # The last line can be just server.run if you don't want to join the thread used.
62
+ # If you don't though Ruby will mysteriously just exit on you.
63
+ #
64
+ # Ruby's thread implementation is "interesting" to say the least. Experiments with
65
+ # *many* different types of IO processing simply cannot make a dent in it. Future
66
+ # releases of Mongrel will find other creative ways to make threads faster, but don't
67
+ # hold your breath until Ruby 1.9 is actually finally useful.
68
+ class HttpServer
69
+ attr_reader :acceptor
70
+ attr_reader :workers
71
+ attr_reader :classifier
72
+ attr_reader :host
73
+ attr_reader :port
74
+ attr_reader :throttle
75
+ attr_reader :timeout
76
+ attr_reader :num_processors
77
+
78
+ attr_accessor :logger
79
+
80
+ # Creates a working server on host:port (strange things happen if port isn't a Number).
81
+ # Use HttpServer::run to start the server and HttpServer.acceptor.join to
82
+ # join the thread that's processing incoming requests on the socket.
83
+ #
84
+ # The num_processors optional argument is the maximum number of concurrent
85
+ # processors to accept, anything over this is closed immediately to maintain
86
+ # server processing performance. This may seem mean but it is the most efficient
87
+ # way to deal with overload. Other schemes involve still parsing the client's request
88
+ # which defeats the point of an overload handling system.
89
+ #
90
+ # The throttle parameter is a sleep timeout (in hundredths of a second) that is placed between
91
+ # socket.accept calls in order to give the server a cheap throttle time. It defaults to 0 and
92
+ # actually if it is 0 then the sleep is not done at all.
93
+ def initialize(host, port, num_processors=950, throttle=0, timeout=60, log=nil, log_level=:debug)
94
+
95
+ tries = 0
96
+ @socket = TCPServer.new(host, port)
97
+
98
+ @classifier = URIClassifier.new
99
+ @host = host
100
+ @port = port
101
+ @workers = ThreadGroup.new
102
+ @throttle = throttle
103
+ @num_processors = num_processors
104
+ @timeout = timeout
105
+ @logger = Mongrel::Log.new(log || "log/mongrel-#{host}-#{port}.log", log_level)
106
+ end
107
+
108
+ # Does the majority of the IO processing. It has been written in Ruby using
109
+ # about 7 different IO processing strategies and no matter how it's done
110
+ # the performance just does not improve. It is currently carefully constructed
111
+ # to make sure that it gets the best possible performance, but anyone who
112
+ # thinks they can make it faster is more than welcome to take a crack at it.
113
+ def process_client(client)
114
+ begin
115
+ parser = HttpParser.new
116
+ params = HttpParams.new
117
+ request = nil
118
+ data = client.readpartial(Const::CHUNK_SIZE)
119
+ nparsed = 0
120
+
121
+ # Assumption: nparsed will always be less since data will get filled with more
122
+ # after each parsing. If it doesn't get more then there was a problem
123
+ # with the read operation on the client socket. Effect is to stop processing when the
124
+ # socket can't fill the buffer for further parsing.
125
+ while nparsed < data.length
126
+ nparsed = parser.execute(params, data, nparsed)
127
+
128
+ if parser.finished?
129
+ if not params[Const::REQUEST_PATH]
130
+ # it might be a dumbass full host request header
131
+ uri = URI.parse(params[Const::REQUEST_URI])
132
+ params[Const::REQUEST_PATH] = uri.path
133
+ end
134
+
135
+ raise "No REQUEST PATH" if not params[Const::REQUEST_PATH]
136
+
137
+ script_name, path_info, handlers = @classifier.resolve(params[Const::REQUEST_PATH])
138
+
139
+ if handlers
140
+ params[Const::PATH_INFO] = path_info
141
+ params[Const::SCRIPT_NAME] = script_name
142
+
143
+ # From http://www.ietf.org/rfc/rfc3875 :
144
+ # "Script authors should be aware that the REMOTE_ADDR and REMOTE_HOST
145
+ # meta-variables (see sections 4.1.8 and 4.1.9) may not identify the
146
+ # ultimate source of the request. They identify the client for the
147
+ # immediate request to the server; that client may be a proxy, gateway,
148
+ # or other intermediary acting on behalf of the actual source client."
149
+ params[Const::REMOTE_ADDR] = client.peeraddr.last
150
+
151
+ # select handlers that want more detailed request notification
152
+ notifiers = handlers.select { |h| h.request_notify }
153
+ request = HttpRequest.new(params, client, notifiers)
154
+
155
+ # in the case of large file uploads the user could close the socket, so skip those requests
156
+ break if request.body == nil # nil signals from HttpRequest::initialize that the request was aborted
157
+
158
+ # request is good so far, continue processing the response
159
+ response = HttpResponse.new(client)
160
+
161
+ # Process each handler in registered order until we run out or one finalizes the response.
162
+ handlers.each do |handler|
163
+ handler.process(request, response)
164
+ break if response.done or client.closed?
165
+ end
166
+
167
+ # And finally, if nobody closed the response off, we finalize it.
168
+ unless response.done or client.closed?
169
+ response.finished
170
+ end
171
+ else
172
+ # Didn't find it, return a stock 404 response.
173
+ client.write(Const::ERROR_404_RESPONSE)
174
+ end
175
+
176
+ break #done
177
+ else
178
+ # Parser is not done, queue up more data to read and continue parsing
179
+ chunk = client.readpartial(Const::CHUNK_SIZE)
180
+ break if !chunk or chunk.length == 0 # read failed, stop processing
181
+
182
+ data << chunk
183
+ if data.length >= Const::MAX_HEADER
184
+ raise HttpParserError.new("HEADER is longer than allowed, aborting client early.")
185
+ end
186
+ end
187
+ end
188
+ rescue EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,Errno::EBADF
189
+ client.close rescue nil
190
+ rescue HttpParserError => e
191
+ Mongrel.log(:error, "#{Time.now.httpdate}: HTTP parse error, malformed request (#{params[Const::HTTP_X_FORWARDED_FOR] || client.peeraddr.last}): #{e.inspect}")
192
+ Mongrel.log(:error, "#{Time.now.httpdate}: REQUEST DATA: #{data.inspect}\n---\nPARAMS: #{params.inspect}\n---\n")
193
+ # http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4
194
+ client.write(Const::ERROR_400_RESPONSE)
195
+ rescue Errno::EMFILE
196
+ reap_dead_workers('too many files')
197
+ rescue Object => e
198
+ Mongrel.log(:error, "#{Time.now.httpdate}: Read error: #{e.inspect}")
199
+ Mongrel.log(:error, e.backtrace.join("\n"))
200
+ ensure
201
+ begin
202
+ client.close
203
+ rescue IOError
204
+ # Already closed
205
+ rescue Object => e
206
+ Mongrel.log(:error, "#{Time.now.httpdate}: Client error: #{e.inspect}")
207
+ Mongrel.log(:error, e.backtrace.join("\n"))
208
+ end
209
+ request.body.delete if request and request.body.class == Tempfile
210
+ end
211
+ end
212
+
213
+ # Used internally to kill off any worker threads that have taken too long
214
+ # to complete processing. Only called if there are too many processors
215
+ # currently servicing. It returns the count of workers still active
216
+ # after the reap is done. It only runs if there are workers to reap.
217
+ def reap_dead_workers(reason='unknown')
218
+ if @workers.list.length > 0
219
+ Mongrel.log(:error, "#{Time.now.httpdate}: Reaping #{@workers.list.length} threads for slow workers because of '#{reason}'")
220
+ error_msg = "#{Time.now.httpdate}: Mongrel timed out this thread: #{reason}"
221
+ mark = Time.now
222
+ @workers.list.each do |worker|
223
+ worker[:started_on] = Time.now if not worker[:started_on]
224
+
225
+ if mark - worker[:started_on] > @timeout + @throttle
226
+ Mongrel.log(:error, "#{Time.now.httpdate}: Thread #{worker.inspect} is too old, killing.")
227
+ worker.raise(TimeoutError.new(error_msg))
228
+ end
229
+ end
230
+ end
231
+
232
+ return @workers.list.length
233
+ end
234
+
235
+ # Performs a wait on all the currently running threads and kills any that take
236
+ # too long. It waits by @timeout seconds, which can be set in .initialize or
237
+ # via mongrel_rails. The @throttle setting does extend this waiting period by
238
+ # that much longer.
239
+ def graceful_shutdown
240
+ while reap_dead_workers("shutdown") > 0
241
+ Mongrel.log(:error, "#{Time.now.httpdate}: Waiting for #{@workers.list.length} requests to finish, could take #{@timeout + @throttle} seconds.")
242
+ sleep @timeout / 10
243
+ end
244
+ end
245
+
246
+ def configure_socket_options
247
+ case RUBY_PLATFORM
248
+ when /linux/
249
+ # 9 is currently TCP_DEFER_ACCEPT
250
+ $tcp_defer_accept_opts = [Socket::SOL_TCP, 9, 1]
251
+ $tcp_cork_opts = [Socket::SOL_TCP, 3, 1]
252
+ when /freebsd(([1-4]\..{1,2})|5\.[0-4])/
253
+ # Do nothing, just closing a bug when freebsd <= 5.4
254
+ when /freebsd/
255
+ # Use the HTTP accept filter if available.
256
+ # The struct made by pack() is defined in /usr/include/sys/socket.h as accept_filter_arg
257
+ unless `/sbin/sysctl -nq net.inet.accf.http`.empty?
258
+ $tcp_defer_accept_opts = [Socket::SOL_SOCKET, Socket::SO_ACCEPTFILTER, ['httpready', nil].pack('a16a240')]
259
+ end
260
+ end
261
+ end
262
+
263
+ # Runs the thing. It returns the thread used so you can "join" it. You can also
264
+ # access the HttpServer::acceptor attribute to get the thread later.
265
+ def run
266
+ BasicSocket.do_not_reverse_lookup=true
267
+
268
+ configure_socket_options
269
+
270
+ if defined?($tcp_defer_accept_opts) and $tcp_defer_accept_opts
271
+ @socket.setsockopt(*$tcp_defer_accept_opts) rescue nil
272
+ end
273
+
274
+ @acceptor = Thread.new do
275
+ begin
276
+ while true
277
+ begin
278
+ client = @socket.accept
279
+
280
+ num_workers = @workers.list.length
281
+ if num_workers >= @num_processors
282
+ Mongrel.log(:error, "#{Time.now.httpdate}: Server overloaded with #{num_workers} processors (#@num_processors max). Dropping connection.")
283
+ client.close rescue nil
284
+ reap_dead_workers("max processors")
285
+ else
286
+ if defined?($tcp_cork_opts) and $tcp_cork_opts
287
+ client.setsockopt(*$tcp_cork_opts) rescue nil
288
+ end
289
+ thread = Thread.new(client) {|c| process_client(c) }
290
+ thread[:started_on] = Time.now
291
+ @workers.add(thread)
292
+
293
+ sleep @throttle/100.0 if @throttle > 0
294
+ end
295
+ rescue StopServer
296
+ break
297
+ rescue Errno::EMFILE
298
+ reap_dead_workers("too many open files")
299
+ sleep 0.5
300
+ rescue Errno::ECONNABORTED
301
+ # client closed the socket even before accept
302
+ client.close rescue nil
303
+ rescue Object => e
304
+ Mongrel.log(:error, "#{Time.now.httpdate}: Unhandled listen loop exception #{e.inspect}.")
305
+ Mongrel.log(:error, e.backtrace.join("\n"))
306
+ end
307
+ end
308
+ graceful_shutdown
309
+ ensure
310
+ @socket.close
311
+ # Mongrel.log(:error, "#{Time.now.httpdate}: Closed socket.")
312
+ end
313
+ end
314
+
315
+ return @acceptor
316
+ end
317
+
318
+ # Simply registers a handler with the internal URIClassifier. When the URI is
319
+ # found in the prefix of a request then your handler's HttpHandler::process method
320
+ # is called. See Mongrel::URIClassifier#register for more information.
321
+ #
322
+ # If you set in_front=true then the passed in handler will be put in the front of the list
323
+ # for that particular URI. Otherwise it's placed at the end of the list.
324
+ def register(uri, handler, in_front=false)
325
+ begin
326
+ @classifier.register(uri, [handler])
327
+ rescue URIClassifier::RegistrationError
328
+ handlers = @classifier.resolve(uri)[2]
329
+ method_name = in_front ? 'unshift' : 'push'
330
+ handlers.send(method_name, handler)
331
+ end
332
+ handler.listener = self
333
+ end
334
+
335
+ # Removes any handlers registered at the given URI. See Mongrel::URIClassifier#unregister
336
+ # for more information. Remember this removes them *all* so the entire
337
+ # processing chain goes away.
338
+ def unregister(uri)
339
+ @classifier.unregister(uri)
340
+ end
341
+
342
+ # Stops the acceptor thread and then causes the worker threads to finish
343
+ # off the request queue before finally exiting.
344
+ def stop(synchronous=false)
345
+ @acceptor.raise(StopServer.new)
346
+
347
+ if synchronous
348
+ sleep(0.5) while @acceptor.alive?
349
+ end
350
+ end
351
+
352
+ end
353
+ end
354
+
355
+ # Load experimental library, if present. We put it here so it can override anything
356
+ # in regular Mongrel.
357
+
358
+ $LOAD_PATH.unshift 'projects/mongrel_experimental/lib/'
359
+ Mongrel::Gems.require 'mongrel_experimental', ">=#{Mongrel::Const::MONGREL_VERSION}"
@@ -0,0 +1,107 @@
1
+ # Copyright (c) 2005 Zed A. Shaw
2
+ # You can redistribute it and/or modify it under the same terms as Ruby.
3
+ #
4
+ # Additional work donated by contributors. See http://mongrel.rubyforge.org/attributions.html
5
+ # for more information.
6
+
7
+ require 'mongrel'
8
+
9
+
10
+ module Mongrel
11
+ # Support for the Camping micro framework at http://camping.rubyforge.org
12
+ # This implements the unusually long Postamble that Camping usually
13
+ # needs and shrinks it down to just a single line or two.
14
+ #
15
+ # Your Postamble would now be:
16
+ #
17
+ # Mongrel::Camping::start("0.0.0.0",3001,"/tepee",Tepee).join
18
+ #
19
+ # If you wish to get fancier than this then you can use the
20
+ # Camping::CampingHandler directly instead and do your own
21
+ # wiring:
22
+ #
23
+ # h = Mongrel::HttpServer.new(server, port)
24
+ # h.register(uri, CampingHandler.new(Tepee))
25
+ # h.register("/favicon.ico", Mongrel::Error404Handler.new(""))
26
+ #
27
+ # I add the /favicon.ico since camping apps typically don't
28
+ # have them and it's just annoying anyway.
29
+ module Camping
30
+
31
+ # This is a specialized handler for Camping applications
32
+ # that has them process the request and then translates
33
+ # the results into something the Mongrel::HttpResponse
34
+ # needs.
35
+ class CampingHandler < Mongrel::HttpHandler
36
+ attr_reader :files
37
+ attr_reader :guard
38
+ @@file_only_methods = ["GET","HEAD"]
39
+
40
+ def initialize(klass)
41
+ @files = Mongrel::DirHandler.new(nil, false)
42
+ @guard = Mutex.new
43
+ @klass = klass
44
+ end
45
+
46
+ def process(request, response)
47
+ if response.socket.closed?
48
+ return
49
+ end
50
+
51
+ controller = nil
52
+ @guard.synchronize {
53
+ controller = @klass.run(request.body, request.params)
54
+ }
55
+
56
+ sendfile, clength = nil
57
+ response.status = controller.status
58
+ controller.headers.each do |k, v|
59
+ if k =~ /^X-SENDFILE$/i
60
+ sendfile = v
61
+ elsif k =~ /^CONTENT-LENGTH$/i
62
+ clength = v.to_i
63
+ else
64
+ [*v].each do |vi|
65
+ response.header[k] = vi
66
+ end
67
+ end
68
+ end
69
+
70
+ if sendfile
71
+ request.params[Mongrel::Const::PATH_INFO] = sendfile
72
+ @files.process(request, response)
73
+ elsif controller.body.respond_to? :read
74
+ response.send_status(clength)
75
+ response.send_header
76
+ while chunk = controller.body.read(16384)
77
+ response.write(chunk)
78
+ end
79
+ if controller.body.respond_to? :close
80
+ controller.body.close
81
+ end
82
+ else
83
+ body = controller.body.to_s
84
+ response.send_status(body.length)
85
+ response.send_header
86
+ response.write(body)
87
+ end
88
+ end
89
+ end
90
+
91
+ # This is a convenience method that wires up a CampingHandler
92
+ # for your application on a given port and uri. It's pretty
93
+ # much all you need for a camping application to work right.
94
+ #
95
+ # It returns the Mongrel::HttpServer which you should either
96
+ # join or somehow manage. The thread is running when
97
+ # returned.
98
+
99
+ def Camping.start(server, port, uri, klass)
100
+ h = Mongrel::HttpServer.new(server, port)
101
+ h.register(uri, CampingHandler.new(klass))
102
+ h.register("/favicon.ico", Mongrel::Error404Handler.new(""))
103
+ h.run
104
+ return h
105
+ end
106
+ end
107
+ end