1 # -*- encoding: binary -*-
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.
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
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
45 'type' => CONTENT_TYPE,
46 'server' => 'Server'.freeze,
47 'language' => 'Content-Language'.freeze,
48 'expires' => 'Expires'.freeze,
49 'length' => CONTENT_LENGTH,
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
58 def initialize(rack_env, *args)
62 @headv = Hash.new { |hash,key| hash[key] = [] }
63 @body = StringIO.new("")
67 # finalizes the response in a way Rack applications would expect
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?
75 # Capitalized "Status:", with human-readable status code (e.g. "200 OK")
76 @status ||= @head.delete(Status)
78 [ @status || 500, @head, [ @body.string ] ]
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
87 @head[CONTENT_TYPE] ||= options
89 HEADER_MAP.each_pair do |from, to|
90 from = options.delete(from) or next
94 @head[CONTENT_TYPE] ||= "text/html"
95 if charset = options.delete(CHARSET)
96 @head[CONTENT_TYPE] << "; charset=#{charset}"
99 # lots of ways to set cookies
100 if cookie = options.delete(COOKIE)
101 set_cookies = @headv[SET_COOKIE]
104 cookie.each { |c| set_cookies << c.to_s }
106 cookie.each_value { |c| set_cookies << c.to_s }
108 set_cookies << cookie.to_s
111 @status ||= options.delete(STATUS) # all lower-case
113 # drop the keys we don't want anymore
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 }
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
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")
132 @body.size == 0 or return
133 @body << yield if block_given?
136 # Used to wrap the normal stdinput variable used inside CGI.
138 @env_table[RACK_INPUT]
141 # return a pointer to the StringIO body since it's STDOUT-like