From 0e9b73be39c3fded466a64b22b09811690267d22 Mon Sep 17 00:00:00 2001 From: Neil Schemenauer Date: Fri, 24 Jun 2016 18:08:22 +0000 Subject: [PATCH] For simple_server.py, inherit listening socket if present. This adds support for getting the listening socket from systemd. --- quixote/server/simple_server.py | 39 ++++++++++++++++++- quixote/server/systemd_socket.py | 66 ++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 quixote/server/systemd_socket.py diff --git a/quixote/server/simple_server.py b/quixote/server/simple_server.py index 6d0cdcf..c0f7b36 100755 --- a/quixote/server/simple_server.py +++ b/quixote/server/simple_server.py @@ -2,11 +2,48 @@ """A simple, single threaded, synchronous HTTP server. """ import sys +import socket from http.server import BaseHTTPRequestHandler, HTTPServer +from socketserver import BaseServer import urllib.request, urllib.parse, urllib.error import quixote from quixote import get_publisher from quixote.util import import_object +from scgi.systemd_socket import get_systemd_socket + + +class SockInheritHTTPServer(HTTPServer): + def __init__(self, address_info, handler, bind_and_activate=True): + # This is ugly. We have to re-implement HTTPServer.__init__ + # and server_bind(). We want to get the inherited socket if + # available. If we inherit then we need to skip the bind() call. + BaseServer.__init__(self, address_info, handler) + sock = get_systemd_socket() + if sock is not None: + print('Using inherited socket %s' % (sock.getsockname(),)) + self._skip_bind = True + else: + sock = socket.socket(self.address_family, self.socket_type) + self._skip_bind = False + self.socket = sock + if bind_and_activate: + try: + self.server_bind() + self.server_activate() + except: + self.server_close() + raise + + def server_bind(self): + if not self._skip_bind: + HTTPServer.server_bind(self) + else: + self.server_address = self.socket.getsockname() + host, port = self.socket.getsockname()[:2] + self.server_name = socket.getfqdn(host) + self.server_port = port + + class HTTPRequestHandler(BaseHTTPRequestHandler): @@ -83,7 +120,7 @@ def run(create_publisher, host='', port=80, https=False): """ if https: HTTPRequestHandler.required_cgi_environment['HTTPS'] = 'on' - httpd = HTTPServer((host, port), HTTPRequestHandler) + httpd = SockInheritHTTPServer((host, port), HTTPRequestHandler) def handle_error(request, client_address): HTTPServer.handle_error(httpd, request, client_address) if sys.exc_info()[0] is SystemExit: diff --git a/quixote/server/systemd_socket.py b/quixote/server/systemd_socket.py new file mode 100644 index 0000000..4ebafd3 --- /dev/null +++ b/quixote/server/systemd_socket.py @@ -0,0 +1,66 @@ +# Inherit activation sockets from systemd, see systemd man page for +# sd_listen_fds(). +import os +import socket + +SD_LISTEN_FDS_START = 3 + +def _set_close_on_exec(fds): + try: + import fcntl + except ImportError: + return + if not hasattr(fcntl, 'FD_CLOEXEC'): + return + for fd in range(SD_LISTEN_FDS_START, SD_LISTEN_FDS_START + fds): + fcntl.fcntl(fd, fcntl.F_SETFD, fcntl.FD_CLOEXEC) + + +def sd_listen_fds(): + """Return the number of inherited sockets. Return zero if there are + none. + """ + try: + pid = int(os.environ['LISTEN_PID']) + except (ValueError, KeyError): + return 0 + if os.getpid() != pid: + return 0 + try: + fds = int(os.environ['LISTEN_FDS']) + except (ValueError, KeyError): + raise OSError('invalid LISTEN_FDS value') + _set_close_on_exec(fds) + return fds + + +def _socket_from_fd(fd): + # This is ugly; Python doesn't provide a nice way to use + # getsockopt() and getsockname() to determine the type of + # socket. Using AF_UNIX is a kludge to avoid messing up the + # getsockname() return value. + s = socket.fromfd(fd, socket.AF_UNIX, socket.SOCK_STREAM) + name = s.getsockname() + s.close() # fromfd() calls dup, close the new fd + if isinstance(name, (str, bytes)): + family = socket.AF_UNIX + elif ':' in name[0]: + family = socket.AF_INET6 + else: + family = socket.AF_INET + # we assume we are getting a SOCK_STREAM socket + s = socket.fromfd(fd, family, socket.SOCK_STREAM) + os.close(fd) # fromfd() calls dup, close old fd + return s + + +def get_systemd_socket(): + """Return the inherited socket, if there is one. If not, return None. + """ + num = sd_listen_fds() + if not num: + return None + if num > 1: + raise OSError('only one inherited socket supported') + sock = _socket_from_fd(SD_LISTEN_FDS_START) + return sock