- PVSM.RU - https://www.pvsm.ru -

Простейший способ добавить WebSocket в Django

Примечение переводчика: вебсокеты и Django — это довольно сложная тема, которая уже не раз поднималась на хабрахабре и основной идеей является написание параллельного бэкенда для вебсокетов. Автор же предлагает довольно лаконичное решение этой проблемы, которому правда еще предстоит проверка временем.

TL;DR — Я пришел к очень простому решению для работы с вебсокетами в Django. Все что вам нужно — это установить django-websocket-request [1], запустить скрипт, и теперь ваше приложение поддерживает вебсокеты! Это решение заставляет Django думать, будто он получает нормальный (в какой-то мере) HTTP-запрос, поэтому оно будет совместимо почти со всем вашим существующим кодом. Решение работает нормально как с Django Rest Framework [2], так и с обычными функциями-представлениями и представлениями, основанными на классах (Class Based Views).

Подробнее

Мы разрабатываем Blimp 2 [3] — это говорит о том, что у нас постоянно множество изменений в коде и инфраструктуре, и о том, как мы решаем старые и новые проблемы. Одно из решений, которое мы сделали в отношении нашего приложения, существенно изменило взаимодействие фронтенда и бэкенда.

В данный момент Blimp работает на Django. Он обслуживает наш HTML-код, обрабатывает наше публичное и приватное API, всю бизнес-логику. До последнего времени большинство веб-приложений примерно так и строились, однако Blimp имеет толстый JavaScript-клиент. С обычными запросами все происходит примерно следующим образом: вы запрашиваете URL, происходит какая-то работа со стороны бэкенда — запросы к базе данных, кеширование, обработка данных, отрисовка HTML-страниц, подгрузка сторонних CSS и JavaScript-библиотек, загрузка нашего собственного JavaScript-приложения, еще немного обработки данных, и наконец отрисовка результата.

Через несколько месяцев практики и роста нашего проекта, мы обнаружили несколько ключевых улучшений, которые мы можем внедрить — новая версия бэкенда должна будет заниматься формированием JSON, но не отрисовкой HTML, а наше фронтенд-приложение, которое выполняется со стороны клиента, будет просто потреблять данные через API. Мы решили, что оно будет использовать вебсокеты там, где это возможно, и обычный XHR во всех остальных случаях.

Веб-фреймворк Django и ему подобные построены для работы с циклом HTTP-запрос/ответ, поэтому всё в них, и представления, и механизмы аутентификации принимают HTTP-запрос на входе и отдают HTTP-ответ на выходе. С другой стороны, сервер вебсокетов не знает ничего о таком цикле.

Нашими главными целями были:
1. Одни и те же механизмы сериализации и десериализации данных, как со стороны HTTP-бэкенда, так и со стороны API вебсокетов.
2. Одна и та же бизнес-логика для всех.

Первой мыслью, которая пришла в голову, было превращение всей бизнес логики в методы наших моделей. Таким образом мы могли бы написать код единожды и разделить его между двумя бэкендами. В тот момент это казалось правильным решением, но через несколько часов было доказано, что нет. Так как мы работаем поверх Django REST framework, мы были бы вынуждены унаследовать и доработать десятки его классов и примесей, что на самом деле звучало не так уж плохо, но мы были настроены достаточно скептически и решили поискать другие идеи.

Мы знали, что хотим единое REST API, которое бы работало по двум каналам: HTTP и вебсокеты. Самым лучшим вариантом стало бы избежание переписывания чего бы то ни было только для работы его с вебсокетами. В какой-то момент меня озарило. Я вспомнил о Sails.js [4], который делает нечто подобное с тем, чего мы хотим достичь.

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

Говоря более простым языком, мы хотели внедрить непривязанный ни к чему конкретному механизм обмена данными, который бы позволил нам использовать все что есть из Django-цикла запрос/ответ, чтобы автоматически формировать вебсокет-сообщения.

Решение

WebSocketRequest оказался неожиданно простым решением. Это простой класс, который принимает JSON-строку, содержащую следующие ключи: method, url, data, и token. Ключ method может быть любым HTTP-методом: GET, POST, PUT, DELETE, PATCH, HEAD или OPTIONS. Ключ url является абсолютным URL без доменного имени. Ключ data — это необязательный параметр — словарь, содержащий данные. Ключ token также является необязательным — используется для воссоздания заголовка HTTP-авторизации, авторизации через JSON Web Token или для ваших собственных ключей. Вы можете посмотреть мою статью [5], чтобы узнать больше о JSON Web Token, а если вы пользуетесь Django REST framework, то вам наверное понравится django-rest-framework-jwt [6].

WebSocketRequest работает следующим образом:

  1. Проверяет пришедшую JSON-строку
  2. Создает экземпляр фабрики запросов [7] (RequestFactory)
  3. Динамически вызывает один из методов фабрики запросов, который возвращает экземпляр WSGIRequest
  4. Находит соответствующее URL представление
  5. Запускает найденное представление вместе со всеми данными, которые пришли из URL.

Да-да, фабрика запросов, вы прочитали верно. Вы можете быть знакомы с ней, если когда-нибудь писали тесты для Django-приложений, но если нет, фабрика запросов занимается тем, что генерирует объект запроса, который может быть использован как аргумент для любого представления. Единственным минусом является то, что такой запрос не поддерживает механизм middleware [8], что для некоторых может стать проблемой.

Мне определенно хотелось бы услышать о возможных проблемах данного подхода. В чем он может быть улучшен? Что может сломаться? Что насчет промышленного использования?

Демо

Обратите внимание, что Django в этом примере не используется вообще. Tornado [9] выдает статический HTML-файл и передает все вебсокет-запросы django-websocket-request, в котором уже и происходит вся магия.

Я установил демо-приложение на Heroku: http://dwr-example.herokuapp.com/ [10]. Будьте внимательны, данные периодически стираются. Если вы обнаружите какую-то ошибку, напишите мне в твиттере [11] о ней.

Вы можете установить WebSocketRequest при помощи pip:

pip install django-websocket-request

Исходный код:
https://github.com/GetBlimp/django-websocket-request [1]

Демо-приложение:
https://github.com/GetBlimp/django-websocket-request-example [12]

Автор: batment

Источник [13]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/news/54065

Ссылки в тексте:

[1] django-websocket-request: https://github.com/GetBlimp/django-websocket-request

[2] Django Rest Framework: http://www.django-rest-framework.org/

[3] Blimp 2: http://blog.getblimp.com/category/odp/

[4] Sails.js: http://sailsjs.org/

[5] статью : http://jpadilla.com/post/73791304724/auth-with-json-web-tokens

[6] django-rest-framework-jwt: https://github.com/GetBlimp/django-rest-framework-jwt

[7] фабрики запросов : https://docs.djangoproject.com/en/dev/topics/testing/advanced/#the-request-factory

[8] middleware: https://docs.djangoproject.com/en/dev/topics/http/middleware/

[9] Tornado: http://www.tornadoweb.org

[10] http://dwr-example.herokuapp.com/: http://dwr-example.herokuapp.com/

[11] твиттере: https://twitter.com/jpadilla_

[12] https://github.com/GetBlimp/django-websocket-request-example: https://github.com/GetBlimp/django-websocket-request-example

[13] Источник: http://habrahabr.ru/post/211094/