response.rb 8.3 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 68
    CONTENT_TYPE = "Content-Type".freeze
    SET_COOKIE   = "Set-Cookie".freeze
    LOCATION     = "Location".freeze
69
    NO_CONTENT_CODES = [204, 304]
S
Santiago Pastorino 已提交
70

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

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

79 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
    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

107
    # The underlying body, as a streamable object.
108 109
    attr_reader :stream

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

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

115
      self.body, self.header, self.status = body, header, status
116

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

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

130 131 132 133
      prepare_cache_control!

      yield self if block_given?
    end
134

135 136 137 138 139 140 141 142
    def await_commit
      synchronize do
        @cv.wait_until { @committed }
      end
    end

    def commit!
      synchronize do
143
        finalize_response
144 145 146 147 148 149
        @committed = true
        @cv.broadcast
      end
    end

    def committed?
150
      @committed
151 152
    end

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

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

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

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

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

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

    def to_path
195
      stream.to_path
196 197
    end

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

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

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

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

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

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

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

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

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

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

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

275
  private
J
Joshua Peek 已提交
276

277 278 279
    def finalize_response
    end

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

      default.merge(original)
    end

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

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

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

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

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

303 304
      headers[CONTENT_TYPE] = type
    end
305

306 307 308 309
    def append_charset?
      !@sending_file && @charset != false
    end

310
    def rack_response(status, header)
311
      assign_default_content_type_and_charset!(header)
312 313 314 315
      handle_conditional_get!

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

316
      if NO_CONTENT_CODES.include?(@status)
317 318 319
        header.delete CONTENT_TYPE
        [status, header, []]
      else
320
        [status, header, Rack::BodyProxy.new(self){}]
321 322
      end
    end
D
Initial  
David Heinemeier Hansson 已提交
323
  end
324
end