~michaelforrest/use-case-mapper/trunk

« back to all changes in this revision

Viewing changes to vendor/rails/actionpack/lib/action_controller/response.rb

  • Committer: Michael Forrest
  • Date: 2010-10-15 16:28:50 UTC
  • Revision ID: michael.forrest@canonical.com-20101015162850-tj2vchanv0kr0dun
refrozeĀ gems

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
require 'digest/md5'
 
2
 
 
3
module ActionController # :nodoc:
 
4
  # Represents an HTTP response generated by a controller action. One can use
 
5
  # an ActionController::Response object to retrieve the current state
 
6
  # of the response, or customize the response. An Response object can
 
7
  # either represent a "real" HTTP response (i.e. one that is meant to be sent
 
8
  # back to the web browser) or a test response (i.e. one that is generated
 
9
  # from integration tests). See CgiResponse and TestResponse, respectively.
 
10
  #
 
11
  # Response is mostly a Ruby on Rails framework implement detail, and
 
12
  # should never be used directly in controllers. Controllers should use the
 
13
  # methods defined in ActionController::Base instead. For example, if you want
 
14
  # to set the HTTP response's content MIME type, then use
 
15
  # ActionControllerBase#headers instead of Response#headers.
 
16
  #
 
17
  # Nevertheless, integration tests may want to inspect controller responses in
 
18
  # more detail, and that's when Response can be useful for application
 
19
  # developers. Integration test methods such as
 
20
  # ActionController::Integration::Session#get and
 
21
  # ActionController::Integration::Session#post return objects of type
 
22
  # TestResponse (which are of course also of type Response).
 
23
  #
 
24
  # For example, the following demo integration "test" prints the body of the
 
25
  # controller response to the console:
 
26
  #
 
27
  #  class DemoControllerTest < ActionController::IntegrationTest
 
28
  #    def test_print_root_path_to_console
 
29
  #      get('/')
 
30
  #      puts @response.body
 
31
  #    end
 
32
  #  end
 
33
  class Response < Rack::Response
 
34
    DEFAULT_HEADERS = { "Cache-Control" => "no-cache" }
 
35
    attr_accessor :request
 
36
 
 
37
    attr_accessor :session, :assigns, :template, :layout
 
38
    attr_accessor :redirected_to, :redirected_to_method_params
 
39
 
 
40
    delegate :default_charset, :to => 'ActionController::Base'
 
41
 
 
42
    def initialize
 
43
      @status = 200
 
44
      @header = Rack::Utils::HeaderHash.new(DEFAULT_HEADERS)
 
45
 
 
46
      @writer = lambda { |x| @body << x }
 
47
      @block = nil
 
48
 
 
49
      @body = "",
 
50
      @session = []
 
51
      @assigns = []
 
52
    end
 
53
 
 
54
    def location; headers['Location'] end
 
55
    def location=(url) headers['Location'] = url end
 
56
 
 
57
 
 
58
    # Sets the HTTP response's content MIME type. For example, in the controller
 
59
    # you could write this:
 
60
    #
 
61
    #  response.content_type = "text/plain"
 
62
    #
 
63
    # If a character set has been defined for this response (see charset=) then
 
64
    # the character set information will also be included in the content type
 
65
    # information.
 
66
    def content_type=(mime_type)
 
67
      self.headers["Content-Type"] =
 
68
        if mime_type =~ /charset/ || (c = charset).nil?
 
69
          mime_type.to_s
 
70
        else
 
71
          "#{mime_type}; charset=#{c}"
 
72
        end
 
73
    end
 
74
 
 
75
    # Returns the response's content MIME type, or nil if content type has been set.
 
76
    def content_type
 
77
      content_type = String(headers["Content-Type"] || headers["type"]).split(";")[0]
 
78
      content_type.blank? ? nil : content_type
 
79
    end
 
80
 
 
81
    # Set the charset of the Content-Type header. Set to nil to remove it.
 
82
    # If no content type is set, it defaults to HTML.
 
83
    def charset=(charset)
 
84
      headers["Content-Type"] =
 
85
        if charset
 
86
          "#{content_type || Mime::HTML}; charset=#{charset}"
 
87
        else
 
88
          content_type || Mime::HTML.to_s
 
89
        end
 
90
    end
 
91
 
 
92
    def charset
 
93
      charset = String(headers["Content-Type"] || headers["type"]).split(";")[1]
 
94
      charset.blank? ? nil : charset.strip.split("=")[1]
 
95
    end
 
96
 
 
97
    def last_modified
 
98
      if last = headers['Last-Modified']
 
99
        Time.httpdate(last)
 
100
      end
 
101
    end
 
102
 
 
103
    def last_modified?
 
104
      headers.include?('Last-Modified')
 
105
    end
 
106
 
 
107
    def last_modified=(utc_time)
 
108
      headers['Last-Modified'] = utc_time.httpdate
 
