Implemented begin of client handling.

parent 77cb903f
...@@ -141,6 +141,7 @@ class CLI: ...@@ -141,6 +141,7 @@ class CLI:
# The chat connection is ran in a separate thread. # The chat connection is ran in a separate thread.
self.connection_thread = threading.Thread() self.connection_thread = threading.Thread()
self.connection_thread.daemon = True
self.connection_thread.run = main.connection.init_loop self.connection_thread.run = main.connection.init_loop
self.connection_thread.start() self.connection_thread.start()
...@@ -152,6 +153,8 @@ class CLI: ...@@ -152,6 +153,8 @@ class CLI:
and the 'Offline' message is set to the info bar. and the 'Offline' message is set to the info bar.
""" """
self.connection.close()
del self.connection_thread
self.connection = None self.connection = None
self.display_info('Offline. Type "/connect HOST" to connect' \ self.display_info('Offline. Type "/connect HOST" to connect' \
+ ' to another chat server.') + ' to another chat server.')
...@@ -204,13 +207,6 @@ All commands listed below should be preceded by a slash: ...@@ -204,13 +207,6 @@ All commands listed below should be preceded by a slash:
main.chat_window.window.refresh() main.chat_window.window.refresh()
def quit(main): def quit(main):
# Disconnect the connection
e = None
try:
del self.connection
except Exception, e:
pass
# Reverse the curses-friendly terminal settings. # Reverse the curses-friendly terminal settings.
curses.nocbreak(); curses.nocbreak();
self.stdscr.keypad(0); self.stdscr.keypad(0);
...@@ -219,9 +215,10 @@ All commands listed below should be preceded by a slash: ...@@ -219,9 +215,10 @@ All commands listed below should be preceded by a slash:
# Restore the terminal to its original operating mode. # Restore the terminal to its original operating mode.
curses.endwin() curses.endwin()
if e: # Disconnect the connection
raise e if hasattr(self, 'connection_thread'):
del self.connection_thread
self.connection = None
sys.exit(0) sys.exit(0)
def raw(main, cmd): def raw(main, cmd):
......
...@@ -4,6 +4,7 @@ import asyncore ...@@ -4,6 +4,7 @@ import asyncore
import logging import logging
import logging.config import logging.config
import os import os
import re
import socket import socket
import sys import sys
...@@ -14,6 +15,12 @@ GREETING_MSG = 'Hi there!' ...@@ -14,6 +15,12 @@ GREETING_MSG = 'Hi there!'
MAJOR_VERSION = 1 MAJOR_VERSION = 1
MINOR_VERSION = 0 MINOR_VERSION = 0
class SocketError(RuntimeError):
pass
class ClientData(object):
pass
class Server(asyncore.dispatcher): class Server(asyncore.dispatcher):
""" """
Basic server which will listen on an host address and port. The given Basic server which will listen on an host address and port. The given
...@@ -29,16 +36,17 @@ class Server(asyncore.dispatcher): ...@@ -29,16 +36,17 @@ class Server(asyncore.dispatcher):
self.create_socket(socket.AF_INET, socket.SOCK_STREAM) self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.set_reuse_addr()
self.bind((ip, port))
logging.config.fileConfig('logging.conf') logging.config.fileConfig('logging.conf')
self.log = logging.getLogger('Server') 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.log.info('waiting for incoming connections on port %s.' % port)
self.listen(5) self.listen(5)
self.active_clients = {} # Dictonary which maps ip/port tuple to client data object.
self.clients = {}
def handle_accept(self): def handle_accept(self):
""" """
...@@ -48,6 +56,7 @@ class Server(asyncore.dispatcher): ...@@ -48,6 +56,7 @@ class Server(asyncore.dispatcher):
try: try:
conn, addr = self.accept() conn, addr = self.accept()
self.log.info('accepted client %s:%d' % addr) self.log.info('accepted client %s:%d' % addr)
client = self.connect_client(addr)
except socket.error: except socket.error:
self.log.warning('warning: server accept() threw an exception.') self.log.warning('warning: server accept() threw an exception.')
return return
...@@ -56,14 +65,26 @@ class Server(asyncore.dispatcher): ...@@ -56,14 +65,26 @@ class Server(asyncore.dispatcher):
return return
# creates an instance of the handler class to handle the # creates an instance of the handler class to handle the
# request/response on the incoming connection. # 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): class RequestHandler(AsyncBase, asyncore.dispatcher):
def __init__(self, conn, address, server): def __init__(self, conn, address, client, server):
AsyncBase.__init__(self) AsyncBase.__init__(self)
asyncore.dispatcher.__init__(self, conn) asyncore.dispatcher.__init__(self, conn)
self.address = address self.address = address
self.client = client
self.server = server self.server = server
self.log = self.server.log self.log = self.server.log
...@@ -75,26 +96,65 @@ class RequestHandler(AsyncBase, asyncore.dispatcher): ...@@ -75,26 +96,65 @@ class RequestHandler(AsyncBase, asyncore.dispatcher):
def handle_read(self): def handle_read(self):
buf = '' buf = ''
# Receive a message from the client. # Receive a message from the client. If the connection is somehow
# broken, disconnect the client and clean the corresponding data.
try:
while True: while True:
chunk = self.recv(1) chunk = self.recv(1)
if not chunk: if not chunk:
raise RuntimeError('socket connection broken') raise SocketError('socket connection broken')
elif chunk == '\n' and buf[-1] == '\r': elif chunk == '\n' and buf[-1] == '\r':
break break
buf += chunk 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] buf = buf[:-1]
self.debug_log('< %s' % buf) 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): def send_positive(self, msg):
self.send_raw('+%s' % msg) self.send_raw('+%s' % msg)
return True
def send_negative(self, msg): def send_negative(self, msg):
self.send_raw('-%s' % msg) self.send_raw('-%s' % msg)
return False
def set_username(self, username):
self.server.change_username(self.address, username)
if __name__ == '__main__': if __name__ == '__main__':
if len(sys.argv) != 3: 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