Введение
Сегодня работал над добавлением в
aiohttp.web
свойства scheme
для request object.
Идея простая: отвечать что request.scheme
"http"
для HTTP
запросов, иначе "https"
.
У меня есть правило: перед началом погляди как другие уже справились с этой задачей.
У создателей популярных библиотек есть большой опыт по преодолению неочевидных проблем, учиться у мастеров -- полезно.
Так вышло что сегодня я смотрел код Django.
И было в том коде примерно такое:
@property
def scheme(self):
if settings.SECURE_PROXY_SSL_HEADER:
try:
header, value = settings.SECURE_PROXY_SSL_HEADER
except ValueError:
raise ImproperlyConfigured(
'The SECURE_PROXY_SSL_HEADER setting must be a tuple containing two values.'
)
if self.META.get(header, None) == value:
return 'https'
return 'http'
В целом очень хорошо: Django показала, как работать с HTTP и что делать если сервер расположен за HTTPS Reverse Proxy (Nginx, например).
В последнем случае я сконфигурирую Nginx чтобы он добавил несколько полезных HTTP HEADERS для HTTPS connection:
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
По X-Forwarded-Proto
я пойму что это был HTTPS.
В целом стандартная и всем (надеюсь) известная процедура.
У aiohttp свободы чуть больше: оно может понять что сокет, по которому подключились напрямую, сам уже SSL -- но это пригодно только если мы готовы выставить наш aiohttp сервер прямо в веб.
Куда чаще его прячут за Nginx, HAProxy или похожим reverse proxy и там уже работают с сертификататами, проксируя обычный HTTP connection.
В общем всё прекрасно: Nginx выставит X-Forwarded-Proto HTTP
HEADER который будет или "http"
или "https"
.
Django глянет на settings.SECURE_PROXY_SSL_HEADER
и если там
("X-Forwarded-Proto", "https")
то scheme тоже будет "https"
.
Очень грамотно сделано, мне нравится.
Проблема
Так почему я этот пост написал?
А потому что settings.SECURE_PROXY_SSL_HEADER
может быть чем угодно
-- строкой, числом или ещё какой непотребной константой.
Проверка выполняется на момент получения request.scheme
.
Нам, питонщикам, плевать на производительность в данном случае -- на самом деле try/except обходится дешево и никак не отразится на работе сайта.
Беда в другом -- ошибка неправильной конфигурации выявится не на этапе старта приложения а тогда, когда его выкатят в production.
У тестов будет свой правильный settings.py
, а на production server
админ чуть-чуть ошибётся.
И это тоже не слишком большая проблема -- при ручном тестировании команда QA, наверное, обнаружит ошибку очень быстро. Или на следующее утро -- тут уж зависит от того как техроцесс построен.
А теперь представьте что вы ошиблись в другой настройке. Очень редко используемой, но при этом важной.
Решение
Проблему вроде бы можно выявить рано, перестроив процесс запуска программы.
Для начала нужно отказаться от использования общего конфига в API.
Строить код библиотеки так, чтобы она никогда не лезла в settings.py
(это и Flask касается если что).
Пусть все нужные классы принимают конфигурационные параметры явно, прямо в конструкторах.
Тогда можно быстро понять, что формат параметра не тот или IP address недоступен.
Разделение на этапы:
- чтение конфига, анализ его и подготовка приложения к работе
- запуск и работа
помогает избежать досадных недоразумений.
Плюс, к тому же, на явном этапе подготовки к старту можно позволить себе довольно дорогостоящие проверки на корректное функционирование системы (послать PING чтобы убедится что Redis живой, например).
Комментариев нет:
Отправить комментарий