headius-mongrel 1.1.6.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 (71) hide show
  1. data/CHANGELOG +21 -0
  2. data/COPYING +55 -0
  3. data/LICENSE +55 -0
  4. data/Manifest +69 -0
  5. data/README +74 -0
  6. data/Rakefile +202 -0
  7. data/TODO +5 -0
  8. data/bin/mongrel_rails +283 -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 +14 -0
  23. data/ext/http11/extconf.rb +6 -0
  24. data/ext/http11/http11.c +402 -0
  25. data/ext/http11/http11_parser.c +1221 -0
  26. data/ext/http11/http11_parser.h +49 -0
  27. data/ext/http11/http11_parser.java.rl +170 -0
  28. data/ext/http11/http11_parser.rl +152 -0
  29. data/ext/http11/http11_parser_common.rl +54 -0
  30. data/ext/http11_java/Http11Service.java +13 -0
  31. data/ext/http11_java/org/jruby/mongrel/Http11.java +353 -0
  32. data/ext/http11_java/org/jruby/mongrel/Http11Parser.java +572 -0
  33. data/lib/mongrel.rb +364 -0
  34. data/lib/mongrel/camping.rb +107 -0
  35. data/lib/mongrel/cgi.rb +181 -0
  36. data/lib/mongrel/command.rb +222 -0
  37. data/lib/mongrel/configurator.rb +388 -0
  38. data/lib/mongrel/const.rb +110 -0
  39. data/lib/mongrel/debug.rb +203 -0
  40. data/lib/mongrel/gems.rb +22 -0
  41. data/lib/mongrel/handlers.rb +468 -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/mime_types.yml +616 -0
  47. data/lib/mongrel/rails.rb +192 -0
  48. data/lib/mongrel/stats.rb +89 -0
  49. data/lib/mongrel/tcphack.rb +18 -0
  50. data/lib/mongrel/uri_classifier.rb +76 -0
  51. data/mongrel-public_cert.pem +20 -0
  52. data/mongrel.gemspec +47 -0
  53. data/setup.rb +1585 -0
  54. data/test/mime.yaml +3 -0
  55. data/test/mongrel.conf +1 -0
  56. data/test/test_cgi_wrapper.rb +26 -0
  57. data/test/test_command.rb +86 -0
  58. data/test/test_conditional.rb +107 -0
  59. data/test/test_configurator.rb +88 -0
  60. data/test/test_debug.rb +25 -0
  61. data/test/test_handlers.rb +126 -0
  62. data/test/test_http11.rb +156 -0
  63. data/test/test_redirect_handler.rb +45 -0
  64. data/test/test_request_progress.rb +100 -0
  65. data/test/test_response.rb +127 -0
  66. data/test/test_stats.rb +35 -0
  67. data/test/test_uriclassifier.rb +261 -0
  68. data/test/test_ws.rb +115 -0
  69. data/test/testhelp.rb +79 -0
  70. data/tools/trickletest.rb +45 -0
  71. metadata +199 -0
