feat: Option to use unix sockets for xml-rpc

This commit is contained in:
2026-08-06 09:59:53 +02:00
parent 0f04b210d0
commit c4531a33bf
2 changed files with 66 additions and 18 deletions
+55 -18
View File
@@ -15,7 +15,7 @@ from queue import Queue
from re import search from re import search
from threading import Lock from threading import Lock
from uuid import uuid4 from uuid import uuid4
from xmlrpc.client import Binary, ServerProxy from xmlrpc.client import Binary, ServerProxy, Transport
from PyQt5 import QtCore from PyQt5 import QtCore
from paramiko.ssh_exception import AuthenticationException from paramiko.ssh_exception import AuthenticationException
@@ -32,6 +32,21 @@ homedir = environ.get("HOME", "") or environ.get("APPDATA", "")
"""Home dir of user.""" """Home dir of user."""
class UnixStreamTransport(Transport):
"""Transport for xmlrpc to use unix domain sockets."""
def __init__(self, socket_path):
super().__init__()
self._socket_path = socket_path
def make_connection(self, host):
import http.client
conn = http.client.HTTPConnection("localhost")
conn.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
conn.sock.connect(self._socket_path)
return conn
class ConnectionFail(IntEnum): class ConnectionFail(IntEnum):
NO_XML_RPC = 1 NO_XML_RPC = 1
SSH_CONNECT = 2 SSH_CONNECT = 2
@@ -91,6 +106,11 @@ class RevPiSettings:
if load_index is not None: if load_index is not None:
self.load_from_index(load_index) self.load_from_index(load_index)
@property
def is_unix_socket(self) -> bool:
"""Check if connection is a unix domain socket."""
return self.address.startswith("/") or self.address.startswith("./")
def load_from_index(self, settings_index: int) -> None: def load_from_index(self, settings_index: int) -> None:
"""Load settings from 'connections' index.""" """Load settings from 'connections' index."""
self._settings.beginReadArray("connections") self._settings.beginReadArray("connections")
@@ -129,8 +149,11 @@ class RevPiSettings:
pass pass
# These values must exists # These values must exists
if not (self.name and self.address and self.port): if not (self.name and self.address):
raise ValueError("Could not geht all required values from saved settings") raise ValueError("Could not get all required values from saved settings")
if not self.is_unix_socket and not self.port:
raise ValueError("Port is required for IP connections")
self._settings.endArray() self._settings.endArray()
@@ -379,10 +402,10 @@ class ConnectionManager(QtCore.QThread):
) )
return False return False
sp = ServerProxy("http://127.0.0.1:{0}".format(ssh_tunnel_port)) sp = create_server_proxy(revpi_settings, ssh_tunnel_port)
else: else:
sp = ServerProxy("http://{0}:{1}".format(revpi_settings.address, revpi_settings.port)) sp = create_server_proxy(revpi_settings)
# Load values and test connection to Revolution Pi # Load values and test connection to Revolution Pi
try: try:
@@ -435,10 +458,7 @@ class ConnectionManager(QtCore.QThread):
with self._lck_cli: with self._lck_cli:
self.ssh_tunnel_server = ssh_tunnel_server self.ssh_tunnel_server = ssh_tunnel_server
self._cli = sp self._cli = sp
self._cli_connect.put_nowait(( self._cli_connect.put_nowait((revpi_settings, ssh_tunnel_port))
"127.0.0.1" if revpi_settings.ssh_use_tunnel else revpi_settings.address,
ssh_tunnel_port if revpi_settings.ssh_use_tunnel else revpi_settings.port
))
self.connection_established.emit() self.connection_established.emit()
@@ -552,8 +572,8 @@ class ConnectionManager(QtCore.QThread):
self.status_changed.emit(self.tr("Not connected"), "lightblue") self.status_changed.emit(self.tr("Not connected"), "lightblue")
elif not self._cli_connect.empty(): elif not self._cli_connect.empty():
# Get new connection information to create object in this thread # Get new connection information to create object in this thread
item = self._cli_connect.get() revpi_settings, ssh_tunnel_port = self._cli_connect.get()
sp = ServerProxy("http://{0}:{1}".format(*item)) sp = create_server_proxy(revpi_settings, ssh_tunnel_port)
self._cli_connect.task_done() self._cli_connect.task_done()
if sp: if sp:
@@ -582,7 +602,7 @@ class ConnectionManager(QtCore.QThread):
self.settings.ssh_user, self.settings.ssh_user,
self.ssh_pass self.ssh_pass
) )
sp = ServerProxy("http://127.0.0.1:{0}".format(ssh_tunnel_port)) sp = create_server_proxy(self.settings, ssh_tunnel_port)
with self._lck_cli: with self._lck_cli:
self.ssh_tunnel_server = ssh_tunnel_server self.ssh_tunnel_server = ssh_tunnel_server
self._cli = sp self._cli = sp
@@ -669,12 +689,8 @@ class ConnectionManager(QtCore.QThread):
Use connection_recovered signal to figure out new parameters. Use connection_recovered signal to figure out new parameters.
""" """
if not self.settings.ssh_use_tunnel and self.settings.address and self.settings.port: ssh_tunnel_port = self.ssh_tunnel_server.local_tunnel_port if self.ssh_tunnel_server else None
return ServerProxy("http://{0}:{1}".format(self.settings.address, self.settings.port)) return create_server_proxy(self.settings, ssh_tunnel_port)
if self.settings.ssh_use_tunnel and self.ssh_tunnel_server and self.ssh_tunnel_server.connected:
return ServerProxy("http://127.0.0.1:{0}".format(self.ssh_tunnel_server.local_tunnel_port))
return None
@property @property
def connected(self) -> bool: def connected(self) -> bool:
@@ -699,6 +715,27 @@ cm = ConnectionManager()
"""Clobal connection manager instance.""" """Clobal connection manager instance."""
def create_server_proxy(revpi_settings: RevPiSettings, ssh_tunnel_port: int = None) -> ServerProxy:
"""
Create a ServerProxy instance based on the given settings.
:param revpi_settings: Revolution Pi saved connection settings
:param ssh_tunnel_port: Use this port if an SSH tunnel is already established
:return: ServerProxy instance
"""
if revpi_settings.is_unix_socket:
return ServerProxy("http://localhost", transport=UnixStreamTransport(revpi_settings.address))
if ssh_tunnel_port:
return ServerProxy("http://127.0.0.1:{0}".format(ssh_tunnel_port))
if revpi_settings.ssh_use_tunnel:
# This case is usually handled by passing ssh_tunnel_port after connecting the tunnel
return ServerProxy("http://127.0.0.1:{0}".format(revpi_settings.port))
return ServerProxy("http://{0}:{1}".format(revpi_settings.address, revpi_settings.port))
def all_revpi_settings() -> [RevPiSettings]: def all_revpi_settings() -> [RevPiSettings]:
"""Get all revpi settings objects.""" """Get all revpi settings objects."""
# Get length of array and close it, the RevPiSettings-class need it # Get length of array and close it, the RevPiSettings-class need it
+11
View File
@@ -206,6 +206,16 @@ class RevPiPlcList(QtWidgets.QDialog, Ui_diag_connections):
self.sbx_ssh_port.setEnabled(con_item) self.sbx_ssh_port.setEnabled(con_item)
self.txt_ssh_user.setEnabled(con_item) self.txt_ssh_user.setEnabled(con_item)
if con_item:
address = self.txt_address.text()
is_unix = address.startswith("/") or address.startswith("./")
if is_unix:
self.sbx_port.setEnabled(False)
self.cbx_ssh_use_tunnel.setChecked(False)
self.cbx_ssh_use_tunnel.setEnabled(False)
self.sbx_ssh_port.setEnabled(False)
self.txt_ssh_user.setEnabled(False)
def _get_folder_item(self, name: str): def _get_folder_item(self, name: str):
"""Find the folder entry by name.""" """Find the folder entry by name."""
for i in range(self.tre_connections.topLevelItemCount()): for i in range(self.tre_connections.topLevelItemCount()):
@@ -369,6 +379,7 @@ class RevPiPlcList(QtWidgets.QDialog, Ui_diag_connections):
settings = self.__current_item.data(0, WidgetData.revpi_settings) # type: RevPiSettings settings = self.__current_item.data(0, WidgetData.revpi_settings) # type: RevPiSettings
settings.address = text settings.address = text
self.changes = True self.changes = True
self._edit_state()
@QtCore.pyqtSlot(int) @QtCore.pyqtSlot(int)
def on_sbx_port_valueChanged(self, value: int): def on_sbx_port_valueChanged(self, value: int):