lib/hatetepe/body.rb in hatetepe-0.2.4 vs lib/hatetepe/body.rb in hatetepe-0.3.0
- old
+ new
@@ -1,80 +1,166 @@
require "em-synchrony"
require "eventmachine"
require "stringio"
module Hatetepe
+ # Thin wrapper around StringIO for asynchronous body processing.
class Body
include EM::Deferrable
+ # The wrapped StringIO.
attr_reader :io
+
+ # The origin Client or Server connection.
attr_accessor :source
- def initialize(string = "")
+ # Create a new Body instance.
+ #
+ # @param [String] data
+ # Initial content of the StringIO object.
+ def initialize(data = "")
@receivers = []
- @io = StringIO.new(string)
+ @io = StringIO.new(data)
end
+ # Blocks until the Body is write-closed.
+ #
+ # Use this if you want to wait until _all_ of the body has arrived before
+ # continuing. It will resume the originating connection if it's paused.
+ #
+ # @return [undefined]
def sync
source.resume if source && source.paused?
EM::Synchrony.sync self
end
+ # Forwards to StringIO#length.
+ #
+ # Blocks until the Body is write-closed. Returns the current length of the
+ # underlying StringIO's content.
+ #
+ # @return [Fixnum]
+ # The StringIO's length.
def length
- # TODO maybe I want to #sync here
- @io.length
+ sync
+ io.length
end
+ # Returns true if the underlying StringIO is empty, false otherwise.
+ #
+ # @return [Boolean]
+ # True if empty, false otherwise.
def empty?
length == 0
end
+ # Forwards to StringIO#pos.
+ #
+ # Returns the underlying StringIO's current pointer position.
+ #
+ # @return [Fixnum]
+ # The current pointer position.
def pos
- @io.pos
+ io.pos
end
+ # Forwards to StringIO#rewind.
+ #
+ # Moves the underlying StringIO's pointer back to the beginnung.
+ #
+ # @return [undefined]
def rewind
- @io.rewind
+ io.rewind
end
+ # Forwards to StringIO#close_write.
+ #
+ # Write-closes the body and succeeds, thus releasing all blocking method
+ # calls like #length, #each, #read and #get.
+ #
+ # @return [undefined]
def close_write
- ret = @io.close_write
+ io.close_write
succeed
- ret
end
+ # Forwards to StringIO#closed_write?.
+ #
+ # Returns true if the body is write-closed, false otherwise.
+ #
+ # @return [Boolean]
+ # True if the body is write-closed, false otherwise.
def closed_write?
- @io.closed_write?
+ io.closed_write?
end
+ # Yields incoming body data.
+ #
+ # Immediately yields all data that has already arrived. Blocks until the
+ # Body is write-closed and yields for each call to #write until then.
+ #
+ # @yield [String] Block to execute for each incoming data chunk
+ #
+ # @return [undefined]
def each(&block)
@receivers << block
- block.call @io.string.dup unless @io.string.empty?
+ block.call io.string.dup unless io.string.empty?
sync
end
+ # Forwards to StringIO#read.
+ #
+ # From the Rack Spec: If given, +length+ must be a non-negative Integer
+ # (>= 0) or +nil+, and +buffer+ must be a String and may not be nil. If
+ # +length+ is given and not nil, then this method reads at most +length+
+ # bytes from the input stream. If +length+ is not given or nil, then this
+ # method reads all data until EOF. When EOF is reached, this method returns
+ # nil if +length+ is given and not nil, or "" if +length+ is not given or
+ # is nil. If +buffer+ is given, then the read data will be placed into
+ # +buffer+ instead of a newly created String object.
+ #
+ # @param [Fixnum] length (optional)
+ # How many bytes to read.
+ # @param [String] buffer (optional)
+ # Buffer for read data.
+ #
+ # @return [nil]
+ # +nil+ if EOF has been reached.
+ # @return [String]
+ # All data or at most +length+ bytes of data if +length+ is given.
def read(*args)
sync
- @io.read *args
+ io.read *args
end
+ # Forwards to StringIO#gets.
+ #
+ # Reads one line from the IO. Returns the line or +nil+ if EOF has been
+ # reached.
+ #
+ # @return [String]
+ # One line.
+ # @return [nil]
+ # If has been reached.
def gets
sync
- @io.gets
+ io.gets
end
- def write(chunk)
- ret = @io.write chunk
+ # Forwards to StringIO#write.
+ #
+ # Appends the given String to the underlying StringIO annd returns the
+ # number of bytes written.
+ #
+ # @param [String] data
+ # The data to append
+ #
+ # @return [Fixnum]
+ # The number of bytes written.
+ def write(data)
+ ret = io.write data
Fiber.new {
- @receivers.each {|r| r.call chunk }
- }.resume
- ret
- end
-
- def <<(chunk)
- ret = @io << chunk
- Fiber.new {
- @receivers.each {|r| r.call chunk }
+ @receivers.each {|r| r.call data }
}.resume
ret
end
end
end