Создание файлового сервера на языке Python

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

Основные подходы к созданию файлового сервера на Python

Как раздать файлы из текущей директории без сторонних библиотек?

Встроенный модуль http.server позволяет запустить простой HTTP-сервер для загрузки файлов клиентами. Он не требует установки дополнительных пакетов и подходит для быстрой передачи файлов в локальной сети или на удалённом сервере с базовой аутентификацией (через reverse proxy).

Запуск сервера выполняется одной командой в терминале:

python3 -m http.server 8000

Python file server (файловый сервер на python)

После этого сервер будет слушать порт 8000 и отдавать содержимое текущей директории по URL http://localhost:8000. Для смены порта достаточно указать другое значение, например 8080. Сервер поддерживает только чтение файлов; загрузка (upload) по умолчанию недоступна.

# Альтернативный запуск с привязкой к конкретному интерфейсу
python3 -m http.server 8000 --bind 0.0.0.0  # доступ со всех сетевых интерфейсов

Python https server (запуск https сервера на python)

Типичная ошибка: при попытке загрузить файл через PUT или POST клиент получает код ответа 501 (Not Implemented). Решение: для поддержки загрузки необходимо реализовать собственный обработчик, наследующий от BaseHTTPRequestHandler и переопределяющий метод do_PUT или do_POST. Ниже приведён пример такого обработчика.

from http.server import HTTPServer, BaseHTTPRequestHandler
import os

class UploadHandler(BaseHTTPRequestHandler):
    def do_PUT(self):
        # Извлекаем имя файла из пути
        filename = os.path.basename(self.path)
        length = int(self.headers.get('Content-Length'))
        with open(filename, 'wb') as f:
            f.write(self.rfile.read(length))
        self.send_response(200)
        self.end_headers()
        self.wfile.write(b'Upload successful')

    def do_GET(self):
        # Стандартное поведение для GET – список файлов
        super().do_GET()

if __name__ == '__main__':
    server = HTTPServer(('0.0.0.0', 8000), UploadHandler)
    server.serve_forever()

