response.rb 7.6 KB
Newer Older
1
require 'digest/md5'
W
wycats 已提交
2
require 'active_support/core_ext/class/attribute_accessors'
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
    attr_accessor :request, :header
    attr_reader :status
37
    attr_writer :sending_file
P
Pratik Naik 已提交
38

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

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

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

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

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

63
    include Rack::Response::Helpers
64
    include ActionDispatch::Http::FilterRedirect
65
    include ActionDispatch::Http::Cache::Response
66
    include MonitorMixin
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 97
    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

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

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

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

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

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

118 119 120 121
      prepare_cache_control!

      yield self if block_given?
    end
122

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

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

    def committed?
137
      @committed
138 139
    end

140
    # Allows you to set the HTTP status code
141
    def status=(status)
142
      @status = Rack::Utils.status_code(status)
143 144
    end

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

149 150
    # The response code of the request
    def response_code
151
      @status
152 153 154 155
    end

    # Returns a String to ensure compatibility with Net::HTTPResponse
    def code
156
      @status.to_s
157 158
    end

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

170 171
    def respond_to?(method)
      if method.to_sym == :to_path
172
        stream.respond_to?(:to_path)
173 174 175 176 177 178
      else
        super
      end
    end

    def to_path
179
      stream.to_path
180 181
    end

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

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

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

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

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

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

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

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

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

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

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

253
  private
J
Joshua Peek 已提交
254

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

      default.merge(original)
    end

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

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

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

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

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

278 279
      headers[CONTENT_TYPE] = type
    end
280

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

285 286 287 288 289 290 291 292 293 294 295 296 297
    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 已提交
298
  end
299
end