mongrel 1.2.0.pre2-x86-mswin32

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