WebsocketDatahandler.lua

-- [impl->dsn~logging-with-remotelog~1]
local log = require("remotelog")
local ExaError = require("ExaError")

--- This internal class is registered as a callback for incoming messages when connecting to a websocket.
-- It collects incoming messages and logs warnings in case a websocket error occurs.
-- @classmod luasql.exasol.WebsocketDatahandler
-- @field private expecting_data boolean flag indicating if we are expecting to receive data from the websocket or not
-- @field private data table a list for collecting all data received from the websocket
local WebsocketDatahandler = {}

--- Create a new instance of the WebsocketDatahandler class.
-- @treturn luasql.exasol.WebsocketDatahandler a new instance
function WebsocketDatahandler:create()
    local object = {expecting_data = false, data = {}}
    self.__index = self
    setmetatable(object, self)
    return object
end

--- Checks if the given websocket opcode represents an error or not.
-- @tparam boolean|number opcode the received websocket opcode
-- @treturn boolean `true` if the opcode represents an error that should be logged
local function is_websocket_error(opcode)
    -- LuWS uses false to indicate an error
    return type(opcode) == "boolean" and opcode == false
end

--- Callback function for handling data received from a websocket. This method collects valid
-- data in a list and logs and logs a warning in case an error was received.
-- @tparam table conn the websocket connection
-- @tparam boolean|number opcode the websocket opcode. See
-- [RFC 6455](https://datatracker.ietf.org/doc/html/rfc6455#section-11.8)
-- for details about opcodes
-- @tparam string message the received message
-- @raise an error in case we where not expecting to receive any data
function WebsocketDatahandler:handle_data(conn, opcode, message)
    if is_websocket_error(opcode) then
        log.warn("Received error from websocket connection %s: '%s'", conn, message)
        return
    end
    if not self.expecting_data then
        local err = ExaError:new("E-EDL-5", "Not expecting data from websocket but received message "
                                         .. "with opcode {{opcode}} and data {{message}}",
                                 {opcode = tostring(opcode), message = message}):add_ticket_mitigation()
        log.warn(tostring(err))
        return
    end
    table.insert(self.data, message)
    log.trace("Received message #%d with opcode %s and %d bytes of data: '%s'.", #self.data, opcode, #message, message)
end

--- Tell this handler that we are expecting incoming data,
-- e.g. if we are waiting for a response after sending a request.
-- This also resets the collected data to start a fresh collection.
function WebsocketDatahandler:expect_data()
    log.trace("Expecting to receive data")
    self.expecting_data = true
    self.data = {}
end

--- Tell this handler hat we have received the expected data,
-- e.g. when the response for a request has been received.
function WebsocketDatahandler:expected_data_received()
    log.trace("Stop expecting data, received %d messages", #self.data)
    self.expecting_data = false
end

--- Get all message data collected by this handler.
-- @treturn string|nil the concatenated received messages or `nil` if no message was received
function WebsocketDatahandler:get_data()
    if #self.data == 0 then
        log.debug("No messages received since collection started")
        return nil
    end
    log.trace("Received %d messages", #self.data)
    return table.concat(self.data)
end

--- Check if this handler has received any data.
-- @treturn boolean `true` if at least one message was received
function WebsocketDatahandler:has_received_data()
    return #self.data > 0
end

return WebsocketDatahandler
generated by LDoc 1.5.0 Last updated 2025-03-06 11:54:17