Implemented begin of client handling.

parent 77cb903f
......@@ -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:
......
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