1import logging
2import os
3import select
4import SimpleHTTPServer
5import socket
6import SocketServer
7import threading
8
9HERE = os.path.dirname(__file__)
10logger = logging.getLogger(__name__)
11
12
13class ThisDirHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
14    def translate_path(self, path):
15        path = path.split("?", 1)[0].split("#", 1)[0]
16        return os.path.join(HERE, *filter(None, path.split("/")))
17
18    def log_message(self, s, *args):
19        # output via logging so nose can catch it
20        logger.info(s, *args)
21
22
23class ShutdownServer(SocketServer.TCPServer):
24    """Mixin that allows serve_forever to be shut down.
25
26    The methods in this mixin are backported from SocketServer.py in the Python
27    2.6.4 standard library. The mixin is unnecessary in 2.6 and later, when
28    BaseServer supports the shutdown method directly.
29    """
30
31    def __init__(self, use_tls, *args, **kwargs):
32        self.__use_tls = use_tls
33        SocketServer.TCPServer.__init__(self, *args, **kwargs)
34        self.__is_shut_down = threading.Event()
35        self.__serving = False
36
37    def server_bind(self):
38        SocketServer.TCPServer.server_bind(self)
39        if self.__use_tls:
40            import ssl
41
42            self.socket = ssl.wrap_socket(
43                self.socket,
44                os.path.join(os.path.dirname(__file__), "server.key"),
45                os.path.join(os.path.dirname(__file__), "server.pem"),
46                True,
47            )
48
49    def serve_forever(self, poll_interval=0.1):
50        """Handle one request at a time until shutdown.
51
52        Polls for shutdown every poll_interval seconds. Ignores
53        self.timeout. If you need to do periodic tasks, do them in
54        another thread.
55        """
56        self.__serving = True
57        self.__is_shut_down.clear()
58        while self.__serving:
59            r, w, e = select.select([self.socket], [], [], poll_interval)
60            if r:
61                self._handle_request_noblock()
62        self.__is_shut_down.set()
63
64    def shutdown(self):
65        """Stops the serve_forever loop.
66
67        Blocks until the loop has finished. This must be called while
68        serve_forever() is running in another thread, or it will deadlock.
69        """
70        self.__serving = False
71        self.__is_shut_down.wait()
72
73    def handle_request(self):
74        """Handle one request, possibly blocking.
75
76        Respects self.timeout.
77        """
78        # Support people who used socket.settimeout() to escape
79        # handle_request before self.timeout was available.
80        timeout = self.socket.gettimeout()
81        if timeout is None:
82            timeout = self.timeout
83        elif self.timeout is not None:
84            timeout = min(timeout, self.timeout)
85        fd_sets = select.select([self], [], [], timeout)
86        if not fd_sets[0]:
87            self.handle_timeout()
88            return
89        self._handle_request_noblock()
90
91    def _handle_request_noblock(self):
92        """Handle one request, without blocking.
93
94        I assume that select.select has returned that the socket is
95        readable before this function was called, so there should be
96        no risk of blocking in get_request().
97        """
98        try:
99            request, client_address = self.get_request()
100        except socket.error:
101            return
102        if self.verify_request(request, client_address):
103            try:
104                self.process_request(request, client_address)
105            except:
106                self.handle_error(request, client_address)
107                self.close_request(request)
108
109
110def start_server(handler, use_tls=False):
111    httpd = ShutdownServer(use_tls, ("", 0), handler)
112    threading.Thread(target=httpd.serve_forever).start()
113    _, port = httpd.socket.getsockname()
114    return httpd, port
115