Этот код позволяет загружать файлы через PUT-запросы (например, curl -T file.txt http://localhost:8000/). Сервер сохраняет файлы в текущую директорию.

Как добавить авторизацию и поддержку загрузки/скачивания через веб-интерфейс?

Для более гибкого управления доступом и функциональностью удобно использовать веб-фреймворки, такие как Flask или FastAPI. Они предоставляют встроенные механизмы маршрутизации, обработки форм и сессий. Рассмотрим пример на Flask.

from flask import Flask, request, send_from_directory, abort
import os

app = Flask(__name__)
UPLOAD_FOLDER = './uploads'
ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg'}
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024  # 16 MB

def allowed_file(filename):
    return '.' in filename and 
           filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

@app.route('/upload', methods=['POST'])
def upload_file():
    if 'file' not in request.files:
        abort(400, 'No file part')
    file = request.files['file']
    if file.filename == '':
        abort(400, 'No selected file')
    if file and allowed_file(file.filename):
        file.save(os.path.join(UPLOAD_FOLDER, file.filename))
        return 'File uploaded successfully'
    else:
        abort(400, 'File type not allowed')

@app.route('/download/<filename>')
def download_file(filename):
    return send_from_directory(UPLOAD_FOLDER, filename)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

Сервер запускается на порту 5000. Для загрузки файла используется POST-запрос с multipart/form-data, для скачивания – GET-запрос. Проверяется расширение файла и максимальный размер. Для использования в продакшене рекомендуется добавить аутентификацию (например, через Flask-Login или HTTP Basic Auth).

Проблема: при загрузке больших файлов превышается лимит оперативной памяти. Решение: использовать потоковую загрузку с помощью request.stream или настроить веб-сервер (Nginx) для буферизации. Другой подход – использовать FastAPI с асинхронным чтением.

Как организовать обмен файлами по протоколу FTP?

FTP остаётся популярным протоколом для передачи файлов, особенно в автоматизированных системах. Библиотека pyftpdlib позволяет создать полноценный FTP-сервер с поддержкой виртуальных пользователей, пассивного режима и шифрования (TLS). Рассмотрим минимальный пример.

from pyftpdlib.authorizers import DummyAuthorizer
from pyftpdlib.handlers import FTPHandler
from pyftpdlib.servers import FTPServer

# Создаём авторизатор с учётными записями
authorizer = DummyAuthorizer()
authorizer.add_user('user', 'password', './ftp_root', perm='elradfmw')
handler = FTPHandler
handler.authorizer = authorizer

# Настройка пассивного режима (если за NAT)
handler.passive_ports = range(60000, 60010)

server = FTPServer(('0.0.0.0', 21), handler)
server.serve_forever()

Сервер слушает порт 21. Клиент подключается с логином user и паролем password. Параметр perm определяет разрешённые операции (например, 'e' – чтение, 'm' – загрузка, 'w' – запись). Для работы в пассивном режиме через NAT/брандмауэр необходимо указать диапазон пассивных портов и настроить маршрутизацию.

Ошибка: клиент не может подключиться в пассивном режиме из-за блокировки портов брандмауэром. Решение: открыть порты 60000-60010 (или другие) на файрволе и настроить перенаправление портов, если сервер за NAT. Альтернативно можно использовать активный режим, но он требует наличия прямого доступа от сервера к клиенту.

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

Асинхронный фреймворк aiohttp позволяет обрабатывать множество соединений одновременно без блокировок. Это особенно полезно при раздаче больших файлов или при большом количестве одновременных запросов. Пример сервера, который отдаёт файлы из папки.

import aiohttp
from aiohttp import web
import os

async def download(request):
    filename = request.match_info.get('filename', '')
    filepath = os.path.join('./public', filename)
    if not os.path.isfile(filepath):
        raise web.HTTPNotFound()
    return web.FileResponse(filepath)

app = web.Application()
app.router.add_get('/{filename}', download)

if __name__ == '__main__':
    web.run_app(app, port=8080)

Сервер слушает порт 8080. Для каждого GET-запроса по пути /{filename} создаётся корутина, которая асинхронно отправляет файл. Фреймворк поддерживает range-запросы для докачки, что важно для стриминга видео.

Проблема: необработанные исключения в корутинах могут привести к утечке памяти. Решение: добавить обработчики исключений (web.HTTPException) и вести логирование. Кроме того, необходимо ограничивать размер буфера для больших файлов.

Расширенные примеры и сценарии использования

1. Кастомный обработчик http.server с поддержкой загрузки и ограничением размера

Пример
from http.server import HTTPServer, BaseHTTPRequestHandler
import os

LIMIT = 100 * 1024 * 1024  # 100 MB

class LimitedUploadHandler(BaseHTTPRequestHandler):
    def do_PUT(self):
        filename = os.path.basename(self.path)
        length = int(self.headers.get('Content-Length', 0))
        if length > LIMIT:
            self.send_response(413)
            self.end_headers()
            self.wfile.write(b'File too large')
            return
        with open(filename, 'wb') as f:
            chunk_size = 8192
            remaining = length
            while remaining > 0:
                chunk = self.rfile.read(min(chunk_size, remaining))
                if not chunk:
                    break
                f.write(chunk)
                remaining -= len(chunk)
        self.send_response(200)
        self.end_headers()
        self.wfile.write(b'Upload complete')

    def do_GET(self):
        # Упрощённый список файлов (без шаблонов)
        self.send_response(200)
        self.send_header('Content-type', 'text/html; charset=utf-8')
        self.end_headers()
        files = os.listdir('.')
        for f in files:
            self.wfile.write(f'{f}
'.encode('utf-8')) server = HTTPServer(('', 8888), LimitedUploadHandler) print('Server running on port 8888...') server.serve_forever()

Результат: при запросе curl -T hugefile.bin http://localhost:8888/hugefile.bin, если файл превышает 100 MB, возвращается ошибка 413. Иначе файл сохраняется по частям (chunked).

2. FTP-сервер с виртуальными пользователями и каталогами

Пример
from pyftpdlib.authorizers import DummyAuthorizer
from pyftpdlib.handlers import FTPHandler
from pyftpdlib.servers import FTPServer

authorizer = DummyAuthorizer()
# Добавляем несколько пользователей с разными правами
authorizer.add_user('admin', 'adminpass', '/home/admin', perm='elradfmw')
authorizer.add_user('guest', 'guestpass', '/home/guest', perm='elr')
authorizer.add_anonymous('/home/anonymous', perm='elr')

handler = FTPHandler
handler.authorizer = authorizer

# Включаем логирование в файл
import logging
handler.log_prefix = 'ftp:'
handler.log_prefix_len = 4
logging.basicConfig(filename='ftp.log', level=logging.INFO)

server = FTPServer(('0.0.0.0', 21), handler)
server.serve_forever()

Результат: клиенты могут подключаться с разными учётными данными. Анонимный пользователь получает только чтение (переменная perm). Все действия логируются.

3. Асинхронный сервер на aiohttp с проверкой токена

Пример
from aiohttp import web
import async_timeout
import os

async def handle(request):
    token = request.headers.get('Authorization')
    if token != 'secret_token':
        raise web.HTTPUnauthorized()
    filename = request.match_info.get('filename')
    filepath = os.path.join('/secure/files', filename)
    if not os.path.isfile(filepath):
        raise web.HTTPNotFound()
    resp = web.FileResponse(filepath)
    resp.headers['Content-Disposition'] = f'attachment; filename="{filename}"'
    return resp

app = web.Application()
app.router.add_get('/{filename}', handle)
web.run_app(app, port=9090)

Результат: запрос без заголовка Authorization возвращает 401. С правильным токеном файл скачивается с заголовком Content-Disposition, принуждающим браузер сохранить файл.

Файловый сервер на Python - comments

En
Python file server (python)