from socket import socket, AF_INET, SOCK_STREAM, SHUT_RDWR, SOL_SOCKET, SO_REUSEADDR from time import strftime, gmtime import logging import db from router import run from utils import ( parse_cookies, parse_query, parse_headers, validate_url, add_text_headers, format_cookies, NOT_FOUND, BAD_REQUEST ) from templater import render_template from config import * logging.basicConfig(filename=db.get_config_entry('log_path'), level=logging.INFO) def log_requests(func): def wrapper(*args, **kwargs): address = kwargs['address'] if 'address' in kwargs else args[0] request = kwargs['request'] if 'request' in kwargs else args[1] response = func(*args, **kwargs) status = response.split('\n').pop(0).split()[1] if not db.get_config_entry('short_log', False): # Формируем строку заголовков для вывода header_pairs = [] for header, value in request['headers'].items(): if header == 'Cookie': value = format_cookies(value) header_pairs.append(f'{header}: {value}') # В логе желательно не использовать переводы строк, когда они не предусмотрены, поэтому # \r\n заменяем на , чтобы мы могли понимать где в строке должны были находиться переводы строк headers_line = ''.join(header_pairs) # Формируем строку тела запроса для вывода query_pairs = [] for key, value in request['query'].items(): query_pairs.append(f'{key}={value}') query_line = '&'.join(query_pairs) to_logger = f'Headers: {headers_line} | Query: {query_line}' else: to_logger = f'Cookies: {format_cookies(request["cookies"])} ' log_line = ( f'{address[0]}: {strftime("[%d %b %Y %H:%M:%S]", gmtime())} ' f'"{request["method"]} {request["url"]} {request["http_ver"]}"' f' | {to_logger} | ' f'Status: {status}' ) print(log_line) logging.info(log_line) return response return wrapper @log_requests def process_request(address, request: dict): if validate_url(request['url']): response = run(request) else: response = add_text_headers(NOT_FOUND, '404 NOT FOUND') return response def parse_request(connection): buffer = b'' request = b'' while not request.endswith(b'\r\n\r\n'): data = connection.recv(1024).split(b'\r\n\r\n') if len(data) == 1: if not data[0]: return None request += data[0] else: request += data[0] + b'\r\n\r\n' buffer += data[1] method, url, http_ver, headers = parse_headers(request.strip().decode()) try: cookies = parse_cookies(headers['Cookie']) except (ValueError, KeyError): cookies = {} if 'Content-Type' in headers and headers['Content-Type'] == 'application/x-www-form-urlencoded': chunk = db.get_config_entry('chunk') length = int(headers['Content-Length']) - len(buffer) while length > 0: if length < chunk: data = connection.recv(length) length = 0 else: data = connection.recv(chunk) length -= chunk buffer += data[0] query = parse_query(buffer.strip().decode()) if buffer else {} return { 'method': method, 'url': url, 'http_ver': http_ver, 'headers': headers, 'query': query, 'cookies': cookies } def get_color(): color = db.get_cookie('bg_color', 'white') if color not in ['green', 'white']: color = 'white' return color def handle_connection(connection, address): try: request = parse_request(connection) except ValueError as e: response = add_text_headers(BAD_REQUEST, render_template( TEXT_TEMPLATE_NAME, color=get_color(), text=BAD_REQUEST + ('
' + str(e) if db.get_config_entry('show_errors') else '') )) connection.sendall(response.encode()) return if request is None: return response = process_request(address, request) connection.sendall(response.encode()) def main(): host = db.get_config_entry('host') port = db.get_config_entry('port') sock = socket(AF_INET, SOCK_STREAM) sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) sock.bind((host, port)) sock.listen(5) print(f'* Server started on http://{host}:{port}/') print('* Listening to new connections...') while True: try: connection, address = sock.accept() handle_connection(connection, address) connection.shutdown(SHUT_RDWR) connection.close() del connection except (KeyboardInterrupt, ConnectionError): print('Stopping server...') try: connection.shutdown(SHUT_RDWR) connection.close() except UnboundLocalError: # Если переменная connection еще не создана, то нам не нужно закрывать # соединение, а значит можно игнорировать это исключение. pass sock.shutdown(SHUT_RDWR) sock.close() print('Server stopped') break if __name__ == '__main__': main()