HttpRequest::DEF_PARAMS => HttpRequest::DEFAULTS
[unicorn.git] / lib / unicorn / cgi_wrapper.rb
blobbc622ea62837cf063f2aaeb8e9225793d08271d6
1 # This code is based on the original CGIWrapper from Mongrel
2 # Copyright (c) 2005 Zed A. Shaw
3 # Copyright (c) 2009 Eric Wong
4 # You can redistribute it and/or modify it under the same terms as Ruby.
6 # Additional work donated by contributors.  See CONTRIBUTORS for more info.
8 require 'cgi'
10 module Unicorn; end
12 # The beginning of a complete wrapper around Unicorn's internal HTTP
13 # processing system but maintaining the original Ruby CGI module.  Use
14 # this only as a crutch to get existing CGI based systems working.  It
15 # should handle everything, but please notify us if you see special
16 # warnings.  This work is still very alpha so we need testers to help
17 # work out the various corner cases.
18 class Unicorn::CGIWrapper < ::CGI
19   undef_method :env_table
20   attr_reader :env_table
21   attr_reader :body
23   # these are stripped out of any keys passed to CGIWrapper.header function
24   NPH = 'nph'.freeze # Completely ignored, Unicorn outputs the date regardless
25   CONNECTION = 'connection'.freeze # Completely ignored. Why is CGI doing this?
26   CHARSET = 'charset'.freeze # this gets appended to Content-Type
27   COOKIE = 'cookie'.freeze # maps (Hash,Array,String) to "Set-Cookie" headers
28   STATUS = 'status'.freeze # stored as @status
29   Status = 'Status'.freeze # code + human-readable text, Rails sets this
31   # some of these are common strings, but this is the only module
32   # using them and the reason they're not in Unicorn::Const
33   SET_COOKIE = 'Set-Cookie'.freeze
34   CONTENT_TYPE = 'Content-Type'.freeze
35   CONTENT_LENGTH = 'Content-Length'.freeze # this is NOT Const::CONTENT_LENGTH
36   RACK_INPUT = 'rack.input'.freeze
37   RACK_ERRORS = 'rack.errors'.freeze
39   # this maps CGI header names to HTTP header names
40   HEADER_MAP = {
41     'status' => Status,
42     'type' => CONTENT_TYPE,
43     'server' => 'Server'.freeze,
44     'language' => 'Content-Language'.freeze,
45     'expires' => 'Expires'.freeze,
46     'length' => CONTENT_LENGTH,
47   }.freeze
49   # Takes an a Rackable environment, plus any additional CGI.new
50   # arguments These are used internally to create a wrapper around the
51   # real CGI while maintaining Rack/Unicorn's view of the world.  This
52   # this will NOT deal well with large responses that take up a lot of
53   # memory, but neither does the CGI nor the original CGIWrapper from
54   # Mongrel...
55   def initialize(rack_env, *args)
56     @env_table = rack_env
57     @status = nil
58     @head = {}
59     @headv = Hash.new { |hash,key| hash[key] = [] }
60     @body = StringIO.new
61     super(*args)
62   end
64   # finalizes the response in a way Rack applications would expect
65   def rack_response
66     # @head[CONTENT_LENGTH] ||= @body.size
67     @headv[SET_COOKIE] += @output_cookies if @output_cookies
68     @headv.each_pair do |key,value|
69       @head[key] ||= value.join("\n") unless value.empty?
70     end
72     # Capitalized "Status:", with human-readable status code (e.g. "200 OK")
73     parseable_status = @head.delete(Status)
74     @status ||= parseable_status.split(/ /)[0].to_i rescue 500
76     [ @status || 500, @head, [ @body.string ] ]
77   end
79   # The header is typically called to send back the header.  In our case we
80   # collect it into a hash for later usage.  This can be called multiple
81   # times to set different cookies.
82   def header(options = "text/html")
83     # if they pass in a string then just write the Content-Type
84     if String === options
85       @head[CONTENT_TYPE] ||= options
86     else
87       HEADER_MAP.each_pair do |from, to|
88         from = options.delete(from) or next
89         @head[to] = from.to_s
90       end
92       @head[CONTENT_TYPE] ||= "text/html"
93       if charset = options.delete(CHARSET)
94         @head[CONTENT_TYPE] << "; charset=#{charset}"
95       end
97       # lots of ways to set cookies
98       if cookie = options.delete(COOKIE)
99         set_cookies = @headv[SET_COOKIE]
100         case cookie
101         when Array
102           cookie.each { |c| set_cookies << c.to_s }
103         when Hash
104           cookie.each_value { |c| set_cookies << c.to_s }
105         else
106           set_cookies << cookie.to_s
107         end
108       end
109       @status ||= options.delete(STATUS) # all lower-case
111       # drop the keys we don't want anymore
112       options.delete(NPH)
113       options.delete(CONNECTION)
115       # finally, set the rest of the headers as-is, allowing duplicates
116       options.each_pair { |k,v| @headv[k] << v }
117     end
119     # doing this fakes out the cgi library to think the headers are empty
120     # we then do the real headers in the out function call later
121     ""
122   end
124   # The dumb thing is people can call header or this or both and in
125   # any order.  So, we just reuse header and then finalize the
126   # HttpResponse the right way.  This will have no effect if called
127   # the second time if the first "outputted" anything.
128   def out(options = "text/html")
129     header(options)
130     @body.size == 0 or return
131     @body << yield if block_given?
132   end
134   # Used to wrap the normal stdinput variable used inside CGI.
135   def stdinput
136     @env_table[RACK_INPUT]
137   end
139   # The stdoutput should be completely bypassed but we'll drop a
140   # warning just in case
141   def stdoutput
142     err = @env_table[RACK_ERRORS]
143     err.puts "WARNING: Your program is doing something not expected."
144     err.puts "Please tell Eric that stdoutput was used and what software " \
145              "you are running.  Thanks."
146     @body
147   end