Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
.idea/
.idea/
__pycache__/
Empty file added messenger/Tests/__init__.py
Empty file.
30 changes: 30 additions & 0 deletions messenger/Tests/test_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import os
import sys
import unittest

sys.path.append(os.path.join(os.getcwd(), '..'))
from common.settings import RESPONSE, ERROR, USER, ACCOUNT_NAME, TIME, ACTION, PRESENCE
from client import Client


class TestClass(unittest.TestCase):
def setUp(self):
self.client = Client()

def test_def_presense(self):
test = self.client.presence()
test[TIME] = 5.2
self.assertEqual(test, {ACTION: PRESENCE, TIME: 5.2, USER: {ACCOUNT_NAME: 'Guest'}})

def test_200_ans(self):
self.assertEqual(self.client.response({RESPONSE: 200}), 'Соединение установлено')

def test_400_ans(self):
self.assertEqual(self.client.response({RESPONSE: 400, ERROR: 'Bad Request'}), 'Ошибка соединения с сервером: Bad Request')

def test_no_response(self):
self.assertRaises(ValueError, self.client.response, {ERROR: 'Bad Request'})


if __name__ == '__main__':
unittest.main()
43 changes: 43 additions & 0 deletions messenger/Tests/test_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import os
import sys
import unittest

sys.path.append(os.path.join(os.getcwd(), '..'))
from common.settings import RESPONSE, ERROR, USER, ACCOUNT_NAME, TIME, ACTION, PRESENCE
from server import Server


class TestServer(unittest.TestCase):
err_dict = {
RESPONSE: 400,
ERROR: 'Bad Request'
}
ok_dict = {RESPONSE: 200}

def setUp(self):
self.server = Server()

def test_ok_check(self):
self.assertEqual(self.server.process({ACTION: PRESENCE, TIME: 1.1, USER: {ACCOUNT_NAME: 'Guest'}}),
self.ok_dict)

def test_no_action(self):
self.assertEqual(self.server.process({TIME: '1.1', USER: {ACCOUNT_NAME: 'Guest'}}), self.err_dict)

def test_wrong_action(self):
self.assertEqual(self.server.process({ACTION: 'Wrong', TIME: '1.1', USER: {ACCOUNT_NAME: 'Guest'}}),
self.err_dict)

def test_no_time(self):
self.assertEqual(self.server.process({ACTION: PRESENCE, USER: {ACCOUNT_NAME: 'Guest'}}), self.err_dict)

def test_no_user(self):
self.assertEqual(self.server.process({ACTION: PRESENCE, TIME: '1.1'}), self.err_dict)

def test_unknown_user(self):
self.assertEqual(self.server.process({ACTION: PRESENCE, TIME: 1.1, USER: {ACCOUNT_NAME: 'Guest_1'}}),
self.err_dict)


if __name__ == '__main__':
unittest.main()
61 changes: 61 additions & 0 deletions messenger/Tests/test_utilites.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import os
import sys
import unittest

sys.path.append(os.path.join(os.getcwd(), '..'))
from common.settings import USER, ACCOUNT_NAME, TIME, ACTION, PRESENCE
from common.utilites import Encoder, Message

test_dict_send = {
ACTION: PRESENCE,
TIME: 111111.111111,
USER: {
ACCOUNT_NAME: 'test_test'
}
}
test_dict_encoded = b'{"action": "presence", "time": 111111.111111, "user": {"account_name": "test_test"}}'


class TestSocket:
def __init__(self, test_dict, dict_encoded):
self.test_dict = test_dict
self.dict_encoded = dict_encoded
self.received_message = None

def send(self, message_to_send):
self.received_message = message_to_send
return self.received_message

def recv(self, max_len):
self.max_len = max_len
return self.dict_encoded


class TestUtilites(unittest.TestCase):
def setUp(self):
self.encoder = Encoder()
self.message = Message()
self.test_socket = TestSocket(test_dict_send, test_dict_encoded)