@@ -0,0 +1,364 @@
1
+
2
+ # Standard libraries
3
+ require 'socket'
4
+ require 'tempfile'
5
+ require 'yaml'
6
+ require 'time'
7
+ require 'etc'
8
+ require 'uri'
9
+ require 'stringio'
10
+
11
+ # Compiled Mongrel extension
12
+ require 'http11'
13
+
14
+ # Gem conditional loader
15
+ require 'mongrel/gems'
16
+ Mongrel::Gems.require 'cgi_multipart_eof_fix'
17
+ Mongrel::Gems.require 'fastthread'
18
+ require 'thread'
19
+
20
+ # Ruby Mongrel
21
+ require 'mongrel/cgi'
22
+ require 'mongrel/handlers'
23
+ require 'mongrel/command'
24
+ require 'mongrel/tcphack'
25
+ require 'mongrel/configurator'
26
+ require 'mongrel/uri_classifier'
27
+ require 'mongrel/const'
28
+ require 'mongrel/http_request'
29
+ require 'mongrel/header_out'
30
+ require 'mongrel/http_response'
31
+
32
+ # Mongrel module containing all of the classes (include C extensions) for running
33
+ # a Mongrel web server. It contains a minimalist HTTP server with just enough
34
+ # functionality to service web application requests fast as possible.
35
+ module Mongrel
36
+
37
+ # Used to stop the HttpServer via Thread.raise.
38
+ class StopServer < Exception; end
39
+
40
+ # Thrown at a thread when it is timed out.
41
+ class TimeoutError < Exception; end
42
+
43
+ # A Hash with one extra parameter for the HTTP body, used internally.
44
+ class HttpParams < Hash
45
+ attr_accessor :http_body
46
+ end
47
+
48
+
49
+ # This is the main driver of Mongrel, while the Mongrel::HttpParser and Mongrel::URIClassifier
50
+ # make up the majority of how the server functions. It's a very simple class that just
51
+ # has a thread accepting connections and a simple HttpServer.process_client function
52
+ # to do the heavy lifting with the IO and Ruby.
53
+ #
54
+ # You use it by doing the following:
55
+ #
56
+ # server = HttpServer.new("0.0.0.0", 3000)
57
+ # server.register("/stuff", MyNiftyHandler.new)
58
+ # server.run.join
59
+ #
60
+ # The last line can be just server.run if you don't want to join the thread used.
61
+ # If you don't though Ruby will mysteriously just exit on you.
62
+ #
63
+ # Ruby's thread implementation is "interesting" to say the least. Experiments with
64
+ # *many* different types of IO processing simply cannot make a dent in it. Future
65
+ # releases of Mongrel will find other creative ways to make threads faster, but don't
66
+ # hold your breath until Ruby 1.9 is actually finally useful.
67
+ class HttpServer
68
+ attr_reader :acceptor
69
+ attr_reader :workers
70
+ attr_reader :classifier
71
+ attr_reader :host
72
+ attr_reader :port
73
+ attr_reader :throttle
74
+ attr_reader :timeout
75
+ attr_reader :num_processors
76
+
77
+ # Creates a working server on host:port (strange things happen if port isn't a Number).
78
+ # Use HttpServer::run to start the server and HttpServer.acceptor.join to
79
+ # join the thread that's processing incoming requests on the socket.
80
+ #
81
+ # The num_processors optional argument is the maximum number of concurrent
82
+ # processors to accept, anything over this is closed immediately to maintain
83
+ # server processing performance. This may seem mean but it is the most efficient
84
+ # way to deal with overload. Other schemes involve still parsing the client's request
85
+ # which defeats the point of an overload handling system.
86
+ #
87
+ # The throttle parameter is a sleep timeout (in hundredths of a second) that is placed between
88
+ # socket.accept calls in order to give the server a cheap throttle time. It defaults to 0 and
89
+ # actually if it is 0 then the sleep is not done at all.
90
+ def initialize(host, port, num_processors=950, throttle=0, timeout=60)
91
+
92
+ tries = 0
93
+ @socket = TCPServer.new(host, port)
94
+
95
+ @classifier = URIClassifier.new
96
+ @host = host
97
+ @port = port
98
+ @workers = ThreadGroup.new
99
+ @throttle = throttle / 100.0
100
+ @num_processors = num_processors
101
+ @timeout = timeout
102
+ end
103
+
104
+ # Does the majority of the IO processing. It has been written in Ruby using
105
+ # about 7 different IO processing strategies and no matter how it's done
106
+ # the performance just does not improve. It is currently carefully constructed
107
+ # to make sure that it gets the best possible performance, but anyone who
108
+ # thinks they can make it faster is more than welcome to take a crack at it.
109
+ def process_client(client)
110
+ continue = true
111
+ begin
112
+ while continue
113
+ parser = HttpParser.new
114
+ params = HttpParams.new
115
+ request = nil
116
+ data = client.readpartial(Const::CHUNK_SIZE)
117
+ nparsed = 0
118
+
119
+ # Assumption: nparsed will always be less since data will get filled with more
120
+ # after each parsing. If it doesn't get more then there was a problem
121
+ # with the read operation on the client socket. Effect is to stop processing when the
122
+ # socket can't fill the buffer for further parsing.
123
+ while nparsed < data.length
124
+ nparsed = parser.execute(params, data, nparsed)
125
+
126
+ if parser.finished?
127
+ if not params[Const::REQUEST_PATH]
128
+ # it might be a dumbass full host request header
129
+ uri = URI.parse(params[Const::REQUEST_URI])
130
+ params[Const::REQUEST_PATH] = uri.path
131
+ end
132
+
133
+ continue = (params["HTTP_CONNECTION"] == "Keep-Alive")
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
+ # Keep alive if client requested it
162
+ response.header["Connection"] = continue ? "Keep-Alive" : "Close"
163
+
164
+ # Process each handler in registered order until we run out or one finalizes the response.
165
+ handlers.each do |handler|
166
+ handler.process(request, response)
167
+ break if response.done or client.closed?
168
+ end
169
+
170
+ # And finally, if nobody closed the response off, we finalize it.
171
+ unless response.done or client.closed?
172
+ response.finished
173
+ end
174
+ else
175
+ # Didn't find it, return a stock 404 response.
176
+ client.write(Const::ERROR_404_RESPONSE)
177
+ end
178
+
179
+ break #done
180
+ else
181
+ # Parser is not done, queue up more data to read and continue parsing
182
+ chunk = client.readpartial(Const::CHUNK_SIZE)
183
+ break if !chunk or chunk.length == 0 # read failed, stop processing
184
+
185
+ data << chunk
186
+ if data.length >= Const::MAX_HEADER
187
+ raise HttpParserError.new("HEADER is longer than allowed, aborting client early.")
188
+ end
189
+ end
190
+ end
191
+ client.flush unless client.closed?
192
+ end
193
+ rescue EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,Errno::EBADF
194
+ client.close rescue nil
195
+ rescue HttpParserError => e
196
+ STDERR.puts "#{Time.now}: HTTP parse error, malformed request (#{params[Const::HTTP_X_FORWARDED_FOR] || client.peeraddr.last}): #{e.inspect}"
197
+ STDERR.puts "#{Time.now}: REQUEST DATA: #{data.inspect}\n---\nPARAMS: #{params.inspect}\n---\n"
198
+ rescue Errno::EMFILE
199
+ reap_dead_workers('too many files')
200
+ rescue Object => e
201
+ STDERR.puts "#{Time.now}: Read error: #{e.inspect}"
202
+ STDERR.puts e.backtrace.join("\n")
203
+ ensure
204
+ begin
205
+ client.close
206
+ rescue IOError
207
+ # Already closed
208
+ rescue Object => e
209
+ STDERR.puts "#{Time.now}: Client error: #{e.inspect}"
210
+ STDERR.puts e.backtrace.join("\n")
211
+ end
212
+ request.body.delete if request and request.body.class == Tempfile
213
+ end
214
+ end
215
+
216
+ # Used internally to kill off any worker threads that have taken too long
217
+ # to complete processing. Only called if there are too many processors
218
+ # currently servicing. It returns the count of workers still active
219
+ # after the reap is done. It only runs if there are workers to reap.
220
+ def reap_dead_workers(reason='unknown')
221
+ if @workers.list.length > 0
222
+ STDERR.puts "#{Time.now}: Reaping #{@workers.list.length} threads for slow workers because of '#{reason}'"
223
+ error_msg = "Mongrel timed out this thread: #{reason}"
224
+ mark = Time.now
225
+ @workers.list.each do |worker|
226
+ worker[:started_on] = Time.now if not worker[:started_on]
227
+
228
+ if mark - worker[:started_on] > @timeout + @throttle
229
+ STDERR.puts "Thread #{worker.inspect} is too old, killing."
230
+ worker.raise(TimeoutError.new(error_msg))
231
+ end
232
+ end
233
+ end
234
+
235
+ return @workers.list.length
236
+ end
237
+
238
+ # Performs a wait on all the currently running threads and kills any that take
239
+ # too long. It waits by @timeout seconds, which can be set in .initialize or
240
+ # via mongrel_rails. The @throttle setting does extend this waiting period by
241
+ # that much longer.
242
+ def graceful_shutdown
243
+ while reap_dead_workers("shutdown") > 0
244
+ STDERR.puts "Waiting for #{@workers.list.length} requests to finish, could take #{@timeout + @throttle} seconds."
245
+ sleep @timeout / 10
246
+ end
247
+ end
248
+
249
+ def configure_socket_options
250
+ case RUBY_PLATFORM
251
+ when /linux/
252
+ # 9 is currently TCP_DEFER_ACCEPT
253
+ $tcp_defer_accept_opts = [Socket::SOL_TCP, 9, 1]
254
+ $tcp_cork_opts = [Socket::SOL_TCP, 3, 1]
255
+ when /freebsd(([1-4]\..{1,2})|5\.[0-4])/
256
+ # Do nothing, just closing a bug when freebsd <= 5.4
257
+ when /freebsd/
258
+ # Use the HTTP accept filter if available.
259
+ # The struct made by pack() is defined in /usr/include/sys/socket.h as accept_filter_arg
260
+ unless `/sbin/sysctl -nq net.inet.accf.http`.empty?
261
+ $tcp_defer_accept_opts = [Socket::SOL_SOCKET, Socket::SO_ACCEPTFILTER, ['httpready', nil].pack('a16a240')]
262
+ end
263
+ end
264
+ end
265
+
266
+ # Runs the thing. It returns the thread used so you can "join" it. You can also
267
+ # access the HttpServer::acceptor attribute to get the thread later.
268
+ def run
269
+ BasicSocket.do_not_reverse_lookup=true
270
+
271
+ configure_socket_options
272
+
273
+ if defined?($tcp_defer_accept_opts) and $tcp_defer_accept_opts
274
+ @socket.setsockopt(*$tcp_defer_accept_opts) rescue nil
275
+ end
276
+
277
+ @acceptor = Thread.new do
278
+ begin
279
+ while true
280
+ begin
281
+ client = @socket.accept
282
+
283
+ if defined?($tcp_cork_opts) and $tcp_cork_opts
284
+ client.setsockopt(*$tcp_cork_opts) rescue nil
285
+ end
286
+
287
+ worker_list = @workers.list
288
+
289
+ if worker_list.length >= @num_processors
290
+ STDERR.puts "Server overloaded with #{worker_list.length} processors (#@num_processors max). Dropping connection."
291
+ client.close rescue nil
292
+ reap_dead_workers("max processors")
293
+ else
294
+ thread = Thread.new(client) {|c| process_client(c) }
295
+ thread[:started_on] = Time.now
296
+ @workers.add(thread)
297
+
298
+ sleep @throttle if @throttle > 0
299
+ end
300
+ rescue StopServer
301
+ break
302
+ rescue Errno::EMFILE
303
+ reap_dead_workers("too many open files")
304
+ sleep 0.5
305
+ rescue Errno::ECONNABORTED
306
+ # client closed the socket even before accept
307
+ client.close rescue nil
308
+ rescue Object => e
309
+ STDERR.puts "#{Time.now}: Unhandled listen loop exception #{e.inspect}."
310
+ STDERR.puts e.backtrace.join("\n")
311
+ end
312
+ end
313
+ graceful_shutdown
314
+ ensure
315
+ @socket.close
316
+ # STDERR.puts "#{Time.now}: Closed socket."
317
+ end
318
+ end
319
+
320
+ return @acceptor
321
+ end
322
+
323
+ # Simply registers a handler with the internal URIClassifier. When the URI is
324
+ # found in the prefix of a request then your handler's HttpHandler::process method
325
+ # is called. See Mongrel::URIClassifier#register for more information.
326
+ #
327
+ # If you set in_front=true then the passed in handler will be put in the front of the list
328
+ # for that particular URI. Otherwise it's placed at the end of the list.
329
+ def register(uri, handler, in_front=false)
330
+ begin
331
+ @classifier.register(uri, [handler])
332
+ rescue URIClassifier::RegistrationError
333
+ handlers = @classifier.resolve(uri)[2]
334
+ method_name = in_front ? 'unshift' : 'push'
335
+ handlers.send(method_name, handler)
336
+ end
337
+ handler.listener = self
338
+ end
339
+
340
+ # Removes any handlers registered at the given URI. See Mongrel::URIClassifier#unregister
341
+ # for more information. Remember this removes them *all* so the entire
342
+ # processing chain goes away.
343
+ def unregister(uri)
344
+ @classifier.unregister(uri)
345
+ end
346
+
347
+ # Stops the acceptor thread and then causes the worker threads to finish
348
+ # off the request queue before finally exiting.
349
+ def stop(synchronous=false)
350
+ @acceptor.raise(StopServer.new)
351
+
352
+ if synchronous
353
+ sleep(0.5) while @acceptor.alive?
354
+ end
355
+ end
356
+
357
+ end
358
+ end
359
+
360
+ # Load experimental library, if present. We put it here so it can override anything
361
+ # in regular Mongrel.
362
+
363
+ $LOAD_PATH.unshift 'projects/mongrel_experimental/lib/'
364
+ 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