Архитектура взаимодействия клиентской и серверной частей Web приложения

в 12:53, , рубрики: html, javascript, jquery, архитектура, метки: , ,

Хотел рассказать, как я вижу устройство архитектуры взаимодействия серверной и клиентской частей. И хотел бы узнать спросить, чем плоха или хорошо такая архитектура.
Архитектура взаимодействия клиентской и серверной частей Web приложения

Клиентская структура

Даже устаревшие браузеры предлагают нам набор возможностей для создания полнофункциональных интерактивных Web-приложений. А благодаря таким библиотекам как jQuery, которые предлагают не скромные кроссбраузерные и мультиплатформенные решения, разработка клиентских частей ускоряется во много раз. Чем веб-мастера и пользуются на полную катушку, используя при этом самые разнообразные интерфейсы для взаимодействия серверной и клиентской частей.

Почему jQuery? В данной статье я использую её просто для примера. Для взаимодействия клиентской и серверной частей приложений не важно, что вы используете: какую-либо библиотеку, собственную разработку или голый javascript. Главное – добиваться цели: чтобы все работало и работало правильно.

Конечно, многие из нас начинали изучение jquery с циклов статей для новичков, внедряя на свои сайты и динамическую подгрузку содержимого, и проверку форм на серверной части, и многое другое.

Со временем размер кода вырос: в проекте появилось множество обработчиков ответов от серверной части. Где-то запрашивается целая страница и из нее вырывается кусок контента, где-то идет запрос на различные файлы, где-то ожидается JSON, а кое-где XML. Все это надо прибирать, чтобы и кода было меньше, и работал он быстрее, и работать с ним было проще.

Во-первых. Создадим единый интерфейс отправки запросов.
Для этого обернем $.ajax в собственную сахарную функцию.

Конечно, $.ajax можно настроить через $.ajaxSetup. И это удобнее и практичнее. Но тогда возникает несколько проблем.

  • Внезапно может понадобиться отправить запрос, который бы являлся исключением из общих правил. Эту проблему легко решить, сбросив установленные настройки используя опцию «global» в значении «false».
    Например так:

    // где-то определяем глобальные настройки
    $.ajaxSetup({
     url: 'mysite.ru/ajax.php',
     dataType: 'xml'
    });
    /* ...спустя много кода вызываем */
    $.ajax({
      url: 'ajax.mysite.ru',
      data: {
        action: 'do_something'
        username: 'my_username'
      },
      global:false // сбрасываем настройки
    });

  • Вторая проблема может возникнуть при использовании ajaxSetup, когда доработка функционала будет отдана стороннему разработчику. Посмотрев код он увидит привычные для него функции $.ajax и не будет вникать, что где-то что-то глобальное переопределяется. А если и заметит, то нет никакой гарантии, что он не попытается что-то в нем поменять, и в результате, не сломает какой-нибудь старый кусок кода.

Я предпочитаю обернуть все в другую функцию. Пусть это не так практично, и порождает больше кода, но безопаснее при использовании. Другой программист, увидев незнакомую функцию, сразу обратит на нее внимание. И, возможно, посмотрев, как отправляется другой запрос, сделает также. А если не поймет, то ничто не помешает воспользоваться стандартной функцией $.ajax.

// расширяем
$.extend({
  gajax: function(s) {
    // наши настройки здесь
    var options = {
      url: "ajax.mysite.ru",
    };
    // объединяем настройки
    $.extend( options, s, { action:s.action } );
    $.ajax( options );
  }
});
// вызываем
$.gajax({
  data: {
    username: 'my_username'
  },
  action: 'do_something'
});

Во-вторых, надо сократить количество точек входа для AJAX запросов. Если запросы отправлялись и на index.php, и на request.pl, и на upload.xml, то это огромный объем работы, и нередко бывает, что сделать это невозможно не переписав заново всю серверную часть. Хотя это надо сделать, если хочется быстро и просто расширять клиентскую часть. Как и у всех правил, у этого есть исключения. О них чуть ниже.

