Как стать автором
Обновить

Разработка встраиваемого JavaScript приложения

Время на прочтение4 мин
Количество просмотров13K

Вместо вступления


Я хочу рассказать вам о некоторых трудностях, с которыми мы столкнулись при разработке встраиваемого JavaScript приложения, виджета комментариев "Комментатр".
В этой статье я опишу некоторые проблемы и тонкости разработки подобных приложений, а также предложу варианты их решения.
В качестве backend–решения мы используем приложение на Ruby on Rails, поэтому некоторые фрагменты этой статьи будут специфичны для Rails–окружения.

Комментатр состоит из двух проектов: API и виджета, который устанавливается на сайт клиента. Речь пойдет об их взаимодействии между собой и виджета с сайтом клиента. В основном общение виджета и API происходит посредством JSONP, который, как известно, поддерживает только GET–запросы. В связи с этим возникает первая сложность.


Добавление комментария


Так как комментарий может быть достаточно объемным, мы не можем использовать для его отправки метод GET, например, из-за ограничений по длине запроса в Internet Explorer, соответственно, про JSONP можем тоже смело забыть. Мы не можем отправить POST–запрос на домен приложения с сайта клиента через AJAX из–за ограничений, которые накладывает Same Origin Policy, но мы можем отправить форму методом POST в невидимый фрейм.
Результат добавления комментария можно получить разными способами, например, с помощью easyXDM.
Но мы пойдем другим путем и будем использовать Pusher. Мы уже используем Pusher для моментального добавления комментариев на открытые у всех пользователей страницы, а также для отображения его в интерфейсе для модераторов. Гораздо удобнее использовать один инструмент для выполнения нескольких задач, тем более что Pusher с ними прекрасно справляется.
В итоге у нас получается следующая схема: пользователь отправляет комментарий к нам на сервер, мы его обрабатываем и передаем результат добавления Pusher–у, который в свою очередь передает его пользователю.



Pusher и Unicorn


В качестве веб–сервера мы используем Unicorn. Отправка события Pusher–у занимает некоторое время, которое не так заметно в случае отправки всего одного события. Однако при отправке нескольких событий время отклика значительно увеличивается и становится неприемлемым. Unicorn не относится к семейству evented–серверов, значит, мы не можем сделать отправку сообытий асинхронной.

Вариант решения этой проблемы, предложенный разработчиками Pusher, — это запуск EventMachine в треде, которой делегируется отправка сообщений, рядом с каждым процессом Unicorn.

module Pusher
  module Async
    class << self
      def spawn
        Thread.new { EM.run } unless EM.reactor_running?
      end

      def respawn
        EM.stop if EM.reactor_running?
        spawn
      end
    end
  end

  class Request
    alias :send_async_without_next_tick :send_async

    def send_async
      df = EM::DefaultDeferrable.new
      EM.next_tick do
        send_async_without_next_tick
          .callback{ |response| df.succeed(response) }
          .errback{ |error| df.fail(error) }
      end
      df
    end
  end
end


Об этом решении написана небольшая статья разработчиками Gauges, а также есть готовое решение.
Запуск дополнительного треда с EventMachine для каждого воркера не кажется мне хорошей идеей.

Второй вариант — запуск рядом с нашим веб–сервером с Unicorn еще одного маленького веб–сервера с Thin, который без проблем справляетя со своей единственной задачей — проксированием запросов к Pusher таким образом, что отправка прокси–серверу запроса занимает время, стремящееся к нулю, а уже переправка им запроса в Pusher — дело второе.

Мы какое-то время использовали такое решение, но впоследствии от него отказались, и остановились на третьем варианте — передавать отправку сообщений delayed job процессору, в нашем случае, выбрав для этого Sidekiq. Он моментально берет в обработку задачи, которые появляются в очереди, и способен очень быстро их выполнить.

Вставка виджета с кодировкой UTF-8 на страницу с другой кодировкой


Теперь, когда проблема с кросс–доменными запросами решена, нас ждет следующая — кодировки. Если просто вставить джаваскрипт с UTF-8 на страницу с windows-1251, то все символы UTF-8 в нем будут отображаться некорректно. Чтобы решить эту проблему, необходимо добавить тэгу аттрибут charset со значением UTF-8.

<script src="http://example.com/script_in_unicode.js" charset="UTF-8"></script>


В целях уменьшения количества дополнительных запросов для виджета, мы "упаковываем" его стили внутрь скрипта, а после установки виджета копируем их внутрь тэга , который создаем сразу после загрузки. При использовании юникод–символов в стилях, например, для создания разделителей, необходимо добавить в начало стиля медиа–запрос @charset.
Это необходимо сделать даже в случае, если мы уже получили код стилей в корректной кодировке, иначе стили будут обработаны в кодировке принимающей страницы.

<style type="text/css">
@charset "UTF-8";
.breadcrumbs li:before {
  content: "→";
}
.breadcrumbs li:first-child:before {
  content: "";
}
</style>


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

<form action="http://example.com/comments" method="post" target="hidden-iframe" enctype="application/x-www-form-urlencoded;charset=UTF-8" accept-charset="UTF-8">
  <textarea name="body"></textarea>
</form>


HTTP–заголовки


И последнее, о чем хочется рассказать в этой статье, — это внимание к заголовкам, которые отдает веб–сервер для динамических страниц и статики.

Для того, чтобы успешно отдавать страницу авторизации пользователя во фрейме, необходимо, чтобы веб–сервер не отдавал заголовки X-Frame-Options, X-Content-Type-Options и X-XSS-Protection, но обязательно отдавал Content-Type с указанной кодировкой.

С отдачей статики тоже есть свои тонкости. Firefox, например, не будет загружать шрифты с вашего сайта, пока вы не добавите заголовок Access-Control-Allow-Origin: *

Резюмируя


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

Если у вас будут замечания, пожелания, предложения по поводу сервиса, с большим удовольствием отвечу на них по почте или в личных сообщениях.
Теги:
Хабы:
Всего голосов 30: ↑28 и ↓2+26
Комментарии24

Публикации

Истории

Работа

React разработчик
80 вакансий
Ruby on Rails
12 вакансий

Ближайшие события

27 августа – 7 октября
Премия digital-кейсов «Проксима»
МоскваОнлайн
28 сентября – 5 октября
О! Хакатон
Онлайн
3 – 18 октября
Kokoc Hackathon 2024
Онлайн
10 – 11 октября
HR IT & Team Lead конференция «Битва за IT-таланты»
МоскваОнлайн
25 октября
Конференция по росту продуктов EGC’24
МоскваОнлайн
7 – 8 ноября
Конференция byteoilgas_conf 2024
МоскваОнлайн