response.rb 7.6 KB
Newer Older
W
wycats 已提交
1
require 'active_support/core_ext/class/attribute_accessors'
2
require 'monitor'
3

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

38
    alias_method :headers=, :header=
39 40 41
    alias_method :headers,  :header

    delegate :[], :[]=, :to => :@header
42
    delegate :each, :to => :@stream
43 44 45 46 47 48 49 50 51

    # 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 已提交
52 53
    attr_accessor :charset
    attr_reader   :content_type
54

55 56 57
    CONTENT_TYPE = "Content-Type".freeze
    SET_COOKIE   = "Set-Cookie".freeze
    LOCATION     = "Location".freeze
S
Santiago Pastorino 已提交
58

59
    cattr_accessor(:default_charset) { "utf-8" }
E
Egor Homakov 已提交
60
    cattr_accessor(:default_headers)
61

62
    include Rack::Response::Helpers
63
    include ActionDispatch::Http::FilterRedirect
64
    include ActionDispatch::Http::Cache::Response
65
    include MonitorMixin
66

67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
    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

    attr_reader :stream

97
    def initialize(status = 200, header = {}, body = [])
98 99
      super()

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

102
      self.body, self.header, self.status = body, header, status
103

104
      @sending_file = false
105 106 107
      @blank        = false
      @cv           = new_cond
      @committed    = false
108 109
      @content_type = nil
      @charset      = nil
J
Joshua Peek 已提交
110

111
      if content_type = self[CONTENT_TYPE]
112 113
        type, charset = content_type.split(/;\s*charset=/)
        @content_type = Mime::Type.lookup(type)
114
        @charset = charset || self.class.default_charset
115
      end
116

117 118 119 120
      prepare_cache_control!

      yield self if block_given?
    end
121

122 123 124 125 126 127 128 129 130 131 132 133 134 135
    def await_commit
      synchronize do
        @cv.wait_until { @committed }
      end
    end

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

    def committed?
136
      @committed
137 138
    end

139
    # Sets the HTTP status code.
140
    def status=(status)
141
      @status = Rack::Utils.status_code(status)
142 143
    end

S
Santiago Pastorino 已提交
144 145 146 147
    def content_type=(content_type)
      @content_type = content_type.to_s
    end

148
    # The response code of the request.
149
    def response_code
150
      @status
151 152
    end

153
    # Returns a string to ensure compatibility with <tt>Net::HTTPResponse</tt>.
154
    def code
155
      @status.to_s
156 157
    end

158 159
    # Returns the corresponding message for the current HTTP status code:
    #
160 161 162 163 164
    #   response.status = 200
    #   response.message # => "OK"
    #
    #   response.status = 404
    #   response.message # => "Not Found"
165
    #
166
    def message
167
      Rack::Utils::HTTP_STATUS_CODES[@status]
168
    end
169
    alias_method :status_message, :message
170

171
    def respond_to?(method)
S
Santiago Pastorino 已提交
172
      if method.to_s == 'to_path'
173
        stream.respond_to?(:to_path)
174 175 176 177 178 179
      else
        super
      end
    end

    def to_path
180
      stream.to_path
181 182
    end

183
    # Returns the content of the response as a string. This contains the contents
184
    # of any calls to <tt>render</tt>.
185
    def body
186 187 188
      strings = []
      each { |part| strings << part.to_s }
      strings.join
189
    end
190

Y
Yehuda Katz 已提交
191 192
    EMPTY = " "

193
    # Allows you to manually set or override the response body.
194
    def body=(body)
Y
Yehuda Katz 已提交
195
      @blank = true if body == EMPTY
196

197 198 199 200 201
      if body.respond_to?(:to_path)
        @stream = body
      else
        @stream = build_buffer self, munge_body_object(body)
      end
202 203 204
    end

    def body_parts
205 206 207
      parts = []
      @stream.each { |x| parts << x }
      parts
D
Initial  
David Heinemeier Hansson 已提交
208 209
    end

210 211 212 213 214 215 216 217
    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

218
    def location
219
      headers[LOCATION]
220 221
    end
    alias_method :redirect_url, :location
222

223
    def location=(url)
224
      headers[LOCATION] = url
225
    end
226

227
    def close
228
      stream.close if stream.respond_to?(:close)
229 230
    end

231
    def to_a
232
      rack_response @status, @header.to_hash
233
    end
234 235
    alias prepare! to_a
    alias to_ary   to_a # For implicit splat on 1.9.2
236

237 238 239 240 241
    # Returns the response cookies, converted to a Hash of (name => value) pairs
    #
    #   assert_equal 'AuthorOfNewPage', r.cookies['author']
    def cookies
      cookies = {}
242
      if header = self[SET_COOKIE]
243 244 245 246 247 248 249
        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
250 251 252 253
      end
      cookies
    end

254
  private
J
Joshua Peek 已提交
255

256 257 258 259 260 261
    def merge_default_headers(original, default)
      return original unless default.respond_to?(:merge)

      default.merge(original)
    end

262 263 264 265 266 267 268 269
    def build_buffer(response, body)
      Buffer.new response, body
    end

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

270
    def assign_default_content_type_and_charset!(headers)
271
      return if headers[CONTENT_TYPE].present?
J
Joshua Peek 已提交
272

273
      @content_type ||= Mime::HTML
274
      @charset      ||= self.class.default_charset unless @charset == false
J
Joshua Peek 已提交
275

276
      type = @content_type.to_s.dup
277
      type << "; charset=#{@charset}" if append_charset?
J
Joshua Peek 已提交
278

279 280
      headers[CONTENT_TYPE] = type
    end
281

282 283 284 285
    def append_charset?
      !@sending_file && @charset != false
    end

286 287 288 289 290 291 292 293 294 295 296 297 298
    def rack_response(status, header)
      assign_default_content_type_and_charset!(header)
      handle_conditional_get!

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

      if [204, 304].include?(@status)
        header.delete CONTENT_TYPE
        [status, header, []]
      else
        [status, header, self]
      end
    end
D
Initial  
David Heinemeier Hansson 已提交
299
  end
300
end