Одной из распространенных задач веб-приложений и веб-серверов является отправка файлов. При создании http-сервера на основе HTTPServer для обработки файлов (их открытия и считывания) мы можем использовать встроенные возможности Python, в частности, функцию open()
Для обработки файлов в http-сервере определим следующий скрипт:
from http.server import HTTPServer, BaseHTTPRequestHandler
class MyHandler(BaseHTTPRequestHandler):
def do_GET(self):
# Для других запросов убираем слеш в начале (например, "/content.txt" -> "content.txt")
path_to_file = self.path.lstrip("/")
try:
# ПЫТАЕМСЯ открыть файл в режиме чтения БАЙТОВ (rb - read binary)
# Режим "rb" позволяет отдавать не только текст, но и картинки, PDF и т.д.
with open(path_to_file, "rb") as file:
content = file.read()
# Если файл открылся успешно, отправляем 200 OK
self.send_response(200)
# Определяем тип контента (упрощенно)
if path_to_file.endswith(".txt"):
self.send_header("Content-type", "text/plain; charset=utf-8")
elif path_to_file.endswith(".jpg"):
self.send_header("Content-type", "image/jpeg")
else:
self.send_header("Content-type", "application/octet-stream")
self.end_headers()
# Записываем прочитанные байты файла в ответ
self.wfile.write(content)
except FileNotFoundError:
# Если файл не найден на диске, возвращаем ошибку 404
self.send_error(404, "File Not Found")
# Настройки запуска
hostName = "localhost"
serverPort = 8000
# Инициализация сервера
webServer = HTTPServer((hostName, serverPort), MyHandler)
print(f"Сервер запущен: http://{hostName}:{serverPort}")
# Бесконечный цикл прослушивания порта
try: webServer.serve_forever()
except KeyboardInterrupt: pass
webServer.server_close()
print("Сервер остановлен...")
Для отправки файлов в методе do_GET() класса MyHandler прежде всего получаем путь к файлу:
path_to_file = self.path.lstrip("/")
В данном случае мы предполагаем, что пользователь будет обращаться в браузере по адресу "http://localhost:8000/content.txt", где последняя часть адреса - "content.txt" представляет имя файла, который мы хотим отправить пользователю. Также предполагаем, что файлы будут располагаться в текущей папке.
Далее в конструкции try..except считываем и отправляем запрошенный файл. И здесь надо выделить следующие моменты:
open(path_to_file, "rb")
Мы используем режим "rb" (read binary), что позволяет считать файл как набор байтов, тем более HTTP-сервер работает с байтами. Чтение в бинарном режиме позволит считывать любой файл.
Смотрим на расширение файла, и зависимости от этого расширения устанавливаем соответствующий тип ответа:
if path_to_file.endswith(".txt"):
self.send_header("Content-type", "text/plain; charset=utf-8")
elif path_to_file.endswith(".jpg"):
self.send_header("Content-type", "image/jpeg")
else:
self.send_header("Content-type", "application/octet-stream")
Это позволит клиенту (браузеру) должным образом обрабатывать полученный файл
Обработка ошибок try...except FileNotFoundError
Если пользователь запросит несуществующий файл, и скрипт попытается его открыть, то без блока try/except сервер упадет с ошибкой и выключится.
С блоком с помощью метода self.send_error() он корректно вернет стандартную страницу 404.
except FileNotFoundError:
self.send_error(404, "File Not Found")
Протестируем функциональность. Определим в папке сервера несколько файлов.
eugene@Eugene:~/python/httpserver$ ls -l total 1853 -rwxrwxrwx 1 root root 2180 Dec 8 20:46 app.py -rwxrwxrwx 1 root root 140 Aug 17 11:55 hello.txt -rwxrwxrwx 1 root root 1888236 Feb 25 2013 winter.jpg eugene@Eugene:~/python/httpserver$
Обратимся к файлам. Например, у меня в папке есть файл изображения "winter.jpg":
Однако сразу стоит отметить вопросы безопасности. В данном случае представлен упрощенный пример, но надо учитывать, что мы, например, можем обратиться по адресу "http://localhost:8000/./secrets.txt". В этом случае локальным путем к файлу будет относительный путь "./secrets.txt", который предполагает, что есть файл "secrets.txt", который располагается в родительской папке (каталог на уровень выше). И если такой файл имеется, Python успешно его считает и отправит пользователю.
Если не нужно обрабатывать данные вручную, а нужно только отдавать файлы (как обычный веб-сервер типа Nginx или Apache), в модуле http.server есть класс SimpleHTTPRequestHandler.
ПРичем в этом случае нам даже не нужно писать метод do_GET() и какую-то логику обработки - вся необходимая логика уже встроена в SimpleHTTPRequestHandler,
который умеет автоматически находить файлы в папке, определять их тип (MIME-type) и отдавать их. Он также сам ищет index.html, если запрошен корень.
from http.server import HTTPServer, SimpleHTTPRequestHandler
# Настройки запуска
hostName = "localhost"
serverPort = 8000
# Инициализация сервера
webServer = HTTPServer((hostName, serverPort), SimpleHTTPRequestHandler)
print(f"Сервер запущен: http://{hostName}:{serverPort}")
# Бесконечный цикл прослушивания порта
try: webServer.serve_forever()
except KeyboardInterrupt: pass
webServer.server_close()
print("Сервер остановлен...")
Например, обратимся к текущему файлу скрипта
В данном случае мы видим, что файл с расширением .py (скрипт Python) автоматически определяется как текстовый. При ручной отправке файла нам пришлось бы определять сооветствующую логику, а
SimpleHTTPRequestHandler все делает автоматически. Тем не менее по скриншоту видно, что у нас могут возникнуть проблемы с кодировкой (поддержкой кириллицы).