Move WSGI server to server.py
This commit is contained in:
parent
6f15cddfbc
commit
3d77238a4b
@ -17,17 +17,13 @@
|
||||
# along with Radicale. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Radicale Server module.
|
||||
Radicale WSGI application.
|
||||
|
||||
This module offers a WSGI application class.
|
||||
|
||||
To use this module, you should take a look at the file ``radicale.py`` that
|
||||
should have been included in this package.
|
||||
Can be used with an external WSGI server or the built-in server.
|
||||
|
||||
"""
|
||||
|
||||
import base64
|
||||
import contextlib
|
||||
import datetime
|
||||
import io
|
||||
import itertools
|
||||
@ -38,15 +34,11 @@ import posixpath
|
||||
import pprint
|
||||
import random
|
||||
import socket
|
||||
import socketserver
|
||||
import ssl
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import wsgiref.simple_server
|
||||
import zlib
|
||||
from http import client
|
||||
from urllib.parse import unquote, urlparse
|
||||
from urllib.parse import urlparse
|
||||
from xml.etree import ElementTree as ET
|
||||
|
||||
import vobject
|
||||
@ -98,162 +90,6 @@ INTERNAL_SERVER_ERROR = (
|
||||
DAV_HEADERS = "1, 2, 3, calendar-access, addressbook, extended-mkcol"
|
||||
|
||||
|
||||
class HTTPServer(wsgiref.simple_server.WSGIServer):
|
||||
"""HTTP server."""
|
||||
|
||||
# These class attributes must be set before creating instance
|
||||
client_timeout = None
|
||||
max_connections = None
|
||||
|
||||
def __init__(self, address, handler, bind_and_activate=True):
|
||||
"""Create server."""
|
||||
ipv6 = ":" in address[0]
|
||||
|
||||
if ipv6:
|
||||
self.address_family = socket.AF_INET6
|
||||
|
||||
# Do not bind and activate, as we might change socket options
|
||||
super().__init__(address, handler, False)
|
||||
|
||||
if ipv6:
|
||||
# Only allow IPv6 connections to the IPv6 socket
|
||||
self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
|
||||
|
||||
if self.max_connections:
|
||||
self.connections_guard = threading.BoundedSemaphore(
|
||||
self.max_connections)
|
||||
else:
|
||||
# use dummy context manager
|
||||
self.connections_guard = contextlib.ExitStack()
|
||||
|
||||
if bind_and_activate:
|
||||
try:
|
||||
self.server_bind()
|
||||
self.server_activate()
|
||||
except BaseException:
|
||||
self.server_close()
|
||||
raise
|
||||
|
||||
if self.client_timeout and sys.version_info < (3, 5, 2):
|
||||
logger.warning("Using server.timeout with Python < 3.5.2 "
|
||||
"can cause network connection failures")
|
||||
|
||||
def get_request(self):
|
||||
# Set timeout for client
|
||||
_socket, address = super().get_request()
|
||||
if self.client_timeout:
|
||||
_socket.settimeout(self.client_timeout)
|
||||
return _socket, address
|
||||
|
||||
def handle_error(self, request, client_address):
|
||||
if issubclass(sys.exc_info()[0], socket.timeout):
|
||||
logger.info("client timed out", exc_info=True)
|
||||
else:
|
||||
logger.error("An exception occurred during request: %s",
|
||||
sys.exc_info()[1], exc_info=True)
|
||||
|
||||
|
||||
class HTTPSServer(HTTPServer):
|
||||
"""HTTPS server."""
|
||||
|
||||
# These class attributes must be set before creating instance
|
||||
certificate = None
|
||||
key = None
|
||||
protocol = None
|
||||
ciphers = None
|
||||
certificate_authority = None
|
||||
|
||||
def __init__(self, address, handler):
|
||||
"""Create server by wrapping HTTP socket in an SSL socket."""
|
||||
super().__init__(address, handler, bind_and_activate=False)
|
||||
|
||||
self.socket = ssl.wrap_socket(
|
||||
self.socket, self.key, self.certificate, server_side=True,
|
||||
cert_reqs=ssl.CERT_REQUIRED if self.certificate_authority else
|
||||
ssl.CERT_NONE,
|
||||
ca_certs=self.certificate_authority or None,
|
||||
ssl_version=self.protocol, ciphers=self.ciphers,
|
||||
do_handshake_on_connect=False)
|
||||
|
||||
self.server_bind()
|
||||
self.server_activate()
|
||||
|
||||
|
||||
class ThreadedHTTPServer(socketserver.ThreadingMixIn, HTTPServer):
|
||||
def process_request_thread(self, request, client_address):
|
||||
with self.connections_guard:
|
||||
return super().process_request_thread(request, client_address)
|
||||
|
||||
|
||||
class ThreadedHTTPSServer(socketserver.ThreadingMixIn, HTTPSServer):
|
||||
def process_request_thread(self, request, client_address):
|
||||
try:
|
||||
try:
|
||||
request.do_handshake()
|
||||
except socket.timeout:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise RuntimeError("SSL handshake failed: %s" % e) from e
|
||||
except Exception:
|
||||
try:
|
||||
self.handle_error(request, client_address)
|
||||
finally:
|
||||
self.shutdown_request(request)
|
||||
return
|
||||
with self.connections_guard:
|
||||
return super().process_request_thread(request, client_address)
|
||||
|
||||
|
||||
class ServerHandler(wsgiref.simple_server.ServerHandler):
|
||||
|
||||
# Don't pollute WSGI environ with OS environment
|
||||
os_environ = {}
|
||||
|
||||
def log_exception(self, exc_info):
|
||||
logger.error("An exception occurred during request: %s",
|
||||
exc_info[1], exc_info=exc_info)
|
||||
|
||||
|
||||
class RequestHandler(wsgiref.simple_server.WSGIRequestHandler):
|
||||
"""HTTP requests handler."""
|
||||
|
||||
def log_request(self, code="-", size="-"):
|
||||
"""Disable request logging."""
|
||||
|
||||
def log_error(self, format, *args):
|
||||
msg = format % args
|
||||
logger.error("An error occurred during request: %s" % msg)
|
||||
|
||||
def get_environ(self):
|
||||
env = super().get_environ()
|
||||
if hasattr(self.connection, "getpeercert"):
|
||||
# The certificate can be evaluated by the auth module
|
||||
env["REMOTE_CERTIFICATE"] = self.connection.getpeercert()
|
||||
# Parent class only tries latin1 encoding
|
||||
env["PATH_INFO"] = unquote(self.path.split("?", 1)[0])
|
||||
return env
|
||||
|
||||
def handle(self):
|
||||
"""Copy of WSGIRequestHandler.handle with different ServerHandler"""
|
||||
|
||||
self.raw_requestline = self.rfile.readline(65537)
|
||||
if len(self.raw_requestline) > 65536:
|
||||
self.requestline = ''
|
||||
self.request_version = ''
|
||||
self.command = ''
|
||||
self.send_error(414)
|
||||
return
|
||||
|
||||
if not self.parse_request():
|
||||
return
|
||||
|
||||
handler = ServerHandler(
|
||||
self.rfile, self.wfile, self.get_stderr(), self.get_environ()
|
||||
)
|
||||
handler.request_handler = self
|
||||
handler.run(self.server.get_app())
|
||||
|
||||
|
||||
class Application:
|
||||
"""WSGI application managing collections."""
|
||||
|
||||
|
@ -17,22 +17,16 @@
|
||||
"""
|
||||
Radicale executable module.
|
||||
|
||||
This module can be executed from a command line with ``$python -m radicale`` or
|
||||
from a python programme with ``radicale.__main__.run()``.
|
||||
This module can be executed from a command line with ``$python -m radicale``.
|
||||
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import select
|
||||
import signal
|
||||
import socket
|
||||
import ssl
|
||||
from wsgiref.simple_server import make_server
|
||||
|
||||
from radicale import (VERSION, Application, RequestHandler, ThreadedHTTPServer,
|
||||
ThreadedHTTPSServer, config, log, storage)
|
||||
from radicale import VERSION, config, log, storage
|
||||
from radicale.log import logger
|
||||
from radicale.server import serve
|
||||
|
||||
|
||||
def run():
|
||||
@ -133,107 +127,5 @@ def run():
|
||||
exit(1)
|
||||
|
||||
|
||||
def serve(configuration):
|
||||
"""Serve radicale from configuration."""
|
||||
logger.info("Starting Radicale")
|
||||
|
||||
# Create collection servers
|
||||
servers = {}
|
||||
if configuration.getboolean("server", "ssl"):
|
||||
server_class = ThreadedHTTPSServer
|
||||
server_class.certificate = configuration.get("server", "certificate")
|
||||
server_class.key = configuration.get("server", "key")
|
||||
server_class.certificate_authority = configuration.get(
|
||||
"server", "certificate_authority")
|
||||
server_class.ciphers = configuration.get("server", "ciphers")
|
||||
server_class.protocol = getattr(
|
||||
ssl, configuration.get("server", "protocol"), ssl.PROTOCOL_SSLv23)
|
||||
# Test if the SSL files can be read
|
||||
for name in ["certificate", "key"] + (
|
||||
["certificate_authority"]
|
||||
if server_class.certificate_authority else []):
|
||||
filename = getattr(server_class, name)
|
||||
try:
|
||||
open(filename, "r").close()
|
||||
except OSError as e:
|
||||
raise RuntimeError("Failed to read SSL %s %r: %s" %
|
||||
(name, filename, e)) from e
|
||||
else:
|
||||
server_class = ThreadedHTTPServer
|
||||
server_class.client_timeout = configuration.getint("server", "timeout")
|
||||
server_class.max_connections = configuration.getint(
|
||||
"server", "max_connections")
|
||||
|
||||
if not configuration.getboolean("server", "dns_lookup"):
|
||||
RequestHandler.address_string = lambda self: self.client_address[0]
|
||||
|
||||
shutdown_program = False
|
||||
|
||||
for host in configuration.get("server", "hosts").split(","):
|
||||
try:
|
||||
address, port = host.strip().rsplit(":", 1)
|
||||
address, port = address.strip("[] "), int(port)
|
||||
except ValueError as e:
|
||||
raise RuntimeError(
|
||||
"Failed to parse address %r: %s" % (host, e)) from e
|
||||
application = Application(configuration)
|
||||
try:
|
||||
server = make_server(
|
||||
address, port, application, server_class, RequestHandler)
|
||||
except OSError as e:
|
||||
raise RuntimeError(
|
||||
"Failed to start server %r: %s" % (host, e)) from e
|
||||
servers[server.socket] = server
|
||||
logger.info("Listening to %r on port %d%s",
|
||||
server.server_name, server.server_port, " using SSL"
|
||||
if configuration.getboolean("server", "ssl") else "")
|
||||
|
||||
# Create a socket pair to notify the select syscall of program shutdown
|
||||
# This is not available in python < 3.5 on Windows
|
||||
if hasattr(socket, "socketpair"):
|
||||
shutdown_program_socket_in, shutdown_program_socket_out = (
|
||||
socket.socketpair())
|
||||
else:
|
||||
shutdown_program_socket_in, shutdown_program_socket_out = None, None
|
||||
|
||||
# SIGTERM and SIGINT (aka KeyboardInterrupt) should just mark this for
|
||||
# shutdown
|
||||
def shutdown(*args):
|
||||
nonlocal shutdown_program
|
||||
if shutdown_program:
|
||||
# Ignore following signals
|
||||
return
|
||||
logger.info("Stopping Radicale")
|
||||
shutdown_program = True
|
||||
if shutdown_program_socket_in:
|
||||
shutdown_program_socket_in.sendall(b"goodbye")
|
||||
signal.signal(signal.SIGTERM, shutdown)
|
||||
signal.signal(signal.SIGINT, shutdown)
|
||||
|
||||
# Main loop: wait for requests on any of the servers or program shutdown
|
||||
sockets = list(servers.keys())
|
||||
if shutdown_program_socket_out:
|
||||
# Use socket pair to get notified of program shutdown
|
||||
sockets.append(shutdown_program_socket_out)
|
||||
select_timeout = None
|
||||
if not shutdown_program_socket_out or os.name == "nt":
|
||||
# Fallback to busy waiting. (select.select blocks SIGINT on Windows.)
|
||||
select_timeout = 1.0
|
||||
logger.info("Radicale server ready")
|
||||
while not shutdown_program:
|
||||
try:
|
||||
rlist, _, xlist = select.select(
|
||||
sockets, [], sockets, select_timeout)
|
||||
except (KeyboardInterrupt, select.error):
|
||||
# SIGINT is handled by signal handler above
|
||||
rlist, xlist = [], []
|
||||
if xlist:
|
||||
raise RuntimeError("unhandled socket error")
|
||||
if rlist:
|
||||
server = servers.get(rlist[0])
|
||||
if server:
|
||||
server.handle_request()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
|
295
radicale/server.py
Normal file
295
radicale/server.py
Normal file
@ -0,0 +1,295 @@
|
||||
# This file is part of Radicale Server - Calendar Server
|
||||
# Copyright © 2008 Nicolas Kandel
|
||||
# Copyright © 2008 Pascal Halter
|
||||
# Copyright © 2008-2017 Guillaume Ayoub
|
||||
#
|
||||
# This library is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Radicale. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Radicale WSGI server.
|
||||
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
import os
|
||||
import select
|
||||
import signal
|
||||
import socket
|
||||
import socketserver
|
||||
import ssl
|
||||
import sys
|
||||
import threading
|
||||
import wsgiref.simple_server
|
||||
from urllib.parse import unquote
|
||||
|
||||
from radicale import Application
|
||||
from radicale.log import logger
|
||||
|
||||
|
||||
class HTTPServer(wsgiref.simple_server.WSGIServer):
|
||||
"""HTTP server."""
|
||||
|
||||
# These class attributes must be set before creating instance
|
||||
client_timeout = None
|
||||
max_connections = None
|
||||
|
||||
def __init__(self, address, handler, bind_and_activate=True):
|
||||
"""Create server."""
|
||||
ipv6 = ":" in address[0]
|
||||
|
||||
if ipv6:
|
||||
self.address_family = socket.AF_INET6
|
||||
|
||||
# Do not bind and activate, as we might change socket options
|
||||
super().__init__(address, handler, False)
|
||||
|
||||
if ipv6:
|
||||
# Only allow IPv6 connections to the IPv6 socket
|
||||
self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
|
||||
|
||||
if self.max_connections:
|
||||
self.connections_guard = threading.BoundedSemaphore(
|
||||
self.max_connections)
|
||||
else:
|
||||
# use dummy context manager
|
||||
self.connections_guard = contextlib.ExitStack()
|
||||
|
||||
if bind_and_activate:
|
||||
try:
|
||||
self.server_bind()
|
||||
self.server_activate()
|
||||
except BaseException:
|
||||
self.server_close()
|
||||
raise
|
||||
|
||||
if self.client_timeout and sys.version_info < (3, 5, 2):
|
||||
logger.warning("Using server.timeout with Python < 3.5.2 "
|
||||
"can cause network connection failures")
|
||||
|
||||
def get_request(self):
|
||||
# Set timeout for client
|
||||
_socket, address = super().get_request()
|
||||
if self.client_timeout:
|
||||
_socket.settimeout(self.client_timeout)
|
||||
return _socket, address
|
||||
|
||||
def handle_error(self, request, client_address):
|
||||
if issubclass(sys.exc_info()[0], socket.timeout):
|
||||
logger.info("client timed out", exc_info=True)
|
||||
else:
|
||||
logger.error("An exception occurred during request: %s",
|
||||
sys.exc_info()[1], exc_info=True)
|
||||
|
||||
|
||||
class HTTPSServer(HTTPServer):
|
||||
"""HTTPS server."""
|
||||
|
||||
# These class attributes must be set before creating instance
|
||||
certificate = None
|
||||
key = None
|
||||
protocol = None
|
||||
ciphers = None
|
||||
certificate_authority = None
|
||||
|
||||
def __init__(self, address, handler):
|
||||
"""Create server by wrapping HTTP socket in an SSL socket."""
|
||||
super().__init__(address, handler, bind_and_activate=False)
|
||||
|
||||
self.socket = ssl.wrap_socket(
|
||||
self.socket, self.key, self.certificate, server_side=True,
|
||||
cert_reqs=ssl.CERT_REQUIRED if self.certificate_authority else
|
||||
ssl.CERT_NONE,
|
||||
ca_certs=self.certificate_authority or None,
|
||||
ssl_version=self.protocol, ciphers=self.ciphers,
|
||||
do_handshake_on_connect=False)
|
||||
|
||||
self.server_bind()
|
||||
self.server_activate()
|
||||
|
||||
|
||||
class ThreadedHTTPServer(socketserver.ThreadingMixIn, HTTPServer):
|
||||
def process_request_thread(self, request, client_address):
|
||||
with self.connections_guard:
|
||||
return super().process_request_thread(request, client_address)
|
||||
|
||||
|
||||
class ThreadedHTTPSServer(socketserver.ThreadingMixIn, HTTPSServer):
|
||||
def process_request_thread(self, request, client_address):
|
||||
try:
|
||||
try:
|
||||
request.do_handshake()
|
||||
except socket.timeout:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise RuntimeError("SSL handshake failed: %s" % e) from e
|
||||
except Exception:
|
||||
try:
|
||||
self.handle_error(request, client_address)
|
||||
finally:
|
||||
self.shutdown_request(request)
|
||||
return
|
||||
with self.connections_guard:
|
||||
return super().process_request_thread(request, client_address)
|
||||
|
||||
|
||||
class ServerHandler(wsgiref.simple_server.ServerHandler):
|
||||
|
||||
# Don't pollute WSGI environ with OS environment
|
||||
os_environ = {}
|
||||
|
||||
def log_exception(self, exc_info):
|
||||
logger.error("An exception occurred during request: %s",
|
||||
exc_info[1], exc_info=exc_info)
|
||||
|
||||
|
||||
class RequestHandler(wsgiref.simple_server.WSGIRequestHandler):
|
||||
"""HTTP requests handler."""
|
||||
|
||||
def log_request(self, code="-", size="-"):
|
||||
"""Disable request logging."""
|
||||
|
||||
def log_error(self, format, *args):
|
||||
msg = format % args
|
||||
logger.error("An error occurred during request: %s" % msg)
|
||||
|
||||
def get_environ(self):
|
||||
env = super().get_environ()
|
||||
if hasattr(self.connection, "getpeercert"):
|
||||
# The certificate can be evaluated by the auth module
|
||||
env["REMOTE_CERTIFICATE"] = self.connection.getpeercert()
|
||||
# Parent class only tries latin1 encoding
|
||||
env["PATH_INFO"] = unquote(self.path.split("?", 1)[0])
|
||||
return env
|
||||
|
||||
def handle(self):
|
||||
"""Copy of WSGIRequestHandler.handle with different ServerHandler"""
|
||||
|
||||
self.raw_requestline = self.rfile.readline(65537)
|
||||
if len(self.raw_requestline) > 65536:
|
||||
self.requestline = ''
|
||||
self.request_version = ''
|
||||
self.command = ''
|
||||
self.send_error(414)
|
||||
return
|
||||
|
||||
if not self.parse_request():
|
||||
return
|
||||
|
||||
handler = ServerHandler(
|
||||
self.rfile, self.wfile, self.get_stderr(), self.get_environ()
|
||||
)
|
||||
handler.request_handler = self
|
||||
handler.run(self.server.get_app())
|
||||
|
||||
|
||||
def serve(configuration):
|
||||
"""Serve radicale from configuration."""
|
||||
logger.info("Starting Radicale")
|
||||
|
||||
# Create collection servers
|
||||
servers = {}
|
||||
if configuration.getboolean("server", "ssl"):
|
||||
server_class = ThreadedHTTPSServer
|
||||
server_class.certificate = configuration.get("server", "certificate")
|
||||
server_class.key = configuration.get("server", "key")
|
||||
server_class.certificate_authority = configuration.get(
|
||||
"server", "certificate_authority")
|
||||
server_class.ciphers = configuration.get("server", "ciphers")
|
||||
server_class.protocol = getattr(
|
||||
ssl, configuration.get("server", "protocol"), ssl.PROTOCOL_SSLv23)
|
||||
# Test if the SSL files can be read
|
||||
for name in ["certificate", "key"] + (
|
||||
["certificate_authority"]
|
||||
if server_class.certificate_authority else []):
|
||||
filename = getattr(server_class, name)
|
||||
try:
|
||||
open(filename, "r").close()
|
||||
except OSError as e:
|
||||
raise RuntimeError("Failed to read SSL %s %r: %s" %
|
||||
(name, filename, e)) from e
|
||||
else:
|
||||
server_class = ThreadedHTTPServer
|
||||
server_class.client_timeout = configuration.getint("server", "timeout")
|
||||
server_class.max_connections = configuration.getint(
|
||||
"server", "max_connections")
|
||||
|
||||
if not configuration.getboolean("server", "dns_lookup"):
|
||||
RequestHandler.address_string = lambda self: self.client_address[0]
|
||||
|
||||
shutdown_program = False
|
||||
|
||||
for host in configuration.get("server", "hosts").split(","):
|
||||
try:
|
||||
address, port = host.strip().rsplit(":", 1)
|
||||
address, port = address.strip("[] "), int(port)
|
||||
except ValueError as e:
|
||||
raise RuntimeError(
|
||||
"Failed to parse address %r: %s" % (host, e)) from e
|
||||
application = Application(configuration)
|
||||
try:
|
||||
server = wsgiref.simple_server.make_server(
|
||||
address, port, application, server_class, RequestHandler)
|
||||
except OSError as e:
|
||||
raise RuntimeError(
|
||||
"Failed to start server %r: %s" % (host, e)) from e
|
||||
servers[server.socket] = server
|
||||
logger.info("Listening to %r on port %d%s",
|
||||
server.server_name, server.server_port, " using SSL"
|
||||
if configuration.getboolean("server", "ssl") else "")
|
||||
|
||||
# Create a socket pair to notify the select syscall of program shutdown
|
||||
# This is not available in python < 3.5 on Windows
|
||||
if hasattr(socket, "socketpair"):
|
||||
shutdown_program_socket_in, shutdown_program_socket_out = (
|
||||
socket.socketpair())
|
||||
else:
|
||||
shutdown_program_socket_in, shutdown_program_socket_out = None, None
|
||||
|
||||
# SIGTERM and SIGINT (aka KeyboardInterrupt) should just mark this for
|
||||
# shutdown
|
||||
def shutdown(*args):
|
||||
nonlocal shutdown_program
|
||||
if shutdown_program:
|
||||
# Ignore following signals
|
||||
return
|
||||
logger.info("Stopping Radicale")
|
||||
shutdown_program = True
|
||||
if shutdown_program_socket_in:
|
||||
shutdown_program_socket_in.sendall(b"goodbye")
|
||||
signal.signal(signal.SIGTERM, shutdown)
|
||||
signal.signal(signal.SIGINT, shutdown)
|
||||
|
||||
# Main loop: wait for requests on any of the servers or program shutdown
|
||||
sockets = list(servers.keys())
|
||||
if shutdown_program_socket_out:
|
||||
# Use socket pair to get notified of program shutdown
|
||||
sockets.append(shutdown_program_socket_out)
|
||||
select_timeout = None
|
||||
if not shutdown_program_socket_out or os.name == "nt":
|
||||
# Fallback to busy waiting. (select.select blocks SIGINT on Windows.)
|
||||
select_timeout = 1.0
|
||||
logger.info("Radicale server ready")
|
||||
while not shutdown_program:
|
||||
try:
|
||||
rlist, _, xlist = select.select(
|
||||
sockets, [], sockets, select_timeout)
|
||||
except (KeyboardInterrupt, select.error):
|
||||
# SIGINT is handled by signal handler above
|
||||
rlist, xlist = [], []
|
||||
if xlist:
|
||||
raise RuntimeError("unhandled socket error")
|
||||
if rlist:
|
||||
server = servers.get(rlist[0])
|
||||
if server:
|
||||
server.handle_request()
|
Loading…
Reference in New Issue
Block a user