response.rb 10.3 KB
Newer Older
1
require 'active_support/core_ext/module/attribute_accessors'
2
require 'active_support/deprecation'
3
require 'action_dispatch/http/filter_redirect'
4
require 'monitor'
5

6
module ActionDispatch # :nodoc:
7 8 9 10 11
  # Represents an HTTP response generated by a controller action. Use it to
  # retrieve the current state of the response, or customize the response. It can
  # either represent a real HTTP response (i.e. one that is meant to be sent
  # back to the web browser) or a TestResponse (i.e. one that is generated
  # from integration tests).
P
Pratik Naik 已提交
12
  #
13
  # \Response is mostly a Ruby on \Rails framework implementation detail, and
14 15 16 17
  # should never be used directly in controllers. Controllers should use the
  # methods defined in ActionController::Base instead. For example, if you want
  # to set the HTTP response's content MIME type, then use
  # ActionControllerBase#headers instead of Response#headers.
P
Pratik Naik 已提交
18
  #
19
  # Nevertheless, integration tests may want to inspect controller responses in
20
  # more detail, and that's when \Response can be useful for application
21
  # developers. Integration test methods such as
J
Joshua Peek 已提交
22 23
  # ActionDispatch::Integration::Session#get and
  # ActionDispatch::Integration::Session#post return objects of type
24
  # TestResponse (which are of course also of type \Response).
P
Pratik Naik 已提交
25
  #
26
  # For example, the following demo integration test prints the body of the
P
Pratik Naik 已提交
27 28
  # controller response to the console:
  #
J
Joshua Peek 已提交
29
  #  class DemoControllerTest < ActionDispatch::IntegrationTest
P
Pratik Naik 已提交
30 31
  #    def test_print_root_path_to_console
  #      get('/')
A
Alexey Vakhov 已提交
32
  #      puts response.body
P
Pratik Naik 已提交
33 34
  #    end
  #  end
35
  class Response
36 37 38 39
    # The request that the response is responding to.
    attr_accessor :request

    # The HTTP status code.
40
    attr_reader :status
41

42
    attr_writer :sending_file
P
Pratik Naik 已提交
43

44 45
    # Get and set headers for this response.
    attr_accessor :header
46

47
    alias_method :headers=, :header=
48 49 50
    alias_method :headers,  :header

    delegate :[], :[]=, :to => :@header
51
    delegate :each, :to => :@stream
52 53 54 55 56 57 58 59 60

    # Sets the HTTP response's content MIME type. For example, in the controller
    # you could write this:
    #
    #  response.content_type = "text/plain"
    #
    # If a character set has been defined for this response (see charset=) then
    # the character set information will also be included in the content type
    # information.
S
Santiago Pastorino 已提交
61
    attr_reader   :content_type
62

63 64 65 66
    # The charset of the response. HTML wants to know the encoding of the
    # content you're giving them, so we need to send that along.
    attr_accessor :charset

67 68 69
    CONTENT_TYPE = "Content-Type".freeze
    SET_COOKIE   = "Set-Cookie".freeze
    LOCATION     = "Location".freeze
70
    NO_CONTENT_CODES = [204, 304]
S
Santiago Pastorino 已提交
71

72
    cattr_accessor(:default_charset) { "utf-8" }
E
Egor Homakov 已提交
73
    cattr_accessor(:default_headers)
74

75
    include Rack::Response::Helpers
76
    include ActionDispatch::Http::FilterRedirect
77
    include ActionDispatch::Http::Cache::Response
78
    include MonitorMixin
79

80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
    class Buffer # :nodoc:
      def initialize(response, buf)
        @response = response
        @buf      = buf
        @closed   = false
      end

      def write(string)
        raise IOError, "closed stream" if closed?

        @response.commit!
        @buf.push string
      end

      def each(&block)
95 96 97 98
        @response.sending!
        x = @buf.each(&block)
        @response.sent!
        x
99 100
      end

101 102 103
      def abort
      end

104 105 106 107 108 109 110 111 112 113
      def close
        @response.commit!
        @closed = true
      end

      def closed?
        @closed
      end
    end

