diff options
Diffstat (limited to 'lib/mongrel/cgi.rb')
-rw-r--r-- | lib/mongrel/cgi.rb | 147 |
1 files changed, 147 insertions, 0 deletions
diff --git a/lib/mongrel/cgi.rb b/lib/mongrel/cgi.rb new file mode 100644 index 0000000..3d7d25a --- /dev/null +++ b/lib/mongrel/cgi.rb @@ -0,0 +1,147 @@ +require 'cgi' + +module Mongrel + # The beginning of a complete wrapper around Mongrel's internal HTTP processing + # system but maintaining the original Ruby CGI module. Use this only as a crutch + # to get existing CGI based systems working. It should handle everything, but please + # notify me if you see special warnings. This work is still very alpha so I need + # testers to help work out the various corner cases. + class CGIWrapper < ::CGI + public :env_table + attr_reader :options + + # these are stripped out of any keys passed to CGIWrapper.header function + REMOVED_KEYS = [ "nph","status","server","connection","type", + "charset","length","language","expires"] + + # Takes an HttpRequest and HttpResponse object, plus any additional arguments + # normally passed to CGI. These are used internally to create a wrapper around + # the real CGI while maintaining Mongrel's view of the world. + def initialize(request, response, *args) + @request = request + @response = response + @args = *args + @input = StringIO.new(request.body) + @head = {} + @out_called = false + super(*args) + end + + # The header is typically called to send back the header. In our case we + # collect it into a hash for later usage. + # + # nph -- Mostly ignored. It'll output the date. + # connection -- Completely ignored. Why is CGI doing this? + # length -- Ignored since Mongrel figures this out from what you write to output. + # + def header(options = "text/html") + # if they pass in a string then just write the Content-Type + if options.class == String + @head['Content-Type'] = options unless @head['Content-Type'] + else + # convert the given options into what Mongrel wants + @head['Content-Type'] = options['type'] || "text/html" + @head['Content-Type'] += "; charset=" + options['charset'] if options.has_key? "charset" if options['charset'] + + # setup date only if they use nph + @head['Date'] = CGI::rfc1123_date(Time.now) if options['nph'] + + # setup the server to use the default or what they set + @head['Server'] = options['server'] || env_table['SERVER_SOFTWARE'] + + # remaining possible options they can give + @head['Status'] = options['status'] if options['status'] + @head['Content-Language'] = options['language'] if options['language'] + @head['Expires'] = options['expires'] if options['expires'] + + # drop the keys we don't want anymore + REMOVED_KEYS.each {|k| options.delete(k) } + + # finally just convert the rest raw (which puts 'cookie' directly) + # 'cookie' is translated later as we write the header out + options.each{|k,v| @head[k] = v} + end + + # doing this fakes out the cgi library to think the headers are empty + # we then do the real headers in the out function call later + "" + end + + # Takes any 'cookie' setting and sends it over the Mongrel header, + # then removes the setting from the options. If cookie is an + # Array or Hash then it sends those on with .to_s, otherwise + # it just calls .to_s on it and hopefully your "cookie" can + # write itself correctly. + def send_cookies(to) + # convert the cookies based on the myriad of possible ways to set a cookie + if @head['cookie'] + cookie = @head['cookie'] + case cookie + when Array + cookie.each {|c| to['Set-Cookie'] = c.to_s } + when Hash + cookie.each_value {|c| to['Set-Cookie'] = c.to_s} + else + to['Set-Cookie'] = options['cookie'].to_s + end + + @head.delete('cookie') + + # @output_cookies seems to never be used, but we'll process it just in case + @output_cookies.each {|c| to['Set-Cookie'] = c.to_s } if @output_cookies + end + end + + # The dumb thing is people can call header or this or both and in any order. + # So, we just reuse header and then finalize the HttpResponse the right way. + # Status is taken from the various options and converted to what Mongrel needs + # via the CGIWrapper.status function. + def out(options = "text/html") + return if @out_called # don't do this more than once + + header(options) + + @response.start status do |head, out| + send_cookies(head) + + @head.each {|k,v| head[k] = v} + out.write(yield || "") + end + end + + # Computes the status once, but lazily so that people who call header twice + # don't get penalized. Because CGI insists on including the options status + # message in the status we have to do a bit of parsing. + def status + if not @status + stat = @head["Status"] + stat = stat.split(' ')[0] if stat + + @status = stat || "200" + end + + @status + end + + # Used to wrap the normal args variable used inside CGI. + def args + @args + end + + # Used to wrap the normal env_table variable used inside CGI. + def env_table + @request.params + end + + # Used to wrap the normal stdinput variable used inside CGI. + def stdinput + @input + end + + # The stdoutput should be completely bypassed but we'll drop a warning just in case + def stdoutput + STDERR.puts "WARNING: Your program is doing something not expected. Please tell Zed that stdoutput was used and what software you are running. Thanks." + @response.body + end + end +end |