Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Https and SIGINT handling #201

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,13 @@ Just define the parameters **username** and **password** in the start call:
start(MyApp, username='myusername', password='mypassword')
```

Protocol security
===
In order to support login forms and data forms you can choose to use https protocol by giving a .pem certificate.
To enable https pass the **https** and **certfile** parameters in the start call
```py
start(MyApp, https=True, certfile="./server.pem")
```

Styling
===
Expand Down
74 changes: 74 additions & 0 deletions examples/https_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@

import remi.gui as gui
from remi.gui import *
from remi import start, App


class untitled(App):
def __init__(self, *args, **kwargs):
if not 'editing_mode' in kwargs.keys():
super(untitled, self).__init__(*args, static_file_path='./res/')

def idle(self):
#idle function called every update cycle
pass

def main(self):
mainContainer = Widget(width=706, height=445, margin='0px auto', style="position: relative")
subContainer = HBox(width=630, height=277, style='position: absolute; left: 40px; top: 150px; background-color: #b6b6b6')
vbox = VBox(width=300, height=250)
bt1 = Button('bt1', width=100, height=30)
vbox.append(bt1,'bt1')
bt3 = Button('bt3', width=100, height=30)
vbox.append(bt3,'bt3')
bt2 = Button('bt2', width=100, height=30)
vbox.append(bt2,'bt2')
subContainer.append(vbox,'vbox')
hbox = HBox(width=300, height=250)
lbl1 = Label('lbl1', width=50, height=50, style='background-color: #ffb509')
hbox.append(lbl1,'lbl1')
lbl2 = Label('lbl2', width=50, height=50, style='background-color: #40ff2b')
hbox.append(lbl2,'lbl2')
lbl3 = Label('lbl3', width=50, height=50, style='background-color: #e706ff')
hbox.append(lbl3,'lbl3')
subContainer.append(hbox,'hbox')
mainContainer.append(subContainer,'subContainer')
comboJustifyContent = gui.DropDown.new_from_list(('flex-start','flex-end','center','space-between','space-around'),
style='left: 160px; position: absolute; top: 60px; width: 148px; height: 30px')
mainContainer.append(comboJustifyContent,'comboJustifyContent')
lblJustifyContent = Label('justify-content', style='left: 40px; position: absolute; top: 60px; width: 100px; height: 30px')
mainContainer.append(lblJustifyContent,'lblJustifyContent')
comboAlignItems = gui.DropDown.new_from_list(('stretch','center','flex-start','flex-end','baseline'),
style='left:160px; position:absolute; top:100px; width:152px; height: 30px')
mainContainer.append(comboAlignItems,'comboAlignItems')
lblAlignItems = Label('align-items', style='left:40px; position:absolute; top:100px; width:100px; height:30px')
mainContainer.append(lblAlignItems,'lblAlignItems')
mainContainer.children['comboJustifyContent'].set_on_change_listener(self.onchange_comboJustifyContent,vbox,hbox)
mainContainer.children['comboAlignItems'].set_on_change_listener(self.onchange_comboAlignItems,vbox,hbox)

lblTitle = gui.Label("The following example shows the two main layout style properties for the VBox and HBox containers. Change the value of the two combo boxes.",
style='position:absolute; left:0px; top:0px')
mainContainer.append(lblTitle)

self.mainContainer = mainContainer
return self.mainContainer

def onchange_comboJustifyContent(self,emitter,new_value,vbox,hbox):
vbox.style['justify-content'] = new_value
hbox.style['justify-content'] = new_value

def onchange_comboAlignItems(self,emitter,new_value,vbox,hbox):
vbox.style['align-items'] = new_value
hbox.style['align-items'] = new_value



#Configuration
configuration = {'config_enable_file_cache': True, 'config_multiple_instance': True, 'config_port': 8081, 'config_address': '0.0.0.0', 'config_start_browser': True, 'config_project_name': 'untitled', 'config_resourcepath': './res/'}

if __name__ == "__main__":

#on linux generate server.pen with
#openssl req -new -x509 -keyout server.pem -out server.pem -days 365 -nodes

start(untitled, address=configuration['config_address'], port=configuration['config_port'], multiple_instance=configuration['config_multiple_instance'], enable_file_cache=configuration['config_enable_file_cache'],start_browser=configuration['config_start_browser'], https=True, certfile="./server.pem")
3 changes: 3 additions & 0 deletions genCertificate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash

openssl req -new -x509 -keyout server.pem -out server.pem -days 365 -nodes
32 changes: 24 additions & 8 deletions remi/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import time
import os
import re
import ssl
try:
from urllib import unquote
from urllib import quote
Expand Down Expand Up @@ -916,8 +917,13 @@ class ThreadedHTTPServer(socketserver.ThreadingMixIn, HTTPServer):
def __init__(self, server_address, RequestHandlerClass, websocket_address,
auth, multiple_instance, enable_file_cache, update_interval,
websocket_timeout_timer_ms, host_name, pending_messages_queue_length,
title, *userdata):
title, https, certfile, *userdata):
HTTPServer.__init__(self, server_address, RequestHandlerClass)
if https:
try:
self.socket = ssl.wrap_socket(self.socket, certfile=certfile, server_side=True)
except Exception as e:
logging.warn("Cannot start https server %s", str(e))
self.websocket_address = websocket_address
self.auth = auth
self.multiple_instance = multiple_instance
Expand All @@ -935,7 +941,7 @@ class Server(object):
def __init__(self, gui_class, title='', start=True, address='127.0.0.1', port=8081, username=None, password=None,
multiple_instance=False, enable_file_cache=True, update_interval=0.1, start_browser=True,
websocket_timeout_timer_ms=1000, websocket_port=0, host_name=None,
pending_messages_queue_length=1000, userdata=()):
pending_messages_queue_length=1000, userdata=(), https=False, certfile=None, ignoreSIGINT=False):
global http_server_instance
http_server_instance = self

Expand All @@ -955,6 +961,7 @@ def __init__(self, gui_class, title='', start=True, address='127.0.0.1', port=80
self._host_name = host_name
self._pending_messages_queue_length = pending_messages_queue_length
self._userdata = userdata

if username and password:
self._auth = base64.b64encode(encode_text("%s:%s" % (username, password)))
else:
Expand All @@ -963,12 +970,18 @@ def __init__(self, gui_class, title='', start=True, address='127.0.0.1', port=80
if not isinstance(userdata, tuple):
raise ValueError('userdata must be a tuple')

if https:
if certfile is None:
raise ValueError('server certificate must be used')



self._log = logging.getLogger('remi.server')
self._alive = True
if start:
self._myid = threading.Thread.ident
self.start()
self.serve_forever()
self.start(https, certfile)
self.serve_forever(ignoreSIGINT)

@property
def title(self):
Expand All @@ -978,7 +991,7 @@ def title(self):
def address(self):
return self._base_address

def start(self):
def start(self, https, certfile):
# here the websocket is started on an ephemereal port
self._wsserver = ThreadedWebsocketServer((self._address, self._websocket_port), WebSocketsHandler,
self._multiple_instance)
Expand All @@ -995,12 +1008,14 @@ def start(self):
self._multiple_instance, self._enable_file_cache,
self._update_interval, self._websocket_timeout_timer_ms,
self._host_name, self._pending_messages_queue_length,
self._title, *self._userdata)
self._title, https, certfile, *self._userdata)
shost, sport = self._sserver.socket.getsockname()[:2]
# when listening on multiple net interfaces the browsers connects to localhost
if shost == '0.0.0.0':
shost = '127.0.0.1'
self._base_address = 'http://%s:%s/' % (shost,sport)
if(https):
self._base_address = 'https://%s:%s/' % (shost,sport)
self._log.info('Started httpserver %s' % self._base_address)
if self._start_browser:
try:
Expand All @@ -1016,15 +1031,15 @@ def start(self):
self._sth.daemon = False
self._sth.start()

def serve_forever(self):
def serve_forever(self, ignoreSIGINT=False):
# we could join on the threads, but join blocks all interupts (including
# ctrl+c, so just spin here
# noinspection PyBroadException
try:
def sig_ignore(sig, _):
self._log.info('*** signal %d ignored.' % sig)
return signal.SIG_IGN
signal.signal(signal.SIGINT, sig_ignore)
if ignoreSIGINT: signal.signal(signal.SIGINT, sig_ignore)
while self._alive:
signal.pause()
self._log.debug(' ** signal received')
Expand Down Expand Up @@ -1085,6 +1100,7 @@ def start(main_gui_class, **kwargs):
logging.getLogger('remi').setLevel(
level=logging.DEBUG if debug else logging.INFO)


if standalone:
s = StandaloneServer(main_gui_class, start=True, **kwargs)
else:
Expand Down
Loading