mongrel 1.0.5 → 1.1

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.

@@ -4,10 +4,11 @@
4
4
  # Additional work donated by contributors. See http://mongrel.rubyforge.org/attributions.html
5
5
  # for more information.
6
6
 
7
- require 'rubygems'
8
7
  require 'singleton'
9
8
  require 'optparse'
10
- require 'gem_plugin'
9
+
10
+ require 'mongrel/gems'
11
+ Mongrel::Gems.require 'gem_plugin'
11
12
 
12
13
  module Mongrel
13
14
 
@@ -211,7 +211,7 @@ module Mongrel
211
211
  # Uses the GemPlugin system to easily load plugins based on their
212
212
  # gem dependencies. You pass in either an :includes => [] or
213
213
  # :excludes => [] setting listing the names of plugins to include
214
- # or exclude from the when determining the dependencies.
214
+ # or exclude from the determining the dependencies.
215
215
  def load_plugins(options={})
216
216
  ops = resolve_defaults(options)
217
217
 
@@ -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".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,23 @@
1
+ module Mongrel
2
+ module Gems
3
+ class << self
4
+
5
+ alias :original_require :require
6
+
7
+ def require(library, version = nil)
8
+ begin
9
+ original_require library
10
+ rescue LoadError, RuntimeError => e
11
+ unless respond_to? 'gem'
12
+ # ActiveSupport breaks 'require' by making it always return a true value
13
+ require 'rubygems'
14
+ gem library, version if version
15
+ retry
16
+ end
17
+ # Fail without reraising
18
+ end
19
+ end
20
+
21
+ end
22
+ end
23
+ end
@@ -132,12 +132,8 @@ module Mongrel
132
132
  # Add the drive letter or root path
133
133
  req_path = File.join(@path, req_path) if @path
134
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
135
+
136
+ if File.exist? req_path
141
137
  # It exists and it's in the right location
142
138
  if File.directory? req_path
143
139
  # The request is for a directory
@@ -157,7 +153,7 @@ module Mongrel
157
153
  return req_path
158
154
  end
159
155
  else
160
- # does not exist or isn't in the right spot or isn't valid because not start with @path
156
+ # does not exist or isn't in the right spot
161
157
  return nil
162
158
  end
163
159
  end
