klippy: Allow to listen to a TCP socket

docs: add instructions to use TCP sockets

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 <contact@toniob.net>
This commit is contained in:
Anthony Bourguignon 2025-02-18 10:39:33 +01:00
parent 1fc6d214f4
commit b0481f1110
3 changed files with 72 additions and 14 deletions

View file

@ -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",

View file

@ -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)

View file

@ -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] <socket filename>"
usage = "%prog [options] <server address>"
opts = optparse.OptionParser(usage)
options, args = opts.parse_args()
if len(args) != 1: