Telematica: minor bug fixes.

parent 10ebfad0
...@@ -110,9 +110,8 @@ class ClientConnection(object, AsyncBase, asyncore.dispatcher): ...@@ -110,9 +110,8 @@ class ClientConnection(object, AsyncBase, asyncore.dispatcher):
try: try:
chunk = self.recv(1) chunk = self.recv(1)
except socket.error, e: except socket.error, e:
# Suppress "Resource temporarily unavailable" exceptions. # Handle "Resource temporarily unavailable" exceptions.
if e.errno == EAGAIN: if e.errno == EAGAIN:
# Wait 5 ms
import time import time
time.sleep(0.005) time.sleep(0.005)
continue continue
...@@ -207,7 +206,7 @@ class ClientConnection(object, AsyncBase, asyncore.dispatcher): ...@@ -207,7 +206,7 @@ class ClientConnection(object, AsyncBase, asyncore.dispatcher):
Helper function used to parse the server notifications. This way, Helper function used to parse the server notifications. This way,
message parsing is separated from the notification logic. message parsing is separated from the notification logic.
""" """
match = re.match('^SAY ([^\r\n:/]+)/(.+)', buf) match = re.match('^SAY ([^\x00-\x1F:/]+)/([^\x00-\x1F]+)', buf)
if match: if match:
if 'message' in self.event_list: if 'message' in self.event_list:
self.event_list['message'](self, match.group(1), match.group(2)) self.event_list['message'](self, match.group(1), match.group(2))
......
...@@ -32,7 +32,8 @@ Since some functionality of the chat connection is used in both the client and ...@@ -32,7 +32,8 @@ Since some functionality of the chat connection is used in both the client and
server, it is wise to create a base class containing those methods: *AsyncBase*. server, it is wise to create a base class containing those methods: *AsyncBase*.
We used the *asyncore.dispatcher* class (part of the python standard library) to We used the *asyncore.dispatcher* class (part of the python standard library) to
create a nice abstraction layer on top of the socket. The abstraction layer is create a nice abstraction layer on top of the socket. The abstraction layer is
event-oriented and provides various event callback functions: event-oriented and provides the following event callback functions (among some
other less relevant ones):
1. **handle_write**. 1. **handle_write**.
...@@ -76,6 +77,12 @@ connection does take messages from the queue, if its local message buffer is ...@@ -76,6 +77,12 @@ connection does take messages from the queue, if its local message buffer is
empty (which means all its data is sent to the server or the connection is empty (which means all its data is sent to the server or the connection is
just initialised). just initialised).
The protocol uses the Backus-Naur form and therefore, we chose regular
expressions to validate incoming messages from the server. Regexes are easier to
maintain than handwritten parsers, since they allow you to check for certain
ranges of characters (for example, the control character range is
**[\\x00-\\x1F]**).
Text-based interface (using curses) Text-based interface (using curses)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
...@@ -191,5 +198,11 @@ We implemented the following commands: ...@@ -191,5 +198,11 @@ We implemented the following commands:
Server side Server side
---------- ----------
Foo Multiple client handling
~~~~~~~~~~~~~~~~~~~~~~~~
The class *Server* accepts the incoming connections from clients and initialises
a request handler (see class *RequestHandler*) for each accepted connection. The
servers logs all its information, warnings and errors to stdout. To change the
behaviour of the server log, alter the configuration file *logging.conf*.
...@@ -32,6 +32,9 @@ class Server(asyncore.dispatcher): ...@@ -32,6 +32,9 @@ class Server(asyncore.dispatcher):
self.port = port self.port = port
self.handler = handler self.handler = handler
# Dictonary which maps ip/port tuple to client data object.
self.clients = {}
logging.config.fileConfig('logging.conf') logging.config.fileConfig('logging.conf')
self.log = logging.getLogger('Server') self.log = logging.getLogger('Server')
...@@ -42,9 +45,6 @@ class Server(asyncore.dispatcher): ...@@ -42,9 +45,6 @@ class Server(asyncore.dispatcher):
self.log.info('waiting for incoming connections on port %s.' % port) self.log.info('waiting for incoming connections on port %s.' % port)
self.listen(5) self.listen(5)
# Dictonary which maps ip/port tuple to client data object.
self.clients = {}
def handle_accept(self): def handle_accept(self):
""" """
Handle the incoming connection's request. Handle the incoming connection's request.
...@@ -149,12 +149,27 @@ class RequestHandler(AsyncBase, asyncore.dispatcher): ...@@ -149,12 +149,27 @@ class RequestHandler(AsyncBase, asyncore.dispatcher):
self.parse_response(buf) self.parse_response(buf)
def handle_error(self): def handle_error(self):
import sys
t, v, tb = sys.exc_info()
tbinfo = []
while tb:
tbinfo.append((
tb.tb_frame.f_code.co_filename,
tb.tb_frame.f_code.co_name,
str(tb.tb_lineno)
))
tb = tb.tb_next
file, function, line = tbinfo[-1]
self.log.error('%s raised "%s" in %s:%s' \
% (str(v), function, file, line))
self.log.error(' '.join(['[%s|%s|%s]' % x for x in tbinfo]))
self.server.disconnect_client(self.address) self.server.disconnect_client(self.address)
def parse_response(self, buf): def parse_response(self, buf):
""" """
>>> class DummyServer(object): >>> class DummyServer(object):
... def __init__(self): self.log = None ... def __init__(self): self.log = None; self.clients = {}
... def change_username(self, addr, username): pass ... def change_username(self, addr, username): pass
... def send_all(self, msg, sender): pass ... def send_all(self, msg, sender): pass
>>> req = RequestHandler(None, None, None, DummyServer()) >>> req = RequestHandler(None, None, None, DummyServer())
...@@ -171,14 +186,20 @@ class RequestHandler(AsyncBase, asyncore.dispatcher): ...@@ -171,14 +186,20 @@ class RequestHandler(AsyncBase, asyncore.dispatcher):
# Client wants to chat. # Client wants to chat.
return self.send_positive('Ok') return self.send_positive('Ok')
if cmd == 'USER': if cmd == 'USER':
# User changes/sets its username. # User changes/sets its username. Make sure the username is not
if re.match(ur'^[^\u0000-\u001f\u007f-\u009f/:]+$', buf[5:]): # already taken. If the name is valid, notify the other clients.
name = buf[5:]
for c in self.server.clients:
if self.server.clients[c].username == name \
and self.server.clients[c].handler != self:
return self.send_negative('Username already taken.')
if re.match(ur'^[^\u0000-\u001f\u007f-\u009f/:]+$', name):
if not self.username: if not self.username:
self.send_all('JOIN %s' % buf[5:], True) self.send_all('JOIN %s' % name, True)
else: else:
self.send_all('RENAME %s/%s' \ self.send_all('RENAME %s/%s' \
% (self.username, buf[5:]), True) % (self.username, name), True)
self.set_username(buf[5:]) self.set_username(name)
return self.send_positive('Ok') return self.send_positive('Ok')
return self.send_negative('Invalid username.') return self.send_negative('Invalid username.')
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment