Разрабатываем web-приложение на Python3x, эпизод второй

Jazz

Опубликован:  2023-09-29T05:16:41.256945Z
Отредактирован:  2023-09-29T05:16:41.256945Z
Статус:  публичный
28
0
0

Продолжаем разрабатывать 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. Жму, находясь в окне браузера, окно разделится на две части, и мы увидим те самые инструменты разработчика. Перехожу в них на вкладку "Сеть".

QfzWfhVeMx.png

Здесь следует отметить, что сразу после перехода на эту вкладку я поставил флажок в поле "Отключить кеш". Дело в том, что браузер кеширует полученные от сервера данные, а web-разработчик, работая над клиентской частью кода (фронтэндом) очень часто в процессе редактирует отправляемые сервером клиенту файлы. Чтобы браузер каждый раз учитывал изменения в новых версиях файлов, кеш нужно отключить на стороне клиента, что я и сделал.

Теперь я могу инициировать процесс отправки браузером HTTP запроса на сервер. Ввожу в адресную строку адрес стартовой страницы selfish и жму enter.

PopiE7sWtE.png

Вот такую интересную картинку я получаю в окне инструментов разработчика. Мы видим оба отправленных браузером запроса, для каждого запроса отображены дополнительные данные, в том числе, размер полученного от сервера ответа и время, затраченное на обработку запроса сервером и получение ответа клиентом. Код статуса для каждого запроса тоже отображается.

Получаем данные заголовков ответа

Навожу указатель мыши на первый запрос - запрос к localhost, и жму левую кнопку мыши. В результате окно ещё раз разделится, и браузер покажет дополнительную информацию об этом запросе. В том числе, мы можем увидеть заголовки полученного от сервера ответа.

VieLM6PNZ9.png

В данном случае меня интересует заголовок Content-Type. Для полученного ответа браузер отобразил в этой строке следующие данные:

text/plain; charset=utf-8

От содержимого этого заголовка будет зависеть, что браузер будет делать с полученными данными. Как мы видим, в этом конкретном случае сервер отправил нам текстовый документ с кодировкой UTF-8, а браузер отобразил в своём окне текст из этого документа, обращаю внимание, что отобразил моноширинным шрифтом, в соответствии с собственными настройками. Полученный от сервера Content-Type в данном случае определяется классом PlainTextResponse, экземпляр которого возвращает функция представления show_index, которую я написал в предыдущей демонстрации.

Давайте обратим внимание на второй запрос, который браузер отправил автоматически. Браузеру нужна иконка для избранного, и он пытается её получить с сервера. Но web-сервис, который работает на сервере, на текущий момент не имеет в своей карте соответствующего этому запросу URL-адреса, и поэтому сервер вернул клиенту код ошибки 404 - страница не найдена. У этого ответа сервера тоже есть Content-Type. Давайте посмотрим на него.

bkV0sL2FbU.png

В своём ответе на этот запрос сервер опять отправил клиенту простой текст, хотя браузер ожидает файл с картинкой. В итоге на текущей вкладке браузер отобразил собственную иконку по-умолчанию.

Если посмотреть в терминале на выхлоп отладочного сервера, мы увидим всё те же два запроса по методу GET.

spxeFGbOtT.png

Диагноз поставлен. Чуть позже я эту досадную ошибку исправлю. А пока давайте посмотрим, как можно изменить 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>')

Сохраняю изменения в файл. Вот как это выглядит в окне моего текстового редактора.

kWlpY9ycV6.png

Вновь запускаю отладочный сервер, запускаю браузер и в его окне стучусь по URL-адресу стартовой страницы selfish. Давайте посмотрим на интересующий нас заголовок полученного от сервера ответа.

cwxCv7ovis.png

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. Сохраняю изменения в файл.

sw1JN8vgdf.png

Обработчик для очередного 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'),
            ...

Опять сохраняю изменения в файл.

3MH1G0tvym.png

Перехожу в окно браузера и ввожу в адресной строке только что созданный URL-адрес, жму enter.

1Vmw5HFGME.png

Обращаю внимание на вкладку в окне браузера, на вкладке появилось изображение подключенной иконки. Давайте посмотрим в терминал, на сообщение сервера.

R7eUOkOvkX.png

Запрос обработан с кодом статуса 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-адресов новый адрес. Сохраняю изменения в файл.

AX1RNe9Ppm.png

Возвращаюсь в окно браузера и набираю в адресной строке только что созданный URL-адрес.

UeFXgWQFQK.png

Если мне будет необходимо уже сейчас развернуть selfish на сервер в сети Интернет, я буду совершенно спокоен, файл robots.txt запрещает всем роботам всех поисковых и прочих систем индексировать любые страницы сервиса. Ещё одна цель достигнута.

Подводим итог

Продемонстрированные в этом описании действия показывают, что с помощью соответствующих инструментов Starlette в уже существующее ASGI-приложение очень не сложно добавить новый функционал, для этого достаточно определить в карте URL-адресов адрес новой функциональной страницы сервиса и разработать для этого адреса обработчик (функцию представления) в соответствии с требованиями заказчика, при этом усилия программиста будут сосредоточены в основном на разработке логики очередного разрабатываемого обработчика - всё просто в этом мире.

Продолжение следует...

Метки:  web, http, selfish, python3x, starlette