Как написать веб-сервер на Python: от простого к сложному

Раздел: Сетевые протоколы -> Веб-сервер на Python

Создание простого HTTP-сервера на Python: обзор подходов

Как запустить сервер для раздачи статических файлов без написания кода?

Наиболее эффективное решение — использовать встроенный модуль http.server. Этот способ не требует создания отдельного скрипта и позволяет быстро поднять сервер в любой директории.

python -m http.server 8000

Python 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.

Создание простого HTTP-сервера на Python - comments

En
Python http server (python)