Разрабатываем web-приложение на Python3x, эпизод второй
Jazz
Опубликован: | 2023-09-29T05:16:41.256945Z |
Отредактирован: | 2023-09-29T05:16:41.256945Z |
Статус: | публичный |
Продолжаем разрабатывать selfish... В очередной демонстрации процесса разработки этого web-сервиса я брошу чуть-чуть более пристальный взгляд на типичный ответ HTTP-сервера, посмотрю на него через призму инструментов разработчика Интернет-браузера Chromium и обращу внимание на один из его заголовков - Content-Type. Кроме этого, в рамках этой демонстрации я добавлю selfish пару обязательных почти для каждого современного web-сервиса URL-адресов, расскажу, зачем они нужны, и почему именно сейчас. Всем заинтересованным напоминаю, что этот выпуск блога продолжает цикл статей, ссылка на весь цикл выше, читать лучше с начала...
Краткое содержание предыдущего выпуска
В предыдущем выпуске этого цикла статей я создал структуру каталогов web-приложения selfish, виртуальное окружение и git-репозиторий, создал на базе web-фреймворка Starlette ASGI-приложение, добавил пару URL-адресов в его карту, запустил его с помощью ASGI-сервера, который планирую использовать для отладки и тестирования в процессе разработки, и протестировал стартовую страницу web-сервиса, которая, впрочем, пока отображает только простое текстовое сообщение. В этом изложении я буду использовать разработанный на предыдущем этапе проектирования код, дополнять и редактировать его в соответствии с замыслом.
Инструменты web-разработчика
Ранее мы увидели, и я обращал на это особое внимание, что информационные сообщения отладочного сервера в терминал являются важной составляющей инструментов web-программиста. Из этих сообщений мы увидели, что web-браузер, обращаясь к web-серверу, отправляет на целевой адрес - адрес, вбитый в адресную строку браузера, HTTP-запрос по методу GET. Кроме этого, он автоматически, по-умолчанию отправляет ещё один GET-запрос, с целью получить иконку для избранного запрашиваемого сервиса.
Мы увидели также, что отправляя клиенту HTTP-ответ, отладочный сервер присвоил этому ответу соответствующий код статуса. Из выхлопа отладочного сервера мы узнали, что код статуса в том числе может принимать следующие значения: 200
и 404
; второй сигнализирует об ошибке - страница с заданным URL-адресом не найдена. HTTP-протокол определяет и другие коды статуса, некоторые из них мы рассмотрим более подробно чуть позже.
Пришло время задаться одним важным вопросом... А что же происходит после отправки запроса серверу на клиенте, и как отражение этого процесса увидеть в браузере? Вопрос правильный и своевременный, от ответа на него зависит, что в конечном итоге увидит пользователь в окне браузера сразу после отправки запроса на целевой адрес web-сервиса. Будем разбираться...
Поскольку с момента последней работы над selfish прошло некоторое время, чтобы возобновить эту работу я должен войти в корневой катало selfish
, активировать виртуальное окружение и запустить отладочный сервер. Выполняю в терминале последовательно три команды:
$ cd ~/workspace/selfish
$ source venv/bin/activate
$ python runserver.py
Запускаю Chromium, но пока не тороплюсь вводить URL-адрес в его адресную строку.
У каждого современного web-браузера в арсенале средств есть так называемые инструменты разработчика. В частности в Chromium получить к ним доступ можно клавиатурным сочетанием crtl+shift+i
. Жму, находясь в окне браузера, окно разделится на две части, и мы увидим те самые инструменты разработчика. Перехожу в них на вкладку "Сеть".
Здесь следует отметить, что сразу после перехода на эту вкладку я поставил флажок в поле "Отключить кеш". Дело в том, что браузер кеширует полученные от сервера данные, а web-разработчик, работая над клиентской частью кода (фронтэндом) очень часто в процессе редактирует отправляемые сервером клиенту файлы. Чтобы браузер каждый раз учитывал изменения в новых версиях файлов, кеш нужно отключить на стороне клиента, что я и сделал.
Теперь я могу инициировать процесс отправки браузером HTTP запроса на сервер. Ввожу в адресную строку адрес стартовой страницы selfish и жму enter
.
Вот такую интересную картинку я получаю в окне инструментов разработчика. Мы видим оба отправленных браузером запроса, для каждого запроса отображены дополнительные данные, в том числе, размер полученного от сервера ответа и время, затраченное на обработку запроса сервером и получение ответа клиентом. Код статуса для каждого запроса тоже отображается.
Получаем данные заголовков ответа
Навожу указатель мыши на первый запрос - запрос к localhost, и жму левую кнопку мыши. В результате окно ещё раз разделится, и браузер покажет дополнительную информацию об этом запросе. В том числе, мы можем увидеть заголовки полученного от сервера ответа.
В данном случае меня интересует заголовок Content-Type. Для полученного ответа браузер отобразил в этой строке следующие данные:
text/plain; charset=utf-8
От содержимого этого заголовка будет зависеть, что браузер будет делать с полученными данными. Как мы видим, в этом конкретном случае сервер отправил нам текстовый документ с кодировкой UTF-8, а браузер отобразил в своём окне текст из этого документа, обращаю внимание, что отобразил моноширинным шрифтом, в соответствии с собственными настройками. Полученный от сервера Content-Type в данном случае определяется классом PlainTextResponse
, экземпляр которого возвращает функция представления show_index
, которую я написал в предыдущей демонстрации.
Давайте обратим внимание на второй запрос, который браузер отправил автоматически. Браузеру нужна иконка для избранного, и он пытается её получить с сервера. Но web-сервис, который работает на сервере, на текущий момент не имеет в своей карте соответствующего этому запросу URL-адреса, и поэтому сервер вернул клиенту код ошибки 404
- страница не найдена. У этого ответа сервера тоже есть Content-Type. Давайте посмотрим на него.
В своём ответе на этот запрос сервер опять отправил клиенту простой текст, хотя браузер ожидает файл с картинкой. В итоге на текущей вкладке браузер отобразил собственную иконку по-умолчанию.
Если посмотреть в терминале на выхлоп отладочного сервера, мы увидим всё те же два запроса по методу GET.
Диагноз поставлен. Чуть позже я эту досадную ошибку исправлю. А пока давайте посмотрим, как можно изменить Content-Type HTTP-ответа средствами Starlette.
Изменяем Content-Type ответа сервера
Стартовая страница web-сервиса должна быть в достаточной мере информативной и при этом хорошо выглядеть. На базе текстового документа удовлетворить эти требования невозможно, поэтому стартовой странице selfish необходимо изменить Content-Type.
Закрываю окно браузера, останавливаю отладочный сервер и в окне терминала запускаю тестовый редактор, мне нужно отредактировать пару файлов, начну с views.py
.
$ vim -g selfish/main/views.py
У меня есть функция представления show_index
, которая на данный момент возвращает экземпляр класса PlainTextResponse
, именно этот класс и формирует HTTP-ответ сервера, следовательно, чтобы изменить заголовок Content-Type в этом ответе, логично будет предположить, что следует использовать для формирования ответа другой класс.
В модуле responses
пакета Starlette есть классы почти на все случаи жизни, и на этой стадии разработки я воспользуюсь классом HTMLResponse
, подключаю его в модуль views.py
.
from starlette.responses import HTMLResponse, PlainTextResponse
Увы, у проекта пока нет штатного шаблонизатора, им я обзаведусь чуть позже, поэтому на текущем этапе проектирования я передам этому классу обычную строку, содержащую элементы HTML-разметки.
async def show_index(request):
return HTMLResponse(
'<div>Сайт в стадии разработки, попробуйте зайти позже.</div>')
Сохраняю изменения в файл. Вот как это выглядит в окне моего текстового редактора.
Вновь запускаю отладочный сервер, запускаю браузер и в его окне стучусь по URL-адресу стартовой страницы selfish. Давайте посмотрим на интересующий нас заголовок полученного от сервера ответа.
Voilà, как говорят французы... Теперь ответ сервера содержится HTML-контент, а это значит, что браузер этот контент будет обрабатывать соответствующим образом, и в потенциале уже сейчас selfish может обрести фронтэнд. Обращаю внимание на отображение текста страницы в окне браузера, по сравнению с прежним вариантом изменился шрифт, теперь мы видим шрифт с засечками, то есть у страницы, хоть и в рудиментарном виде, но появилось какое-то оформление. Прогресс...
Задаём иконку для избранного
Пришло время оснастить страницы selfish иконкой для избранного, раз уж браузер настаивает на её существовании в каждой своей сессии, заодно исправим досадный 404
, который каждый раз портит картину, наша цель - эстетическое наслаждение от работы.
Естественно для реализации задуманного мне потребуется файл изображения. В предыдущей демонстрации я положил такой файл в один из вложенных каталогов каталога static
- favicon.ico
, он уже присутствует в git-репозитории проекта, его я и буду использовать как иконку для избранного.
Перехожу в окно текстового редактора и редактирую файл views.py
. Мне снова потребуется модуль стандартной библиотеки os
, кроме этого мне нужен соответствующий класс, чтобы сформировать HTTP-ответ, подключаю в текущее пространство имён.
import os
from starlette.responses import FileResponse, HTMLResponse, PlainTextResponse
Класс FileResponse
поможет мне достичь заявленную цель. Создаю ещё одну функцию представления - show_favicon
.
async def show_favicon(request):
if request.method == 'GET':
base = os.path.dirname(os.path.dirname(__file__))
return FileResponse(
os.path.join(base, 'static', 'images', 'favicon.ico'))
Здесь я с помощью инструментов модуля os
выразил адрес файла в файловой системе терминами Питона и передал этот адрес классу FileResponse
, полученный экземпляр вернул процедурой return
. Сохраняю изменения в файл.
Обработчик для очередного URL-адреса сервиса готов, необходимо создать этот URL-адрес в карте ASGI-приложения. В командном режиме текстового редактора ввожу следующую команду.
:tabnew selfish/__init__.py
Несложно догадаться, что сейчас я буду править "дандеринит" из базового каталога selfish
.
Во-первых, необходимо только что созданную функцию представления подключить в пространство имён редактируемого файла.
from .main.views import show_favicon, show_index
Во-вторых, в карту URL-адресов нужно добавить новый адрес.
app = Starlette(
...
routes=[...,
Route('/favicon.ico', show_favicon, name='favicon.ico'),
...
Опять сохраняю изменения в файл.
Перехожу в окно браузера и ввожу в адресной строке только что созданный URL-адрес, жму enter
.
Обращаю внимание на вкладку в окне браузера, на вкладке появилось изображение подключенной иконки. Давайте посмотрим в терминал, на сообщение сервера.
Запрос обработан с кодом статуса 200
, у selfish появилась иконка для избранного, цель достигнута.
Обязательные адреса web-сервиса
Можно сделать вывод, что иконка для избранного является обязательным атрибутом web-сервиса. Кроме иконки для избранного у web-сервиса есть ещё один обязательный атрибут - файл robots.txt. И сейчас я создам первую редакцию этого файла.
Любой развёрнутый в сети Интернет web-сервис доступен всем ветрам пользователям этой сети. В Интернет кроме живых людей существует много других сущностей, которые очень часто будут стучаться в развёрнутый web-сервис по разным адресам. Одна из этих сущностей - это роботы поисковых систем, которые, обнаружив в сети новый сервис, тут же стремятся проиндексировать его и добавить данные о нём в свою базу данных. Информацию о целях индексации роботы берут из файла robots.txt
.
Каждый web-сервис имеет определённый растянутый во времени цикл разработки, и на протяжении этого цикла разработчик может развернуть этот сервис в тестовом режиме на доменном имени, и поисковики автоматом начнут этот web-сервис опрашивать и, не обнаружив robots.txt, проиндексируют страницы сервиса на своё усмотрение, и это очень нежелательный сценарий. Отсюда я делаю вывод, что robots.txt является обязательным атрибутом web-приложения даже на стадии разработки.
Перехожу в окно текстового редактора, на вкладку с файлом views.py
и дописываю в конец файла ещё одну функцию представления - show_robots
.
async def show_robots(request):
txt = 'User-agent: *\nDisallow: /'
return PlainTextResponse(txt)
Расширение файла нам говорит, что robots.txt это обычный текст, поэтому я использовал для этой функции представления класс PlainTextResponse
абсолютно обоснованно. В файле robots.txt на текущем этапе разработки будет одна единственная директива, она запрещает поисковым системам индексацию всех страниц web-сервиса. На данный момент это обоснованное решение, впоследствии я сделаю этот файл сервиса selfish редактируемым, и web-мастер сможет изменять его web-инструментами selfish. Сохраняю изменения в файл.
Перехожу в текстовом редакторе на вкладку "дандеринита" и вношу новые правки. Во-первых, подключаю в пространство имён новую функцию представления. Во-вторых, добавляю в карту URL-адресов новый адрес. Сохраняю изменения в файл.
Возвращаюсь в окно браузера и набираю в адресной строке только что созданный URL-адрес.
Если мне будет необходимо уже сейчас развернуть selfish на сервер в сети Интернет, я буду совершенно спокоен, файл robots.txt запрещает всем роботам всех поисковых и прочих систем индексировать любые страницы сервиса. Ещё одна цель достигнута.
Подводим итог
Продемонстрированные в этом описании действия показывают, что с помощью соответствующих инструментов Starlette в уже существующее ASGI-приложение очень не сложно добавить новый функционал, для этого достаточно определить в карте URL-адресов адрес новой функциональной страницы сервиса и разработать для этого адреса обработчик (функцию представления) в соответствии с требованиями заказчика, при этом усилия программиста будут сосредоточены в основном на разработке логики очередного разрабатываемого обработчика - всё просто в этом мире.
Продолжение следует...