Как написать веб-сервер на Python: от простого к сложному
Создание простого HTTP-сервера на Python: обзор подходов
Как запустить сервер для раздачи статических файлов без написания кода?
Наиболее эффективное решение — использовать встроенный модуль http.server. Этот способ не требует создания отдельного скрипта и позволяет быстро поднять сервер в любой директории.
python -m http.server 8000Python http server (создание простого http-сервера на python)
После запуска сервер будет доступен по адресу http://localhost:8000. Он автоматически отображает список файлов и отдаёт их по запросу. Подходит для тестирования статического контента (HTML, CSS, JS) или временного обмена файлами.
Типичная ошибка: PermissionError при попытке использовать порт ниже 1024 (например, 80) в Linux/Mac. Решение — запустить с sudo или выбрать порт выше 1024.
sudo python -m http.server 80
Как создать кастомный HTTP-сервер с обработкой разных URL и методов?
Для более гибкого управления запросами используется класс http.server.BaseHTTPRequestHandler. Нужно переопределить методы do_GET, do_POST и т.д.
from http.server import HTTPServer, BaseHTTPRequestHandler
class MyHandler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header('Content-type', 'text/html; charset=utf-8')
self.end_headers()
self.wfile.write("<h1>Привет от сервера</h1>".encode('utf-8'))
server = HTTPServer(('localhost', 8080), MyHandler)
print('Сервер запущен на порту 8080')
server.serve_forever()
Этот сервер отвечает на любой GET-запрос одной и той же страницей. Вариант подходит для создания простого API или обработчика форм.
Проблема: если не указать кодировку (charset=utf-8), кириллица может отображаться некорректно. Решение — явно задавать заголовок Content-Type с кодировкой.
Как добавить поддержку POST-запросов и чтение тела?
Для обработки POST нужно реализовать метод do_POST и прочитать данные из self.rfile.
import json
from http.server import HTTPServer, BaseHTTPRequestHandler
class PostHandler(BaseHTTPRequestHandler):
def do_POST(self):
length = int(self.headers['Content-Length'])
body = self.rfile.read(length).decode('utf-8')
data = json.loads(body)
print('Получены данные:', data)
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.end_headers()
response = {'status': 'ok', 'received': data}
self.wfile.write(json.dumps(response, ensure_ascii=False).encode('utf-8'))
httpd = HTTPServer(('localhost', 8080), PostHandler)
httpd.serve_forever()
Сервер принимает JSON в теле POST и возвращает его обратно с подтверждением. Используется для тестирования REST API.
Как сделать асинхронный HTTP-сервер с помощью aiohttp?
Библиотека aiohttp позволяет создавать асинхронные серверы, эффективно обрабатывающие много одновременных соединений. Установка: pip install aiohttp.
from aiohttp import web
async def handle(request):
return web.Response(text='Асинхронный сервер работает!')
app = web.Application()
app.router.add_get('/', handle)
web.run_app(app, port=8080)
Этот сервер способен обслуживать тысячи запросов одновременно благодаря asyncio. Подходит для высоконагруженных приложений или WebSocket.
Ошибка: если не установлен aiohttp, возникнет ModuleNotFoundError. Решение — установить через pip.
Как реализовать сервер на чистых сокетах без сторонних модулей?
Самый низкоуровневый вариант — использовать модуль socket. Сервер будет принимать соединения и отправлять HTTP-ответ вручную.
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('localhost', 8888))
server.listen(1)
print('Сервер на сокетах запущен на порту 8888')
while True:
conn, addr = server.accept()
data = conn.recv(1024)
if data:
response = b'HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n<h1>Сокет сервер</h1>'
conn.sendall(response)
conn.close()
Этот сервер не обрабатывает HTTP-заголовки, а просто шлёт фиксированный ответ. Вариант интересен для изучения протокола HTTP, но не рекомендуется для реального использования из-за отсутствия парсинга запросов.
Расширенные примеры использования HTTP-сервера на Python
Пример 1: Сервер с маршрутизацией по URL и параметрами
Ниже показан кастомный обработчик, который различает пути и возвращает разные ответы. Также демонстрируется извлечение параметров из строки запроса.
from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import urlparse, parse_qs
class RouterHandler(BaseHTTPRequestHandler):
def do_GET(self):
parsed = urlparse(self.path)
path = parsed.path
params = parse_qs(parsed.query)
if path == '/hello':
name = params.get('name', ['гость'])[0]
message = f"<h1>Привет, {name}!</h1>"
elif path == '/time':
from datetime import datetime
message = f"<p>Текущее время: {datetime.now()}</p>"
else:
message = '<h1>Страница не найдена</h1>'
self.send_response(200)
self.send_header('Content-type', 'text/html; charset=utf-8')
self.end_headers()
self.wfile.write(message.encode('utf-8'))
server = HTTPServer(('localhost', 8000), RouterHandler)
print('Сервер с маршрутизацией запущен на :8000')
server.serve_forever()
При запросе http://localhost:8000/hello?name=Анна → выведется "Привет, Анна!" При запросе http://localhost:8000/time → выведется текущее время
Этот пример иллюстрирует базовую маршрутизацию, которую можно расширить до полноценного веб-фреймворка.
Пример 2: Сервер с поддержкой CGI-скриптов
Модуль http.server позволяет запускать CGI-скрипты, если указать CGIHTTPRequestHandler. Это нужно для выполнения внешних программ в ответ на запрос.
from http.server import CGIHTTPRequestHandler, HTTPServer
import os
os.chdir('/path/to/cgi-bin') # директория со скриптами
server = HTTPServer(('localhost', 8080), CGIHTTPRequestHandler)
print('CGI сервер запущен')
server.serve_forever()
В директории cgi-bin должны находиться исполняемые файлы (например, Python-скрипты с #!/usr/bin/env python). При запросе к /script.py сервер запустит скрипт и вернёт его вывод. Вариант подходит для интеграции с legacy-системами.
Типичная ошибка: сервер выводит исходный код скрипта вместо исполнения. Решение — убедиться, что файл имеет права на исполнение (chmod +x) и содержит корректный shebang.
Пример 3: Потоковый сервер с ThreadingHTTPServer для параллельной обработки
По умолчанию HTTPServer обрабатывает запросы последовательно. Для параллельной обработки используется ThreadingHTTPServer (Python 3.7+).
from http.server import HTTPServer, BaseHTTPRequestHandler
from socketserver import ThreadingMixIn
class ThreadingServer(ThreadingMixIn, HTTPServer):
pass
class SlowHandler(BaseHTTPRequestHandler):
def do_GET(self):
import time
time.sleep(2) # имитация долгой операции
self.send_response(200)
self.end_headers()
self.wfile.write(b'Done')
server = ThreadingServer(('localhost', 8080), SlowHandler)
print('Потоковый сервер запущен')
server.serve_forever()
Этот сервер может обрабатывать несколько запросов одновременно, каждый в своём потоке. Полезно, когда запросы выполняют длительные операции (например, чтение файлов).
Пример 4: Сервер с логированием запросов в файл
Для отладки и мониторинга можно добавить запись всех запросов в лог-файл с временными метками.
from http.server import HTTPServer, BaseHTTPRequestHandler
import logging
logging.basicConfig(filename='server.log', level=logging.INFO,
format='%(asctime)s - %(message)s')
class LoggedHandler(BaseHTTPRequestHandler):
def do_GET(self):
logging.info(f'GET {self.path} from {self.client_address}')
self.send_response(200)
self.send_header('Content-type', 'text/plain')
self.end_headers()
self.wfile.write(b'Logged')
server = HTTPServer(('localhost', 8888), LoggedHandler)
server.serve_forever()
Содержимое server.log:
2025-03-25 12:34:56,789 - GET /test from ('127.0.0.1', 54321)
Можно расширить для записи POST-данных, кодов ответов и т.д.
Пример 5: Использование http.server в связке с шаблонами (f-строки и условный вывод)
Для генерации динамического HTML можно использовать f-строки или шаблонизатор, например, string.Template.
from http.server import HTTPServer, BaseHTTPRequestHandler
from string import Template
class TemplateHandler(BaseHTTPRequestHandler):
def do_GET(self):
template = Template('''<html>
<head><title>$title</title></head>
<body><h1>$message</h1></body>
</html>''')
content = template.substitute(title='Динамическая страница',
message='Привет из шаблона!')
self.send_response(200)
self.send_header('Content-type', 'text/html; charset=utf-8')
self.end_headers()
self.wfile.write(content.encode('utf-8'))
server = HTTPServer(('localhost', 8080), TemplateHandler)
server.serve_forever()
Этот подход позволяет легко менять содержимое без конкатенации строк. Для сложных шаблонов можно использовать Jinja2.