mongrel 1.1.2-java

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of mongrel might be problematic. Click here for more details.

Files changed (73) hide show
  1. data.tar.gz.sig +1 -0
  2. data/CHANGELOG +12 -0
  3. data/COPYING +55 -0
  4. data/LICENSE +55 -0
  5. data/Manifest +69 -0
  6. data/README +74 -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 +266 -0
  32. data/ext/http11_java/org/jruby/mongrel/Http11Parser.java +572 -0
  33. data/lib/http11.jar +0 -0
  34. data/lib/mongrel.rb +355 -0
  35. data/lib/mongrel/camping.rb +107 -0
  36. data/lib/mongrel/cgi.rb +181 -0
  37. data/lib/mongrel/command.rb +222 -0
  38. data/lib/mongrel/configurator.rb +388 -0
  39. data/lib/mongrel/const.rb +110 -0
  40. data/lib/mongrel/debug.rb +203 -0
  41. data/lib/mongrel/gems.rb +22 -0
  42. data/lib/mongrel/handlers.rb +468 -0
  43. data/lib/mongrel/header_out.rb +28 -0
  44. data/lib/mongrel/http_request.rb +155 -0
  45. data/lib/mongrel/http_response.rb +163 -0
  46. data/lib/mongrel/init.rb +10 -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 +263 -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 +87 -0
  61. data/test/test_debug.rb +25 -0
  62. data/test/test_handlers.rb +103 -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 +115 -0
  70. data/test/testhelp.rb +66 -0
  71. data/tools/trickletest.rb +45 -0
  72. metadata +186 -0
  73. metadata.gz.sig +4 -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="1.1.2".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
+
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
+ if File.exist? req_path
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 : false
209
+ when modified_since && last_response_time > Time.now : false
210
+ when modified_since && mtime > last_response_time : false
211
+ when none_match && none_match == '*' : false
212
+ when none_match && !none_match.strip.split(/\s*,\s*/).include?(etag) : 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