class FakeServerContext(object): def __init__(self, monkeypatch, fail_these, expected_basename): self._monkeypatch = monkeypatch self._fail_these = fail_these self._expected_basename = expected_basename self._url = None self._loop = None self._started = threading.Condition() self._thread = threading.Thread(target=self._run) def __exit__(self, type, value, traceback): if self._loop is not None: # we can ONLY use add_callback here, since the loop is # running in a different thread. self._loop.add_callback(self._stop) self._thread.join() def __enter__(self): self._started.acquire() self._thread.start() self._started.wait() self._started.release() _monkeypatch_client_config(self._monkeypatch, self._url) return self._url def _run(self): self._loop = IOLoop() self._server = FakeAnacondaServer( io_loop=self._loop, fail_these=self._fail_these, expected_basename=self._expected_basename) self._url = self._server.url def notify_started(): self._started.acquire() self._started.notify() self._started.release() self._loop.add_callback(notify_started) self._loop.start() # done self._server.unlisten() def _stop(self): def really_stop(): if self._loop is not None: self._loop.stop() self._loop = None # the delay allows pending next-tick things to go ahead # and happen, which may avoid some problems with trying to # output to stdout after pytest closes it if self._loop is not None: self._loop.call_later(delay=0.05, callback=really_stop)
class FakeServerContext(object): def __init__(self, monkeypatch, fail_these, expected_basename): self._monkeypatch = monkeypatch self._fail_these = fail_these self._expected_basename = expected_basename self._url = None self._loop = None self._started = threading.Condition() self._thread = threading.Thread(target=self._run) def __exit__(self, type, value, traceback): if self._loop is not None: # we can ONLY use add_callback here, since the loop is # running in a different thread. self._loop.add_callback(self._stop) self._thread.join() def __enter__(self): self._started.acquire() self._thread.start() self._started.wait() self._started.release() _monkeypatch_client_config(self._monkeypatch, self._url) return self._url def _run(self): self._loop = IOLoop() self._server = FakeAnacondaServer(io_loop=self._loop, fail_these=self._fail_these, expected_basename=self._expected_basename) self._url = self._server.url def notify_started(): self._started.acquire() self._started.notify() self._started.release() self._loop.add_callback(notify_started) self._loop.start() # done self._server.unlisten() def _stop(self): def really_stop(): if self._loop is not None: self._loop.stop() self._loop = None # the delay allows pending next-tick things to go ahead # and happen, which may avoid some problems with trying to # output to stdout after pytest closes it if self._loop is not None: self._loop.call_later(delay=0.05, callback=really_stop)
class LoopAndGroup(object): def __init__(self, quit_after=None): self.io_loop = IOLoop() self.group = _CallbackGroup(self.io_loop) if quit_after is not None: self.io_loop.call_later(quit_after / 1000.0, lambda: self.io_loop.stop()) def __exit__(self, type, value, traceback): run(self.io_loop) self.io_loop.close() def __enter__(self): return self
class LoopAndGroup(object): def __init__(self, quit_after=None): self.io_loop = IOLoop() self.group = _CallbackGroup(self.io_loop) if quit_after is not None: self.io_loop.call_later(quit_after / 1000.0, lambda: self.io_loop.stop()) def __exit__(self, type, value, traceback): run(self.io_loop) self.io_loop.close() def __enter__(self): return self
class LoadTest: def __init__(self, ws_url, concurrency, recycle, duration): self.ws_url = ws_url self.concurrency = concurrency self.recycle = recycle self.duration = duration self.loop = IOLoop() self.result = None def print_summary(self): print(f"Total of websockets opened: {self.result.websockets_opened}") print(f"Total of websockets closed: {self.result.websockets_closed}") async def websocket_connect(self): ws = await websocket_connect(self.ws_url) await gen.moment self.result.websockets_opened += 1 self.loop.call_later(self.recycle, self.recycle_ws(ws)) def recycle_ws(self, ws): async def handle(): ws.close() self.result.websockets_closed += 1 await self.websocket_connect() return handle def start(self): self.loop.make_current() self.result = Result() for _ in range(self.concurrency): self.loop.call_later(0, self.websocket_connect) self.loop.call_later(self.duration, self.stop) self.loop.start() def stop(self): self.print_summary() self.loop.stop()
class TornadoServer(AbstractServer): """ Flexx Server implemented in Tornado. """ def __init__(self, host, port, new_loop): self._new_loop = new_loop super(TornadoServer, self).__init__(host, port) def _open(self, host, port): # Get a new ioloop or the current ioloop for this thread if self._new_loop: self._loop = IOLoop() else: self._loop = IOLoop.current(instance=is_main_thread()) if self._loop is None: self._loop = IOLoop(make_current=True) # Create tornado application self._app = tornado.web.Application([ (r"/(.*)/ws", WSHandler), (r"/(.*)", MainHandler), ]) # Create tornado server, bound to our own ioloop self._server = tornado.httpserver.HTTPServer(self._app, io_loop=self._loop) # Start server (find free port number if port not given) if port: # Turn port into int, use hashed port number if a string was given try: port = int(port) except ValueError: port = port_hash(port) self._server.listen(port, host) else: # Try N ports in a repeatable range (easier, browser history, etc.) prefered_port = port_hash('Flexx') for i in xrange(8): port = prefered_port + i try: self._server.listen(port, host) break except OSError: pass # address already in use else: # Ok, let Tornado figure out a port [sock] = netutil.bind_sockets(None, host, family=socket.AF_INET) self._server.add_sockets([sock]) port = sock.getsockname()[1] # Notify address, so its easy to e.g. copy and paste in the browser self._serving = self._app._flexx_serving = host, port logger.info('Serving apps at http://%s:%i/' % (host, port)) def _start(self): # Ensure that our loop is the current loop for this thread if self._new_loop: self._loop.make_current() elif IOLoop.current(instance=is_main_thread()) is not self._loop: raise RuntimeError( 'Server must use ioloop that is current to this thread.') # Make use of the semi-standard defined by IPython to determine # if the ioloop is "hijacked" (e.g. in Pyzo). There is no public # way to determine if a loop is already running, but the # AbstractServer class keeps track of this. if not getattr(self._loop, '_in_event_loop', False): self._loop.start() def _stop(self): # todo: explicitly close all websocket connections logger.debug('Stopping Tornado server') self._loop.stop() def _close(self): self._server.stop() def call_later(self, delay, callback, *args, **kwargs): if delay <= 0: self._loop.add_callback(callback, *args, **kwargs) else: self._loop.call_later(delay, callback, *args, **kwargs) @property def app(self): """ The Tornado Application object being used.""" return self._app @property def loop(self): """ The Tornado IOLoop object being used.""" return self._loop @property def server(self): """ The Tornado HttpServer object being used.""" return self._server
class TornadoServer(AbstractServer): """ Flexx Server implemented in Tornado. """ def __init__(self, host, port, new_loop): self._new_loop = new_loop self._app = None self._server = None self._get_io_loop() super().__init__(host, port) def _get_io_loop(self): # Get a new ioloop or the current ioloop for this thread if self._new_loop: self._loop = IOLoop() else: self._loop = IOLoop.current(instance=is_main_thread()) if self._loop is None: self._loop = IOLoop(make_current=True) def _open(self, host, port): # Note: does not get called if host is False. That way we can # run Flexx in e.g. JLab's application. # Create tornado application self._app = tornado.web.Application([ (r"/flexx/ws/(.*)", WSHandler), (r"/flexx/(.*)", MainHandler), (r"/(.*)", AppHandler), ]) # Create tornado server, bound to our own ioloop self._server = tornado.httpserver.HTTPServer(self._app, io_loop=self._loop) # Start server (find free port number if port not given) if port: # Turn port into int, use hashed port number if a string was given try: port = int(port) except ValueError: port = port_hash(port) self._server.listen(port, host) else: # Try N ports in a repeatable range (easier, browser history, etc.) prefered_port = port_hash('Flexx') for i in range(8): port = prefered_port + i try: self._server.listen(port, host) break except OSError: pass # address already in use else: # Ok, let Tornado figure out a port [sock] = netutil.bind_sockets(None, host, family=socket.AF_INET) self._server.add_sockets([sock]) port = sock.getsockname()[1] # Notify address, so its easy to e.g. copy and paste in the browser self._serving = self._app._flexx_serving = host, port logger.info('Serving apps at http://%s:%i/' % (host, port)) def _start(self): # Ensure that our loop is the current loop for this thread if self._new_loop: self._loop.make_current() elif IOLoop.current(instance=is_main_thread()) is not self._loop: raise RuntimeError( 'Server must use ioloop that is current to this thread.') # Make use of the semi-standard defined by IPython to determine # if the ioloop is "hijacked" (e.g. in Pyzo). There is no public # way to determine if a loop is already running, but the # AbstractServer class keeps track of this. if not getattr(self._loop, '_in_event_loop', False): self._loop.start() def _stop(self): # todo: explicitly close all websocket connections logger.debug('Stopping Tornado server') self._loop.stop() def _close(self): self._server.stop() def call_later(self, delay, callback, *args, **kwargs): # We use a wrapper func so that exceptions are processed via our # logging system. Also fixes that Tornado seems to close websockets # when an exception occurs (issue #164) though one could also # use ``with tornado.stack_context.NullContext()`` to make callbacks # be called more "independently". def wrapper(): try: callback(*args, **kwargs) except Exception as err: err.skip_tb = 1 logger.exception(err) if delay <= 0: self._loop.add_callback(wrapper) else: self._loop.call_later(delay, wrapper) @property def app(self): """ The Tornado Application object being used.""" return self._app @property def loop(self): """ The Tornado IOLoop object being used.""" return self._loop @property def server(self): """ The Tornado HttpServer object being used.""" return self._server
class TornadoServer(AbstractServer): """ Flexx Server implemented in Tornado. """ def __init__(self, host, port, new_loop): self._new_loop = new_loop super().__init__(host, port) def _open(self, host, port): # Get a new ioloop or the current ioloop for this thread if self._new_loop: self._loop = IOLoop() else: self._loop = IOLoop.current(instance=is_main_thread()) if self._loop is None: self._loop = IOLoop(make_current=True) # Create tornado application self._app = tornado.web.Application([(r"/(.*)/ws", WSHandler), (r"/(.*)", MainHandler), ]) # Create tornado server, bound to our own ioloop self._server = tornado.httpserver.HTTPServer(self._app, io_loop=self._loop) # Start server (find free port number if port not given) if port: # Turn port into int, use hashed port number if a string was given try: port = int(port) except ValueError: port = port_hash(port) self._server.listen(port, host) else: # Try N ports in a repeatable range (easier, browser history, etc.) prefered_port = port_hash('Flexx') for i in range(8): port = prefered_port + i try: self._server.listen(port, host) break except OSError: pass # address already in use else: # Ok, let Tornado figure out a port [sock] = netutil.bind_sockets(None, host, family=socket.AF_INET) self._server.add_sockets([sock]) port = sock.getsockname()[1] # Notify address, so its easy to e.g. copy and paste in the browser self._serving = self._app._flexx_serving = host, port logger.info('Serving apps at http://%s:%i/' % (host, port)) def _start(self): # Ensure that our loop is the current loop for this thread if self._new_loop: self._loop.make_current() elif IOLoop.current(instance=is_main_thread()) is not self._loop: raise RuntimeError('Server must use ioloop that is current to this thread.') # Make use of the semi-standard defined by IPython to determine # if the ioloop is "hijacked" (e.g. in Pyzo). There is no public # way to determine if a loop is already running, but the # AbstractServer class keeps track of this. if not getattr(self._loop, '_in_event_loop', False): self._loop.start() def _stop(self): # todo: explicitly close all websocket connections logger.debug('Stopping Tornado server') self._loop.stop() def _close(self): self._server.stop() def call_later(self, delay, callback, *args, **kwargs): if delay <= 0: self._loop.add_callback(callback, *args, **kwargs) else: self._loop.call_later(delay, callback, *args, **kwargs) @property def app(self): """ The Tornado Application object being used.""" return self._app @property def loop(self): """ The Tornado IOLoop object being used.""" return self._loop @property def server(self): """ The Tornado HttpServer object being used.""" return self._server
class TornadoServer(AbstractServer): """ Flexx Server implemented in Tornado. """ def __init__(self, host, port, new_loop, **kwargs): self._new_loop = new_loop self._app = None self._server = None self._get_io_loop() super().__init__(host, port, **kwargs) def _get_io_loop(self): # Get a new ioloop or the current ioloop for this thread if self._new_loop: self._loop = IOLoop() else: self._loop = IOLoop.current(instance=is_main_thread()) if self._loop is None: self._loop = IOLoop(make_current=True) def _open(self, host, port, **kwargs): # Note: does not get called if host is False. That way we can # run Flexx in e.g. JLab's application. # handle ssl, wether from configuration or given args if config.ssl_certfile: if 'ssl_options' not in kwargs: kwargs['ssl_options'] = {} if 'certfile' not in kwargs['ssl_options']: kwargs['ssl_options']['certfile'] = config.ssl_certfile if config.ssl_keyfile: if 'ssl_options' not in kwargs: kwargs['ssl_options'] = {} if 'keyfile' not in kwargs['ssl_options']: kwargs['ssl_options']['keyfile'] = config.ssl_keyfile if config.tornado_debug: app_kwargs = dict(debug=True) else: app_kwargs = dict() # Create tornado application self._app = Application([(r"/flexx/ws/(.*)", WSHandler), (r"/flexx/(.*)", MainHandler), (r"/(.*)", AppHandler), ], **app_kwargs) # Create tornado server, bound to our own ioloop self._server = HTTPServer(self._app, io_loop=self._loop, **kwargs) # Start server (find free port number if port not given) if port: # Turn port into int, use hashed port number if a string was given try: port = int(port) except ValueError: port = port_hash(port) self._server.listen(port, host) else: # Try N ports in a repeatable range (easier, browser history, etc.) prefered_port = port_hash('Flexx') for i in range(8): port = prefered_port + i try: self._server.listen(port, host) break except (OSError, IOError): pass # address already in use else: # Ok, let Tornado figure out a port [sock] = netutil.bind_sockets(None, host, family=socket.AF_INET) self._server.add_sockets([sock]) port = sock.getsockname()[1] # Notify address, so its easy to e.g. copy and paste in the browser self._serving = self._app._flexx_serving = host, port proto = 'http' if 'ssl_options' in kwargs: proto = 'https' logger.info('Serving apps at %s://%s:%i/' % (proto, host, port)) def _start(self): # Ensure that our loop is the current loop for this thread if self._new_loop: self._loop.make_current() elif IOLoop.current(instance=is_main_thread()) is not self._loop: raise RuntimeError('Server must use ioloop that is current to this thread.') # Make use of the semi-standard defined by IPython to determine # if the ioloop is "hijacked" (e.g. in Pyzo). There is no public # way to determine if a loop is already running, but the # AbstractServer class keeps track of this. if not getattr(self._loop, '_in_event_loop', False): self._loop.start() def _stop(self): # todo: explicitly close all websocket connections logger.debug('Stopping Tornado server') self._loop.stop() def _close(self): self._server.stop() def call_later(self, delay, callback, *args, **kwargs): # We use a wrapper func so that exceptions are processed via our # logging system. Also fixes that Tornado seems to close websockets # when an exception occurs (issue #164) though one could also # use ``with tornado.stack_context.NullContext()`` to make callbacks # be called more "independently". def wrapper(): try: callback(*args, **kwargs) except Exception as err: err.skip_tb = 1 logger.exception(err) if delay <= 0: self._loop.add_callback(wrapper) else: self._loop.call_later(delay, wrapper) @property def app(self): """ The Tornado Application object being used.""" return self._app @property def loop(self): """ The Tornado IOLoop object being used.""" return self._loop @property def server(self): """ The Tornado HttpServer object being used.""" return self._server @property def protocol(self): """ Get a string representing served protocol.""" if self._server.ssl_options is not None: return 'https' return 'http'