rack.git  about / heads / tags
a modular Ruby webserver interface
blob 7f0c6b71b44f4c2f741b4832f090b69aaa5e8af5 4434 bytes (raw)
$ git show HEAD:lib/rack/response.rb	# shows this blob on the CLI

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 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
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
 
require 'rack/request'
require 'rack/utils'
require 'rack/body_proxy'
require 'time'

module Rack
  # Rack::Response provides a convenient interface to create a Rack
  # response.
  #
  # It allows setting of headers and cookies, and provides useful
  # defaults (a OK response containing HTML).
  #
  # You can use Response#write to iteratively generate your response,
  # but note that this is buffered by Rack::Response until you call
  # +finish+.  +finish+ however can take a block inside which calls to
  # +write+ are synchronous with the Rack response.
  #
  # Your application's +call+ should end returning Response#finish.

  class Response
    attr_accessor :length

    CHUNKED = 'chunked'.freeze
    TRANSFER_ENCODING = 'Transfer-Encoding'.freeze
    def initialize(body=[], status=200, header={})
      @status = status.to_i
      @header = Utils::HeaderHash.new.merge(header)

      @chunked = CHUNKED == @header[TRANSFER_ENCODING]
      @writer  = lambda { |x| @body << x }
      @block   = nil
      @length  = 0

      @body = []

      if body.respond_to? :to_str
        write body.to_str
      elsif body.respond_to?(:each)
        body.each { |part|
          write part.to_s
        }
      else
        raise TypeError, "stringable or iterable required"
      end

      yield self  if block_given?
    end

    attr_reader :header
    attr_accessor :status, :body

    def [](key)
      header[key]
    end

    def []=(key, value)
      header[key] = value
    end

    def set_cookie(key, value)
      Utils.set_cookie_header!(header, key, value)
    end

    def delete_cookie(key, value={})
      Utils.delete_cookie_header!(header, key, value)
    end

    def redirect(target, status=302)
      self.status = status
      self["Location"] = target
    end

    def finish(&block)
      @block = block

      if [204, 205, 304].include?(status.to_i)
        header.delete CONTENT_TYPE
        header.delete CONTENT_LENGTH
        close
        [status.to_i, header, []]
      else
        [status.to_i, header, BodyProxy.new(self){}]
      end
    end
    alias to_a finish           # For *response
    alias to_ary finish         # For implicit-splat on Ruby 1.9.2

    def each(&callback)
      @body.each(&callback)
      @writer = callback
      @block.call(self)  if @block
    end

    # Append to body and update Content-Length.
    #
    # NOTE: Do not mix #write and direct #body access!
    #
    def write(str)
      s = str.to_s
      @length += Rack::Utils.bytesize(s) unless @chunked
      @writer.call s

      header[CONTENT_LENGTH] = @length.to_s unless @chunked
      str
    end

    def close
      body.close if body.respond_to?(:close)
    end

    def empty?
      @block == nil && @body.empty?
    end

    alias headers header

    module Helpers
      def invalid?;            status < 100 || status >= 600;        end

      def informational?;      status >= 100 && status < 200;        end
      def successful?;         status >= 200 && status < 300;        end
      def redirection?;        status >= 300 && status < 400;        end
      def client_error?;       status >= 400 && status < 500;        end
      def server_error?;       status >= 500 && status < 600;        end

      def ok?;                 status == 200;                        end
      def created?;            status == 201;                        end
      def accepted?;           status == 202;                        end
      def bad_request?;        status == 400;                        end
      def unauthorized?;       status == 401;                        end
      def forbidden?;          status == 403;                        end
      def not_found?;          status == 404;                        end
      def method_not_allowed?; status == 405;                        end
      def i_m_a_teapot?;       status == 418;                        end
      def unprocessable?;      status == 422;                        end

      def redirect?;           [301, 302, 303, 307].include? status; end

      # Headers
      attr_reader :headers, :original_headers

      def include?(header)
        !!headers[header]
      end

      def content_type
        headers[CONTENT_TYPE]
      end

      def content_length
        cl = headers[CONTENT_LENGTH]
        cl ? cl.to_i : cl
      end

      def location
        headers["Location"]
      end
    end

    include Helpers
  end
end

git clone https://yhbt.net/rack.git