response.rb 9.8 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
    # The request that the response is responding to.
    attr_accessor :request

    # The HTTP status code.
39
    attr_reader :status
40

41
    attr_writer :sending_file
P
Pratik Naik 已提交
42

43
    # Get headers for this response.
44
    attr_reader :header
45

46 47 48
    alias_method :headers,  :header

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

    # 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 已提交
59
    attr_reader   :content_type
60

61 62
    # 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.
63
    attr_reader :charset
64

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

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

73
    include Rack::Response::Helpers
74
    include ActionDispatch::Http::FilterRedirect
75
    include ActionDispatch::Http::Cache::Response
76
    include MonitorMixin
77

78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
    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)
93 94 95 96
        @response.sending!
        x = @buf.each(&block)
        @response.sent!
        x
97 98
      end

99 100 101
      def abort
      end

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

      def closed?
        @closed
      end
    end

112
    # The underlying body, as a streamable object.
113 114
    attr_reader :stream

115
    def initialize(status = 200, header = {}, body = [], default_headers: self.class.default_headers)
116 117
      super()

118
      header = merge_default_headers(header, default_headers)
119
      @header = header
E
Egor Homakov 已提交
120

121
      self.body, self.status = body, status
122

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

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

138 139 140 141
      prepare_cache_control!

      yield self if block_given?
    end
142

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

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

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

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

169 170 171 172 173 174 175 176 177 178 179
    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

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

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

190 191 192 193 194 195 196 197 198
    # Sets the HTTP character set.
    def charset=(charset)
      if nil == charset
        @charset = self.class.default_charset
      else
        @charset = charset
      end
    end

199
    # The response code of the request.
200
    def response_code
201
      @status
202 203
    end

204
    # Returns a string to ensure compatibility with <tt>Net::HTTPResponse</tt>.
205
    def code
206
      @status.to_s
207 208
    end

209 210
    # Returns the corresponding message for the current HTTP status code:
    #
211 212 213 214 215
    #   response.status = 200
    #   response.message # => "OK"
    #
    #   response.status = 404
    #   response.message # => "Not Found"
216
    #
217
    def message
218
      Rack::Utils::HTTP_STATUS_CODES[@status]
219
    end
220
    alias_method :status_message, :message
221

222
    # Returns the content of the response as a string. This contains the contents
223
    # of any calls to <tt>render</tt>.
224
    def body
225 226 227
      strings = []
      each { |part| strings << part.to_s }
      strings.join
228
    end
229

Y
Yehuda Katz 已提交
230 231
    EMPTY = " "

232
    # Allows you to manually set or override the response body.
233
    def body=(body)
Y
Yehuda Katz 已提交
234
      @blank = true if body == EMPTY
235

236 237 238
      if body.respond_to?(:to_path)
        @stream = body
      else
239 240 241
        synchronize do
          @stream = build_buffer self, munge_body_object(body)
        end
242
      end
243 244 245
    end

    def body_parts
246 247 248
      parts = []
      @stream.each { |x| parts << x }
      parts
D
Initial  
David Heinemeier Hansson 已提交
249 250
    end

251 252 253 254 255 256 257 258
    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

259
    # The location header we'll be responding with.
260
    def location
261
      headers[LOCATION]
262 263
    end
    alias_method :redirect_url, :location
264

265
    # Sets the location header we'll be responding with.
266
    def location=(url)
267
      headers[LOCATION] = url
268
    end
269

270
    def close
271
      stream.close if stream.respond_to?(:close)
272 273
    end

274 275 276 277 278 279 280 281 282 283 284
    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

285
    # Turns the Response into a Rack-compatible array of the status, headers,
286
    # and body. Allows explicit splatting:
287 288
    #
    #   status, headers, body = *response
289
    def to_a
290
      commit!
291
      rack_response @status, @header.to_hash
292
    end
293
    alias prepare! to_a
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
  private
J
Joshua Peek 已提交
313

314
    def before_committed
315 316 317
      return if committed?
      assign_default_content_type_and_charset!
      handle_conditional_get!
318 319 320
    end

    def before_sending
321 322
    end

323
    def merge_default_headers(original, default)
324
      default.respond_to?(:merge) ? default.merge(original) : original
325 326
    end

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

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

335 336
    def assign_default_content_type_and_charset!
      return if self[CONTENT_TYPE].present?
J
Joshua Peek 已提交
337

338
      @content_type ||= Mime::HTML
J
Joshua Peek 已提交
339

340
      type = @content_type.to_s.dup
341
      type << "; charset=#{charset}" if append_charset?
J
Joshua Peek 已提交
342

343
      self[CONTENT_TYPE] = type
344
    end
345

346 347 348 349
    def append_charset?
      !@sending_file && @charset != false
    end

350 351 352 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
    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
380 381 382 383

      def to_ary
        nil
      end
384 385
    end

386 387 388
    def rack_response(status, header)
      header[SET_COOKIE] = header[SET_COOKIE].join("\n") if header[SET_COOKIE].respond_to?(:join)

389
      if NO_CONTENT_CODES.include?(@status)
390 391 392
        header.delete CONTENT_TYPE
        [status, header, []]
      else
393
        [status, header, RackBody.new(self)]
394 395
      end
    end
D
Initial  
David Heinemeier Hansson 已提交
396
  end
397
end