Skip to content
Snippets Groups Projects
Commit 692d3737 authored by Sander Mathijs van Veen's avatar Sander Mathijs van Veen
Browse files

Implemented begin of client handling.

parent 77cb903f
No related branches found
No related tags found
No related merge requests found
......@@ -141,6 +141,7 @@ class CLI:
# The chat connection is ran in a separate thread.
self.connection_thread = threading.Thread()
self.connection_thread.daemon = True
self.connection_thread.run = main.connection.init_loop
self.connection_thread.start()
......@@ -152,6 +153,8 @@ class CLI:
and the 'Offline' message is set to the info bar.
"""
self.connection.close()
del self.connection_thread
self.connection = None
self.display_info('Offline. Type "/connect HOST" to connect' \
+ ' to another chat server.')
......@@ -204,13 +207,6 @@ All commands listed below should be preceded by a slash:
main.chat_window.window.refresh()
def quit(main):
# Disconnect the connection
e = None
try:
del self.connection
except Exception, e:
pass
# Reverse the curses-friendly terminal settings.
curses.nocbreak();
self.stdscr.keypad(0);
......@@ -219,9 +215,10 @@ All commands listed below should be preceded by a slash:
# Restore the terminal to its original operating mode.
curses.endwin()
if e:
raise e
# Disconnect the connection
if hasattr(self, 'connection_thread'):
del self.connection_thread
self.connection = None
sys.exit(0)
def raw(main, cmd):
......
......@@ -4,6 +4,7 @@ import asyncore
import logging
import logging.config
import os
import re
import socket
import sys
......@@ -14,6 +15,12 @@ GREETING_MSG = 'Hi there!'
MAJOR_VERSION = 1
MINOR_VERSION = 0
class SocketError(RuntimeError):
pass
class ClientData(object):
pass
class Server(asyncore.dispatcher):
"""
Basic server which will listen on an host address and port. The given
......@@ -29,16 +36,17 @@ class Server(asyncore.dispatcher):
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.set_reuse_addr()
self.bind((ip, port))
logging.config.fileConfig('logging.conf')
self.log = logging.getLogger('Server')
self.set_reuse_addr()
self.bind((ip, port))
self.log.info('waiting for incoming connections on port %s.' % port)
self.listen(5)
self.active_clients = {}
# Dictonary which maps ip/port tuple to client data object.
self.clients = {}
def handle_accept(self):
"""
......@@ -48,6 +56,7 @@ class Server(asyncore.dispatcher):
try:
conn, addr = self.accept()
self.log.info('accepted client %s:%d' % addr)
client = self.connect_client(addr)
except socket.error:
self.log.warning('warning: server accept() threw an exception.')
return
......@@ -56,14 +65,26 @@ class Server(asyncore.dispatcher):
return
# creates an instance of the handler class to handle the
# request/response on the incoming connection.
self.handler(conn, addr, self)
self.handler(conn, addr, client, self)
def connect_client(self, addr):
self.clients['%s:%d' % addr] = ClientData()
return self.clients['%s:%d' % addr]
def disconnect_client(self, addr):
del self.clients['%s:%d' % addr]
def change_username(self, addr, username):
self.clients['%s:%d' % addr].username = username
# TODO: notify other clients of changed username
class RequestHandler(AsyncBase, asyncore.dispatcher):
def __init__(self, conn, address, server):
def __init__(self, conn, address, client, server):
AsyncBase.__init__(self)
asyncore.dispatcher.__init__(self, conn)
self.address = address
self.client = client
self.server = server
self.log = self.server.log
......@@ -75,26 +96,65 @@ class RequestHandler(AsyncBase, asyncore.dispatcher):
def handle_read(self):
buf = ''
# Receive a message from the client.
while True:
chunk = self.recv(1)
if not chunk:
raise RuntimeError('socket connection broken')
elif chunk == '\n' and buf[-1] == '\r':
break
buf += chunk
# Receive a message from the client. If the connection is somehow
# broken, disconnect the client and clean the corresponding data.
try:
while True:
chunk = self.recv(1)
if not chunk:
raise SocketError('socket connection broken')
elif chunk == '\n' and buf[-1] == '\r':
break
buf += chunk
except SocketError:
self.log.info('client %s:%d disconnected or socket is broken.' \
% self.address)
self.server.disconnect_client(self.address)
return
# Received a message, so it's time to parse the message.
buf = buf[:-1]
self.debug_log('< %s' % buf)
self.send_positive('Ok')
#self.send_negative('Ok')
self.parse_response(buf)
def parse_response(self, buf):
"""
>>> class DummyServer(object):
... def __init__(self): self.log = None
... def change_username(self, addr, username): pass
>>> req = RequestHandler(None, None, None, DummyServer())
>>> assert req.parse_response('CHAT')
>>> assert req.parse_response('USER foo')
>>> # Some error handling
>>> assert not req.parse_response('CHAT ') # Client must send CHAT
>>> assert not req.parse_response('USER') # No username given.
>>> assert not req.parse_response('USER j/k') # username has a /
"""
cmd = buf.split(' ')[0]
if buf == 'CHAT':
# Client wants to chat.
return self.send_positive('Ok')
if cmd == 'USER':
# User changes/sets its username.
if re.match(ur'^[^\u0000-\u001f\u007f-\u009f/:]+$', buf[5:]):
self.set_username(buf[5:])
return self.send_positive('Ok')
return self.send_negative('Invalid username.')
return self.send_negative('Unsupported command.')
def send_positive(self, msg):
self.send_raw('+%s' % msg)
return True
def send_negative(self, msg):
self.send_raw('-%s' % msg)
return False
def set_username(self, username):
self.server.change_username(self.address, username)
if __name__ == '__main__':
if len(sys.argv) != 3:
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment