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

C9 #200

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open

C9 #200

Changes from 1 commit
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
59 changes: 44 additions & 15 deletions remi/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,11 +133,11 @@ def __init__(self, *args, **kwargs):
def setup(self):
global clients
socketserver.StreamRequestHandler.setup(self)
self._log.info('connection established: %r' % (self.client_address,))
self._log.info('WebSocket connection established: %r' % (self.client_address,))
self.handshake_done = False

def handle(self):
self._log.debug('handle')
self._log.debug('WebSocket handle')
# on some systems like ROS, the default socket timeout
# is less than expected, we force it to infinite (None) as default socket value
self.request.settimeout(None)
Expand All @@ -147,10 +147,15 @@ def handle(self):
else:
if not self.read_next_message():
k = get_instance_key(self)
clients[k].websockets.remove(self)
self.handshake_done = False
self._log.debug('ws ending websocket service')
break
try:
clients[k].websockets.remove(self)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello @dirkcgrunwald !
Looking at changes you made, I suppose that beside others, these 8 grouped lines (from 150 to 158) are the necessary part to be C9 compatible. Others are refinements. Is this correct?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are 2-3 others parts that are needed.

In if 'C9_HOSTNAME' in os.environ:

  •        key = data.decode().split('sec-websocket-key: ')[1].split('\r\n')[0]
    
  •    else:
    
  •        key = data.decode().split('Sec-WebSocket-Key: ')[1].split('\r\n')[0]
    

it appears that the C9 proxy makes the response headers in lower case. It's also possible to use a try...except to try the upper case, fail and then revert to lower case.

In addition, in the default setup, if you use '0.0.0.0' as the host IP, remi changes this to 127.0.0.1, which isn't proxied. The user doesn't know the IP address of their server because of the proxy, so they need to leave '0.0.0.0' as an option. I had guarded those changes with checks for the C9 environment.

This may all be moot since C9 is being changed to AWS Cloud and I think those don't use the proxy.

self.handshake_done = False
self._log.debug('ws ending websocket service')
break
except:
self.handshake_done = True
self._log.debug('Tried to remove self when not in client...')
return

@staticmethod
def bytetonum(b):
Expand All @@ -165,6 +170,10 @@ def read_next_message(self):
length = self.rfile.read(2)
except ValueError:
# socket was closed, just return without errors
self._log.debug('Socket was closed, returning without errors')
return False
self._log.debug('WS response, length is %r' % length)
if len(length) < 1:
return False
length = self.bytetonum(length[1]) & 127
if length == 126:
Expand All @@ -175,10 +184,13 @@ def read_next_message(self):
decoded = ''
for char in self.rfile.read(length):
decoded += chr(self.bytetonum(char) ^ masks[len(decoded) % 4])
self._log.debug('Decoded WebSocket message %r' % decoded)
self.on_message(from_websocket(decoded))
except socket.timeout:
self._log.debug('WebSocket timeout')
return False
except Exception:
except Exception as ex:
self._log.debug('Other exception %r' % ex )
return False
return True

Expand Down Expand Up @@ -211,17 +223,25 @@ def send_message(self, message):
self.request.send(out)

def handshake(self):
self._log.debug('handshake')
self._log.debug('WebSocket handshake')
data = self.request.recv(1024).strip()
key = data.decode().split('Sec-WebSocket-Key: ')[1].split('\r\n')[0]
#self._log.debug('WebSocket handshake data is %s' % data.decode())
#
# Code used to look for Sec-WebSocket-Key, some clients respond in lower case
#
if 'C9_HOSTNAME' in os.environ:
key = data.decode().split('sec-websocket-key: ')[1].split('\r\n')[0]
else:
key = data.decode().split('Sec-WebSocket-Key: ')[1].split('\r\n')[0]
digest = hashlib.sha1((key.encode("utf-8")+self.magic))
self._log.debug('key is ' + key)
digest = digest.digest()
digest = base64.b64encode(digest)
response = 'HTTP/1.1 101 Switching Protocols\r\n'
response += 'Upgrade: websocket\r\n'
response += 'Connection: Upgrade\r\n'
response += 'Sec-WebSocket-Accept: %s\r\n\r\n' % digest.decode("utf-8")
self._log.info('handshake complete')
self._log.info('WebSocket handshake complete')
self.request.sendall(response.encode("utf-8"))
self.handshake_done = True

Expand Down Expand Up @@ -345,7 +365,6 @@ def run(self):

# noinspection PyPep8Naming
class App(BaseHTTPRequestHandler, object):

"""
This class will handles any incoming request from the browser
The main application class can subclass this
Expand Down Expand Up @@ -382,6 +401,7 @@ def log_message(self, format_string, *args):
def log_error(self, format_string, *args):
msg = format_string % args
self._log.error("%s %s" % (self.address_string(), msg))
raise('Exception')

def _instance(self):
global clients
Expand Down Expand Up @@ -636,6 +656,8 @@ def _instance(self):
};
</script>""" % (net_interface_ip, wsport, pending_messages_queue_length, websocket_timeout_timer_ms)

self._log.debug('Prpeare javascript with interface %s and port %s' % (net_interface_ip, wsport))

# add built in js, extend with user js
clients[k].js_body_end += ('\n' + '\n'.join(self._get_list_from_app_args('js_body_end')))
# use the default css, but append a version based on its hash, to stop browser caching
Expand Down Expand Up @@ -932,9 +954,14 @@ def __init__(self, server_address, RequestHandlerClass, websocket_address,

class Server(object):
# noinspection PyShadowingNames
def __init__(self, gui_class, title='', start=True, address='127.0.0.1', port=8081, username=None, password=None,
def __init__(self, gui_class, title='', start=True,
address=os.getenv('IP','0.0.0.0'),
port=int(os.getenv('PORT',8080)),
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,
websocket_timeout_timer_ms=1000,
websocket_port=int(os.getenv('PORT',8080))+1,
host_name=os.getenv('C9_HOSTNAME', None),
pending_messages_queue_length=1000, userdata=()):
global http_server_instance
http_server_instance = self
Expand Down Expand Up @@ -998,8 +1025,10 @@ def start(self):
self._title, *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':
if shost == '0.0.0.0' and 'C9_IP' not in os.environ:
shost = '127.0.0.1'
else:
os.environ['BROWSER'] = '/bin/true' # prevent curses based display not visible on c9
self._base_address = 'http://%s:%s/' % (shost,sport)
self._log.info('Started httpserver %s' % self._base_address)
if self._start_browser:
Expand Down Expand Up @@ -1051,7 +1080,7 @@ def stop(self):
class StandaloneServer(Server):
def __init__(self, gui_class, title='', width=800, height=600, resizable=True, fullscreen=False, start=True,
userdata=()):
Server.__init__(self, gui_class, title=title, start=False, address='127.0.0.1', port=0, username=None,
Server.__init__(self, gui_class, title=title, start=False, address='0.0.0.0', port=0, username=None,
password=None,
multiple_instance=False, enable_file_cache=True, update_interval=0.1, start_browser=False,
websocket_timeout_timer_ms=1000, websocket_port=0, host_name=None,
Expand Down