response.rb 12.6 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 82 83 84
    # Aliasing these off because AD::Http::Cache::Response defines them
    alias :_cache_control :cache_control
    alias :_cache_control= :cache_control=

85
    include ActionDispatch::Http::FilterRedirect
86
    include ActionDispatch::Http::Cache::Response
87
    include MonitorMixin
88

89 90 91 92 93
    class Buffer # :nodoc:
      def initialize(response, buf)
        @response = response
        @buf      = buf
        @closed   = false
94 95 96 97 98 99 100 101 102
        @str_body = nil
      end

      def body
        @str_body ||= begin
                        buf = ''
                        each { |chunk| buf << chunk }
                        buf
                      end
103 104 105 106 107
      end

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

108
        @str_body = nil
109 110 111 112 113
        @response.commit!
        @buf.push string
      end

      def each(&block)
114 115 116 117
        @response.sending!
        x = @buf.each(&block)
        @response.sent!
        x
118 119
      end

120 121 122
      def abort
      end

123 124 125 126 127 128 129 130 131 132
      def close
        @response.commit!
        @closed = true
      end

      def closed?
        @closed
      end
    end

133 134 135 136 137 138 139 140 141
    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

142
    # The underlying body, as a streamable object.
143 144
    attr_reader :stream

145
    def initialize(status = 200, header = {}, body = [])
146 147
      super()

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

150
      self.body, self.status = body, status
151

152 153
      @cv           = new_cond
      @committed    = false
154 155
      @sending      = false
      @sent         = false
J
Joshua Peek 已提交
156

157 158 159 160
      prepare_cache_control!

      yield self if block_given?
    end
161

162
    def has_header?(key);   headers.key? key;   end
163 164 165 166
    def get_header(key);    headers[key];       end
    def set_header(key, v); headers[key] = v;   end
    def delete_header(key); headers.delete key; end

167 168 169 170 171 172
    def await_commit
      synchronize do
        @cv.wait_until { @committed }
      end
    end

173 174 175 176
    def await_sent
      synchronize { @cv.wait_until { @sent } }
    end

177 178
    def commit!
      synchronize do
179
        before_committed
180 181 182 183 184
        @committed = true
        @cv.broadcast
      end
    end

185 186 187 188 189 190
    def sending!
      synchronize do
        before_sending
        @sending = true
        @cv.broadcast
      end
191 192
    end

193 194 195 196 197 198 199 200 201 202 203
    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

204
    # Sets the HTTP status code.
205
    def status=(status)
206
      @status = Rack::Utils.status_code(status)
207 208
    end

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

A
Aaron Patterson 已提交
215 216 217 218 219 220 221 222
    # 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.
223

A
Aaron Patterson 已提交
224
    def content_type
225
      parse_content_type.mime_type
226 227 228 229 230 231
    end

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

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

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

256
    # The response code of the request.
257
    def response_code
258
      @status
259 260
    end

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

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

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

285 286 287 288
    def write(string)
      @stream.write string
    end

289
    # Allows you to manually set or override the response body.
290
    def body=(body)
291 292 293
      if body.respond_to?(:to_path)
        @stream = body
      else
294 295 296
        synchronize do
          @stream = build_buffer self, munge_body_object(body)
        end
297
      end
298 299
    end

300 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
    # 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

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

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

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

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

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

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

385
  private
J
Joshua Peek 已提交
386

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

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

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

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

    def before_sending
415 416 417 418 419 420 421
      # Normally we've already committed by now, but it's possible
      # (e.g., if the controller action tries to read back its own
      # response) to get here before that. In that case, we must force
      # an "early" commit: we're about to freeze the headers, so this is
      # our last chance.
      commit! unless committed?

E
eileencodes 已提交
422
      headers.freeze
423
      request.commit_cookie_jar! unless committed?
424 425
    end

426 427 428 429 430 431 432 433
    def build_buffer(response, body)
      Buffer.new response, body
    end

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

434
    def assign_default_content_type_and_charset!
435
      return if content_type
J
Joshua Peek 已提交
436

437
      ct = parse_content_type
438
      set_content_type(ct.mime_type || Mime[:html].to_s,
A
Aaron Patterson 已提交
439
                       ct.charset || self.class.default_charset)
440
    end
441

442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471
    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
472 473 474 475

      def to_ary
        nil
      end
476 477
    end

478 479 480 481 482 483 484
    def handle_no_content!
      if NO_CONTENT_CODES.include?(@status)
        @header.delete CONTENT_TYPE
        @header.delete 'Content-Length'
      end
    end

485
    def rack_response(status, header)
486
      if NO_CONTENT_CODES.include?(status)
487 488
        [status, header, []]
      else
489
        [status, header, RackBody.new(self)]
490 491
      end
    end
D
Initial  
David Heinemeier Hansson 已提交
492
  end
493
end