rainbows.git  about / heads / tags
Unicorn for sleepy apps and slow clients
blob eaa4af45edb04ec87164f46a40ed639f52d977bf 3387 bytes (raw)
$ git show v3.2.0:lib/rainbows/dev_fd_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
 
# -*- encoding: binary -*-

# Rack response middleware wrapping any IO-like object with an
# OS-level file descriptor associated with it.  May also be used to
# create responses from integer file descriptors or existing +IO+
# objects.  This may be used in conjunction with the #to_path method
# on servers that support it to pass arbitrary file descriptors into
# the HTTP response without additional open(2) syscalls
#
# This middleware is currently a no-op for Rubinius, as it lacks
# IO.copy_stream in 1.9 and also due to a bug here:
#   http://github.com/evanphx/rubinius/issues/379

class Rainbows::DevFdResponse < Struct.new(:app)

  # :stopdoc:
  FD_MAP = Rainbows::FD_MAP
  Content_Length = "Content-Length".freeze
  Transfer_Encoding = "Transfer-Encoding".freeze
  Rainbows_autochunk = "rainbows.autochunk".freeze
  Rainbows_model = "rainbows.model"
  HTTP_1_0 = "HTTP/1.0"
  HTTP_VERSION = "HTTP_VERSION"
  Chunked = "chunked"

  # make this a no-op under Rubinius, it's pointless anyways
  # since Rubinius doesn't have IO.copy_stream
  def self.new(app)
    app
  end if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx'
  include Rack::Utils

  # Rack middleware entry point, we'll just pass through responses
  # unless they respond to +to_io+ or +to_path+
  def call(env)
    status, headers, body = response = app.call(env)

    # totally uninteresting to us if there's no body
    if STATUS_WITH_NO_ENTITY_BODY.include?(status.to_i) ||
       File === body ||
       (body.respond_to?(:to_path) && File.file?(body.to_path))
      return response
    end

    io = body.to_io if body.respond_to?(:to_io)
    io ||= File.open(body.to_path) if body.respond_to?(:to_path)
    return response if io.nil?

    headers = Rack::Utils::HeaderHash.new(headers) unless Hash === headers
    st = io.stat
    fileno = io.fileno
    FD_MAP[fileno] = io
    if st.file?
      headers[Content_Length] ||= st.size.to_s
      headers.delete(Transfer_Encoding)
    elsif st.pipe? || st.socket? # epoll-able things
      unless headers.include?(Content_Length)
        if env[Rainbows_autochunk] && HTTP_1_0 != env[HTTP_VERSION]
          headers[Transfer_Encoding] = Chunked
        else
          env[Rainbows_autochunk] = false
        end
      end

      # we need to make sure our pipe output is Fiber-compatible
      case env[Rainbows_model]
      when :FiberSpawn, :FiberPool, :RevFiberSpawn, :CoolioFiberSpawn
        io.respond_to?(:kgio_wait_readable) or
          io = Rainbows::Fiber::IO.new(io)
      when :Revactor
        io = Rainbows::Revactor::Proxy.new(io)
      end
    else # unlikely, char/block device file, directory, ...
      return response
    end
    [ status, headers, Body.new(io, "/dev/fd/#{fileno}", body) ]
  end

  class Body < Struct.new(:to_io, :to_path, :orig_body)
    # called by the webserver or other middlewares if they can't
    # handle #to_path
    def each
      to_io.each { |x| yield x }
    end

    # remain Rack::Lint-compatible for people with wonky systems :P
    unless File.directory?("/dev/fd")
      alias to_path_orig to_path
      undef_method :to_path
    end

    # called by the web server after #each
    def close
      to_io.close unless to_io.closed?
      orig_body.close if orig_body.respond_to?(:close) # may not be an IO
    rescue IOError # could've been IO::new()'ed and closed
    end
  end
  #:startdoc:
end # class

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