def test_encode_message(self):
self.assertEqual(self.encoder.encoding(test_dict_send), test_dict_encoded)

def test_encode_error_value(self):
self.assertRaises(ValueError, self.encoder.encoding, 'non_dict_data')

def test_decode_message(self):
self.assertEqual(self.encoder.decoding(test_dict_encoded), test_dict_send)

def test_decode_error_json_loads(self):
self.assertRaises(ValueError, self.encoder.encoding, 'non_byte_data')

def test_get_message(self):
self.assertEqual(self.message.get(self.test_socket), test_dict_send)

def test_send_message(self):
self.message.send(self.test_socket, test_dict_send)
self.assertEqual(self.test_socket.received_message, test_dict_encoded)


if __name__ == '__main__':
unittest.main()
Empty file added messenger/__init__.py
Empty file.
79 changes: 79 additions & 0 deletions messenger/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import socket
import time
import logging
from log import client_log_config
from json import JSONDecodeError
from sys import argv

from common.settings import ACTION, PRESENCE, TIME, USER, ACCOUNT_NAME, RESPONSE, ERROR, DEFAULT_IP_ADDRESS, \
DEFAULT_PORT
from common.utilites import Message


CLIENT_LOGGER = logging.getLogger('client')


class Client:
def presence(self, account_name='Guest'):
out = {
ACTION: PRESENCE,
TIME: time.time(),
USER: {
ACCOUNT_NAME: account_name
}
}
CLIENT_LOGGER.debug(f'Сформировано {PRESENCE} сообщение для пользователя {account_name}')
return out

def response(self, message):
CLIENT_LOGGER.debug(f'Разбор сообщения {message} от сервера')
if RESPONSE in message:
if message[RESPONSE] == 200:
return 'Соединение установлено'
return f'Ошибка соединения с сервером: {message[ERROR]}'
CLIENT_LOGGER.error('Неверный формат сообщения от сервера')
raise ValueError


def start(self, account_name='Guest'):
try:
if '-a' in argv:
address = argv[argv.index('-a') + 1]
else:
address = DEFAULT_IP_ADDRESS
except IndexError:
CLIENT_LOGGER.critical('После параметра \'a\'- необходимо указать адрес, к которому будет подключаться клиент.')
exit(1)
try:
if '-p' in argv:
port = int(argv[argv.index('-p') + 1])
else:
port = DEFAULT_PORT
if 1024 > port > 65535:
raise ValueError
except IndexError:
CLIENT_LOGGER.critical('После параметра -\'p\' необходимо указать номер порта.')
exit(1)
except ValueError:
CLIENT_LOGGER.critical('Порт может быть в диапазоне от 1024 до 65535.')
exit(1)
transport = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
CLIENT_LOGGER.info(f'Подключение к серверу {address}:{port}')
transport.connect((address, port))
except ConnectionRefusedError:
CLIENT_LOGGER.critical(f'Сервер не запущен на адресе {address}:{port}')
exit(1)
message_to_server = self.presence(account_name)
CLIENT_LOGGER.debug(f'Сформировано сообщение для отправки на сервер: {message_to_server}')
Message.send(transport, message_to_server)
try:
answer = self.response(Message.get(transport))
print(answer)
except (ValueError, JSONDecodeError):
CLIENT_LOGGER.error('Ошибка декодирования сообщения.')