В третьих, самое главное: необходимо унифицировать обработчик ответа.

Например, у нас в проекте все ajax-запросы идут только на файл ajax.php. Он всегда возвращает некоторые данные в виде XML, довольно просто структурированные.

Единый обработчик ответа парсит XML и раскладывает по полочкам:

• Список js-файлов, которые необходимы для обработки ответа.
• Сallback-функции, которые необходимо запустить, и какие аргументы в эти функции надо передать.
• Куски HTML-кода, которые будут применены в вышеуказанных функциях.
• Список css файлов, которые нужны для украшения html кода.

// расширяем
$.extend({
  gajax: function(s) {
  var recognize = function( xml, s ) {
   /* я сознательно описал только комментариями, потому что иначе получилось бы целое полотно */
   // парсим xml (вообще jQuery сам парсит XML, так что этот пункт можно пропустить)
   // смотрим есть ли в нем js-файлы, которые надо погрузить и начинаем их загружать
   // смотрим есть ли стили, которые надо погрузить
   // смотрим есть ли в нем callback`и
   // ищем html
  };
  // наши настройки здесь
  var options = {
   url: "ajax.mysite.ru",
   success: function(xml){
    recognize( xml, s );
   }
  };
  $.extend( options, s, { action:s.action } );
  $.ajax( options );
  }
});

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

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

Когда все скрипты загружены, запускаем callback-функции.

Надо обратить внимание, что для ускорения загрузки все js файлы загружаются асинхронно, и соответственно здесь может возникнуть проблема с запуском callback-функций. Ведь они могут быть в этих файлах.
Решением этой проблемы может стать создание зависимостей функций от js файлов. Это, в свою очередь, порождает проблемы контроля этих зависимостей программистом и передаче их вместе с ответом клиентской стороне.
Второе решение — это дождаться загрузки абсолютно всех запрошенных файлов и уже потом начать выполнения функций.
Как сделать это красиво, можно послушать в этом чудесном подкасте habrahabr.ru/blogs/hpodcasts/138522/

Я для своих проектов выбрал самое простое решение — второе. Я исходил и того, что подгрузка необходимых js файлов нужна крайне редко, потому что все скрипты объединяются на серверной стороне в пакеты. И обычно пакет заранее содержит все необходимые callback`и. А если подгрузка файлов и будет нужна, то потребует максимум один-два файла, что не сильно задержит обработку ответа.

Напомню, что пользователь к этому моменту все еще не видит изменений на экране. Что ж, покажем ему их — начнем выполнять функции. Главное, что стоит учитывать — все функции-обработчики ответа должны быть независимыми друг от друга. Они могут зависеть от одного крупного компонента (например, функций ядра проекта), но не должны друг от друга. Это позволит перемещать и интегрировать callback`и в другие части проекта без каких-либо особых заморочек. Например, выводить всплывающие окна с сообщениями об ошибках может потребоваться на всех страницах.

Функции выполнены, пользователь работает дальше.

Обработка запросов сервером

Теперь я бы хотел разобрать ситуацию, когда был послан запрос, который сервер не смог разобрать по какой-то причине.

У нас все запросы не только посылаются на один файл, но и все данные в нем посылаются методом POST. На серверной стороне ожидается некоторый POST-параметр «action». Значение этого параметра определяет то, какой модуль сайта должен отработать. Сами пары с ожидаемыми значениями и именами модулей записаны в конфигурационном файле. Если в конфигурации есть соответствующий модуль – он запускается, если нет — запускается дефолтный модуль, который настроен на возврат сообщения об ошибке.
Также, мы можем записать в серверный лог все параметры запроса. Анализ лога потом позволит быстро отследить и исправить ошибку. Или во время выполнения скрипта забанить IP пользователя на минут 15 за перебор значений, если есть уверенность, что ошибок в нашем приложении нет. Но это уже крайность.

Обработка ошибочных запросов на клиентской стороне

Нельзя забывать про обработку сорвавшихся запросов, например по таймауту или внезапному отключению пользователя от сети.