109
    end
 
110
 
 
111
    def etag
 
112
      headers['ETag']
 
113
    end
 
114
 
 
115
    def etag?
 
116
      headers.include?('ETag')
 
117
    end
 
118
 
 
119
    def etag=(etag)
 
120
      if etag.blank?
 
121
        headers.delete('ETag')
 
122
      else
 
123
        headers['ETag'] = %("#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(etag))}")
 
124
      end
 
125
    end
 
126
 
 
127
    def redirect(url, status)
 
128
      self.status = status
 
129
      self.location = url.gsub(/[\r\n]/, '')
 
130
      self.body = "<html><body>You are being <a href=\"#{CGI.escapeHTML(url)}\">redirected</a>.</body></html>"
 
131
    end
 
132
 
 
133
    def sending_file?
 
134
      headers["Content-Transfer-Encoding"] == "binary"
 
135
    end
 
136
 
 
137
    def assign_default_content_type_and_charset!
 
138
      self.content_type ||= Mime::HTML
 
139
      self.charset ||= default_charset unless sending_file?
 
140
    end
 
141
 
 
142
    def prepare!
 
143
      assign_default_content_type_and_charset!
 
144
      handle_conditional_get!
 
145
      set_content_length!
 
146
      convert_content_type!
 
147
      convert_language!
 
148
      convert_cookies!
 
149
    end
 
150
 
 
151
    def each(&callback)
 
152
      if @body.respond_to?(:call)
 
153
        @writer = lambda { |x| callback.call(x) }
 
154
        @body.call(self, self)
 
155
      elsif @body.respond_to?(:to_str)
 
156
        yield @body
 
157
      else
 
158
        @body.each(&callback)
 
159
      end
 
160
 
 
161
      @writer = callback
 
162
      @block.call(self) if @block
 
163
    end
 
164
 
 
165
    def write(str)
 
166
      @writer.call str.to_s
 
167
      str
 
168
    end
 
169
 
 
170
    def flush #:nodoc:
 
171
      ActiveSupport::Deprecation.warn(
 
172
        'Calling output.flush is no longer needed for streaming output ' +
 
173
        'because ActionController::Response automatically handles it', caller)
 
174
    end
 
175
 
 
176
    def set_cookie(key, value)
 
177
      if value.has_key?(:http_only)
 
178
        ActiveSupport::Deprecation.warn(
 
179
          "The :http_only option in ActionController::Response#set_cookie " +
 
180
          "has been renamed. Please use :httponly instead.", caller)
 
181
        value[:httponly] ||= value.delete(:http_only)
 
182
      end
 
183
 
 
184
      super(key, value)
 
185
    end
 
186
 
 
187
    private
 
188
      def handle_conditional_get!
 
189
        if etag? || last_modified?
 
190
          set_conditional_cache_control!
 
191
        elsif nonempty_ok_response?
 
192
          self.etag = body
 
193
 
 
194
          if request && request.etag_matches?(etag)
 
195
            self.status = '304 Not Modified'
 
196
            self.body = ''
 
197
          end
 
198
 
 
199
          set_conditional_cache_control!
 
200
        end
 
201
      end
 
202
 
 
203
      def nonempty_ok_response?
 
204
        ok = !status || status.to_s[0..2] == '200'
 
205
        ok && body.is_a?(String) && !body.empty?
 
206
      end
 
207
 
 
208
      def set_conditional_cache_control!
 
209
        if headers['Cache-Control'] == DEFAULT_HEADERS['Cache-Control']
 
210
          headers['Cache-Control'] = 'private, max-age=0, must-revalidate'
 
211
        end
 
212
      end
 
213
 
 
214
      def convert_content_type!
 
215
        headers['Content-Type'] ||= "text/html"
 
216
        headers['Content-Type'] += "; charset=" + headers.delete('charset') if headers['charset']
 
217
      end
 
218
 
 
219
      # Don't set the Content-Length for block-based bodies as that would mean
 
220
      # reading it all into memory. Not nice for, say, a 2GB streaming file.
 
221
      def set_content_length!
 
222
        if status && status.to_s[0..2] == '204'
 
223
          headers.delete('Content-Length')
 
224
        elsif length = headers['Content-Length']
 
225
          headers['Content-Length'] = length.to_s
 
226
        elsif !body.respond_to?(:call) && (!status || status.to_s[0..2] != '304')
 
227
          headers["Content-Length"] = (body.respond_to?(:bytesize) ? body.bytesize : body.size).to_s
 
228
        end
 
229
      end
 
230
 
 
231
      def convert_language!
 
232
        headers["Content-Language"] = headers.delete("language") if headers["language"]
 
233
      end
 
234
 
 
235
      def convert_cookies!
 
236
        headers['Set-Cookie'] = Array(headers['Set-Cookie']).compact
 
237
      end
 
238
  end
 
239
end