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

5
module ActionDispatch # :nodoc:
6 7 8 9 10
  # 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 已提交
11
  #
12
  # \Response is mostly a Ruby on \Rails framework implementation detail, and
13 14 15 16
  # 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 已提交
17
  #
18
  # Nevertheless, integration tests may want to inspect controller responses in
19
  # more detail, and that's when \Response can be useful for application
20
  # developers. Integration test methods such as
J
Joshua Peek 已提交
21 22
  # ActionDispatch::Integration::Session#get and
  # ActionDispatch::Integration::Session#post return objects of type
23
  # TestResponse (which are of course also of type \Response).
P
Pratik Naik 已提交
24
  #
25
  # For example, the following demo integration test prints the body of the
P
Pratik Naik 已提交
26 27
  # controller response to the console:
  #
J
Joshua Peek 已提交
28
  #  class DemoControllerTest < ActionDispatch::IntegrationTest
P
Pratik Naik 已提交
29 30
  #    def test_print_root_path_to_console
  #      get('/')
A
Alexey Vakhov 已提交
31
  #      puts response.body
P
Pratik Naik 已提交
32 33
  #    end
  #  end
34
  class Response
35 36 37 38 39 40 41
    class Header < DelegateClass(Hash) # :nodoc:
      def initialize(response, header)
        @response = response
        super(header)
      end

      def []=(k,v)
42
        if @response.sending? || @response.sent?
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
          raise ActionDispatch::IllegalStateError, 'header already sent'
        end

        super
      end

      def merge(other)
        self.class.new @response, __getobj__.merge(other)
      end

      def to_hash
        __getobj__.dup
      end
    end

58 59 60 61
    # The request that the response is responding to.
    attr_accessor :request

    # The HTTP status code.
62
    attr_reader :status
63

64
    # Get headers for this response.
65
    attr_reader :header
66

67 68 69
    alias_method :headers,  :header

    delegate :[], :[]=, :to => :@header
70
    delegate :each, :to => :@stream
71

72 73 74
    CONTENT_TYPE = "Content-Type".freeze
    SET_COOKIE   = "Set-Cookie".freeze
    LOCATION     = "Location".freeze
75
    NO_CONTENT_CODES = [100, 101, 102, 204, 205, 304]
S
Santiago Pastorino 已提交
76

77
    cattr_accessor(:default_charset) { "utf-8" }
E
Egor Homakov 已提交
78
    cattr_accessor(:default_headers)
79

80
    include Rack::Response::Helpers
81
    include ActionDispatch::Http::FilterRedirect
82
    include ActionDispatch::Http::Cache::Response
83
    include MonitorMixin
84

85 86 87 88 89
    class Buffer # :nodoc:
      def initialize(response, buf)
        @response = response
        @buf      = buf
        @closed   = false
90 91 92 93 94 95 96 97 98
        @str_body = nil
      end

      def body
        @str_body ||= begin
                        buf = ''
                        each { |chunk| buf << chunk }
                        buf
                      end
99 100 101 102 103
      end

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

104
        @str_body = nil
105 106 107 108 109
        @response.commit!
        @buf.push string
      end

      def each(&block)
110 111 112 113
        @response.sending!
        x = @buf.each(&block)
        @response.sent!
        x
114 115
      end

116 117 118
      def abort
      end

119 120 121 122 123 124 125 126 127 128
      def close
        @response.commit!
        @closed = true
      end

      def closed?
        @closed
      end
    end

129 130 131 132 133 134 135 136 137
    def self.create(status = 200, header = {}, body = [], default_headers: self.default_headers)
      header = merge_default_headers(header, default_headers)
      new status, header, body
    end

    def self.merge_default_headers(original, default)
      default.respond_to?(:merge) ? default.merge(original) : original
    end

138
    # The underlying body, as a streamable object.
139 140
    attr_reader :stream

141
    def initialize(status = 200, header = {}, body = [])
142 143
      super()

144
      @header = Header.new(self, header)
