import base64
import json
import socket
from typing import Optional, Union
from platypush.plugins import Plugin, action
[docs]
class TcpPlugin(Plugin):
"""
Plugin for raw TCP communications.
"""
[docs]
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._sockets = {}
def _connect(self, host: str, port: int, timeout: Optional[float] = None) -> socket.socket:
sd = self._sockets.get((host, port))
if sd:
return sd
sd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
if timeout:
sd.settimeout(timeout)
sd.connect((host, port))
self._sockets[(host, port)] = sd
return sd
[docs]
@action
def connect(self, host: str, port: int, timeout: Optional[float] = None):
"""
Open a TCP connection.
:param host: Host IP/name.
:param port: TCP port.
:param timeout: Connection timeout in seconds (default: None).
"""
self._connect(host, port, timeout)
[docs]
@action
def close(self, host: str, port: int):
"""
Close an active TCP connection.
:param host: Host IP/name.
:param port: TCP port.
"""
sd = self._sockets.get((host, port))
if not sd:
self.logger.warning('Not connected to ({}, {})'.format(host, port))
return
sd.close()
[docs]
@action
def send(self, data: Union[bytes, str], host: str, port: int, binary: bool = False,
timeout: Optional[float] = None, recv_response: bool = False, **recv_opts):
"""
Send data over a TCP connection. If the connection isn't active it will be created.
:param data: Data to be sent, as bytes or string.
:param host: Host IP/name.
:param port: TCP port.
:param binary: If set to True and ``data`` is a string then will be treated as base64-encoded binary input.
:param timeout: Connection timeout in seconds (default: None).
:param recv_response: If True then the action will wait for a response from the server before closing the
connection. Note that ``recv_opts`` must be specified in this case - at least ``length``.
"""
if isinstance(data, list) or isinstance(data, dict):
data = json.dumps(data)
if isinstance(data, str):
data = data.encode()
if binary:
data = base64.decodebytes(data)
sd = self._connect(host, port, timeout)
try:
sd.send(data)
if recv_response:
recv_opts.update({
'host': host,
'port': port,
'timeout': timeout,
'binary': binary,
})
return self.recv(**recv_opts)
finally:
self.close(host, port)
[docs]
@action
def recv(self, length: int, host: str, port: int, binary: bool = False, timeout: Optional[float] = None) -> str:
"""
Receive data from a TCP connection. If the connection isn't active it will be created.
:param length: Maximum number of bytes to be received.
:param host: Host IP/name.
:param port: TCP port.
:param binary: If set to True then the output will be base64-encoded, otherwise decoded as string.
:param timeout: Connection timeout in seconds (default: None).
"""
sd = self._connect(host, port, timeout)
try:
data = sd.recv(length)
if binary:
data = base64.encodebytes(data).decode()
else:
data = data.decode()
return data
finally:
self.close(host, port)
# vim:sw=4:ts=4:et: