90 lines
3.2 KiB
Lua
90 lines
3.2 KiB
Lua
-- base connector class for buffered reading.
|
|
--
|
|
-- Use this base class if the sockets do NOT yield.
|
|
-- So LuaSocket for example, when using Copas or OpenResty
|
|
-- use the non-buffered base class.
|
|
--
|
|
-- This base class derives from `non_buffered_base` it implements the
|
|
-- `receive` and `buffer_clear` methods. But adds the `plain_receive` method
|
|
-- that must be implemented.
|
|
--
|
|
-- NOTE: the `plain_receive` method is supposed to be non-blocking (see its
|
|
-- description), but the `send` method has no such facilities, so is `blocking`
|
|
-- in this class. Make sure to set the proper timeouts in either method before
|
|
-- starting the send/receive. So for example for LuaSocket call `settimeout(0)`
|
|
-- before receiving, and `settimeout(30)` before sending.
|
|
--
|
|
-- @class mqtt.connector.base.buffered_base
|
|
|
|
|
|
local super = require "mqtt.connector.base.non_buffered_base"
|
|
local buffered = setmetatable({}, super)
|
|
buffered.__index = buffered
|
|
buffered.super = super
|
|
buffered.type = "buffered, blocking i/o"
|
|
|
|
-- debug helper function
|
|
-- function buffered:buffer_state(msg)
|
|
-- print(string.format("buffer: size = %03d last-byte-done = %03d -- %s",
|
|
-- #(self.buffer_string or ""), self.buffer_pointer or 0, msg))
|
|
-- end
|
|
|
|
-- bytes read were handled, clear those
|
|
function buffered:buffer_clear()
|
|
-- self:buffer_state("before clearing buffer")
|
|
self.buffer_string = nil
|
|
self.buffer_pointer = nil
|
|
end
|
|
|
|
-- read bytes, first from buffer, remaining from function
|
|
-- if function returns "idle" then reset read pointer
|
|
function buffered:receive(size)
|
|
-- self:buffer_state("receive start "..size.." bytes")
|
|
|
|
local buf = self.buffer_string or ""
|
|
local idx = self.buffer_pointer or 0
|
|
|
|
while size > (#buf - idx) do
|
|
-- buffer is lacking bytes, read more...
|
|
local data, err = self:plain_receive(size - (#buf - idx))
|
|
if not data then
|
|
if err == self.signal_idle then
|
|
-- read timedout, retry entire packet later, reset buffer
|
|
self.buffer_pointer = 0
|
|
end
|
|
return data, err
|
|
end
|
|
|
|
-- append received data, and try again
|
|
buf = buf .. data
|
|
self.buffer_string = buf
|
|
-- self:buffer_state("receive added "..#data.." bytes")
|
|
end
|
|
|
|
self.buffer_pointer = idx + size
|
|
local data = buf:sub(idx + 1, idx + size)
|
|
-- print("data: ", require("mqtt.tools").hex(data))
|
|
-- self:buffer_state("receive done "..size.." bytes\n")
|
|
return data
|
|
end
|
|
|
|
--- Retrieves the requested number of bytes from the socket, in a non-blocking
|
|
-- manner.
|
|
-- The implementation MUST read with a timeout that immediately returns if there
|
|
-- is no data to read. If there is no data, then it MUST return
|
|
-- `nil, self.signal_idle` to indicate it no data was there and we need to retry later.
|
|
--
|
|
-- If there is partial data, it should return that data (less than the requested
|
|
-- number of bytes), with no error/signal.
|
|
--
|
|
-- If the receive errors, because of a closed connection it should return
|
|
-- `nil, self.signal_closed` to indicate this. Any other errors can be returned
|
|
-- as a regular `nil, err`.
|
|
-- @tparam size int number of bytes to receive.
|
|
-- @return data, or `false, err`, where `err` can be a signal.
|
|
function buffered:plain_receive(size) -- luacheck: ignore
|
|
error("method 'plain_receive' on buffered connector wasn't implemented")
|
|
end
|
|
|
|
return buffered
|