response.rb 6.3 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'
4

5
module ActionDispatch # :nodoc:
6
  # Represents an HTTP response generated by a controller action. One can use
J
Joshua Peek 已提交
7
  # an ActionDispatch::Response object to retrieve the current state
8 9 10 11
  # of the response, or customize the response. An Response object can
  # either represent a "real" HTTP response (i.e. one that is meant to be sent
  # back to the web browser) or a test response (i.e. one that is generated
  # from integration tests). See CgiResponse and TestResponse, respectively.
P
Pratik Naik 已提交
12
  #
13 14 15 16 17
  # Response is mostly a Ruby on Rails framework implement detail, and
  # 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 已提交
18
  #
19 20 21
  # Nevertheless, integration tests may want to inspect controller responses in
  # more detail, and that's when Response can be useful for application
  # developers. Integration test methods such as
J
Joshua Peek 已提交
22 23
  # ActionDispatch::Integration::Session#get and
  # ActionDispatch::Integration::Session#post return objects of type
24
  # TestResponse (which are of course also of type Response).
P
Pratik Naik 已提交
25 26 27 28
  #
  # For example, the following demo integration "test" prints the body of the
  # controller response to the console:
  #
J
Joshua Peek 已提交
29
  #  class DemoControllerTest < ActionDispatch::IntegrationTest
P
Pratik Naik 已提交
30 31 32 33 34
  #    def test_print_root_path_to_console
  #      get('/')
  #      puts @response.body
  #    end
  #  end
35
  class Response < Rack::Response
Y
Yehuda Katz 已提交
36
    attr_accessor :request, :blank
P
Pratik Naik 已提交
37

38
    attr_writer :header, :sending_file
39 40
    alias_method :headers=, :header=

41 42 43 44 45 46
    module Setup
      def initialize(status = 200, header = {}, body = [])
        @writer = lambda { |x| @body << x }
        @block = nil
        @length = 0

47 48
        @status, @header = status, header
        self.body = body
49

50 51
        @cookie = []
        @sending_file = false
52

53
        @blank = false
54

55 56 57 58 59
        if content_type = self["Content-Type"]
          type, charset = content_type.split(/;\s*charset=/)
          @content_type = Mime::Type.lookup(type)
          @charset = charset || "UTF-8"
        end
J
Joshua Peek 已提交
60

61 62
        yield self if block_given?
      end
63 64
    end

65 66 67
    include Setup
    include ActionDispatch::Http::Cache::Response

68
    def status=(status)
69
      @status = Rack::Utils.status_code(status)
70 71
    end

72 73
    # The response code of the request
    def response_code
74
      @status
75 76 77 78
    end

    # Returns a String to ensure compatibility with Net::HTTPResponse
    def code
79
      @status.to_s
80 81 82
    end

    def message
83
      Rack::Utils::HTTP_STATUS_CODES[@status]
84
    end
85
    alias_method :status_message, :message
86

87 88 89 90 91 92 93 94 95 96 97 98
    def respond_to?(method)
      if method.to_sym == :to_path
        @body.respond_to?(:to_path)
      else
        super
      end
    end

    def to_path
      @body.to_path
    end

99 100 101 102 103
    def body
      str = ''
      each { |part| str << part.to_s }
      str
    end
104

Y
Yehuda Katz 已提交
105 106
    EMPTY = " "

107
    def body=(body)
Y
Yehuda Katz 已提交
108
      @blank = true if body == EMPTY
109
      @body = body.respond_to?(:to_str) ? [body] : body
110 111 112 113
    end

    def body_parts
      @body
D
Initial  
David Heinemeier Hansson 已提交
114 115
    end

116 117 118 119
    def location
      headers['Location']
    end
    alias_method :redirect_url, :location
120

121 122 123
    def location=(url)
      headers['Location'] = url
    end
124

P
Pratik Naik 已提交
125 126 127 128 129 130 131 132
    # 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.
133
    attr_accessor :charset, :content_type
134

135 136 137
    CONTENT_TYPE    = "Content-Type"

    cattr_accessor(:default_charset) { "utf-8" }
138

139
    def to_a
140
      assign_default_content_type_and_charset!
141
      handle_conditional_get!
J
Joshua Peek 已提交
142
      self["Set-Cookie"] = @cookie.join("\n") unless @cookie.blank?
143
      self["ETag"]       = @_etag if @_etag
144
      super
145
    end
146

147 148
    alias prepare! to_a

149 150 151 152 153
    def each(&callback)
      if @body.respond_to?(:call)
        @writer = lambda { |x| callback.call(x) }
        @body.call(self, self)
      else
154
        @body.each { |part| callback.call(part.to_s) }
155 156 157 158 159 160 161
      end

      @writer = callback
      @block.call(self) if @block
    end

    def write(str)
162 163
      str = str.to_s
      @writer.call str
164 165 166
      str
    end

167 168 169 170 171
    # Returns the response cookies, converted to a Hash of (name => value) pairs
    #
    #   assert_equal 'AuthorOfNewPage', r.cookies['author']
    def cookies
      cookies = {}
Y
Yehuda Katz 已提交
172
      if header = @cookie
173 174 175 176 177 178 179
        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
180 181 182 183
      end
      cookies
    end

Y
Yehuda Katz 已提交
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
    def set_cookie(key, value)
      case value
      when Hash
        domain  = "; domain="  + value[:domain]    if value[:domain]
        path    = "; path="    + value[:path]      if value[:path]
        # According to RFC 2109, we need dashes here.
        # N.B.: cgi.rb uses spaces...
        expires = "; expires=" + value[:expires].clone.gmtime.
          strftime("%a, %d-%b-%Y %H:%M:%S GMT")    if value[:expires]
        secure = "; secure"  if value[:secure]
        httponly = "; HttpOnly" if value[:httponly]
        value = value[:value]
      end
      value = [value]  unless Array === value
      cookie = Rack::Utils.escape(key) + "=" +
        value.map { |v| Rack::Utils.escape v }.join("&") +
        "#{domain}#{path}#{expires}#{secure}#{httponly}"

      @cookie << cookie
    end

    def delete_cookie(key, value={})
      @cookie.reject! { |cookie|
        cookie =~ /\A#{Rack::Utils.escape(key)}=/
      }

      set_cookie(key,
                 {:value => '', :path => nil, :domain => nil,
                   :expires => Time.at(0) }.merge(value))
    end

215
    private
J
Joshua Peek 已提交
216 217 218 219 220 221 222 223 224 225 226 227
      def assign_default_content_type_and_charset!
        return if headers[CONTENT_TYPE].present?

        @content_type ||= Mime::HTML
        @charset      ||= self.class.default_charset

        type = @content_type.to_s.dup
        type << "; charset=#{@charset}" unless @sending_file

        headers[CONTENT_TYPE] = type
      end

D
Initial  
David Heinemeier Hansson 已提交
228
  end
229
end