@@ -0,0 +1,28 @@
1
+ module Mongrel
2
+ # This class implements a simple way of constructing the HTTP headers dynamically
3
+ # via a Hash syntax. Think of it as a write-only Hash. Refer to HttpResponse for
4
+ # information on how this is used.
5
+ #
6
+ # One consequence of this write-only nature is that you can write multiple headers
7
+ # by just doing them twice (which is sometimes needed in HTTP), but that the normal
8
+ # semantics for Hash (where doing an insert replaces) is not there.
9
+ class HeaderOut
10
+ attr_reader :out
11
+ attr_accessor :allowed_duplicates
12
+
13
+ def initialize(out)
14
+ @sent = {}
15
+ @allowed_duplicates = {"Set-Cookie" => true, "Set-Cookie2" => true,
16
+ "Warning" => true, "WWW-Authenticate" => true}
17
+ @out = out
18
+ end
19
+
20
+ # Simply writes "#{key}: #{value}" to an output buffer.
21
+ def[]=(key,value)
22
+ if not @sent.has_key?(key) or @allowed_duplicates.has_key?(key)
23
+ @sent[key] = true
24
+ @out.write(Const::HEADER_FORMAT % [key, value])
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,155 @@
1
+
2
+ module Mongrel
3
+ #
4
+ # When a handler is found for a registered URI then this class is constructed
5
+ # and passed to your HttpHandler::process method. You should assume that
6
+ # *one* handler processes all requests. Included in the HttpRequest is a
7
+ # HttpRequest.params Hash that matches common CGI params, and a HttpRequest.body
8
+ # which is a string containing the request body (raw for now).
9
+ #
10
+ # The HttpRequest.initialize method will convert any request that is larger than
11
+ # Const::MAX_BODY into a Tempfile and use that as the body. Otherwise it uses
12
+ # a StringIO object. To be safe, you should assume it works like a file.
13
+ #
14
+ # The HttpHandler.request_notify system is implemented by having HttpRequest call
15
+ # HttpHandler.request_begins, HttpHandler.request_progress, HttpHandler.process during
16
+ # the IO processing. This adds a small amount of overhead but lets you implement
17
+ # finer controlled handlers and filters.
18
+ #
19
+ class HttpRequest
20
+ attr_reader :body, :params
21
+
22
+ # You don't really call this. It's made for you.
23
+ # Main thing it does is hook up the params, and store any remaining
24
+ # body data into the HttpRequest.body attribute.
25
+ def initialize(params, socket, dispatchers)
26
+ @params = params
27
+ @socket = socket
28
+ @dispatchers = dispatchers
29
+ content_length = @params[Const::CONTENT_LENGTH].to_i
30
+ remain = content_length - @params.http_body.length
31
+
32
+ # tell all dispatchers the request has begun
33
+ @dispatchers.each do |dispatcher|
34
+ dispatcher.request_begins(@params)
35
+ end unless @dispatchers.nil? || @dispatchers.empty?
36
+
37
+ # Some clients (like FF1.0) report 0 for body and then send a body. This will probably truncate them but at least the request goes through usually.
38
+ if remain <= 0
39
+ # we've got everything, pack it up
40
+ @body = StringIO.new
41
+ @body.write @params.http_body
42
+ update_request_progress(0, content_length)
43
+ elsif remain > 0
44
+ # must read more data to complete body
45
+ if remain > Const::MAX_BODY
46
+ # huge body, put it in a tempfile
47
+ @body = Tempfile.new(Const::MONGREL_TMP_BASE)
48
+ @body.binmode
49
+ else
50
+ # small body, just use that
51
+ @body = StringIO.new
52
+ end
53
+
54
+ @body.write @params.http_body
55
+ read_body(remain, content_length)
56
+ end
57
+
58
+ @body.rewind if @body
59
+ end
60
+
61
+ # updates all dispatchers about our progress
62
+ def update_request_progress(clen, total)
63
+ return if @dispatchers.nil? || @dispatchers.empty?
64
+ @dispatchers.each do |dispatcher|
65
+ dispatcher.request_progress(@params, clen, total)
66
+ end
67
+ end
68
+ private :update_request_progress
69
+
70
+ # Does the heavy lifting of properly reading the larger body requests in
71
+ # small chunks. It expects @body to be an IO object, @socket to be valid,
72
+ # and will set @body = nil if the request fails. It also expects any initial
73
+ # part of the body that has been read to be in the @body already.
74
+ def read_body(remain, total)
75
+ begin
76
+ # write the odd sized chunk first
77
+ @params.http_body = read_socket(remain % Const::CHUNK_SIZE)
78
+
79
+ remain -= @body.write(@params.http_body)
80
+
81
+ update_request_progress(remain, total)
82
+
83
+ # then stream out nothing but perfectly sized chunks
84
+ until remain <= 0 or @socket.closed?
85
+ # ASSUME: we are writing to a disk and these writes always write the requested amount
86
+ @params.http_body = read_socket(Const::CHUNK_SIZE)
87
+ remain -= @body.write(@params.http_body)
88
+
89
+ update_request_progress(remain, total)
90
+ end
91
+ rescue Object => e
92
+ STDERR.puts "#{Time.now}: Error reading HTTP body: #{e.inspect}"
93
+ STDERR.puts e.backtrace.join("\n")
94
+ # any errors means we should delete the file, including if the file is dumped
95
+ @socket.close rescue nil
96
+ @body.delete if @body.class == Tempfile
97
+ @body = nil # signals that there was a problem
98
+ end
99
+ end
100
+
101
+ def read_socket(len)
102
+ if !@socket.closed?
103
+ data = @socket.read(len)
104
+ if !data
105
+ raise "Socket read return nil"
106
+ elsif data.length != len
107
+ raise "Socket read returned insufficient data: #{data.length}"
108
+ else
109
+ data
110
+ end
111
+ else
112
+ raise "Socket already closed when reading."
113
+ end
114
+ end
115
+
116
+ # Performs URI escaping so that you can construct proper
117
+ # query strings faster. Use this rather than the cgi.rb
118
+ # version since it's faster. (Stolen from Camping).
119
+ def self.escape(s)
120
+ s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
121
+ '%'+$1.unpack('H2'*$1.size).join('%').upcase
122
+ }.tr(' ', '+')
123
+ end
124
+
125
+
126
+ # Unescapes a URI escaped string. (Stolen from Camping).
127
+ def self.unescape(s)
128
+ s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
129
+ [$1.delete('%')].pack('H*')
130
+ }
131
+ end
132
+
133
+ # Parses a query string by breaking it up at the '&'
134
+ # and ';' characters. You can also use this to parse
135
+ # cookies by changing the characters used in the second
136
+ # parameter (which defaults to '&;'.
137
+ def self.query_parse(qs, d = '&;')
138
+ params = {}
139
+ (qs||'').split(/[#{d}] */n).inject(params) { |h,p|
140
+ k, v=unescape(p).split('=',2)
141
+ if cur = params[k]
142
+ if cur.class == Array
143
+ params[k] << v
144
+ else
145
+ params[k] = [cur, v]
146
+ end
147
+ else
148
+ params[k] = v
149
+ end
150
+ }
151
+
152
+ return params
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,163 @@
1
+ module Mongrel
2
+ # Writes and controls your response to the client using the HTTP/1.1 specification.
3
+ # You use it by simply doing:
4
+ #
5
+ # response.start(200) do |head,out|
6
+ # head['Content-Type'] = 'text/plain'
7
+ # out.write("hello\n")
8
+ # end
9
+ #
10
+ # The parameter to start is the response code--which Mongrel will translate for you
11
+ # based on HTTP_STATUS_CODES. The head parameter is how you write custom headers.
12
+ # The out parameter is where you write your body. The default status code for
13
+ # HttpResponse.start is 200 so the above example is redundant.
14
+ #
15
+ # As you can see, it's just like using a Hash and as you do this it writes the proper
16
+ # header to the output on the fly. You can even intermix specifying headers and
17
+ # writing content. The HttpResponse class with write the things in the proper order
18
+ # once the HttpResponse.block is ended.
19
+ #
20
+ # You may also work the HttpResponse object directly using the various attributes available
21
+ # for the raw socket, body, header, and status codes. If you do this you're on your own.
22
+ # A design decision was made to force the client to not pipeline requests. HTTP/1.1
23
+ # pipelining really kills the performance due to how it has to be handled and how
24
+ # unclear the standard is. To fix this the HttpResponse gives a "Connection: close"
25
+ # header which forces the client to close right away. The bonus for this is that it
26
+ # gives a pretty nice speed boost to most clients since they can close their connection
27
+ # immediately.
28
+ #
29
+ # One additional caveat is that you don't have to specify the Content-length header
30
+ # as the HttpResponse will write this for you based on the out length.
31
+ class HttpResponse
32
+ attr_reader :socket
33
+ attr_reader :body
34
+ attr_writer :body
35
+ attr_reader :header
36
+ attr_reader :status
37
+ attr_writer :status
38
+ attr_reader :body_sent
39
+ attr_reader :header_sent
40
+ attr_reader :status_sent
41
+
42
+ def initialize(socket)
43
+ @socket = socket
44
+ @body = StringIO.new
45
+ @status = 404
46
+ @reason = HTTP_STATUS_CODES[@status]
47
+ @header = HeaderOut.new(StringIO.new)
48
+ @header[Const::DATE] = Time.now.httpdate
49
+ @body_sent = false
50
+ @header_sent = false
51
+ @status_sent = false
52
+ end
53
+
54
+ # Receives a block passing it the header and body for you to work with.
55
+ # When the block is finished it writes everything you've done to
56
+ # the socket in the proper order. This lets you intermix header and
57
+ # body content as needed. Handlers are able to modify pretty much
58
+ # any part of the request in the chain, and can stop further processing
59
+ # by simple passing "finalize=true" to the start method. By default
60
+ # all handlers run and then mongrel finalizes the request when they're
61
+ # all done.
62
+ def start(status=200, finalize=false, reason=HTTP_STATUS_CODES[status])
63
+ @status = status.to_i
64
+ @reason = reason
65
+ yield @header, @body
66
+ finished if finalize
67
+ end
68
+
69
+ # Primarily used in exception handling to reset the response output in order to write
70
+ # an alternative response. It will abort with an exception if you have already
71
+ # sent the header or the body. This is pretty catastrophic actually.
72
+ def reset
73
+ if @body_sent
74
+ raise "You have already sent the request body."
75
+ elsif @header_sent
76
+ raise "You have already sent the request headers."
77
+ else
78
+ @header.out.truncate(0)
79
+ @body.close
80
+ @body = StringIO.new
81
+ end
82
+ end
83
+
84
+ def send_status(content_length=@body.length)
85
+ if not @status_sent
86
+ @header['Content-Length'] = content_length if content_length and @status != 304
87
+ write(Const::STATUS_FORMAT % [@status, @reason])
88
+ @status_sent = true
89
+ end
90
+ end
91
+
92
+ def send_header
93
+ if not @header_sent
94
+ @header.out.rewind
95
+ write(@header.out.read + Const::LINE_END)
96
+ @header_sent = true
97
+ end
98
+ end
99
+
100
+ def send_body
101
+ if not @body_sent
102
+ @body.rewind
103
+ write(@body.read)
104
+ @body_sent = true
105
+ end
106
+ end
107
+
108
+ # Appends the contents of +path+ to the response stream. The file is opened for binary
109
+ # reading and written in chunks to the socket.
110
+ #
111
+ # Sendfile API support has been removed in 0.3.13.4 due to stability problems.
112
+ def send_file(path, small_file = false)
113
+ if small_file
114
+ File.open(path, "rb") {|f| @socket << f.read }
115
+ else
116
+ File.open(path, "rb") do |f|
117
+ while chunk = f.read(Const::CHUNK_SIZE) and chunk.length > 0
118
+ begin
119
+ write(chunk)
120
+ rescue Object => exc
121
+ break
122
+ end
123
+ end
124
+ end
125
+ end
126
+ @body_sent = true
127
+ end
128
+
129
+ def socket_error(details)
130
+ # ignore these since it means the client closed off early
131
+ @socket.close rescue nil
132
+ done = true
133
+ raise details
134
+ end
135
+
136
+ def write(data)
137
+ @socket.write(data)
138
+ rescue => details
139
+ socket_error(details)
140
+ end
141
+
142
+ # This takes whatever has been done to header and body and then writes it in the
143
+ # proper format to make an HTTP/1.1 response.
144
+ def finished
145
+ send_status
146
+ send_header
147
+ send_body
148
+ end
149
+
150
+ # Used during error conditions to mark the response as "done" so there isn't any more processing
151
+ # sent to the client.
152
+ def done=(val)
153
+ @status_sent = true
154
+ @header_sent = true
155
+ @body_sent = true
156
+ end
157
+
158
+ def done
159
+ (@status_sent and @header_sent and @body_sent)
160
+ end
161
+
162
+ end
163
+ end