rf-web/vendor/bundle/gems/eventmachine-1.2.7/lib/em/streamer.rb
2019-10-21 10:18:17 +02:00

119 lines
3.6 KiB
Ruby

module EventMachine
# Streams a file over a given connection. Streaming begins once the object is
# instantiated. Typically FileStreamer instances are not reused.
#
# Streaming uses buffering for files larger than 16K and uses so-called fast file reader (a C++ extension)
# if available (it is part of eventmachine gem itself).
#
# @example
#
# module FileSender
# def post_init
# streamer = EventMachine::FileStreamer.new(self, '/tmp/bigfile.tar')
# streamer.callback{
# # file was sent successfully
# close_connection_after_writing
# }
# end
# end
#
#
# @author Francis Cianfrocca
class FileStreamer
include Deferrable
# Use mapped streamer for files bigger than 16k
MappingThreshold = 16384
# Wait until next tick to send more data when 50k is still in the outgoing buffer
BackpressureLevel = 50000
# Send 16k chunks at a time
ChunkSize = 16384
# @param [EventMachine::Connection] connection
# @param [String] filename File path
#
# @option args [Boolean] :http_chunks (false) Use HTTP 1.1 style chunked-encoding semantics.
def initialize connection, filename, args = {}
@connection = connection
@http_chunks = args[:http_chunks]
if File.exist?(filename)
@size = File.size(filename)
if @size <= MappingThreshold
stream_without_mapping filename
else
stream_with_mapping filename
end
else
fail "file not found"
end
end
# @private
def stream_without_mapping filename
if @http_chunks
@connection.send_data "#{@size.to_s(16)}\r\n"
@connection.send_file_data filename
@connection.send_data "\r\n0\r\n\r\n"
else
@connection.send_file_data filename
end
succeed
end
private :stream_without_mapping
# @private
def stream_with_mapping filename
ensure_mapping_extension_is_present
@position = 0
@mapping = EventMachine::FastFileReader::Mapper.new filename
stream_one_chunk
end
private :stream_with_mapping
# Used internally to stream one chunk at a time over multiple reactor ticks
# @private
def stream_one_chunk
loop {
if @position < @size
if @connection.get_outbound_data_size > BackpressureLevel
EventMachine::next_tick {stream_one_chunk}
break
else
len = @size - @position
len = ChunkSize if (len > ChunkSize)
@connection.send_data( "#{len.to_s(16)}\r\n" ) if @http_chunks
@connection.send_data( @mapping.get_chunk( @position, len ))
@connection.send_data("\r\n") if @http_chunks
@position += len
end
else
@connection.send_data "0\r\n\r\n" if @http_chunks
@mapping.close
succeed
break
end
}
end
#
# We use an outboard extension class to get memory-mapped files.
# It's outboard to avoid polluting the core distro, but that means
# there's a "hidden" dependency on it. The first time we get here in
# any run, try to load up the dependency extension. User code will see
# a LoadError if it's not available, but code that doesn't require
# mapped files will work fine without it. This is a somewhat difficult
# compromise between usability and proper modularization.
#
# @private
def ensure_mapping_extension_is_present
@@fastfilereader ||= (require 'fastfilereaderext')
end
private :ensure_mapping_extension_is_present
end # FileStreamer
end # EventMachine