// наши настройки здесь
var options = {
  url: "ajax.mysite.ru",
  success: function(xml){
    recognize( xml, s );
  },
  error: function( s,err ){
    // серверная ошибка
    if ( s.status == 200 && err == 'parsererror' ) {
      alert( s.responseText );
    }
    // Обрабатываем срыв запроса по таймауту
    else if ( err == 'timeout' ) {
      alert('Connection to the server was reset on timeout.');
    }
    // или из-за отсутствия подключения к сети.
    else if ( s.status == 12029 || s.status == 0 ) {
      alert('No connection with network.');
    }
};

Кэширование

Вернемся к вопросу про точки входа. C точки зрения написания красивого кода, указывание только одного адреса для точки входа не совсем правильное решение. Это ни в коем случае не противоречит моему второму совету. Попробую разобрать все стороны данного вопроса.

Как можно было бы сделать все красиво? — Отправлять запросы на определенные адреса. Например, ajax.php?action=feedback или ajax.example.com/feedback. Это избавило бы запрос от лишнего мусора. Когда определен модуль, который будет обрабатывать данные, эта информация ему уже не нужна. Мухи отдельно, котлеты отдельно. Красиво? Красиво.

Если POST параметров в запросе вообще нет, то это хорошая возможность использовать возможности кэширования на промежуточных проксях или кэш браузера.
В рекомендациях от Google для Web-мастеров сказано, что отсутствие GET-параметров в http-запросе провоцирует прокси-сервера использовать кэш. Поэтому запрос без POST и GET параметров, например ajax.mysite.com/footer, будет идеальным для добавления его в кэш прокси сервером. Из дополнительных плюсов отмечу, что если ответ будет от прокси сервера, то это немного разгрузит и наш сервер.
Когда это вообще может понадобится? Когда мы подгружаем части страницы через ajax. При этом они являются неизменными в течение долгого времени.

Но использовать ajax-запрос без GET или POST параметров не стоит, раз существует вероятность получения не актуальных данных.
Предположим, что у нас чат и мы не используем веб-сокеты. Каждую секунду долбимся на сервер, чтобы посмотреть новые сообщения. Пользователь попался за каким-то прокси сервером. Первый запрос пройдет удачно и вернет, что сообщений нет. На все последующие запросы, прокси сервер будет возвращать первоначальный ответ. А это нарушит всю работу чата. Проблем поможет избежать добавление параметра в запрос. Что, в свою очередь, создаст тот самый мусор, которого мы так старались избежать.

В вопросах кэширования каждый раз нужно подходить индивидуально и использовать то, что необходимо для проекта.

Изобретая велосипед

В нашем проекте данные в запросе отправляется только методом POST, потому что механизм кэширования у нас заложен в том самом едином интерфейсе отправки ajax-запросов. По умолчанию абсолютно каждый запрос и ответ на него запоминается на 3600 секунд. При последующем аналогичном запросе отработает кэш, возьмет из коробочки ответ и запустит сразу все механизмы по его анализу, без отправки непосредственно запроса на сервер. Ведь мы уже уверены, что все стили и скрипты уже на месте.
Если запоминать пару запрос-ответ не нужно или надо просто уменьшить время жизни кэша, то серверная сторона сообщает об этом в первоначальном ответе, изменяя время кэша на 0 или другое количество секунд.

Почему только POST, а не совмещенный вариант? Так проще сравнивать запросы.
Данные отправляемые через функцию ajax в jQuery, это js-объект.
Запрошенные данные довольно быстро сравниваются перебором с объектами в кэше, конечно, если у клиента современный браузер. Если браузер старый, приходится принудительно отключать кэш.

В итоге

В итоге мы получаем устойчиво работающую связку клиентской и серверной частей. И эта связка позволяет нам создавать безопасные и простые для дальнейшего расширения Web приложения.

Пост написан во имя спасения наносекунд и множества нервных клеток программистов, создающих свои прекрасные творения.

Автор: dumistoklus


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js