response.rb 8.5 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 44
    # Get and set headers for this response.
    attr_accessor :header
45

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

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

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

62 63 64 65
    # 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

66 67
    attr_accessor :no_content_type # :nodoc:

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

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

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

81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
    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)
        @buf.each(&block)
      end

      def close
        @response.commit!
        @closed = true
      end

      def closed?
        @closed
      end
    end

109
    # The underlying body, as a streamable object.
110 111
    attr_reader :stream

112
    def initialize(status = 200, header = {}, body = [])
113 114
      super()

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

117
      self.body, self.header, self.status = body, header, status
118

119
      @sending_file = false
120 121 122
      @blank        = false
      @cv           = new_cond
      @committed    = false
123 124
      @content_type = nil
      @charset      = nil
J
Joshua Peek 已提交
125

126
      if content_type = self[CONTENT_TYPE]
127 128
        type, charset = content_type.split(/;\s*charset=/)
        @content_type = Mime::Type.lookup(type)
129
        @charset = charset || self.class.default_charset
130
      end
131

132 133 134 135
      prepare_cache_control!

      yield self if block_given?
    end
136

137 138 139 140 141 142 143 144 145 146 147 148 149 150
    def await_commit
      synchronize do
        @cv.wait_until { @committed }
      end
    end

    def commit!
      synchronize do
        @committed = true
        @cv.broadcast
      end
    end

    def committed?
151
      @committed
152 153
    end

154
    # Sets the HTTP status code.
155
    def status=(status)
156
      @status = Rack::Utils.status_code(status)
157 158
    end

159
    # Sets the HTTP content type.
S
Santiago Pastorino 已提交
160 161 162 163
    def content_type=(content_type)
      @content_type = content_type.to_s
    end

164
    # The response code of the request.
165
    def response_code
166
      @status
167 168
    end

169
    # Returns a string to ensure compatibility with <tt>Net::HTTPResponse</tt>.
170
    def code
171
      @status.to_s
172 173
    end

174 175
    # Returns the corresponding message for the current HTTP status code:
    #
176 177 178 179 180
    #   response.status = 200
    #   response.message # => "OK"
    #
    #   response.status = 404
    #   response.message # => "Not Found"
181
    #
182
    def message
183
      Rack::Utils::HTTP_STATUS_CODES[@status]
184
    end
185
    alias_method :status_message, :message
186

187
    def respond_to?(method, include_private = false)
S
Santiago Pastorino 已提交
188
      if method.to_s == 'to_path'
189
        stream.respond_to?(method)
190 191 192 193 194 195
      else
        super
      end
    end

    def to_path
196
      stream.to_path
197 198
    end

199
    # Returns the content of the response as a string. This contains the contents
200
    # of any calls to <tt>render</tt>.
201
    def body
202 203 204
      strings = []
      each { |part| strings << part.to_s }
      strings.join
205
    end
206

Y
Yehuda Katz 已提交
207 208
    EMPTY = " "

209
    # Allows you to manually set or override the response body.
210
    def body=(body)
Y
Yehuda Katz 已提交
211
      @blank = true if body == EMPTY
212

213 214 215
      if body.respond_to?(:to_path)
        @stream = body
      else
216 217 218
        synchronize do
          @stream = build_buffer self, munge_body_object(body)
        end
219
      end
220 221 222
    end

    def body_parts
223 224 225
      parts = []
      @stream.each { |x| parts << x }
      parts
D
Initial  
David Heinemeier Hansson 已提交
226 227
    end

228 229 230 231 232 233 234 235
    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

236
    # The location header we'll be responding with.
237
    def location
238
      headers[LOCATION]
239 240
    end
    alias_method :redirect_url, :location
241

242
    # Sets the location header we'll be responding with.
243
    def location=(url)
244
      headers[LOCATION] = url
245
    end
246

247
    def close
248
      stream.close if stream.respond_to?(:close)
249 250
    end

251 252
    # Turns the Response into a Rack-compatible array of the status, headers,
    # and body.
253
    def to_a
254
      rack_response @status, @header.to_hash
255
    end
256
    alias prepare! to_a
257
    alias to_ary   to_a
258

259 260 261 262 263
    # Returns the response cookies, converted to a Hash of (name => value) pairs
    #
    #   assert_equal 'AuthorOfNewPage', r.cookies['author']
    def cookies
      cookies = {}
264
      if header = self[SET_COOKIE]
265 266 267 268 269 270 271
        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
272 273 274 275
      end
      cookies
    end

276
  private
J
Joshua Peek 已提交
277

278 279 280 281 282 283
    def merge_default_headers(original, default)
      return original unless default.respond_to?(:merge)

      default.merge(original)
    end

284 285 286 287 288 289 290 291
    def build_buffer(response, body)
      Buffer.new response, body
    end

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

292
    def assign_default_content_type_and_charset!(headers)
293
      return if headers[CONTENT_TYPE].present?
J
Joshua Peek 已提交
294

295
      @content_type ||= Mime::HTML
296
      @charset      ||= self.class.default_charset unless @charset == false
J
Joshua Peek 已提交
297

298
      type = @content_type.to_s.dup
299
      type << "; charset=#{@charset}" if append_charset?
J
Joshua Peek 已提交
300

301 302
      headers[CONTENT_TYPE] = type
    end
303

304 305 306 307
    def append_charset?
      !@sending_file && @charset != false
    end

308 309 310 311
    def remove_content_type!
      headers.delete CONTENT_TYPE
    end

312
    def rack_response(status, header)
313 314 315 316 317 318
      if no_content_type
        remove_content_type!
      else
        assign_default_content_type_and_charset!(header)
      end

319 320 321 322
      handle_conditional_get!

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

323
      if NO_CONTENT_CODES.include?(@status)
324 325 326
        header.delete CONTENT_TYPE
        [status, header, []]
      else
327
        [status, header, Rack::BodyProxy.new(self){}]
328 329
      end
    end
D
Initial  
David Heinemeier Hansson 已提交
330
  end
331
end