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