114
    # The underlying body, as a streamable object.
115 116
    attr_reader :stream

117
    def initialize(status = 200, header = {}, body = [])
118 119
      super()

120
      header = merge_default_headers(header, self.class.default_headers)
E
Egor Homakov 已提交
121

122
      self.body, self.header, self.status = body, header, status
123

124
      @sending_file = false
125 126 127
      @blank        = false
      @cv           = new_cond
      @committed    = false
128 129
      @sending      = false
      @sent         = false
130 131
      @content_type = nil
      @charset      = nil
J
Joshua Peek 已提交
132

133
      if content_type = self[CONTENT_TYPE]
134 135
        type, charset = content_type.split(/;\s*charset=/)
        @content_type = Mime::Type.lookup(type)
136
        @charset = charset || self.class.default_charset
137
      end
138

139 140 141 142
      prepare_cache_control!

      yield self if block_given?
    end
143

144 145 146 147 148 149
    def await_commit
      synchronize do
        @cv.wait_until { @committed }
      end
    end

150 151 152 153
    def await_sent
      synchronize { @cv.wait_until { @sent } }
    end

154 155
    def commit!
      synchronize do
156
        before_committed
157 158 159 160 161
        @committed = true
        @cv.broadcast
      end
    end

162 163 164 165 166 167
    def sending!
      synchronize do
        before_sending
        @sending = true
        @cv.broadcast
      end
168 169
    end

170 171 172 173 174 175 176 177 178 179 180
    def sent!
      synchronize do
        @sent = true
        @cv.broadcast
      end
    end

    def sending?;   synchronize { @sending };   end
    def committed?; synchronize { @committed }; end
    def sent?;      synchronize { @sent };      end

181
    # Sets the HTTP status code.
182
    def status=(status)
183
      @status = Rack::Utils.status_code(status)
184 185
    end

186
    # Sets the HTTP content type.
S
Santiago Pastorino 已提交
187 188 189 190
    def content_type=(content_type)
      @content_type = content_type.to_s
    end

191
    # The response code of the request.
192
    def response_code
193
      @status
194 195
    end

196
    # Returns a string to ensure compatibility with <tt>Net::HTTPResponse</tt>.
197
    def code
198
      @status.to_s
199 200
    end

201 202
    # Returns the corresponding message for the current HTTP status code:
    #
203 204 205 206 207
    #   response.status = 200
    #   response.message # => "OK"
    #
    #   response.status = 404
    #   response.message # => "Not Found"
208
    #
209
    def message
210
      Rack::Utils::HTTP_STATUS_CODES[@status]
211
    end
212
    alias_method :status_message, :message
213

214
    # Returns the content of the response as a string. This contains the contents
215
    # of any calls to <tt>render</tt>.
216
    def body
217 218 219
      strings = []
      each { |part| strings << part.to_s }
      strings.join
220
    end
221

Y
Yehuda Katz 已提交
222 223
    EMPTY = " "

224
    # Allows you to manually set or override the response body.
225
    def body=(body)
Y
Yehuda Katz 已提交
226
      @blank = true if body == EMPTY
227

228 229 230
      if body.respond_to?(:to_path)
        @stream = body
      else
231 232 233
        synchronize do
          @stream = build_buffer self, munge_body_object(body)
        end
234
      end
235 236 237
    end

    def body_parts
238 239 240
      parts = []
      @stream.each { |x| parts << x }
      parts
D
Initial  
David Heinemeier Hansson 已提交
241 242
    end

243 244 245 246 247 248 249 250
    def set_cookie(key, value)
      ::Rack::Utils.set_cookie_header!(header, key, value)
    end

    def delete_cookie(key, value={})
      ::Rack::Utils.delete_cookie_header!(header, key, value)
    end

251
    # The location header we'll be responding with.
252
    def location
253
      headers[LOCATION]
254 255
    end
    alias_method :redirect_url, :location
256

257
    # Sets the location header we'll be responding with.
258
    def location=(url)
259
      headers[LOCATION] = url
260
    end
261