E
Egor Homakov 已提交
145

146
      self.body, self.status = body, status
147

148 149 150
      @blank        = false
      @cv           = new_cond
      @committed    = false
151 152
      @sending      = false
      @sent         = false
J
Joshua Peek 已提交
153

154 155 156 157
      prepare_cache_control!

      yield self if block_given?
    end
158

159
    def has_header?(key);   headers.key? key;   end
160 161 162 163
    def get_header(key);    headers[key];       end
    def set_header(key, v); headers[key] = v;   end
    def delete_header(key); headers.delete key; end

164 165 166 167 168 169
    def await_commit
      synchronize do
        @cv.wait_until { @committed }
      end
    end

170 171 172 173
    def await_sent
      synchronize { @cv.wait_until { @sent } }
    end

174 175
    def commit!
      synchronize do
176
        before_committed
177 178 179 180 181
        @committed = true
        @cv.broadcast
      end
    end

182 183 184 185 186 187
    def sending!
      synchronize do
        before_sending
        @sending = true
        @cv.broadcast
      end
188 189
    end

190 191 192 193 194 195 196 197 198 199 200
    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

201
    # Sets the HTTP status code.
202
    def status=(status)
203
      @status = Rack::Utils.status_code(status)
204 205
    end

206
    # Sets the HTTP content type.
S
Santiago Pastorino 已提交
207
    def content_type=(content_type)
208
      header_info = parse_content_type
A
Aaron Patterson 已提交
209
      set_content_type content_type.to_s, header_info.charset || self.class.default_charset
210 211
    end

A
Aaron Patterson 已提交
212 213 214 215 216 217 218 219
    # 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.
220

A
Aaron Patterson 已提交
221
    def content_type
222
      parse_content_type.mime_type
223 224 225 226 227 228
    end

    def sending_file=(v)
      if true == v
        self.charset = false
      end
S
Santiago Pastorino 已提交
229 230
    end

231 232 233 234
    # Sets the HTTP character set. In case of nil parameter
    # it sets the charset to utf-8.
    #
    #   response.charset = 'utf-16' # => 'utf-16'
235
    #   response.charset = nil      # => 'utf-8'
236
    def charset=(charset)
237
      header_info = parse_content_type
238
      if false == charset
239
        set_header CONTENT_TYPE, header_info.mime_type
240
      else
241
        content_type = header_info.mime_type
242
        set_content_type content_type, charset || self.class.default_charset
243
      end
244 245
    end

A
Aaron Patterson 已提交
246 247 248
    # 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.
    def charset
249
      header_info = parse_content_type
A
Aaron Patterson 已提交
250 251 252
      header_info.charset || self.class.default_charset
    end

253
    # The response code of the request.
254
    def response_code
255
      @status
256 257
    end

258
    # Returns a string to ensure compatibility with <tt>Net::HTTPResponse</tt>.
259
    def code
260
      @status.to_s
261 262
    end

263 264
    # Returns the corresponding message for the current HTTP status code:
    #
265 266 267 268 269
    #   response.status = 200
    #   response.message # => "OK"
    #
    #   response.status = 404
    #   response.message # => "Not Found"
270
    #
271
    def message
272
      Rack::Utils::HTTP_STATUS_CODES[@status]
273
    end
274
    alias_method :status_message, :message
275

276
    # Returns the content of the response as a string. This contains the contents
277
    # of any calls to <tt>render</tt>.
278
    def body
279
      @stream.body
280
    end
281

282 283 284 285
    def write(string)
      @stream.write string
    end

Y
Yehuda Katz 已提交
286 287
    EMPTY = " "

288
    # Allows you to manually set or override the response body.
289
    def body=(body)
Y
Yehuda Katz 已提交
290
      @blank = true if body == EMPTY
291

292 293 294
      if body.respond_to?(:to_path)
        @stream = body
      else
295 296 297
        synchronize do
          @stream = build_buffer self, munge_body_object(body)
        end
298
      end
299 300
    end

