# -*- encoding: binary -*- # :enddoc: module Rainbows::Response include Unicorn::HttpResponse Rainbows.config!(self, :copy_stream) # private file class for IO objects opened by Rainbows! itself (and not # the app or middleware) class F < File; end # called after forking def self.setup Kgio.accept_class = Rainbows::Client 0 == Rainbows.server.keepalive_timeout and Rainbows::HttpParser.keepalive_requests = 0 end def hijack_socket @hp.env['rack.hijack'].call end # returns the original body on success # returns nil if the headers hijacked the response body def write_headers(status, headers, alive, body) @hp.headers? or return body hijack = nil code = status.to_i msg = Rack::Utils::HTTP_STATUS_CODES[code] buf = "HTTP/1.1 #{msg ? %Q(#{code} #{msg}) : status}\r\n" \ "Date: #{httpdate}\r\n" headers.each do |key, value| case key when %r{\A(?:Date|Connection)\z}i next when "rack.hijack" # this was an illegal key in Rack < 1.5, so it should be # OK to silently discard it for those older versions hijack = value alive = false # No persistent connections for hijacking else if /\n/ =~ value # avoiding blank, key-only cookies with /\n+/ value.split(/\n+/).each { |v| buf << "#{key}: #{v}\r\n" } else buf << "#{key}: #{value}\r\n" end end end write(buf << (alive ? "Connection: keep-alive\r\n\r\n".freeze : "Connection: close\r\n\r\n".freeze)) if hijack body = nil # ensure caller does not close body hijack.call(hijack_socket) end body end def close_if_private(io) io.close if F === io end def io_for_fd(fd) Rainbows::FD_MAP.delete(fd) || F.for_fd(fd) end # to_io is not part of the Rack spec, but make an exception here # since we can conserve path lookups and file descriptors. # \Rainbows! will never get here without checking for the existence # of body.to_path first. def body_to_io(body) if body.respond_to?(:to_io) body.to_io else # try to take advantage of Rainbows::DevFdResponse, calling F.open # is a last resort path = body.to_path %r{\A/dev/fd/(\d+)\z} =~ path ? io_for_fd($1.to_i) : F.open(path) end end module Each # generic body writer, used for most dynamically-generated responses def write_body_each(body) body.each { |chunk| write(chunk) } end # generic response writer, used for most dynamically-generated responses # and also when copy_stream and/or IO#trysendfile is unavailable def write_response(status, headers, body, alive) body = write_headers(status, headers, alive, body) write_body_each(body) if body body ensure body.close if body.respond_to?(:close) end end include Each if IO.method_defined?(:trysendfile) module Sendfile def write_body_file(body, range) io = body_to_io(body) range ? sendfile(io, range[0], range[1]) : sendfile(io, 0) ensure close_if_private(io) end end include Sendfile end if COPY_STREAM unless IO.method_defined?(:trysendfile) module CopyStream def write_body_file(body, range) # ensure sendfile gets used for SyncClose objects: if !body.kind_of?(IO) && body.respond_to?(:to_path) body = body.to_path end range ? COPY_STREAM.copy_stream(body, self, range[1], range[0]) : COPY_STREAM.copy_stream(body, self) end end include CopyStream end # write_body_stream is an alias for write_body_each if copy_stream # isn't used or available. def write_body_stream(body) COPY_STREAM.copy_stream(io = body_to_io(body), self) ensure close_if_private(io) end else # ! COPY_STREAM alias write_body_stream write_body_each end # ! COPY_STREAM if IO.method_defined?(:trysendfile) || COPY_STREAM # This does not support multipart responses (does anybody actually # use those?) def sendfile_range(status, headers) status = status.to_i if 206 == status if %r{\Abytes (\d+)-(\d+)/\d+\z} =~ headers['Content-Range'.freeze] a, b = $1.to_i, $2.to_i return 206, headers, [ a, b - a + 1 ] end return # wtf... end 200 == status && /\Abytes=(\d+-\d*|\d*-\d+)\z/ =~ @hp.env['HTTP_RANGE'] or return a, b = $1.split('-'.freeze) # HeaderHash is quite expensive, and Rack::File currently # uses a regular Ruby Hash with properly-cased headers the # same way they're presented in rfc2616. headers = Rack::Utils::HeaderHash.new(headers) unless Hash === headers clen = headers['Content-Length'.freeze] or return size = clen.to_i if b.nil? # bytes=M- offset = a.to_i count = size - offset elsif a.empty? # bytes=-N offset = size - b.to_i count = size - offset else # bytes=M-N offset = a.to_i count = b.to_i + 1 - offset end if 0 > count || offset >= size headers['Content-Length'.freeze] = "0" headers['Content-Range'.freeze] = "bytes */#{clen}" return 416, headers, nil else count = size if count > size headers['Content-Length'.freeze] = count.to_s headers['Content-Range'.freeze] = "bytes #{offset}-#{offset+count-1}/#{clen}" return 206, headers, [ offset, count ] end end def write_response_path(status, headers, body, alive) if File.file?(body.to_path) if r = sendfile_range(status, headers) status, headers, range = r body = write_headers(status, headers, alive, body) write_body_file(body, range) if body && range else body = write_headers(status, headers, alive, body) write_body_file(body, nil) if body end else body = write_headers(status, headers, alive, body) write_body_stream(body) if body end body ensure body.close if body.respond_to?(:close) end module ToPath # returns nil if hijacked def write_response(status, headers, body, alive) if body.respond_to?(:to_path) write_response_path(status, headers, body, alive) else super end end end include ToPath end # COPY_STREAM || IO.method_defined?(:trysendfile) end