if __name__ == '__main__':
client = Client()
client.start()
Empty file added messenger/common/__init__.py
Empty file.
31 changes: 31 additions & 0 deletions messenger/common/settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""Здесь производятся настройки приложения"""
import logging

# Порт по умолчанию для сетевого ваимодействия
DEFAULT_PORT = 7777

# IP адрес по умолчанию для подключения клиента
DEFAULT_IP_ADDRESS = '127.0.0.1'

# Максимальная очередь подключений
MAX_CONNECTIONS = 5

# Максимальная длинна сообщения в байтах
MAX_PACKAGE_LENGTH = 1024

# Кодировка проекта
ENCODING = 'utf-8'

# Текущий уровень логирования
LOGGING_LEVEL = logging.DEBUG

# Прококол JIM основные ключи:
ACTION = 'action'
TIME = 'time'
USER = 'user'
ACCOUNT_NAME = 'account_name'

# Прочие ключи, используемые в протоколе
PRESENCE = 'presence'
RESPONSE = 'response'
ERROR = 'error'
35 changes: 35 additions & 0 deletions messenger/common/utilites.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import json
from common.settings import MAX_PACKAGE_LENGTH, ENCODING


class Encoder:
@staticmethod
def encoding(data):
if isinstance(data, dict):
return json.dumps(data).encode(ENCODING)
else:
raise ValueError('Данные должны быть словарем')

@staticmethod
def decoding(data):
if isinstance(data, bytes):
msg = json.loads(data.decode(ENCODING))
if isinstance(msg, dict):
return msg
else:
raise ValueError('Данные должны быть словарем')
else:
raise ValueError('Данные должны быть байтами')


class Message:
@staticmethod
def get(client):
encoded_response = client.recv(MAX_PACKAGE_LENGTH)
response = Encoder.decoding(encoded_response)
return response

@staticmethod
def send(sock, message):
encoded_message = Encoder.encoding(message)
sock.send(encoded_message)
Empty file added messenger/log/__init__.py
Empty file.
26 changes: 26 additions & 0 deletions messenger/log/client_log_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import sys
import os
import logging
from common.settings import LOGGING_LEVEL
sys.path.append('../')


CLIENT_FORMATTER = logging.Formatter('%(asctime)s %(levelname)s %(filename)s %(message)s')
PATH = os.path.dirname(os.path.abspath(__file__))
PATH = os.path.join(PATH, 'client.log')
STREAM_HANDLER = logging.StreamHandler(sys.stderr)
STREAM_HANDLER.setFormatter(CLIENT_FORMATTER)
STREAM_HANDLER.setLevel(logging.ERROR)
LOG_FILE = logging.FileHandler(PATH, encoding='utf8')
LOG_FILE.setFormatter(CLIENT_FORMATTER)
LOGGER = logging.getLogger('client')
LOGGER.addHandler(STREAM_HANDLER)
LOGGER.addHandler(LOG_FILE)
LOGGER.setLevel(LOGGING_LEVEL)


if __name__ == '__main__':
LOGGER.critical('Критическая ошибка')
LOGGER.error('Ошибка')
LOGGER.debug('Отладочная информация')
LOGGER.info('Информационное сообщение')
27 changes: 27 additions & 0 deletions messenger/log/server_log_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import sys
import os
# import logging
import logging.handlers
from common.settings import LOGGING_LEVEL
sys.path.append('../')


SERVER_FORMATTER = logging.Formatter('%(asctime)s %(levelname)s %(filename)s %(message)s')
PATH = os.path.dirname(os.path.abspath(__file__))
PATH = os.path.join(PATH, 'server.log')
STREAM_HANDLER = logging.StreamHandler(sys.stderr)
STREAM_HANDLER.setFormatter(SERVER_FORMATTER)
STREAM_HANDLER.setLevel(logging.ERROR)
LOG_FILE = logging.handlers.TimedRotatingFileHandler(PATH, encoding='utf8', interval=1, when='D')
LOG_FILE.setFormatter(SERVER_FORMATTER)
LOGGER = logging.getLogger('server')
LOGGER.addHandler(STREAM_HANDLER)
LOGGER.addHandler(LOG_FILE)
LOGGER.setLevel(LOGGING_LEVEL)


if __name__ == '__main__':
LOGGER.critical('Критическая ошибка')
LOGGER.error('Ошибка')
LOGGER.debug('Отладочная информация')
LOGGER.info('Информационное сообщение')
Loading