Некоторые из читателей уже подписаны на мой Twitter и видели опрос на тему безопасного способа передачи пароля в веб. Тема важная, так что стоит на ней остановиться подробнее, но для начала разберемся как вообще работают веб-приложения.
Ниже приведена схема, на которой я очень упрощенно описал типичный веб-сервис и его взаимодействие с веб-браузером. Однако, тут показаны все важные компоненты, которые участвуют в процессе.
Далее рассмотрим по шагам что происходит.
Поиск сервера
Процесс начинается, когда пользователь запускает любимый веб-браузер и вбивает в строку адреса текст вида https://www.vk.com/codeatcpp, а затем нажимает Enter. После этого веб-браузер посылает запрос DNS-серверу, чтобы узнать какой IP-адрес соответствует серверу www.vk.com. Тут нужно отметить, что большие сервисы используют не один сервер, чтобы справляться с нагрузкой. В таких случаях одному доменному имени будет соответствовать множество IP-адресов. DNS-клиенты обычно используют первый IP-адрес из ответа DNS-сервера, поэтому простейший способ балансировки нагрузки — выдавать разным клиентам различные ответы. Стандартно это делается по алгоритму Round-robin, что не всегда эффективно, так как не учитывает состояние отдельных серверов (решение этой проблемы — тема отдельной статьи). Вы можете легко проверить данный факт, запустив в командной строке следующую команду:$ nslookup vk.com Name: vk.com Address: 87.240.165.85 Name: vk.com Address: 87.240.165.86 Name: vk.com Address: 87.240.165.83 Name: vk.com Address: 87.240.165.84Если у вас в системе разрешен протокол IPv6, то в ответе вы увидите еще и IPv6 адреса. Можно попробовать запустить команду несколько раз подряд и самостоятельно убедиться, что порядок адресов в ответе меняется.
Подключение к серверу
Определив IP-адрес сервера, можно попытаться к нему подключиться. Для этого веб-браузер открывает сокет с адресом 87.240.165.85 (первый из списка выше) и портом 443 для HTTPS или 80 для HTTP.Если используется протокол HTTP, то веб-браузер просто посылает примерно такой текст в канал:
GET HTTP/1.1 Host: vk.comЕсли вы не используете прокси сервер, то можете попробовать самостоятельно послать такой текст, используя утилиту telnet и указав порт 80. Если используется HTTPS, то веб-браузер сначала договаривается с сервером о шифровании канала по протоколу TLS, а дальше все запросы и ответы передаются по зашифрованному каналу. Формат запросов и ответов остается таким же, как при обычном HTTP.
В ответ на ваш запрос сервер вышлет HTML код, который обычно лежит в файле index.html. Примитивная на первый взгляд страница www.google.ru присылает текст размером почти 70 килобайт и уже содержит в себе сжатые скрипты. Мы рассмотрим более простой пример, чтобы лучше разобраться что к чему. Наш сервер (пусть www.example.com) прислал веб-браузеру такой текст:
<html> <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> <body> <div id="content">Пожалуйста, разрешите выполнение JavaScript, чтобы увидеть сайт.</div> </body> <script type="text/javascript" src="scripts/app.js"></script> </html>Здесь видно, что в теле странице есть единственный тег с именем content. Также подключается некий внешний скрипт app.js — это и есть наше веб-приложение. Если выполнение скриптов не разрешено в веб-браузере, то ничего не произойдет и будет выведен текст из тега.
А вот если выполнение JavaScript разрешено, то веб-браузер получит с сервера файл app.js и будет выполнять код из него.
Веб-приложение
Код, который получается с сервера и представляет собой веб-приложение. Этот код на лету сгенерирует необходимое содержимое и вставит его внутрь тега content. Простейший вариант app.js приведен ниже:function onLogin(form) { var xhttp = new XMLHttpRequest(); xhttp.open("GET", "/rest_api/v1/me_page", false); xhttp.setRequestHeader("Content-type", "application/json"); xhttp.setRequestHeader("Authorization", "Basic " + btoa(form.username.value + ":" + form.username.password)); xhttp.send(); var response = JSON.parse(xhttp.responseText); } (function(){ document.getElementById("content").innerHTML = '\ <form name="login">\ Username: <input type="text" name="username"/></p>\ Password: <input type="password" name="passwd"/></p>\ <input type="button" onclick="onLogin(this.form)" value="Login"/>\ </form>'; })()Данный код найдет на странице элемент с именем content и заменит его содержимое на форму логина, которая состоит из двух полей ввода и кнопки Login. По кнопке будет запускаться функция onLogin, которая определена тут же.
Тут начинается самое интересное. Функция onLogin посылает серверу запрос GET. В виде текста это может выглядеть следующим образом:
GET /rest_api/v1/me_page HTTP/1.1 Host: www.example.com Authorization:Basic YWRtaW46dW5kZWZpbmVkЛегко заметить, что это практически тоже самое, если бы вы адресной строке браузера написали текст вида http://www.example.com/rest_api/v1/me_page. Тут и начинается взаимодействие с приложением-сервером (см. схему в начале статьи).
Приложение-сервер
На схеме видно, что приложение-сервер не взаимодействует с Интернетом напрямую. Ему не нужно заботиться о передачи статического контента (в нашем случае, файлов index.html и app.js), который не меняется от запроса к запросу. И не нужно думать про организацию защищенного HTTPS канала до веб-браузера. Для этих целей лучше использовать проверенные средства, например, проксирующий веб-сервер nginx, который умеет эффективно отдавать статический контент и проксировать запросы к нашему приложению-серверу. В реальном применении на nginx также удобно возложить вопросы отказоустойчивости и распределения нагрузки, а также многие другие.Какие запросы обрабатывает nginx, а какие передаются приложению-серверу, определяется настройками в файле nginx.conf.
Продолжение следует...