отчет лаба 4 гаранин
.docx
ФЕДЕРАЛЬНОЕ
АГЕНСТВО ВОЗДУШНОГО ТРАНСПОРТА
(РОСАВИАЦИЯ)
ФЕДЕРАЛЬНОЕ ГОСУДАРСТВЕННОЕ БЮДЖЕТНОЕ ОБРАЗОВАТЕЛЬНОЕ УЧРЕЖДЕНИЕ ВЫСШЕГО ОБРАЗОВАНИЯ
«МОСКОВСКИЙ ГОСУДАРСТВЕННЫЙ ТЕХНИЧЕСКИЙ УНИВЕРСИТЕТ ГРАЖДАНСКОЙ АВИАЦИИ» (МГТУ ГА)
Кафедра вычислительных машин, комплексов, сетей и систем.
Лабораторная работа защищена с оценкой ____________________
____________________
(подпись преподавателя, дата)
ЛАБОРАТОРНАЯ РАБОТА №4
по дисциплине «Программирование сетевых приложений».
Тема: «Создание прокси-сервера.»
Выполнила студентка группы ИС221
Магальник Екатерина Борисовна
Руководитель: Гаранин Сергей Александрович
МОСКВА – 2025
Цель работы.
Изучить принципы работы прокси-серверов, освоить методы обработки сетевых запросов и реализовать базовый функционал прокси-сервера. Разработать многопоточный HTTP/HTTPS прокси-сервер, способный обрабатывать и перенаправлять веб-запросы.
Задание.
Разработать многопоточный HTTP/HTTPS прокси-сервер (должен быть реализован без использования внешних библиотек), выполняющий следующие функции:
Прослушивание входящих соединений на заданном хосте и порту (по умолчанию localhost:8080).
Разбор входящих HTTP-запросов, включая извлечение метода, целевого хоста, порта и URL.
Поддержка HTTPS-трафика через метод CONNECT, включая установку туннеля между клиентом и целевым сервером.
Пересылка запросов от клиента к целевому серверу и передача ответов обратно клиенту в реальном времени.
Блокировка доступа к доменам из чёрного списка (например, google.com, gmail.com) с возвратом HTTP-статуса 403 Forbidden.
Кэширование успешных HTTP-ответов на GET-запросы с кодом 200 OK для уменьшения задержек и сетевой нагрузки.
Ведение логов всех обработанных запросов в файл proxy.log с указанием: временной метки, IP-адреса клиента, метода и URL, статуса ответа (200, 403, 500, BLOCKED и т.п.).
Корректная обработка ошибок и освобождение ресурсов при завершении работы (в том числе по сигналу Ctrl+C).
Индивидуальные задания (Варианты).
Вариант 1. Логирование сырого трафика в отдельный файл raw_requests.log для отладки.
Листинг.
import socket import threading import urllib.parse import os import time from datetime import datetime from http import HTTPStatus # Конфигурация PROXY_HOST = '127.0.0.1' PROXY_PORT = 8080 CACHE_DIR = 'cache' LOG_FILE = 'proxy.log' BLACKLIST = {'google.com', 'gmail.com'} # Создание директории кэша os.makedirs(CACHE_DIR, exist_ok=True) def log_request(client_addr, method, url, status): timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') log_entry = f"[{timestamp}] {client_addr} - {method} {url} - {status}\n" with open(LOG_FILE, 'a', encoding='utf-8') as f: f.write(log_entry) def is_blacklisted(host): # Нормализуем домен: убираем порт, приводим к нижнему регистру host = host.split(':')[0].lower() return host in BLACKLIST def get_cache_path(url): safe_name = url.replace('/', '_').replace(':', '_').replace('?', '_').replace('&', '_') return os.path.join(CACHE_DIR, safe_name) def handle_http(client_socket, client_addr, request_line, headers, body): parts = request_line.split() if len(parts) < 3: client_socket.close() return method, full_url, version = parts # Парсинг URL parsed = urllib.parse.urlparse(full_url) host = parsed.hostname port = parsed.port or 80 path = parsed.path or '/' if parsed.query: path += '?' + parsed.query if is_blacklisted(host): response = b"HTTP/1.1 403 Forbidden\r\n\r\n" client_socket.sendall(response) log_request(client_addr[0], method, full_url, "403 BLOCKED") client_socket.close() return # Попытка загрузить из кэша (только для GET) use_cache = (method == 'GET') cache_path = get_cache_path(full_url) if use_cache else None from_cache = False if use_cache and os.path.exists(cache_path): # Кэш существует — отправляем его try: with open(cache_path, 'rb') as f: cached_data = f.read() client_socket.sendall(cached_data) log_request(client_addr[0], method, full_url, "200 CACHED") from_cache = True except Exception: pass # Игнорируем ошибки кэша if from_cache: client_socket.close() return # Пересылка запроса на целевой сервер try: server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.settimeout(10) server_socket.connect((host, port)) # Формируем корректный HTTP-запрос (без полного URL) forwarded_request = f"{method} {path} {version}\r\n" forwarded_request += f"Host: {host}\r\n" for key, value in headers.items(): forwarded_request += f"{key}: {value}\r\n" forwarded_request += "\r\n" server_socket.sendall(forwarded_request.encode() + body) # Получение ответа и пересылка клиенту response_data = b"" while True: try: chunk = server_socket.recv(4096) if not chunk: break client_socket.sendall(chunk) response_data += chunk except socket.timeout: break # Сохранение в кэш (только если 200 OK и GET) if use_cache: try: # Проверка статуса — ищем "HTTP/1.1 200" first_line_end = response_data.find(b'\r\n') if first_line_end != -1: status_line = response_data[:first_line_end].decode('utf-8', errors='ignore') if '200' in status_line: with open(cache_path, 'wb') as f: f.write(response_data) except Exception: pass # Не критично, если не удалось закэшировать # Логируем статус (пытаемся извлечь код) status_code = "200" try: first_line = response_data[:response_data.find(b'\r\n')].decode() status_code = first_line.split()[1] except: pass log_request(client_addr[0], method, full_url, status_code) server_socket.close() except Exception as e: client_socket.sendall(b"HTTP/1.1 500 Internal Server Error\r\n\r\n") log_request(client_addr[0], method, full_url, "500") finally: client_socket.close() def parse_headers(header_lines): headers = {} for line in header_lines: if ': ' in line: key, value = line.split(': ', 1) headers[key] = value return headers def handle_connect(client_socket, client_addr, host): if is_blacklisted(host): client_socket.sendall(b"HTTP/1.1 403 Forbidden\r\n\r\n") log_request(client_addr[0], "CONNECT", f"https://{host}", "403 BLOCKED") client_socket.close() return try: target_host, target_port = host.split(':') if ':' in host else (host, '443') target_port = int(target_port) server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.settimeout(10) server_socket.connect((target_host, target_port)) client_socket.sendall(b"HTTP/1.1 200 Connection Established\r\n\r\n") # Двунаправленная пересылка (туннель) def forward(src, dst): try: while True: data = src.recv(4096) if not data: break dst.sendall(data) except: pass finally: src.close() dst.close() threading.Thread(target=forward, args=(client_socket, server_socket), daemon=True).start() threading.Thread(target=forward, args=(server_socket, client_socket), daemon=True).start() log_request(client_addr[0], "CONNECT", f"https://{host}", "200 TUNNEL") except Exception as e: client_socket.sendall(b"HTTP/1.1 502 Bad Gateway\r\n\r\n") log_request(client_addr[0], "CONNECT", f"https://{host}", "502") client_socket.close() def handle_client(client_socket, client_addr): client_ip = client_addr[0] try: # Считываем все данные до конца заголовков buffer = b"" while b"\r\n\r\n" not in buffer: chunk = client_socket.recv(1024) if not chunk: return buffer += chunk if len(buffer) > 10000: # Защита от бесконечного чтения break # Определяем, есть ли тело (Content-Length или chunked — упрощённо) headers_end = buffer.find(b"\r\n\r\n") headers_part = buffer[:headers_end + 4] # включая \r\n\r\n # Пытаемся определить Content-Length content_length = 0 try: header_lines = headers_part.decode('utf-8', errors='ignore').split('\r\n') for line in header_lines[1:]: if line.lower().startswith('content-length:'): content_length = int(line.split(':', 1)[1].strip()) break except: pass body_part = b"" if content_length > 0: body_part = buffer[headers_end + 4:] remaining = content_length - len(body_part) while remaining > 0 and body_part != b"": chunk = client_socket.recv(min(4096, remaining)) if not chunk: break body_part += chunk remaining -= len(chunk) raw_request = headers_part + body_part # 🔹 ЛОГИРОВАНИЕ СЫРОГО ТРАФИКА log_raw_traffic(client_ip, raw_request) # Дальнейшая обработка как раньше request_str = headers_part.decode('utf-8', errors='ignore') lines = request_str.strip().split('\r\n') request_line = lines[0] if request_line.startswith("CONNECT"): host = request_line.split()[1] handle_connect(client_socket, client_addr, host) return headers = parse_headers(lines[1:-1]) # последние элементы могут быть пустыми handle_http(client_socket, client_addr, request_line, headers, body_part) except Exception as e: try: client_socket.close() except: pass def log_raw_traffic(client_addr, data): timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') with open('raw_requests.log', 'a', encoding='utf-8') as f: f.write(f"\n[RAW TRAFFIC] [{timestamp}] from {client_addr}\n") # Записываем hex-представление f.write("HEX: " + data.hex() + "\n") # Попытка декодировать как UTF-8 (с заменой недекодируемых байтов) try: text_repr = data.decode('utf-8', errors='replace') f.write("TEXT (UTF-8, errors='replace'):\n" + repr(text_repr) + "\n") except Exception: f.write("TEXT: <decoding failed>\n") def main(): server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_socket.bind((PROXY_HOST, PROXY_PORT)) server_socket.listen(5) print(f"[*] Proxy server запущен на {PROXY_HOST}:{PROXY_PORT}") def shutdown_handler(signum, frame): print("\n[!] Завершение работы прокси...") server_socket.close() exit(0) import signal signal.signal(signal.SIGINT, shutdown_handler) try: while True: client_socket, client_addr = server_socket.accept() thread = threading.Thread(target=handle_client, args=(client_socket, client_addr), daemon=True) thread.start() except KeyboardInterrupt: pass finally: server_socket.close() if __name__ == "__main__": main()
Результат работы программы.
Рис. 1. Фрагмент файла proxy.log
Рис. 2. Фрагмент файла raw_requests.log
