Создание файлового сервера на языке Python
Основные подходы к созданию файлового сервера на Python
Как раздать файлы из текущей директории без сторонних библиотек?
Встроенный модуль http.server позволяет запустить простой HTTP-сервер для загрузки файлов клиентами. Он не требует установки дополнительных пакетов и подходит для быстрой передачи файлов в локальной сети или на удалённом сервере с базовой аутентификацией (через reverse proxy).
Запуск сервера выполняется одной командой в терминале:
python3 -m http.server 8000Python 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, принуждающим браузер сохранить файл.