среда, 16 августа 2017 г.

Как работают веб-приложения


Некоторые из читателей уже подписаны на мой 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.

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

Комментировать в ВКонтакте