diff --git a/acme/acme/standalone.py b/acme/acme/standalone.py index 8f604a439..501d239cb 100644 --- a/acme/acme/standalone.py +++ b/acme/acme/standalone.py @@ -4,6 +4,7 @@ import collections import functools import logging import os +import socket import sys import six @@ -52,18 +53,52 @@ class HTTPSServer(TLSServer, BaseHTTPServer.HTTPServer): class ACMEServerMixin: # pylint: disable=old-style-class,no-init - """ACME server common settings mixin.""" + """ACME server common settings mixin. + + .. warning:: + Subclasses have to init ``_stopped = False`` (it's not done here, + because of old-style classes madness). + + """ server_version = "ACME standalone client" allow_reuse_address = True + def serve_forever2(self): + """Serve forever, until other thread calls `shutdown2`.""" + while not self._stopped: + self.handle_request() + + def shutdown2(self): + """Shutdown server loop from `serve_forever2`.""" + self._stopped = True + + # dummy request to terminate last server_forever2.handle_request() + sock = socket.socket() + try: + sock.connect(self.socket.getsockname()) + except socket.error: + pass # thread is probably already finished + finally: + sock.close() + + self.server_close() + class ACMETLSServer(HTTPSServer, ACMEServerMixin): """ACME TLS Server.""" + def __init__(self, *args, **kwargs): + self._stopped = False + HTTPSServer.__init__(self, *args, **kwargs) + class ACMEServer(BaseHTTPServer.HTTPServer, ACMEServerMixin): """ACME Server (non-TLS).""" + def __init__(self, *args, **kwargs): + self._stopped = False + BaseHTTPServer.HTTPServer.__init__(self, *args, **kwargs) + class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): """SimpleHTTP challenge handler. diff --git a/acme/acme/standalone_test.py b/acme/acme/standalone_test.py index 794fb1a6e..9ff99f5ff 100644 --- a/acme/acme/standalone_test.py +++ b/acme/acme/standalone_test.py @@ -25,6 +25,26 @@ class TLSServerTest(unittest.TestCase): server.server_close() # pylint: disable=no-member +class ACMEServerMixinTest(unittest.TestCase): + """Tests for acme.standalone.ACMEServerMixin.""" + + def test_shutdown2_not_running(self): + from acme.standalone import ACMEServer + server = ACMEServer(("", 0), socketserver.BaseRequestHandler) + server.shutdown2() + server.shutdown2() + + +class ACMEServerTest(unittest.TestCase): + """Test for acme.standalone.ACMEServer.""" + + def test_init(self): + from acme.standalone import ACMEServer + server = ACMEServer(("", 0), socketserver.BaseRequestHandler) + # pylint: disable=protected-access + self.assertFalse(server._stopped) + + class ACMESimpleHTTPTLSServerTestEndToEnd(unittest.TestCase): """End-to-end test for ACME TLS server with SimpleHTTP.""" @@ -45,12 +65,13 @@ class ACMESimpleHTTPTLSServerTestEndToEnd(unittest.TestCase): self.server = ACMETLSServer(('', 0), handler, certs=self.certs) self.server_thread = threading.Thread( # pylint: disable=no-member - target=self.server.handle_request) + target=self.server.serve_forever2) self.server_thread.start() self.port = self.server.socket.getsockname()[1] def tearDown(self): + self.server.shutdown2() self.server_thread.join() def test_index(self): diff --git a/letsencrypt/plugins/standalone.py b/letsencrypt/plugins/standalone.py index 5f2bc9292..071fdbf42 100644 --- a/letsencrypt/plugins/standalone.py +++ b/letsencrypt/plugins/standalone.py @@ -53,33 +53,15 @@ class ServerManager(object): except socket.error as error: errors.StandaloneBindError(error, port) - stop = threading.Event() - thread = threading.Thread( - target=self._serve, - args=(server, stop), - ) + thread = threading.Thread(target=server.serve_forever2) thread.start() - self.servers[port] = (server, thread, stop) + self.servers[port] = (server, thread) return self.servers[port] - def _serve(self, server, stop): - while not stop.is_set(): - server.handle_request() - def stop(self, port): """Stop ACME server running on the specified ``port``.""" - server, thread, stop = self.servers[port] - stop.set() - - # dummy request to terminate last handle_request() - sock = socket.socket() - try: - sock.connect(server.socket.getsockname()) - except socket.error: - pass # thread is probably already finished - finally: - sock.close() - + server, thread = self.servers[port] + server.shutdown2() thread.join() del self.servers[port] @@ -170,7 +152,7 @@ class Authenticator(common.Plugin): for achall in achalls: if isinstance(achall, achallenges.SimpleHTTP): - server, _, _ = self.servers.run(self.config.simple_http_port, tls=tls) + server, _ = self.servers.run(self.config.simple_http_port, tls=tls) response, validation = achall.gen_response_and_validation(tls=tls) self.simple_http_resources.add( acme_standalone.SimpleHTTPRequestHandler.SimpleHTTPResource( @@ -179,7 +161,7 @@ class Authenticator(common.Plugin): cert = self.simple_http_cert domain = achall.domain else: # DVSNI - server, _, _ = self.servers.run(self.config.dvsni_port, tls=True) + server, _ = self.servers.run(self.config.dvsni_port, tls=True) response, cert, _ = achall.gen_cert_and_response(self.key) domain = response.z_domain self.certs[domain] = (self.key, cert) @@ -195,6 +177,6 @@ class Authenticator(common.Plugin): for achall in achalls: if achall in server_achalls: server_achalls.remove(achall) - for port, (server, _, _) in self.servers.items(): + for port, (server, _) in self.servers.items(): if not self.served[server]: self.servers.stop(port)