1
|
# Copyright (c) 2005 Zed A. Shaw
|
2
|
# You can redistribute it and/or modify it under the same terms as Ruby.
|
3
|
#
|
4
|
# Additional work donated by contributors. See http://mongrel.rubyforge.org/attributions.html
|
5
|
# for more information.
|
6
|
|
7
|
require 'cgi'
|
8
|
|
9
|
module Mongrel
|
10
|
# The beginning of a complete wrapper around Mongrel's internal HTTP processing
|
11
|
# system but maintaining the original Ruby CGI module. Use this only as a crutch
|
12
|
# to get existing CGI based systems working. It should handle everything, but please
|
13
|
# notify me if you see special warnings. This work is still very alpha so I need
|
14
|
# testers to help work out the various corner cases.
|
15
|
#
|
16
|
# The CGIWrapper.handler attribute is normally not set and is available for
|
17
|
# frameworks that need to get back to the handler. Rails uses this to give
|
18
|
# people access to the RailsHandler#files (DirHandler really) so they can
|
19
|
# look-up paths and do other things with the files managed there.
|
20
|
#
|
21
|
# In Rails you can get the real file for a request with:
|
22
|
#
|
23
|
# path = @request.cgi.handler.files.can_serve(@request['PATH_INFO'])
|
24
|
#
|
25
|
# Which is ugly but does the job. Feel free to write a Rails helper for that.
|
26
|
# Refer to DirHandler#can_serve for more information on this.
|
27
|
class CGIWrapper < ::CGI
|
28
|
public :env_table
|
29
|
attr_reader :options
|
30
|
attr_accessor :handler
|
31
|
# Set this to false if you want calls to CGIWrapper.out to not actually send
|
32
|
# the response until you force it.
|
33
|
attr_accessor :default_really_final
|
34
|
|
35
|
# these are stripped out of any keys passed to CGIWrapper.header function
|
36
|
REMOVED_KEYS = [ "nph","status","server","connection","type",
|
37
|
"charset","length","language","expires"]
|
38
|
|
39
|
# Takes an HttpRequest and HttpResponse object, plus any additional arguments
|
40
|
# normally passed to CGI. These are used internally to create a wrapper around
|
41
|
# the real CGI while maintaining Mongrel's view of the world.
|
42
|
def initialize(request, response, *args)
|
43
|
@request = request
|
44
|
@response = response
|
45
|
@args = *args
|
46
|
@input = request.body
|
47
|
@head = {}
|
48
|
@out_called = false
|
49
|
@default_really_final=true
|
50
|
super(*args)
|
51
|
end
|
52
|
|
53
|
# The header is typically called to send back the header. In our case we
|
54
|
# collect it into a hash for later usage.
|
55
|
#
|
56
|
# nph -- Mostly ignored. It'll output the date.
|
57
|
# connection -- Completely ignored. Why is CGI doing this?
|
58
|
# length -- Ignored since Mongrel figures this out from what you write to output.
|
59
|
#
|
60
|
def header(options = "text/html")
|
61
|
# if they pass in a string then just write the Content-Type
|
62
|
if options.class == String
|
63
|
@head['Content-Type'] = options unless @head['Content-Type']
|
64
|
else
|
65
|
# convert the given options into what Mongrel wants
|
66
|
@head['Content-Type'] = options['type'] || "text/html"
|
67
|
@head['Content-Type'] += "; charset=" + options['charset'] if options.has_key? "charset" if options['charset']
|
68
|
|
69
|
# setup date only if they use nph
|
70
|
@head['Date'] = CGI::rfc1123_date(Time.now) if options['nph']
|
71
|
|
72
|
# setup the server to use the default or what they set
|
73
|
@head['Server'] = options['server'] || env_table['SERVER_SOFTWARE']
|
74
|
|
75
|
# remaining possible options they can give
|
76
|
@head['Status'] = options['status'] if options['status']
|
77
|
@head['Content-Language'] = options['language'] if options['language']
|
78
|
@head['Expires'] = options['expires'] if options['expires']
|
79
|
|
80
|
# drop the keys we don't want anymore
|
81
|
# these two lines added based on https://rails.lighthouseapp.com/projects/8994/tickets/4690
|
82
|
@head['cookie'] = options['cookie'] if options['cookie']
|
83
|
options.delete('cookie')
|
84
|
REMOVED_KEYS.each {|k| options.delete(k) }
|
85
|
|
86
|
# finally just convert the rest raw (which puts 'cookie' directly)
|
87
|
# 'cookie' is translated later as we write the header out
|
88
|
options.each{|k,v| @head[k] = v}
|
89
|
end
|
90
|
|
91
|
# doing this fakes out the cgi library to think the headers are empty
|
92
|
# we then do the real headers in the out function call later
|
93
|
""
|
94
|
end
|
95
|
|
96
|
# Takes any 'cookie' setting and sends it over the Mongrel header,
|
97
|
# then removes the setting from the options. If cookie is an
|
98
|
# Array or Hash then it sends those on with .to_s, otherwise
|
99
|
# it just calls .to_s on it and hopefully your "cookie" can
|
100
|
# write itself correctly.
|
101
|
def send_cookies(to)
|
102
|
# convert the cookies based on the myriad of possible ways to set a cookie
|
103
|
if @head['cookie']
|
104
|
cookie = @head['cookie']
|
105
|
case cookie
|
106
|
when Array
|
107
|
cookie.each {|c| to['Set-Cookie'] = c.to_s }
|
108
|
when Hash
|
109
|
cookie.each_value {|c| to['Set-Cookie'] = c.to_s}
|
110
|
else
|
111
|
to['Set-Cookie'] = options['cookie'].to_s
|
112
|
end
|
113
|
|
114
|
@head.delete('cookie')
|
115
|
end
|
116
|
|
117
|
# @output_cookies seems to never be used, but we'll process it just in case
|
118
|
@output_cookies.each {|c| to['Set-Cookie'] = c.to_s } if @output_cookies
|
119
|
end
|
120
|
|
121
|
# The dumb thing is people can call header or this or both and in any order.
|
122
|
# So, we just reuse header and then finalize the HttpResponse the right way.
|
123
|
# Status is taken from the various options and converted to what Mongrel needs
|
124
|
# via the CGIWrapper.status function.
|
125
|
#
|
126
|
# We also prevent Rails from actually doing the final send by adding a
|
127
|
# second parameter "really_final". Only Mongrel calls this after Rails
|
128
|
# is done. Since this will break other frameworks, it defaults to
|
129
|
# a different setting for rails (false) and (true) for others.
|
130
|
def out(options = "text/html", really_final=@default_really_final)
|
131
|
if @out_called || !really_final
|
132
|
# don't do it more than once or if it's not the really final call
|
133
|
return
|
134
|
end
|
135
|
|
136
|
header(options)
|
137
|
|
138
|
@response.start status do |head, body|
|
139
|
send_cookies(head)
|
140
|
|
141
|
@head.each {|k,v| head[k] = v}
|
142
|
body.write(yield || "")
|
143
|
end
|
144
|
|
145
|
@out_called = true
|
146
|
end
|
147
|
|
148
|
# Computes the status once, but lazily so that people who call header twice
|
149
|
# don't get penalized. Because CGI insists on including the options status
|
150
|
# message in the status we have to do a bit of parsing.
|
151
|
def status
|
152
|
if not @status
|
153
|
stat = @head["Status"]
|
154
|
stat = stat.split(' ')[0] if stat
|
155
|
|
156
|
@status = stat || "200"
|
157
|
end
|
158
|
|
159
|
@status
|
160
|
end
|
161
|
|
162
|
# Used to wrap the normal args variable used inside CGI.
|
163
|
def args
|
164
|
@args
|
165
|
end
|
166
|
|
167
|
# Used to wrap the normal env_table variable used inside CGI.
|
168
|
def env_table
|
169
|
@request.params
|
170
|
end
|
171
|
|
172
|
# Used to wrap the normal stdinput variable used inside CGI.
|
173
|
def stdinput
|
174
|
@input
|
175
|
end
|
176
|
|
177
|
# The stdoutput should be completely bypassed but we'll drop a warning just in case
|
178
|
def stdoutput
|
179
|
STDERR.puts "WARNING: Your program is doing something not expected. Please tell Zed that stdoutput was used and what software you are running. Thanks."
|
180
|
@response.body
|
181
|
end
|
182
|
|
183
|
end
|
184
|
end
|