- PVSM.RU - https://www.pvsm.ru -
Я хочу рассказать вам о некоторых трудностях, с которыми мы столкнулись при разработке встраиваемого JavaScript приложения, виджета комментариев "Комментатр [1]".
В этой статье я опишу некоторые проблемы и тонкости разработки подобных приложений, а также предложу варианты их решения.
В качестве backend–решения мы используем приложение на Ruby on Rails [2], поэтому некоторые фрагменты этой статьи будут специфичны для Rails–окружения.
Комментатр [3] состоит из двух проектов: API и виджета, который устанавливается на сайт клиента. Речь пойдет об их взаимодействии между собой и виджета с сайтом клиента. В основном общение виджета и API происходит посредством JSONP [4], который, как известно, поддерживает только GET–запросы. В связи с этим возникает первая сложность.
Так как комментарий может быть достаточно объемным, мы не можем использовать для его отправки метод GET, например, из-за ограничений по длине запроса в Internet Explorer, соответственно, про JSONP можем тоже смело забыть. Мы не можем отправить POST–запрос на домен приложения с сайта клиента через AJAX из–за ограничений, которые накладывает Same Origin Policy [5], но мы можем отправить форму методом POST в невидимый фрейм.
Результат добавления комментария можно получить разными способами, например, с помощью easyXDM [6].
Но мы пойдем другим путем и будем использовать Pusher [7]. Мы уже используем Pusher для моментального добавления комментариев на открытые у всех пользователей страницы, а также для отображения его в интерфейсе для модераторов. Гораздо удобнее использовать один инструмент для выполнения нескольких задач, тем более что Pusher с ними прекрасно справляется.
В итоге у нас получается следующая схема: пользователь отправляет комментарий к нам на сервер, мы его обрабатываем и передаем результат добавления Pusher–у, который в свою очередь передает его пользователю.
В качестве веб–сервера мы используем Unicorn [8]. Отправка события Pusher–у занимает некоторое время, которое не так заметно в случае отправки всего одного события. Однако при отправке нескольких событий время отклика значительно увеличивается и становится неприемлемым. Unicorn не относится к семейству evented–серверов, значит, мы не можем сделать отправку сообытий асинхронной.
Вариант решения этой проблемы, предложенный разработчиками Pusher, — это запуск EventMachine [9] в треде, которой делегируется отправка сообщений, рядом с каждым процессом 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
Об этом решении написана небольшая статья [10] разработчиками Gauges [11], а также есть готовое решение [12].
Запуск дополнительного треда с EventMachine для каждого воркера не кажется мне хорошей идеей.
Второй вариант — запуск рядом с нашим веб–сервером с Unicorn еще одного маленького веб–сервера с Thin, который без проблем справляетя со своей единственной задачей — проксированием запросов к Pusher таким образом, что отправка прокси–серверу запроса занимает время, стремящееся к нулю, а уже переправка им запроса в Pusher — дело второе.
Мы какое-то время использовали такое решение [13], но впоследствии от него отказались, и остановились на третьем варианте — передавать отправку сообщений delayed job процессору, в нашем случае, выбрав для этого Sidekiq [14]. Он моментально берет в обработку задачи, которые появляются в очереди, и способен очень быстро их выполнить.
Теперь, когда проблема с кросс–доменными запросами решена, нас ждет следующая — кодировки. Если просто вставить джаваскрипт с UTF-8 на страницу с windows-1251, то все символы UTF-8 в нем будут отображаться некорректно. Чтобы решить эту проблему, необходимо добавить тэгу <script>
аттрибут charset
со значением UTF-8
.
<script src="http://example.com/script_in_unicode.js" charset="UTF-8"></script>
В целях уменьшения количества дополнительных запросов для виджета, мы «упаковываем» его стили внутрь скрипта, а после установки виджета копируем их внутрь тэга <style>
, который создаем сразу после загрузки. При использовании юникод–символов в стилях, например, для создания разделителей, необходимо добавить в начало стиля медиа–запрос @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>
И последнее, о чем хочется рассказать в этой статье, — это внимание к заголовкам, которые отдает веб–сервер для динамических страниц и статики.
Для того, чтобы успешно отдавать страницу авторизации пользователя во фрейме, необходимо, чтобы веб–сервер не отдавал заголовки X-Frame-Options
, X-Content-Type-Options
и X-XSS-Protection
, но обязательно отдавал Content-Type
с указанной кодировкой.
С отдачей статики тоже есть свои тонкости. Firefox, например, не будет загружать шрифты с вашего сайта, пока вы не добавите заголовок Access-Control-Allow-Origin: *
Разумеется, это далеко не полный список нюансов, с которыми столкнется разработчик подобных решений, но, я надеюсь, этот материал окажется полезным.
Автор: chez
Источник [15]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/31806
Ссылки в тексте:
[1] Комментатр: http://commentatr.net/demo/one
[2] Ruby on Rails: http://rubyonrails.org/
[3] Комментатр: http://commentatr.net
[4] JSONP: http://ru.wikipedia.org/wiki/JSONP
[5] Same Origin Policy: http://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%B0%D0%B2%D0%B8%D0%BB%D0%BE_%D0%BE%D0%B3%D1%80%D0%B0%D0%BD%D0%B8%D1%87%D0%B5%D0%BD%D0%B8%D1%8F_%D0%B4%D0%BE%D0%BC%D0%B5%D0%BD%D0%B0
[6] easyXDM: https://github.com/oyvindkinsey/easyXDM
[7] Pusher: http://pusher.com/
[8] Unicorn: http://unicorn.bogomips.org/
[9] EventMachine: https://github.com/eventmachine/eventmachine
[10] небольшая статья: http://www.railstips.org/blog/archives/2011/05/04/eventmachine-and-passenger/
[11] Gauges: http://get.gaug.es/
[12] готовое решение: https://github.com/jdudek/pusher-async
[13] такое решение: https://github.com/commentatr/messenger
[14] Sidekiq: http://mperham.github.io/sidekiq/
[15] Источник: http://habrahabr.ru/post/175753/
Нажмите здесь для печати.