Merged updates from trunk into stable branch
[feedcatcher.git] / vendor / rails / actionpack / lib / action_controller / vendor / rack-1.0 / rack / response.rb
1 require 'rack/request'
2 require 'rack/utils'
3
4 module Rack
5 # Rack::Response provides a convenient interface to create a Rack
6 # response.
7 #
8 # It allows setting of headers and cookies, and provides useful
9 # defaults (a OK response containing HTML).
10 #
11 # You can use Response#write to iteratively generate your response,
12 # but note that this is buffered by Rack::Response until you call
13 # +finish+. +finish+ however can take a block inside which calls to
14 # +write+ are syncronous with the Rack response.
15 #
16 # Your application's +call+ should end returning Response#finish.
17
18 class Response
19 attr_accessor :length
20
21 def initialize(body=[], status=200, header={}, &block)
22 @status = status
23 @header = Utils::HeaderHash.new({"Content-Type" => "text/html"}.
24 merge(header))
25
26 @writer = lambda { |x| @body << x }
27 @block = nil
28 @length = 0
29
30 @body = []
31
32 if body.respond_to? :to_str
33 write body.to_str
34 elsif body.respond_to?(:each)
35 body.each { |part|
36 write part.to_s
37 }
38 else
39 raise TypeError, "stringable or iterable required"
40 end
41
42 yield self if block_given?
43 end
44
45 attr_reader :header
46 attr_accessor :status, :body
47
48 def [](key)
49 header[key]
50 end
51
52 def []=(key, value)
53 header[key] = value
54 end
55
56 def set_cookie(key, value)
57 case value
58 when Hash
59 domain = "; domain=" + value[:domain] if value[:domain]
60 path = "; path=" + value[:path] if value[:path]
61 # According to RFC 2109, we need dashes here.
62 # N.B.: cgi.rb uses spaces...
63 expires = "; expires=" + value[:expires].clone.gmtime.
64 strftime("%a, %d-%b-%Y %H:%M:%S GMT") if value[:expires]
65 secure = "; secure" if value[:secure]
66 httponly = "; HttpOnly" if value[:httponly]
67 value = value[:value]
68 end
69 value = [value] unless Array === value
70 cookie = Utils.escape(key) + "=" +
71 value.map { |v| Utils.escape v }.join("&") +
72 "#{domain}#{path}#{expires}#{secure}#{httponly}"
73
74 case self["Set-Cookie"]
75 when Array
76 self["Set-Cookie"] << cookie
77 when String
78 self["Set-Cookie"] = [self["Set-Cookie"], cookie]
79 when nil
80 self["Set-Cookie"] = cookie
81 end
82 end
83
84 def delete_cookie(key, value={})
85 unless Array === self["Set-Cookie"]
86 self["Set-Cookie"] = [self["Set-Cookie"]].compact
87 end
88
89 self["Set-Cookie"].reject! { |cookie|
90 cookie =~ /\A#{Utils.escape(key)}=/
91 }
92
93 set_cookie(key,
94 {:value => '', :path => nil, :domain => nil,
95 :expires => Time.at(0) }.merge(value))
96 end
97
98
99 def finish(&block)
100 @block = block
101
102 if [204, 304].include?(status.to_i)
103 header.delete "Content-Type"
104 [status.to_i, header.to_hash, []]
105 else
106 [status.to_i, header.to_hash, self]
107 end
108 end
109 alias to_a finish # For *response
110
111 def each(&callback)
112 @body.each(&callback)
113 @writer = callback
114 @block.call(self) if @block
115 end
116
117 # Append to body and update Content-Length.
118 #
119 # NOTE: Do not mix #write and direct #body access!
120 #
121 def write(str)
122 s = str.to_s
123 @length += s.size
124 @writer.call s
125
126 header["Content-Length"] = @length.to_s
127 str
128 end
129
130 def close
131 body.close if body.respond_to?(:close)
132 end
133
134 def empty?
135 @block == nil && @body.empty?
136 end
137
138 alias headers header
139
140 module Helpers
141 def invalid?; @status < 100 || @status >= 600; end
142
143 def informational?; @status >= 100 && @status < 200; end
144 def successful?; @status >= 200 && @status < 300; end
145 def redirection?; @status >= 300 && @status < 400; end
146 def client_error?; @status >= 400 && @status < 500; end
147 def server_error?; @status >= 500 && @status < 600; end
148
149 def ok?; @status == 200; end
150 def forbidden?; @status == 403; end
151 def not_found?; @status == 404; end
152
153 def redirect?; [301, 302, 303, 307].include? @status; end
154 def empty?; [201, 204, 304].include? @status; end
155
156 # Headers
157 attr_reader :headers, :original_headers
158
159 def include?(header)
160 !!headers[header]
161 end
162
163 def content_type
164 headers["Content-Type"]
165 end
166
167 def content_length
168 cl = headers["Content-Length"]
169 cl ? cl.to_i : cl
170 end
171
172 def location
173 headers["Location"]
174 end
175 end
176
177 include Helpers
178 end
179 end