Skip to content

Commit

Permalink
Merge pull request #307 from pigay/ssl
Browse files Browse the repository at this point in the history
add support for HTTPS protocol
  • Loading branch information
almarklein committed Dec 10, 2016
2 parents 0f0ac04 + 92f407c commit 1fa5b58
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 16 deletions.
2 changes: 2 additions & 0 deletions flexx/_config.py
Expand Up @@ -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.'),
)
3 changes: 2 additions & 1 deletion flexx/app/_app.py
Expand Up @@ -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):
Expand Down
5 changes: 4 additions & 1 deletion flexx/app/_clientcore.py
Expand Up @@ -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)
Expand Down
6 changes: 4 additions & 2 deletions flexx/app/_funcs.py
Expand Up @@ -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:
Expand Down
18 changes: 13 additions & 5 deletions flexx/app/_server.py
Expand Up @@ -11,7 +11,8 @@
_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,
Expand All @@ -33,6 +34,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()``.
Expand All @@ -53,7 +55,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)
Expand Down Expand Up @@ -120,10 +122,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

Expand Down Expand Up @@ -151,7 +153,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):
Expand All @@ -174,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
37 changes: 30 additions & 7 deletions flexx/app/_tornadoserver.py
Expand Up @@ -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
Expand All @@ -63,17 +63,30 @@ 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.


# 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

# Create tornado application
self._app = Application([(r"/flexx/ws/(.*)", WSHandler),
(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
Expand All @@ -100,7 +113,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
Expand Down Expand Up @@ -156,6 +172,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
Expand Down
34 changes: 34 additions & 0 deletions flexx/app/examples/serve_ssl.py
@@ -0,0 +1,34 @@
"""
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, ui, config

# 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))

# 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')

# run application
app.serve(Example, 'Example')
app.start()

0 comments on commit 1fa5b58

Please sign in to comment.