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,114 @@
1
+
2
+ module Mongrel
3
+
4
+ # Every standard HTTP code mapped to the appropriate message. These are
5
+ # used so frequently that they are placed directly in Mongrel for easy
6
+ # access rather than Mongrel::Const itself.
7
+ HTTP_STATUS_CODES = {
8
+ 100 => 'Continue',
9
+ 101 => 'Switching Protocols',
10
+ 200 => 'OK',
11
+ 201 => 'Created',
12
+ 202 => 'Accepted',
13
+ 203 => 'Non-Authoritative Information',
14
+ 204 => 'No Content',
15
+ 205 => 'Reset Content',
16
+ 206 => 'Partial Content',
17
+ 300 => 'Multiple Choices',
18
+ 301 => 'Moved Permanently',
19
+ 302 => 'Moved Temporarily',
20
+ 303 => 'See Other',
21
+ 304 => 'Not Modified',
22
+ 305 => 'Use Proxy',
23
+ 400 => 'Bad Request',
24
+ 401 => 'Unauthorized',
25
+ 402 => 'Payment Required',
26
+ 403 => 'Forbidden',
27
+ 404 => 'Not Found',
28
+ 405 => 'Method Not Allowed',
29
+ 406 => 'Not Acceptable',
30
+ 407 => 'Proxy Authentication Required',
31
+ 408 => 'Request Time-out',
32
+ 409 => 'Conflict',
33
+ 410 => 'Gone',
34
+ 411 => 'Length Required',
35
+ 412 => 'Precondition Failed',
36
+ 413 => 'Request Entity Too Large',
37
+ 414 => 'Request-URI Too Large',
38
+ 415 => 'Unsupported Media Type',
39
+ 500 => 'Internal Server Error',
40
+ 501 => 'Not Implemented',
41
+ 502 => 'Bad Gateway',
42
+ 503 => 'Service Unavailable',
43
+ 504 => 'Gateway Time-out',
44
+ 505 => 'HTTP Version not supported'
45
+ }
46
+
47
+ # Frequently used constants when constructing requests or responses. Many times
48
+ # the constant just refers to a string with the same contents. Using these constants
49
+ # gave about a 3% to 10% performance improvement over using the strings directly.
50
+ # Symbols did not really improve things much compared to constants.
51
+ #
52
+ # While Mongrel does try to emulate the CGI/1.2 protocol, it does not use the REMOTE_IDENT,
53
+ # REMOTE_USER, or REMOTE_HOST parameters since those are either a security problem or
54
+ # too taxing on performance.
55
+ module Const
56
+ DATE = "Date".freeze
57
+
58
+ # This is the part of the path after the SCRIPT_NAME. URIClassifier will determine this.
59
+ PATH_INFO="PATH_INFO".freeze
60
+
61
+ # This is the initial part that your handler is identified as by URIClassifier.
62
+ SCRIPT_NAME="SCRIPT_NAME".freeze
63
+
64
+ # The original URI requested by the client. Passed to URIClassifier to build PATH_INFO and SCRIPT_NAME.
65
+ REQUEST_URI='REQUEST_URI'.freeze
66
+ REQUEST_PATH='REQUEST_PATH'.freeze
67
+
68
+ MONGREL_VERSION="1.2".freeze
69
+
70
+ MONGREL_TMP_BASE="mongrel".freeze
71
+
72
+ # A standard 400 response for a request which generates a http parse exception
73
+ ERROR_400_RESPONSE="HTTP/1.1 400 Bad Request\r\nConnection: close\r\nServer: Mongrel #{MONGREL_VERSION}\r\n\r\nBAD REQUEST".freeze
74
+
75
+ # The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff.
76
+ ERROR_404_RESPONSE="HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: Mongrel #{MONGREL_VERSION}\r\n\r\nNOT FOUND".freeze
77
+
78
+ CONTENT_LENGTH="CONTENT_LENGTH".freeze
79
+
80
+ # A common header for indicating the server is too busy. Not used yet.
81
+ ERROR_503_RESPONSE="HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze
82
+
83
+ # The basic max request size we'll try to read.
84
+ CHUNK_SIZE=(16 * 1024)
85
+
86
+ # This is the maximum header that is allowed before a client is booted. The parser detects
87
+ # this, but we'd also like to do this as well.
88
+ MAX_HEADER=1024 * (80 + 32)
89
+
90
+ # Maximum request body size before it is moved out of memory and into a tempfile for reading.
91
+ MAX_BODY=MAX_HEADER
92
+
93
+ # A frozen format for this is about 15% faster
94
+ STATUS_FORMAT = "HTTP/1.1 %d %s\r\nConnection: close\r\n".freeze
95
+ CONTENT_TYPE = "Content-Type".freeze
96
+ LAST_MODIFIED = "Last-Modified".freeze
97
+ ETAG = "ETag".freeze
98
+ SLASH = "/".freeze
99
+ REQUEST_METHOD="REQUEST_METHOD".freeze
100
+ GET="GET".freeze
101
+ HEAD="HEAD".freeze
102
+ # ETag is based on the apache standard of hex mtime-size-inode (inode is 0 on win32)
103
+ ETAG_FORMAT="\"%x-%x-%x\"".freeze
104
+ HEADER_FORMAT="%s: %s\r\n".freeze
105
+ LINE_END="\r\n".freeze
106
+ REMOTE_ADDR="REMOTE_ADDR".freeze
107
+ HTTP_X_FORWARDED_FOR="HTTP_X_FORWARDED_FOR".freeze
108
+ HTTP_IF_MODIFIED_SINCE="HTTP_IF_MODIFIED_SINCE".freeze
109
+ HTTP_IF_NONE_MATCH="HTTP_IF_NONE_MATCH".freeze
110
+ REDIRECT = "HTTP/1.1 302 Found\r\nLocation: %s\r\nConnection: close\r\n\r\n".freeze
111
+ HOST = "HOST".freeze
112
+ end
113
+
114
+ end
@@ -0,0 +1,203 @@
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 'logger'
8
+ require 'set'
9
+ require 'socket'
10
+ require 'fileutils'
11
+
12
+ module MongrelDbg
13
+ SETTINGS = { :tracing => {}}
14
+ LOGGING = { }
15
+
16
+ def MongrelDbg::configure(log_dir = File.join("log","mongrel_debug"))
17
+ FileUtils.mkdir_p(log_dir)
18
+ @log_dir = log_dir
19
+ $objects_out = open(File.join("log","mongrel_debug","objects.log"),"w")
20
+ $objects_out.puts "run,classname,last,count,delta,lenmean,lensd,lenmax"
21
+ $objects_out.sync = true
22
+ $last_stat = nil
23
+ $run_count = 0
24
+ end
25
+
26
+
27
+ def MongrelDbg::trace(target, message)
28
+ if SETTINGS[:tracing][target] and LOGGING[target]
29
+ LOGGING[target].log(Logger::DEBUG, message)
30
+ end
31
+ end
32
+
33
+ def MongrelDbg::begin_trace(target)
34
+ SETTINGS[:tracing][target] = true
35
+ if not LOGGING[target]
36
+ LOGGING[target] = Logger.new(File.join(@log_dir, "#{target.to_s}.log"))
37
+ end
38
+ MongrelDbg::trace(target, "#{Time.now.httpdate}: TRACING ON")
39
+ end
40
+
41
+ def MongrelDbg::end_trace(target)
42
+ SETTINGS[:tracing][target] = false
43
+ MongrelDbg::trace(target, "#{Time.now.httpdate}: TRACING OFF")
44
+ LOGGING[target].close
45
+ LOGGING[target] = nil
46
+ end
47
+
48
+ def MongrelDbg::tracing?(target)
49
+ SETTINGS[:tracing][target]
50
+ end
51
+ end
52
+
53
+
54
+
55
+ $open_files = {}
56
+
57
+ class IO
58
+ alias_method :orig_open, :open
59
+ alias_method :orig_close, :close
60
+
61
+ def open(*arg, &blk)
62
+ $open_files[self] = args.inspect
63
+ orig_open(*arg,&blk)
64
+ end
65
+
66
+ def close(*arg,&blk)
67
+ $open_files.delete self
68
+ orig_close(*arg,&blk)
69
+ end
70
+ end
71
+
72
+
73
+ module Kernel
74
+ alias_method :orig_open, :open
75
+
76
+ def open(*arg, &blk)
77
+ $open_files[self] = arg[0]
78
+ orig_open(*arg,&blk)
79
+ end
80
+
81
+ def log_open_files
82
+ open_counts = {}
83
+ $open_files.each do |f,args|
84
+ open_counts[args] ||= 0
85
+ open_counts[args] += 1
86
+ end
87
+ MongrelDbg::trace(:files, open_counts.to_yaml)
88
+ end
89
+ end
90
+
91
+
92
+ module RequestLog
93
+
94
+ class Access < GemPlugin::Plugin "/handlers"
95
+ include Mongrel::HttpHandlerPlugin
96
+
97
+ def process(request,response)
98
+ p = request.params
99
+ Mongrel.log("#{p['REMOTE_ADDR']} \"#{p['REQUEST_METHOD']} #{p["REQUEST_URI"]} HTTP/1.1\"")
100
+ end
101
+ end
102
+
103
+
104
+ class Files < GemPlugin::Plugin "/handlers"
105
+ include Mongrel::HttpHandlerPlugin
106
+
107
+ def process(request, response)
108
+ MongrelDbg::trace(:files, "#{Time.now.httpdate}: FILES OPEN BEFORE REQUEST #{request.params['PATH_INFO']}")
109
+ log_open_files
110
+ end
111
+
112
+ end
113
+
114
+
115
+ # stolen from Robert Klemme
116
+ class Objects < GemPlugin::Plugin "/handlers"
117
+ include Mongrel::HttpHandlerPlugin
118
+
119
+ def process(request,response)
120
+ begin
121
+ stats = Hash.new(0)
122
+ lengths = {}
123
+ begin
124
+ ObjectSpace.each_object do |o|
125
+ begin
126
+ if o.respond_to?(:length)
127
+ len = o.length
128
+ lengths[o.class] ||= Mongrel::Stats.new(o.class)
129
+ lengths[o.class].sample(len)
130
+ end
131
+ rescue Object
132
+ end
133
+
134
+ stats[o.class] += 1
135
+ end
136
+ rescue Object # Ignore since ObjectSpace might not be loaded on JRuby
137
+ end
138
+
139
+ stats.sort {|(k1,v1),(k2,v2)| v2 <=> v1}.each do |k,v|
140
+ if $last_stat
141
+ delta = v - $last_stat[k]
142
+ if v > 10 and delta != 0
143
+ if lengths[k]
144
+ $objects_out.printf "%d,%s,%d,%d,%d,%f,%f,%f\n", $run_count, k, $last_stat[k], v, delta,lengths[k].mean,lengths[k].sd,lengths[k].max
145
+ else
146
+ $objects_out.printf "%d,%s,%d,%d,%d,,,\n", $run_count, k, $last_stat[k], v, delta
147
+ end
148
+ end
149
+ end
150
+ end
151
+
152
+ $run_count += 1
153
+ $last_stat = stats
154
+ rescue Object
155
+ STDERR.puts "#{Time.now.httpdate}: object.log ERROR: #$!"
156
+ end
157
+ end
158
+ end
159
+
160
+
161
+ class Params < GemPlugin::Plugin "/handlers"
162
+ include Mongrel::HttpHandlerPlugin
163
+
164
+ def process(request, response)
165
+ MongrelDbg::trace(:rails, "#{Time.now.httpdate}: REQUEST #{request.params['PATH_INFO']}")
166
+ MongrelDbg::trace(:rails, request.params.to_yaml)
167
+ end
168
+
169
+ end
170
+
171
+
172
+ class Threads < GemPlugin::Plugin "/handlers"
173
+ include Mongrel::HttpHandlerPlugin
174
+
175
+ def process(request, response)
176
+ MongrelDbg::trace(:threads, "#{Time.now.httpdate}: REQUEST #{request.params['PATH_INFO']}")
177
+ begin
178
+ ObjectSpace.each_object do |obj|
179
+ begin
180
+ if obj.class == Mongrel::HttpServer
181
+ worker_list = obj.workers.list
182
+
183
+ if worker_list.length > 0
184
+ keys = "-----\n\tKEYS:"
185
+ worker_list.each {|t| keys << "\n\t\t-- #{t}: #{t.keys.inspect}" }
186
+ end
187
+
188
+ MongrelDbg::trace(:threads, "#{Time.now.httpdate}: #{obj.host}:#{obj.port} -- THREADS: #{worker_list.length} #{keys}")
189
+ end
190
+ rescue Object # Ignore since obj.class can sometimes take parameters
191
+ end
192
+ end
193
+ rescue Object # Ignore since ObjectSpace might not be loaded on JRuby
194
+ end
195
+ end
196
+ end
197
+ end
198
+
199
+
200
+ END {
201
+ MongrelDbg::trace(:files, "#{Time.now.httpdate}: FILES OPEN AT EXIT")
202
+ log_open_files
203
+ }
@@ -0,0 +1,22 @@
1
+ module Mongrel
2
+ module Gems
3
+ class << self
4
+
5
+ def require(library, version = nil)
6
+ begin
7
+ Kernel.require library
8
+ rescue LoadError, RuntimeError => e
9
+ begin
10
+ # ActiveSupport breaks 'require' by making it always return a true value
11
+ Kernel.require 'rubygems'
12
+ version ? gem(library, version) : gem(library)
13
+ retry
14
+ rescue Gem::LoadError, LoadError, RuntimeError
15
+ # puts "** #{library.inspect} could not be loaded" unless library == "mongrel_experimental"
16
+ end
17
+ end
18
+ end
19
+
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,472 @@
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/stats'
8
+ require 'zlib'
9
+ require 'yaml'
10
+
11
+
12
+ module Mongrel
13
+
14
+ # You implement your application handler with this. It's very light giving
15
+ # just the minimum necessary for you to handle a request and shoot back
16
+ # a response. Look at the HttpRequest and HttpResponse objects for how
17
+ # to use them.
18
+ #
19
+ # This is used for very simple handlers that don't require much to operate.
20
+ # More extensive plugins or those you intend to distribute as GemPlugins
21
+ # should be implemented using the HttpHandlerPlugin mixin.
22
+ #
23
+ class HttpHandler
24
+ attr_reader :request_notify
25
+ attr_accessor :listener
26
+
27
+ # This will be called by Mongrel if HttpHandler.request_notify set to *true*.
28
+ # You only get the parameters for the request, with the idea that you'd "bound"
29
+ # the beginning of the request processing and the first call to process.
30
+ def request_begins(params)
31
+ end
32
+
33
+ # Called by Mongrel for each IO chunk that is received on the request socket
34
+ # from the client, allowing you to track the progress of the IO and monitor
35
+ # the input. This will be called by Mongrel only if HttpHandler.request_notify
36
+ # set to *true*.
37
+ def request_progress(params, clen, total)
38
+ end
39
+
40
+ def process(request, response)
41
+ end
42
+
43
+ end
44
+
45
+
46
+ # This is used when your handler is implemented as a GemPlugin.
47
+ # The plugin always takes an options hash which you can modify
48
+ # and then access later. They are stored by default for
49
+ # the process method later.
50
+ module HttpHandlerPlugin
51
+ attr_reader :options
52
+ attr_reader :request_notify
53
+ attr_accessor :listener
54
+
55
+ def request_begins(params)
56
+ end
57
+
58
+ def request_progress(params, clen, total)
59
+ end
60
+
61
+ def initialize(options={})
62
+ @options = options
63
+ @header_only = false
64
+ end
65
+
66
+ def process(request, response)
67
+ end
68
+
69
+ end
70
+
71
+
72
+ #
73
+ # The server normally returns a 404 response if an unknown URI is requested, but it
74
+ # also returns a lame empty message. This lets you do a 404 response
75
+ # with a custom message for special URIs.
76
+ #
77
+ class Error404Handler < HttpHandler
78
+
79
+ # Sets the message to return. This is constructed once for the handler
80
+ # so it's pretty efficient.
81
+ def initialize(msg)
82
+ @response = Const::ERROR_404_RESPONSE + msg
83
+ end
84
+
85
+ # Just kicks back the standard 404 response with your special message.
86
+ def process(request, response)
87
+ response.socket.write(@response)
88
+ end
89
+
90
+ end
91
+
92
+ #
93
+ # Serves the contents of a directory. You give it the path to the root
94
+ # where the files are located, and it tries to find the files based on
95
+ # the PATH_INFO inside the directory. If the requested path is a
96
+ # directory then it returns a simple directory listing.
97
+ #
98
+ # It does a simple protection against going outside it's root path by
99
+ # converting all paths to an absolute expanded path, and then making
100
+ # sure that the final expanded path includes the root path. If it doesn't
101
+ # than it simply gives a 404.
102
+ #
103
+ # If you pass nil as the root path, it will not check any locations or
104
+ # expand any paths. This lets you serve files from multiple drives
105
+ # on win32.
106
+ #
107
+ # The default content type is "text/plain; charset=ISO-8859-1" but you
108
+ # can change it anything you want using the DirHandler.default_content_type
109
+ # attribute.
110
+ #
111
+ class DirHandler < HttpHandler
112
+ attr_accessor :default_content_type
113
+ attr_reader :path
114
+
115
+ MIME_TYPES_FILE = "mime_types.yml"
116
+ MIME_TYPES = YAML.load_file(File.join(File.dirname(__FILE__), MIME_TYPES_FILE))
117
+
118
+ ONLY_HEAD_GET="Only HEAD and GET allowed.".freeze
119
+
120
+ # You give it the path to the directory root and and optional listing_allowed and index_html
121
+ def initialize(path, listing_allowed=true, index_html="index.html")
122
+ @path = File.expand_path(path) if path
123
+ @listing_allowed=listing_allowed
124
+ @index_html = index_html
125
+ @default_content_type = "application/octet-stream".freeze
126
+ end
127
+
128
+ # Checks if the given path can be served and returns the full path (or nil if not).
129
+ def can_serve(path_info)
130
+
131
+ req_path = HttpRequest.unescape(path_info)
132
+ # Add the drive letter or root path
133
+ req_path = File.join(@path, req_path) if @path
134
+ req_path = File.expand_path req_path
135
+
136
+ # do not remove the check for @path at the beginning, it's what prevents
137
+ # the serving of arbitrary files (and good programmer Rule #1 Says: If
138
+ # you don't understand something, it's not because I'm stupid, it's
139
+ # because you are).
140
+ if req_path.index(@path) == 0 and File.exist? req_path
141
+ # It exists and it's in the right location
142
+ if File.directory? req_path
143
+ # The request is for a directory
144
+ index = File.join(req_path, @index_html)
145
+ if File.exist? index
146
+ # Serve the index
147
+ return index
148
+ elsif @listing_allowed
149
+ # Serve the directory
150
+ return req_path
151
+ else
152
+ # Do not serve anything
153
+ return nil
154
+ end
155
+ else
156
+ # It's a file and it's there
157
+ return req_path
158
+ end
159
+ else
160
+ # does not exist or isn't in the right spot or isn't valid because not start with @path
161
+ return nil
162
+ end
163
+ end
164
+
165
+
166
+ # Returns a simplistic directory listing if they're enabled, otherwise a 403.
167
+ # Base is the base URI from the REQUEST_URI, dir is the directory to serve
168
+ # on the file system (comes from can_serve()), and response is the HttpResponse
169
+ # object to send the results on.
170
+ def send_dir_listing(base, dir, response)
171
+ # take off any trailing / so the links come out right
172
+ base = HttpRequest.unescape(base)
173
+ base.chop! if base[-1] == "/"[-1]
174
+
175
+ if @listing_allowed
176
+ response.start(200) do |head,out|
177
+ head[Const::CONTENT_TYPE] = "text/html"
178
+ out << "<html><head><title>Directory Listing</title></head><body>"
179
+ Dir.entries(dir).each do |child|
180
+ next if child == "."
181
+ out << "<a href=\"#{base}/#{ HttpRequest.escape(child)}\">"
182
+ out << (child == ".." ? "Up to parent.." : child)
183
+ out << "</a><br/>"
184
+ end
185
+ out << "</body></html>"
186
+ end
187
+ else
188
+ response.start(403) do |head,out|
189
+ out.write("Directory listings not allowed")
190
+ end
191
+ end
192
+ end
193
+
194
+
195
+ # Sends the contents of a file back to the user. Not terribly efficient since it's
196
+ # opening and closing the file for each read.
197
+ def send_file(req_path, request, response, header_only=false)
198
+
199
+ stat = File.stat(req_path)
200
+
201
+ # Set the last modified times as well and etag for all files
202
+ mtime = stat.mtime
203
+ # Calculated the same as apache, not sure how well the works on win32
204
+ etag = Const::ETAG_FORMAT % [mtime.to_i, stat.size, stat.ino]
205
+
206
+ modified_since = request.params[Const::HTTP_IF_MODIFIED_SINCE]
207
+ none_match = request.params[Const::HTTP_IF_NONE_MATCH]
208
+
209
+ # test to see if this is a conditional request, and test if
210
+ # the response would be identical to the last response
211
+ same_response = case
212
+ when modified_since && !last_response_time = Time.httpdate(modified_since) rescue nil then false
213
+ when modified_since && last_response_time > Time.now then false
214
+ when modified_since && mtime > last_response_time then false
215
+ when none_match && none_match == '*' then false
216
+ when none_match && !none_match.strip.split(/\s*,\s*/).include?(etag) then false
217
+ else modified_since || none_match # validation successful if we get this far and at least one of the header exists
218
+ end
219
+
220
+ header = response.header
221
+ header[Const::ETAG] = etag
222
+
223
+ if same_response
224
+ response.start(304) {}
225
+ else
226
+
227
+ # First we setup the headers and status then we do a very fast send on the socket directly
228
+
229
+ # Support custom responses except 404, which is the default. A little awkward.
230
+ response.status = 200 if response.status == 404
231
+ header[Const::LAST_MODIFIED] = mtime.httpdate
232
+
233
+ # Set the mime type from our map based on the ending
234
+ dot_at = req_path.rindex('.')
235
+ if dot_at
236
+ header[Const::CONTENT_TYPE] = MIME_TYPES[req_path[dot_at .. -1]] || @default_content_type
237
+ else
238
+ header[Const::CONTENT_TYPE] = @default_content_type
239
+ end
240
+
241
+ # send a status with out content length
242
+ response.send_status(stat.size)
243
+ response.send_header
244
+
245
+ if not header_only
246
+ response.send_file(req_path, stat.size < Const::CHUNK_SIZE * 2)
247
+ end
248
+ end
249
+ end
250
+
251
+ # Process the request to either serve a file or a directory listing
252
+ # if allowed (based on the listing_allowed parameter to the constructor).
253
+ def process(request, response)
254
+ req_method = request.params[Const::REQUEST_METHOD] || Const::GET
255
+ req_path = can_serve request.params[Const::PATH_INFO]
256
+ if not req_path
257
+ # not found, return a 404
258
+ response.start(404) do |head,out|
259
+ out << "File not found"
260
+ end
261
+ else
262
+ begin
263
+ if File.directory? req_path
264
+ send_dir_listing(request.params[Const::REQUEST_URI], req_path, response)
265
+ elsif req_method == Const::HEAD
266
+ send_file(req_path, request, response, true)
267
+ elsif req_method == Const::GET
268
+ send_file(req_path, request, response, false)
269
+ else
270
+ response.start(403) {|head,out| out.write(ONLY_HEAD_GET) }
271
+ end
272
+ rescue => details
273
+ STDERR.puts "#{Time.now.httpdate}: Error sending file #{req_path}: #{details}"
274
+ end
275
+ end
276
+ end
277
+
278
+ # There is a small number of default mime types for extensions, but
279
+ # this lets you add any others you'll need when serving content.
280
+ def DirHandler::add_mime_type(extension, type)
281
+ MIME_TYPES[extension] = type
282
+ end
283
+
284
+ end
285
+
286
+
287
+ # When added to a config script (-S in mongrel_rails) it will
288
+ # look at the client's allowed response types and then gzip
289
+ # compress anything that is going out.
290
+ #
291
+ # Valid option is :always_deflate => false which tells the handler to
292
+ # deflate everything even if the client can't handle it.
293
+ class DeflateFilter < HttpHandler
294
+ include Zlib
295
+ HTTP_ACCEPT_ENCODING = "HTTP_ACCEPT_ENCODING"
296
+
297
+ def initialize(ops={})
298
+ @options = ops
299
+ @always_deflate = ops[:always_deflate] || false
300
+ end
301
+
302
+ def process(request, response)
303
+ accepts = request.params[HTTP_ACCEPT_ENCODING]
304
+ # only process if they support compression
305
+ if @always_deflate or (accepts and (accepts.include? "deflate" and not response.body_sent))
306
+ response.header["Content-Encoding"] = "deflate"
307
+ response.body = deflate(response.body)
308
+ end
309
+ end
310
+
311
+ private
312
+ def deflate(stream)
313
+ deflater = Deflate.new(
314
+ DEFAULT_COMPRESSION,
315
+ # drop the zlib header which causes both Safari and IE to choke
316
+ -MAX_WBITS,
317
+ DEF_MEM_LEVEL,
318
+ DEFAULT_STRATEGY)
319
+
320
+ stream.rewind
321
+ gzout = StringIO.new(deflater.deflate(stream.read, FINISH))
322
+ stream.close
323
+ gzout.rewind
324
+ gzout
325
+ end
326
+ end
327
+
328
+
329
+ # Implements a few basic statistics for a particular URI. Register it anywhere
330
+ # you want in the request chain and it'll quickly gather some numbers for you
331
+ # to analyze. It is pretty fast, but don't put it out in production.
332
+ #
333
+ # You should pass the filter to StatusHandler as StatusHandler.new(:stats_filter => stats).
334
+ # This lets you then hit the status URI you want and get these stats from a browser.
335
+ #
336
+ # StatisticsFilter takes an option of :sample_rate. This is a number that's passed to
337
+ # rand and if that number gets hit then a sample is taken. This helps reduce the load
338
+ # and keeps the statistics valid (since sampling is a part of how they work).
339
+ #
340
+ # The exception to :sample_rate is that inter-request time is sampled on every request.
341
+ # If this wasn't done then it wouldn't be accurate as a measure of time between requests.
342
+ class StatisticsFilter < HttpHandler
343
+ attr_reader :stats
344
+
345
+ def initialize(ops={})
346
+ @sample_rate = ops[:sample_rate] || 300
347
+
348
+ @processors = Mongrel::Stats.new("processors")
349
+ @reqsize = Mongrel::Stats.new("request Kb")
350
+ @headcount = Mongrel::Stats.new("req param count")
351
+ @respsize = Mongrel::Stats.new("response Kb")
352
+ @interreq = Mongrel::Stats.new("inter-request time")
353
+ end
354
+
355
+
356
+ def process(request, response)
357
+ if rand(@sample_rate)+1 == @sample_rate
358
+ @processors.sample(listener.workers.list.length)
359
+ @headcount.sample(request.params.length)
360
+ @reqsize.sample(request.body.length / 1024.0)
361
+ @respsize.sample((response.body.length + response.header.out.length) / 1024.0)
362
+ end
363
+ @interreq.tick
364
+ end
365
+
366
+ def dump
367
+ "#{@processors.to_s}\n#{@reqsize.to_s}\n#{@headcount.to_s}\n#{@respsize.to_s}\n#{@interreq.to_s}"
368
+ end
369
+ end
370
+
371
+
372
+ # The :stats_filter is basically any configured stats filter that you've added to this same
373
+ # URI. This lets the status handler print out statistics on how Mongrel is doing.
374
+ class StatusHandler < HttpHandler
375
+ def initialize(ops={})
376
+ @stats = ops[:stats_filter]
377
+ end
378
+
379
+ def table(title, rows)
380
+ results = "<table border=\"1\"><tr><th colspan=\"#{rows[0].length}\">#{title}</th></tr>"
381
+ rows.each do |cols|
382
+ results << "<tr>"
383
+ cols.each {|col| results << "<td>#{col}</td>" }
384
+ results << "</tr>"
385
+ end
386
+ results + "</table>"
387
+ end
388
+
389
+ def describe_listener
390
+ results = ""
391
+ results << "<h1>Listener #{listener.host}:#{listener.port}</h1>"
392
+ results << table("settings", [
393
+ ["host",listener.host],
394
+ ["port",listener.port],
395
+ ["throttle",listener.throttle],
396
+ ["timeout",listener.timeout],
397
+ ["workers max",listener.num_processors],
398
+ ])
399
+
400
+ if @stats
401
+ results << "<h2>Statistics</h2><p>N means the number of samples, pay attention to MEAN, SD, MIN and MAX."
402
+ results << "<pre>#{@stats.dump}</pre>"
403
+ end
404
+
405
+ results << "<h2>Registered Handlers</h2>"
406
+ handler_map = listener.classifier.handler_map
407
+ results << table("handlers", handler_map.map {|uri,handlers|
408
+ [uri,
409
+ "<pre>" +
410
+ handlers.map {|h| h.class.to_s }.join("\n") +
411
+ "</pre>"
412
+ ]
413
+ })
414
+
415
+ results
416
+ end
417
+
418
+ def process(request, response)
419
+ response.start do |head,out|
420
+ out.write <<-END
421
+ <html><body><title>Mongrel Server Status</title>
422
+ #{describe_listener}
423
+ </body></html>
424
+ END
425
+ end
426
+ end
427
+ end
428
+
429
+ # This handler allows you to redirect one url to another.
430
+ # You can use it like String#gsub, where the string is the REQUEST_URI.
431
+ # REQUEST_URI is the full path with GET parameters.
432
+ #
433
+ # Eg. /test/something?help=true&disclaimer=false
434
+ #
435
+ # == Examples
436
+ #
437
+ # h = Mongrel::HttpServer.new('0.0.0.0')
438
+ # h.register '/test', Mongrel::RedirectHandler.new('/to/there') # simple
439
+ # h.register '/to', Mongrel::RedirectHandler.new(/t/, 'w') # regexp
440
+ # # and with a block
441
+ # h.register '/hey', Mongrel::RedirectHandler.new(/(\w+)/) { |match| ... }
442
+ #
443
+ class RedirectHandler < Mongrel::HttpHandler
444
+ # You set the rewrite rules when building the object.
445
+ #
446
+ # pattern => What to look for or replacement if used alone
447
+ #
448
+ # replacement, block => One of them is used to replace the found text
449
+
450
+ def initialize(pattern, replacement = nil, &block)
451
+ unless replacement or block
452
+ @pattern, @replacement = nil, pattern
453
+ else
454
+ @pattern, @replacement, @block = pattern, replacement, block
455
+ end
456
+ end
457
+
458
+ # Process the request and return a redirect response
459
+ def process(request, response)
460
+ unless @pattern
461
+ response.socket.write(Mongrel::Const::REDIRECT % @replacement)
462
+ else
463
+ if @block
464
+ new_path = request.params['REQUEST_URI'].gsub(@pattern, &@block)
465
+ else
466
+ new_path = request.params['REQUEST_URI'].gsub(@pattern, @replacement)
467
+ end
468
+ response.socket.write(Mongrel::Const::REDIRECT % new_path)
469
+ end
470
+ end
471
+ end
472
+ end