From b0481f1110cf5967507616327afb4866a84ea2d2 Mon Sep 17 00:00:00 2001 From: Anthony Bourguignon Date: Tue, 18 Feb 2025 10:39:33 +0100 Subject: [PATCH] klippy: Allow to listen to a TCP socket docs: add instructions to use TCP sockets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This changes the way that the apiserver parameter is handled. It now uses a URL instead of a string which was the path of the unix socket. The string representing a path is still allowed, if no scheme is present it considers its a unix socket. I also log errors if klippy cannot bind to a file or the IP, instead of raising an exception. Signed-off-by: Anthony Bourguignon --- klippy/klippy.py | 3 ++- klippy/webhooks.py | 44 ++++++++++++++++++++++++++++++++++++++++---- scripts/whconsole.py | 39 ++++++++++++++++++++++++++++++--------- 3 files changed, 72 insertions(+), 14 deletions(-) diff --git a/klippy/klippy.py b/klippy/klippy.py index 316343cbd..a620f64b0 100644 --- a/klippy/klippy.py +++ b/klippy/klippy.py @@ -264,7 +264,8 @@ def main(): default='/tmp/printer', help="input tty name (default is /tmp/printer)") opts.add_option("-a", "--api-server", dest="apiserver", - help="api server unix domain socket filename") + help="api server url (unix:///path/to/file " + "or tcp://ip[:port])") opts.add_option("-l", "--logfile", dest="logfile", help="write log to file instead of stderr") opts.add_option("-v", action="store_true", dest="verbose", diff --git a/klippy/webhooks.py b/klippy/webhooks.py index bccc5aace..56cc1439c 100644 --- a/klippy/webhooks.py +++ b/klippy/webhooks.py @@ -5,6 +5,10 @@ # This file may be distributed under the terms of the GNU GPLv3 license import logging, socket, os, sys, errno, json, collections import gcode +try: + from urllib.parse import urlparse +except ImportError: + from urlparse import urlparse REQUEST_LOG_SIZE = 20 @@ -114,10 +118,42 @@ class ServerSocket: if not server_address or is_fileinput: # Do not enable server return - self._remove_socket_file(server_address) - self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - self.sock.setblocking(0) - self.sock.bind(server_address) + # Parsing the server_address configuration string + server_url = urlparse(server_address, allow_fragments=False) + if server_url.scheme == 'unix' or server_url.scheme == '': + try: + self._remove_socket_file(server_address) + self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + self.sock.setblocking(0) + self.sock.bind(server_url.path) + except FileNotFoundError as e: + logging.exception( + "webhooks: Unable to bind to unix socket '%s'" + % (server_url.path)) + return + elif server_url.scheme == 'tcp': + try: + if server_url.netloc.startswith('['): + # IPv6 address + self.sock = socket.socket(socket.AF_INET6, + socket.SOCK_STREAM) + else: + # IPv4 address + self.sock = socket.socket(socket.AF_INET, + socket.SOCK_STREAM) + self.sock.setblocking(0) + self.sock.bind((server_url.hostname, + server_url.port if server_url.port else 7120)) + except OSError as e: + logging.exception( + "webhooks: Unable to bind to '%s:%d'" + % (server_url.hostname, server_url.port)) + return + else: + logging.exception( + "webhooks: Unknown scheme '%s'" + % (server_url.scheme)) + return self.sock.listen(1) self.fd_handle = self.reactor.register_fd( self.sock.fileno(), self._handle_accept) diff --git a/scripts/whconsole.py b/scripts/whconsole.py index 5e76b3bce..8490c5b92 100755 --- a/scripts/whconsole.py +++ b/scripts/whconsole.py @@ -5,25 +5,46 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import sys, os, optparse, socket, fcntl, select, json, errno, time +try: + from urllib.parse import urlparse +except ImportError: + from urlparse import urlparse # Set a file-descriptor as non-blocking def set_nonblock(fd): fcntl.fcntl(fd, fcntl.F_SETFL , fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK) -def webhook_socket_create(uds_filename): - sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - sock.setblocking(0) - sys.stderr.write("Waiting for connect to %s\n" % (uds_filename,)) +def webhook_socket_create(server_address): + server_url = urlparse(server_address, allow_fragments=False) + if server_url.scheme == 'unix' or server_url.scheme == '': + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + sock.setblocking(0) + server_address = server_url.path + sys.stderr.write("Waiting for connect to %s\n" % (server_url.path,)) + elif server_url.scheme == 'tcp': + if server_url.netloc.startswith('['): + # IPv6 address + sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) + else: + # IPv4 address + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server_address = (server_url.hostname, + server_url.port if server_url.port else 7120) + sys.stderr.write("Waiting for connect to %s:%d\n" % (server_address[0], + server_address[1])) + else: + sys.stderr.write("Unknown scheme %s\n" % (server_url.scheme,)) + sys.exit(-1) while 1: try: - sock.connect(uds_filename) + sock.connect(server_address) except socket.error as e: if e.errno == errno.ECONNREFUSED: time.sleep(0.1) continue sys.stderr.write("Unable to connect socket %s [%d,%s]\n" - % (uds_filename, e.errno, + % (server_address, e.errno, errno.errorcode[e.errno])) sys.exit(-1) break @@ -31,10 +52,10 @@ def webhook_socket_create(uds_filename): return sock class KeyboardReader: - def __init__(self, uds_filename): + def __init__(self, server_address): self.kbd_fd = sys.stdin.fileno() set_nonblock(self.kbd_fd) - self.webhook_socket = webhook_socket_create(uds_filename) + self.webhook_socket = webhook_socket_create(server_address) self.poll = select.poll() self.poll.register(sys.stdin, select.POLLIN | select.POLLHUP) self.poll.register(self.webhook_socket, select.POLLIN | select.POLLHUP) @@ -76,7 +97,7 @@ class KeyboardReader: self.process_socket() def main(): - usage = "%prog [options] " + usage = "%prog [options] " opts = optparse.OptionParser(usage) options, args = opts.parse_args() if len(args) != 1: