From b9872ccb84a50a671b7fd398123c314022973e14 Mon Sep 17 00:00:00 2001 From: Pierre Gay Date: Mon, 5 Dec 2016 12:35:01 +0100 Subject: [PATCH 01/12] add support for HTTPS protocol --- flexx/app/_clientcore.py | 5 ++++- flexx/app/_server.py | 11 ++++++----- flexx/app/_tornadoserver.py | 15 +++++++++------ 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/flexx/app/_clientcore.py b/flexx/app/_clientcore.py index 254160cc..5eb3d78c 100644 --- a/flexx/app/_clientcore.py +++ b/flexx/app/_clientcore.py @@ -123,10 +123,13 @@ def initSocket(self): # Construct ws url (for nodejs the location is set by the flexx nodejs runtime) if not self.ws_url: + proto = 'ws' + if window.location.protocol == 'https:': + proto = 'wss' address = window.location.hostname if window.location.port: address += ':' + window.location.port - self.ws_url = 'ws://%s/flexx/ws/%s' % (address, self.app_name) + self.ws_url = '%s://%s/flexx/ws/%s' % (proto, address, self.app_name) # Open web socket in binary mode self.ws = ws = WebSocket(self.ws_url) diff --git a/flexx/app/_server.py b/flexx/app/_server.py index 79702e93..07068a4c 100644 --- a/flexx/app/_server.py +++ b/flexx/app/_server.py @@ -11,7 +11,7 @@ _current_server = None -def create_server(host=None, port=None, new_loop=False, backend='tornado'): +def create_server(host=None, port=None, new_loop=False, backend='tornado', **server_kwargs): """ Create a new server object. This is automatically called; users generally don't need this, unless they want to explicitly specify host/port, @@ -33,6 +33,7 @@ def create_server(host=None, port=None, new_loop=False, backend='tornado'): which is made current when ``start()`` is called. If ``False`` (default) will use the current IOLoop for this thread. backend (str): Stub argument; only Tornado is currently supported. + **server_kwargs: keyword arguments passed to the server constructor Returns: server: The server object, see ``current_server()``. @@ -53,7 +54,7 @@ def create_server(host=None, port=None, new_loop=False, backend='tornado'): if _current_server: _current_server.close() # Start hosting - _current_server = TornadoServer(host, port, new_loop) + _current_server = TornadoServer(host, port, new_loop, **server_kwargs) assert isinstance(_current_server, AbstractServer) # Schedule pending calls _current_server.call_later(0, _loop.loop.iter) @@ -120,10 +121,10 @@ class AbstractServer: port (int): the port to serve at. None or 0 mean to autoselect a port. """ - def __init__(self, host, port): + def __init__(self, host, port, **kwargs): self._serving = None if host is not False: - self._open(host, port) + self._open(host, port, **kwargs) assert self._serving # Check that subclass set private variable self._running = False @@ -151,7 +152,7 @@ def close(self): self._serving = None self._close() - def _open(self, host, port): + def _open(self, host, port, **kwargs): raise NotImplementedError() def _start(self): diff --git a/flexx/app/_tornadoserver.py b/flexx/app/_tornadoserver.py index 9cd2a4ed..3fd51b24 100644 --- a/flexx/app/_tornadoserver.py +++ b/flexx/app/_tornadoserver.py @@ -47,12 +47,12 @@ class TornadoServer(AbstractServer): """ Flexx Server implemented in Tornado. """ - def __init__(self, host, port, new_loop): + 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) + super().__init__(host, port, **kwargs) def _get_io_loop(self): # Get a new ioloop or the current ioloop for this thread @@ -63,7 +63,7 @@ def _get_io_loop(self): if self._loop is None: self._loop = IOLoop(make_current=True) - def _open(self, host, port): + 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. @@ -72,8 +72,8 @@ def _open(self, host, port): (r"/flexx/(.*)", MainHandler), (r"/(.*)", AppHandler), ]) # Create tornado server, bound to our own ioloop - self._server = HTTPServer(self._app, io_loop=self._loop) - + 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 @@ -100,7 +100,10 @@ def _open(self, host, port): # 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)) + 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 From 89298c8bffb5c7612c1faf4f7c1f991437ada523 Mon Sep 17 00:00:00 2001 From: Pierre Gay Date: Tue, 6 Dec 2016 08:05:00 +0100 Subject: [PATCH 02/12] coing style --- flexx/app/_server.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/flexx/app/_server.py b/flexx/app/_server.py index 07068a4c..57706c3e 100644 --- a/flexx/app/_server.py +++ b/flexx/app/_server.py @@ -11,7 +11,8 @@ _current_server = None -def create_server(host=None, port=None, new_loop=False, backend='tornado', **server_kwargs): +def create_server(host=None, port=None, new_loop=False, backend='tornado', + **server_kwargs): """ Create a new server object. This is automatically called; users generally don't need this, unless they want to explicitly specify host/port, From a3fff9a36e6c4942590340eda3d6e577718a8290 Mon Sep 17 00:00:00 2001 From: Pierre Gay Date: Tue, 6 Dec 2016 08:23:06 +0100 Subject: [PATCH 03/12] get correct protocol in app.App.url --- flexx/app/_app.py | 3 ++- flexx/app/_server.py | 6 ++++++ flexx/app/_tornadoserver.py | 7 +++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/flexx/app/_app.py b/flexx/app/_app.py index 736366b6..bdf8b0d7 100644 --- a/flexx/app/_app.py +++ b/flexx/app/_app.py @@ -91,8 +91,9 @@ def url(self): raise RuntimeError('Cannot determine app url if the server is not ' 'yet running.') else: + proto = server.protocol host, port = server.serving - return 'http://%s:%i/%s/' % (host, port, self._path) + return '%s://%s:%i/%s/' % (proto, host, port, self._path) @property def name(self): diff --git a/flexx/app/_server.py b/flexx/app/_server.py index 57706c3e..51d6ad8c 100644 --- a/flexx/app/_server.py +++ b/flexx/app/_server.py @@ -176,3 +176,9 @@ def serving(self): Or None if the server is not serving (anymore). """ return self._serving + + @property + def protocol(self): + """ Get a string representing served protocol + """ + raise NotImplementedError diff --git a/flexx/app/_tornadoserver.py b/flexx/app/_tornadoserver.py index 3fd51b24..d36be175 100644 --- a/flexx/app/_tornadoserver.py +++ b/flexx/app/_tornadoserver.py @@ -159,6 +159,13 @@ 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' def port_hash(name): """ Given a string, returns a port number between 49152 and 65535 From f0e0900e5a8c9a09d7c90a400329e22d11285fb4 Mon Sep 17 00:00:00 2001 From: Pierre Gay Date: Tue, 6 Dec 2016 11:37:06 +0100 Subject: [PATCH 04/12] app.init_interactive uses protocol from server --- flexx/app/_funcs.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/flexx/app/_funcs.py b/flexx/app/_funcs.py index ad9b8bd4..a4df2304 100644 --- a/flexx/app/_funcs.py +++ b/flexx/app/_funcs.py @@ -93,9 +93,11 @@ def init_interactive(cls=None, runtime=None): # Launch web runtime, the server will wait for the connection server = current_server() + proto = server.protocol host, port = server.serving - url = '%s:%i/%s/?session_id=%s' % (host, port, session.app_name, session.id) - session._runtime = launch('http://' + url, runtime=runtime) + url = '%s://%s:%i/%s/?session_id=%s' % (proto, host, port, session.app_name, + session.id) + session._runtime = launch(url, runtime=runtime) class NoteBookHelper: From af8d0f6762ea6f2834440664496d3e7575c37c3d Mon Sep 17 00:00:00 2001 From: Pierre Gay Date: Tue, 6 Dec 2016 12:17:12 +0100 Subject: [PATCH 05/12] document ssl configuration --- flexx/app/_server.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/flexx/app/_server.py b/flexx/app/_server.py index 51d6ad8c..f222d2da 100644 --- a/flexx/app/_server.py +++ b/flexx/app/_server.py @@ -34,10 +34,22 @@ def create_server(host=None, port=None, new_loop=False, backend='tornado', which is made current when ``start()`` is called. If ``False`` (default) will use the current IOLoop for this thread. backend (str): Stub argument; only Tornado is currently supported. - **server_kwargs: keyword arguments passed to the server constructor + **server_kwargs: keyword arguments passed to the server constructor. Returns: server: The server object, see ``current_server()``. + + Examples: + + Configuring the server to use HTTPS with Tornado server: + + .. code-block:: py + + app.create_server(ssl_options = {'certfile' : '/path/to/certfile', + 'keyfile' : '/path/to/keyfile'}) + app.serve(Example, 'Example') + app.run() + """ # Lazy load tornado, so that we can use anything we want there without # preventing other parts of flexx.app from using *this* module. From 502effc0591109f16ce92b1a5fa1e1b0a38be36a Mon Sep 17 00:00:00 2001 From: Pierre Gay Date: Tue, 6 Dec 2016 18:19:02 +0100 Subject: [PATCH 06/12] ssl example --- flexx/app/examples/serve_ssl.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 flexx/app/examples/serve_ssl.py diff --git a/flexx/app/examples/serve_ssl.py b/flexx/app/examples/serve_ssl.py new file mode 100644 index 00000000..12f3777c --- /dev/null +++ b/flexx/app/examples/serve_ssl.py @@ -0,0 +1,32 @@ +""" +Flexx can be configured to use SSL. + +This example first creates a self-signed certificate and then uses it to create +a SSL enabled web server (through Tornado ssl_option argument). + +Application served through this server is loaded on the browser with 'https' protocol and its websocket is using 'wss'. +""" + +from flexx import app, event, ui + +# generate self-signed certificate for this example +import os + +CERTFILE = '/tmp/self-signed.crt' +KEYFILE = '/tmp/self-signed.key' + +os.system('openssl req -x509 -nodes -days 1 -batch -newkey rsa:2048 ' + '-keyout %s -out %s' % (KEYFILE, CERTFILE)) + +# Some very secret Model +class Example(ui.Widget): + def init(self): + ui.Button(text = 'Secret Button') + +# configure web server for SSL +app.create_server(ssl_options = {'certfile' : CERTFILE, + 'keyfile' : KEYFILE}) + +# run application +app.serve(Example, 'Example') +app.run() From edf07c322946f71502c35a3e22d50ab8c9dc8287 Mon Sep 17 00:00:00 2001 From: Pierre Gay Date: Tue, 6 Dec 2016 18:21:59 +0100 Subject: [PATCH 07/12] style again --- flexx/app/examples/serve_ssl.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/flexx/app/examples/serve_ssl.py b/flexx/app/examples/serve_ssl.py index 12f3777c..aa0e95ca 100644 --- a/flexx/app/examples/serve_ssl.py +++ b/flexx/app/examples/serve_ssl.py @@ -4,10 +4,11 @@ This example first creates a self-signed certificate and then uses it to create a SSL enabled web server (through Tornado ssl_option argument). -Application served through this server is loaded on the browser with 'https' protocol and its websocket is using 'wss'. +Application served through this server is loaded on the browser with 'https' +protocol and its websocket is using 'wss'. """ -from flexx import app, event, ui +from flexx import app, ui # generate self-signed certificate for this example import os @@ -21,11 +22,11 @@ # Some very secret Model class Example(ui.Widget): def init(self): - ui.Button(text = 'Secret Button') + ui.Button(text='Secret Button') # configure web server for SSL -app.create_server(ssl_options = {'certfile' : CERTFILE, - 'keyfile' : KEYFILE}) +app.create_server(ssl_options={'certfile' : CERTFILE, + 'keyfile' : KEYFILE}) # run application app.serve(Example, 'Example') From 99aac12f547e260af058bd44bd238abadbcb5807 Mon Sep 17 00:00:00 2001 From: Pierre Gay Date: Thu, 8 Dec 2016 09:12:53 +0100 Subject: [PATCH 08/12] configuration variables for ssl cert+key files --- flexx/_config.py | 2 ++ flexx/app/_server.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/flexx/_config.py b/flexx/_config.py index 9a6dc9ba..ac0127a9 100644 --- a/flexx/_config.py +++ b/flexx/_config.py @@ -7,4 +7,6 @@ webruntime=('', str, 'The default web runtime to use. Default is xul/browser.'), ws_timeout=(20, int, 'If the websocket is idle for this amount of seconds, ' 'it is closed.'), + ssl_certfile=('', str, 'cert file for https server.'), + ssl_keyfile=('', str, 'key file for https server.'), ) diff --git a/flexx/app/_server.py b/flexx/app/_server.py index f222d2da..b98103ea 100644 --- a/flexx/app/_server.py +++ b/flexx/app/_server.py @@ -50,6 +50,8 @@ def create_server(host=None, port=None, new_loop=False, backend='tornado', app.serve(Example, 'Example') app.run() + Alternately, cert and key files can be provided through + ``ssl_certfile`` and ``ssl_keyfile`` configuration variables. """ # Lazy load tornado, so that we can use anything we want there without # preventing other parts of flexx.app from using *this* module. From 9a2c4aa8105f675857a1c53124449b542ea5b4fd Mon Sep 17 00:00:00 2001 From: Pierre Gay Date: Thu, 8 Dec 2016 09:16:51 +0100 Subject: [PATCH 09/12] missing file in 74def10 --- flexx/app/_tornadoserver.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/flexx/app/_tornadoserver.py b/flexx/app/_tornadoserver.py index d36be175..4b003f41 100644 --- a/flexx/app/_tornadoserver.py +++ b/flexx/app/_tornadoserver.py @@ -66,7 +66,20 @@ def _get_io_loop(self): 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 'ssl_certfile' in config and 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 'ssl_keyfile' in config and 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 + # Create tornado application self._app = Application([(r"/flexx/ws/(.*)", WSHandler), (r"/flexx/(.*)", MainHandler), From cb69d6757cb049b1b8b0ad29d4ee9b225b958694 Mon Sep 17 00:00:00 2001 From: Pierre Gay Date: Thu, 8 Dec 2016 09:17:50 +0100 Subject: [PATCH 10/12] use configuration variables in example serve_ssl --- flexx/app/examples/serve_ssl.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/flexx/app/examples/serve_ssl.py b/flexx/app/examples/serve_ssl.py index aa0e95ca..745a0c70 100644 --- a/flexx/app/examples/serve_ssl.py +++ b/flexx/app/examples/serve_ssl.py @@ -8,7 +8,7 @@ protocol and its websocket is using 'wss'. """ -from flexx import app, ui +from flexx import app, ui, config # generate self-signed certificate for this example import os @@ -19,15 +19,16 @@ os.system('openssl req -x509 -nodes -days 1 -batch -newkey rsa:2048 ' '-keyout %s -out %s' % (KEYFILE, CERTFILE)) +# use the self-signed certificate as if specified in normal config +config.ssl_certfile = CERTFILE +config.ssl_keyfile = KEYFILE + + # Some very secret Model class Example(ui.Widget): def init(self): ui.Button(text='Secret Button') -# configure web server for SSL -app.create_server(ssl_options={'certfile' : CERTFILE, - 'keyfile' : KEYFILE}) - # run application app.serve(Example, 'Example') -app.run() +app.start() From b292d2df5eeec693f68dc4aa95d794eadce1aded Mon Sep 17 00:00:00 2001 From: Pierre Gay Date: Sat, 10 Dec 2016 18:45:27 +0100 Subject: [PATCH 11/12] remove unneeded test --- flexx/app/_tornadoserver.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flexx/app/_tornadoserver.py b/flexx/app/_tornadoserver.py index 4b003f41..86571d86 100644 --- a/flexx/app/_tornadoserver.py +++ b/flexx/app/_tornadoserver.py @@ -68,13 +68,13 @@ def _open(self, host, port, **kwargs): # run Flexx in e.g. JLab's application. # handle ssl, wether from configuration or given args - if 'ssl_certfile' in config and config.ssl_certfile: + 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 'ssl_keyfile' in config and config.ssl_keyfile: + if config.ssl_keyfile: if 'ssl_options' not in kwargs: kwargs['ssl_options'] = {} if 'keyfile' not in kwargs['ssl_options']: From 92f407c6593f1779e3468b88db096e27a77e37d3 Mon Sep 17 00:00:00 2001 From: Pierre Gay Date: Sat, 10 Dec 2016 18:48:05 +0100 Subject: [PATCH 12/12] no longer the preferred method for ssl configuration --- flexx/app/_server.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/flexx/app/_server.py b/flexx/app/_server.py index b98103ea..a23eec64 100644 --- a/flexx/app/_server.py +++ b/flexx/app/_server.py @@ -38,20 +38,6 @@ def create_server(host=None, port=None, new_loop=False, backend='tornado', Returns: server: The server object, see ``current_server()``. - - Examples: - - Configuring the server to use HTTPS with Tornado server: - - .. code-block:: py - - app.create_server(ssl_options = {'certfile' : '/path/to/certfile', - 'keyfile' : '/path/to/keyfile'}) - app.serve(Example, 'Example') - app.run() - - Alternately, cert and key files can be provided through - ``ssl_certfile`` and ``ssl_keyfile`` configuration variables. """ # Lazy load tornado, so that we can use anything we want there without # preventing other parts of flexx.app from using *this* module.