response.rb 6.7 KB
Newer Older
1
require 'digest/md5'
J
Jeremy Kemper 已提交
2
require 'active_support/core_ext/module/delegation'
3
require 'active_support/core_ext/object/blank'
W
wycats 已提交
4
require 'active_support/core_ext/class/attribute_accessors'
5
require 'monitor'
6

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

41
    alias_method :headers=, :header=
42 43 44
    alias_method :headers,  :header

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

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

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

62
    cattr_accessor(:default_charset) { "utf-8" }
63

64 65
    include Rack::Response::Helpers
    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
      self.body, self.header, self.status = body, header, status
102

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

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

116 117 118 119
      prepare_cache_control!

      yield self if block_given?
    end
120

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

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

    def committed?
135
      @committed
136 137
    end

138
    def status=(status)
139
      @status = Rack::Utils.status_code(status)
140 141
    end

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

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

    # Returns a String to ensure compatibility with Net::HTTPResponse
    def code
153
      @status.to_s
154 155 156
    end

    def message
157
      Rack::Utils::HTTP_STATUS_CODES[@status]
158
    end
159
    alias_method :status_message, :message
160

161 162
    def respond_to?(method)
      if method.to_sym == :to_path
163
        stream.respond_to?(:to_path)
164 165 166 167 168 169
      else
        super
      end
    end

    def to_path
170
      stream.to_path
171 172
    end

173
    def body
174 175 176
      strings = []
      each { |part| strings << part.to_s }
      strings.join
177
    end
178

Y
Yehuda Katz 已提交
179 180
    EMPTY = " "

181
    def body=(body)
Y
Yehuda Katz 已提交
182
      @blank = true if body == EMPTY
183

184 185 186 187 188
      if body.respond_to?(:to_path)
        @stream = body
      else
        @stream = build_buffer self, munge_body_object(body)
      end
189 190 191
    end

    def body_parts
192 193 194
      parts = []
      @stream.each { |x| parts << x }
      parts
D
Initial  
David Heinemeier Hansson 已提交
195 196
    end

197 198 199 200 201 202 203 204
    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

205
    def location
206
      headers[LOCATION]
207 208
    end
    alias_method :redirect_url, :location
209

210
    def location=(url)
211
      headers[LOCATION] = url
212
    end
213

214
    def close
215
      stream.close
216 217
    end

218
    def to_a
219
      assign_default_content_type_and_charset!
220
      handle_conditional_get!
221

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

224
      if [204, 304].include?(@status)
225
        @header.delete CONTENT_TYPE
226
        [@status, @header, []]
227
      else
228
        [@status, @header, self]
229 230
      end
    end
231 232
    alias prepare! to_a
    alias to_ary   to_a # For implicit splat on 1.9.2
233

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

251
  private
J
Joshua Peek 已提交
252

253 254 255 256 257 258 259 260
    def build_buffer(response, body)
      Buffer.new response, body
    end

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

261 262
    def assign_default_content_type_and_charset!
      return if headers[CONTENT_TYPE].present?
J
Joshua Peek 已提交
263

264 265
      @content_type ||= Mime::HTML
      @charset      ||= self.class.default_charset
J
Joshua Peek 已提交
266

267 268
      type = @content_type.to_s.dup
      type << "; charset=#{@charset}" unless @sending_file
J
Joshua Peek 已提交
269

270 271
      headers[CONTENT_TYPE] = type
    end
D
Initial  
David Heinemeier Hansson 已提交
272
  end
273
end