301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334
    # Avoid having to pass an open file handle as the response body.
    # Rack::Sendfile will usually intercept the response and uses
    # the path directly, so there is no reason to open the file.
    class FileBody #:nodoc:
      attr_reader :to_path

      def initialize(path)
        @to_path = path
      end

      def body
        File.binread(to_path)
      end

      # Stream the file's contents if Rack::Sendfile isn't present.
      def each
        File.open(to_path, 'rb') do |file|
          while chunk = file.read(16384)
            yield chunk
          end
        end
      end
    end

    # Send the file stored at +path+ as the response body.
    def send_file(path)
      commit!
      @stream = FileBody.new(path)
    end

    def reset_body!
      @stream = build_buffer(self, [])
    end

335
    def body_parts
336 337 338
      parts = []
      @stream.each { |x| parts << x }
      parts
D
Initial  
David Heinemeier Hansson 已提交
339 340
    end

341
    # The location header we'll be responding with.
342
    alias_method :redirect_url, :location
343

344
    def close
345
      stream.close if stream.respond_to?(:close)
346 347
    end

348 349 350 351 352 353 354 355 356 357 358
    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

359
    # Turns the Response into a Rack-compatible array of the status, headers,
360
    # and body. Allows explicit splatting:
361 362
    #
    #   status, headers, body = *response
363
    def to_a
364
      commit!
365
      rack_response @status, @header.to_hash
366
    end
367
    alias prepare! to_a
368

369 370 371 372 373
    # Returns the response cookies, converted to a Hash of (name => value) pairs
    #
    #   assert_equal 'AuthorOfNewPage', r.cookies['author']
    def cookies
      cookies = {}
374
      if header = get_header(SET_COOKIE)
375 376 377 378 379 380 381
        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
382 383 384 385
      end
      cookies
    end

386
  private
J
Joshua Peek 已提交
387

A
Aaron Patterson 已提交
388
    ContentTypeHeader = Struct.new :mime_type, :charset
389
    NullContentTypeHeader = ContentTypeHeader.new nil, nil
A
Aaron Patterson 已提交
390

391 392
    def parse_content_type
      content_type = get_header CONTENT_TYPE
A
Aaron Patterson 已提交
393 394
      if content_type
        type, charset = content_type.split(/;\s*charset=/)
395
        type = nil if type.empty?
396
        ContentTypeHeader.new(type, charset)
A
Aaron Patterson 已提交
397
      else
398
        NullContentTypeHeader
A
Aaron Patterson 已提交
399 400 401 402
      end
    end

    def set_content_type(content_type, charset)
403
      type = (content_type || '').dup
A
Aaron Patterson 已提交
404 405 406 407
      type << "; charset=#{charset}" if charset
      set_header CONTENT_TYPE, type
    end

408
    def before_committed
409 410 411
      return if committed?
      assign_default_content_type_and_charset!
      handle_conditional_get!
412
      handle_no_content!
413 414 415
    end

    def before_sending
416 417
    end

418 419 420 421 422 423 424 425
    def build_buffer(response, body)
      Buffer.new response, body
    end

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

426
    def assign_default_content_type_and_charset!
427
      return if content_type
J
Joshua Peek 已提交
428

429
      ct = parse_content_type
430
      set_content_type(ct.mime_type || Mime::Type[:HTML].to_s,
A
Aaron Patterson 已提交
431
                       ct.charset || self.class.default_charset)
432
    end
433

434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463
    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
464 465 466 467

      def to_ary
        nil
      end
468 469
    end

470 471 472 473 474 475 476
    def handle_no_content!
      if NO_CONTENT_CODES.include?(@status)
        @header.delete CONTENT_TYPE
        @header.delete 'Content-Length'
      end
    end

477
    def rack_response(status, header)
478
      if NO_CONTENT_CODES.include?(status)
479 480
        [status, header, []]
      else
481
        [status, header, RackBody.new(self)]
482 483
      end
    end
D
Initial  
David Heinemeier Hansson 已提交
484
  end
485
end