response.rb 6.4 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

6
module ActionDispatch # :nodoc:
7
  # Represents an HTTP response generated by a controller action. One can use
J
Joshua Peek 已提交
8
  # an ActionDispatch::Response object to retrieve the current state
9 10 11 12
  # 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 已提交
13
  #
14 15 16 17 18
  # 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 已提交
19
  #
20 21 22
  # 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 已提交
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 28 29
  #
  # For example, the following demo integration "test" prints the body of the
  # controller response to the console:
  #
J
Joshua Peek 已提交
30
  #  class DemoControllerTest < ActionDispatch::IntegrationTest
P
Pratik Naik 已提交
31 32 33 34 35
  #    def test_print_root_path_to_console
  #      get('/')
  #      puts @response.body
  #    end
  #  end
36
  class Response < Rack::Response
Y
Yehuda Katz 已提交
37
    attr_accessor :request, :blank
P
Pratik Naik 已提交
38

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

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

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

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

54
        @blank = false
55

56 57 58 59 60
        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 已提交
61

62 63
        yield self if block_given?
      end
64 65
    end

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

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

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

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

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

88 89 90 91 92 93 94 95 96 97 98 99
    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

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

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

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

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

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

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

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

136 137 138
    CONTENT_TYPE    = "Content-Type"

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

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

148 149
    alias prepare! to_a

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

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

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

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

Y
Yehuda Katz 已提交
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 215
    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

216
    private
J
Joshua Peek 已提交
217 218 219 220 221 222 223 224 225 226 227 228
      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 已提交
229
  end
230
end