|
8 | 8 | import logging
|
9 | 9 | import socket
|
10 | 10 | from string import Template
|
| 11 | +import threading |
| 12 | +import time |
11 | 13 |
|
12 | 14 | try: # Python 3
|
13 | 15 | from http.server import HTTPServer, BaseHTTPRequestHandler
|
@@ -149,11 +151,7 @@ def get_port(self):
|
149 | 151 | # https://docs.python.org/2.7/library/socketserver.html#SocketServer.BaseServer.server_address
|
150 | 152 | return self._server.server_address[1]
|
151 | 153 |
|
152 |
| - def get_auth_response(self, auth_uri=None, timeout=None, state=None, |
153 |
| - welcome_template=None, success_template=None, error_template=None, |
154 |
| - auth_uri_callback=None, |
155 |
| - browser_name=None, |
156 |
| - ): |
| 154 | + def get_auth_response(self, timeout=None, **kwargs): |
157 | 155 | """Wait and return the auth response. Raise RuntimeError when timeout.
|
158 | 156 |
|
159 | 157 | :param str auth_uri:
|
@@ -192,6 +190,37 @@ def get_auth_response(self, auth_uri=None, timeout=None, state=None,
|
192 | 190 | and https://openid.net/specs/openid-connect-core-1_0.html#AuthResponse
|
193 | 191 | Returns None when the state was mismatched, or when timeout occurred.
|
194 | 192 | """
|
| 193 | + # Historically, the _get_auth_response() uses HTTPServer.handle_request(), |
| 194 | + # because its handle-and-retry logic is conceptually as easy as a while loop. |
| 195 | + # Also, handle_request() honors server.timeout setting, and CTRL+C simply works. |
| 196 | + # All those are true when running on Linux. |
| 197 | + # |
| 198 | + # However, the behaviors on Windows turns out to be different. |
| 199 | + # A socket server waiting for request would freeze the current thread. |
| 200 | + # Neither timeout nor CTRL+C would work. End user would have to do CTRL+BREAK. |
| 201 | + # https://stackoverflow.com/questions/1364173/stopping-python-using-ctrlc |
| 202 | + # |
| 203 | + # The solution would need to somehow put the http server into its own thread. |
| 204 | + # This could be done by the pattern of ``http.server.test()`` which internally |
| 205 | + # use ``ThreadingHTTPServer.serve_forever()`` (only available in Python 3.7). |
| 206 | + # Or create our own thread to wrap the HTTPServer.handle_request() inside. |
| 207 | + result = {} # A mutable object to be filled with thread's return value |
| 208 | + t = threading.Thread( |
| 209 | + target=self._get_auth_response, args=(result,), kwargs=kwargs) |
| 210 | + t.daemon = True # So that it won't prevent the main thread from exiting |
| 211 | + t.start() |
| 212 | + begin = time.time() |
| 213 | + while (time.time() - begin < timeout) if timeout else True: |
| 214 | + time.sleep(1) # Short detection interval to make happy path responsive |
| 215 | + if not t.is_alive(): # Then the thread has finished its job and exited |
| 216 | + break |
| 217 | + return result or None |
| 218 | + |
| 219 | + def _get_auth_response(self, result, auth_uri=None, timeout=None, state=None, |
| 220 | + welcome_template=None, success_template=None, error_template=None, |
| 221 | + auth_uri_callback=None, |
| 222 | + browser_name=None, |
| 223 | + ): |
195 | 224 | welcome_uri = "http://localhost:{p}".format(p=self.get_port())
|
196 | 225 | abort_uri = "{loc}?error=abort".format(loc=welcome_uri)
|
197 | 226 | logger.debug("Abort by visit %s", abort_uri)
|
@@ -238,7 +267,7 @@ def get_auth_response(self, auth_uri=None, timeout=None, state=None,
|
238 | 267 | logger.debug("State mismatch. Ignoring this noise.")
|
239 | 268 | else:
|
240 | 269 | break
|
241 |
| - return self._server.auth_response |
| 270 | + result.update(self._server.auth_response) # Return via writable result param |
242 | 271 |
|
243 | 272 | def close(self):
|
244 | 273 | """Either call this eventually; or use the entire class as context manager"""
|
|
0 commit comments