262
    def close
263
      stream.close if stream.respond_to?(:close)
264 265
    end

266 267 268 269 270 271 272 273 274 275 276
    def abort
      if stream.respond_to?(:abort)
        stream.abort
      elsif stream.respond_to?(:close)
        # `stream.close` should really be reserved for a close from the
        # other direction, but we must fall back to it for
        # compatibility.
        stream.close
      end
    end

277
    # Turns the Response into a Rack-compatible array of the status, headers,
278 279 280
    # and body. Allows explict splatting:
    #
    #   status, headers, body = *response
281
    def to_a
282
      rack_response @status, @header.to_hash
283
    end
284
    alias prepare! to_a
285 286 287 288 289 290 291 292 293

    # Be super clear that a response object is not an Array. Defining this
    # would make implicit splatting work, but it also makes adding responses
    # as arrays work, and "flattening" responses, cascading to the rack body!
    # Not sensible behavior.
    def to_ary
      ActiveSupport::Deprecation.warn 'ActionDispatch::Response#to_ary no longer performs implicit conversion to an Array. Please use response.to_a instead, or a splat like `status, headers, body = *response`'
      to_a
    end
294

295 296 297 298 299
    # Returns the response cookies, converted to a Hash of (name => value) pairs
    #
    #   assert_equal 'AuthorOfNewPage', r.cookies['author']
    def cookies
      cookies = {}
300
      if header = self[SET_COOKIE]
301 302 303 304 305 306 307
        header = header.split("\n") if header.respond_to?(:to_str)
        header.each do |cookie|
          if pair = cookie.split(';').first
            key, value = pair.split("=").map { |v| Rack::Utils.unescape(v) }
            cookies[key] = value
          end
        end
308 309 310 311
      end
      cookies
    end

312
    def _status_code #:nodoc:
313 314
      @status
    end
315
  private
J
Joshua Peek 已提交
316

317 318 319 320
    def before_committed
    end

    def before_sending
321 322
    end

323 324 325 326 327 328
    def merge_default_headers(original, default)
      return original unless default.respond_to?(:merge)

      default.merge(original)
    end

329 330 331 332 333 334 335 336
    def build_buffer(response, body)
      Buffer.new response, body
    end

    def munge_body_object(body)
      body.respond_to?(:each) ? body : [body]
    end

337
    def assign_default_content_type_and_charset!(headers)
338
      return if headers[CONTENT_TYPE].present?
J
Joshua Peek 已提交
339

340
      @content_type ||= Mime::HTML
341
      @charset      ||= self.class.default_charset unless @charset == false
J
Joshua Peek 已提交
342

343
      type = @content_type.to_s.dup
344
      type << "; charset=#{@charset}" if append_charset?
J
Joshua Peek 已提交
345

346 347
      headers[CONTENT_TYPE] = type
    end
348

349 350 351 352
    def append_charset?
      !@sending_file && @charset != false
    end

353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382
    class RackBody
      def initialize(response)
        @response = response
      end

      def each(*args, &block)
        @response.each(*args, &block)
      end

      def close
        # Rack "close" maps to Response#abort, and *not* Response#close
        # (which is used when the controller's finished writing)
        @response.abort
      end

      def body
        @response.body
      end

      def respond_to?(method, include_private = false)
        if method.to_s == 'to_path'
          @response.stream.respond_to?(method)
        else
          super
        end
      end

      def to_path
        @response.stream.to_path
      end
383 384 385 386

      def to_ary
        nil
      end
387 388
    end

389
    def rack_response(status, header)
390
      assign_default_content_type_and_charset!(header)
391 392 393 394
      handle_conditional_get!

      header[SET_COOKIE] = header[SET_COOKIE].join("\n") if header[SET_COOKIE].respond_to?(:join)

395
      if NO_CONTENT_CODES.include?(@status)
396 397 398
        header.delete CONTENT_TYPE
        [status, header, []]
      else
399
        [status, header, RackBody.new(self)]
400 401
      end
    end
D
Initial  
David Heinemeier Hansson 已提交
402
  end
403
end