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

Веб-приложение — ну почти без бек-энда: Flask, Redis, API через JSONP, JSFiddle.net

Данная статья — так называемый «proof-of-concept» создания фронт-енд приложения, работающего с API посредством JSONP, то есть, как говорят, «cross-origin». Также описана организация данных в Redis.

Например, можно с легкостью разместить на jsfiddle.net [1] некое приложение, бек-энд которого будет находится на другом домене.

Согласитесь, что полноценный работающий конечный продукт (требующий наличие некоего сервера для централизации обмена данными), находящийся внутри JSFiddle, выглядит забавно!

Веб приложение — ну почти без бек энда: Flask, Redis, API через JSONP, JSFiddle.net

Цель статьи — поделится своим сегодняшним опытом с двух сторон:

  • Имплементацией JSONP + Long Polling
  • Работой с замечательной Redis

Кое-чем подобным занимаются ребята из BackendLess [2].

Что у нас есть

Итак, у нас есть:

  • Некий собственный сервер с Python 2.x на борту
  • Браузер и доступ к JSFiddle.net [1]
  • Желание построить API-over-JSONP

Что я использовал

JSONP

Думаю, читатель не нуждается в объяснении, что это такое. А насчёт имплементации — тут всё делает один декоратор:

def jsonp(fn):
    def wrapper(*args, **kwargs):
        callback = request.args.get('callback', None)
        if not callback:
            raise BadRequest('Missing callback argument.')
        return '{callback}({data});'.format(
            callback=callback,
            data=dumps(fn(*args, **kwargs))
        )
    wrapper.__name__ = fn.__name__
    return wrapper
Redis

Есть такая замечательная вещь — Redis. Как говорят о ней разработчики,

Redis is an open source, BSD licensed, advanced key-value cache and store. It is often referred to as a data structure server since keys can contain strings, hashes, lists, sets, sorted sets, bitmaps and hyperloglogs.

Или в двух словах:

Redis — это мощная система хранения и кеширования данных в формате «ключ-значение.»

Немного про Redis

Если вы не знакомы с Redis, советую почитать о нём на оф. сайте, поскольку ниже будут описаны не все тонкости работы с ним.
Собственно, сам Redis работает как отдельный демон, а из Python-скрипта мы обращаемся к нему через довольно простой одноимённый модуль-коннектор.
Мы можем создать некий ключ и присвоить ему:

  • Скалярное значение (на самом деле — строку)
  • Список
  • Массив
  • Множество
  • Отсорированное множество

Но! Мы не можем сделать массив из массивов, например. Поэтому в данном случае придется делать ключ со списком индексов и по ключу на каждый индекс. Также ввиду отсутствия выборки по значениям (а-ля WHERE в SQL) иногда приходится делать списки с обратным «маппингом», например, для поиска ID юзера по никнейму и для поиска никнейма юзера по его ID.
Грубо говоря: то, что в SQL является таблицей, в Redis'е будет списком индексов и набором массивов. Также принято разделять части ключа двоеточием.

Пример в SQL: 1 таблица — с полями user_id, user_name, user_email и 5-мя записями.
Аналогия в Redis: 1 список и 5 массивов — список users с данными [1, 2, 3, 4, 5] и 5 массивов с названиями (ключами) вида user: с данными {id: , name: , email: }, а также несколько массивов с обратной связкой, например, nicknames со значениями {andrew: 1, john: 2, mike: 3, ...}

Почему Redis?

  • В Redis не нужно задавать структуры данных: достаточно просто положить их туда.
  • Мы можем делать CRUD (Create-Read-Update-Delete) с данными, находящимися в базе Redis, а также использовать встроенный механизм блокировки — он, кстати, очень упрощает имплементацию механизма long polling.
  • Никаких JOIN или WHERE Redis не умеет, да и не должен — он всего лишь хранит примитивы, максимум списки или ассоциативные массивы из примитивов. Но это не минус, а дополнительная свобода действий и стимул для расширения мышления [3], отличного от паттернов SQL- и NoSQL-СУБД.
Структура БД системы хранения ключей-значений

Вот так выглядят данные в нашей Redis в момент, когда Andrew написал сообщение и John написал сообщение, но первое прочитали все, кроме майка, а второе — лишь сам Джон. Но за несколько моментов все user:X:messages очистятся, т.к. наступит таймаут полинга и данные уедут на клиенты. Т.е. user:X:messages — это такой себе контейнер для еще не полученных сообщений некоего юзера.
Веб приложение — ну почти без бек энда: Flask, Redis, API через JSONP, JSFiddle.net

Long polling

Средствами Redis можна легко реализовать long polling. Примерный алгоритм таков:

  • Запрашиваем в Redis (командой LLEN [4]), есть ли прямо сейчас в списке сообщений для клиента сообщения, если есть — возвращаем сообщения и чистим список через DEL [5]
  • Если сообщений нет, запрашиваем их опять, но на сей раз команой BLPOP [6], которая заблокирует активный поток, пока не появлятся данные либо не истечет таймаут. По разблокировке возвращаем клиенту результат от Redis, в котором будет либо только что пришедшее сообщение, либо ничего.

«Боевая команда, вперёд!»

Фронт-энд для тестирования: http://jsfiddle.net/andunai/kcdtzdww/ [7]
Исходный код бек-энда: https://bitbucket.org/AndrewDunai/nobackend-chat-dirty [8]
Полноэкранная версия: jsfiddle.net/andunai/kcdtzdww/embedded/result/ [9]

Post Scriptum

Мне очень приятно, что вы дочитали аж до этого места. Я, как всегда, рад любым замечаниям и пожеланиям.
Надеюсь, хабраэффект не сильно заденет мой маленький VPS [10].
Спасибо за внимание!

UPDATE: репозиторий теперь публичный, случайно сделал его приватным при создании.

Автор: AndersonDunai

Источник [11]


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

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

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

[1] jsfiddle.net: http://jsfiddle.net

[2] BackendLess: https://backendless.com/

[3] мышления: http://www.braintools.ru

[4] LLEN: http://redis.io/commands/LLEN

[5] DEL: http://redis.io/commands/DEL

[6] BLPOP: http://redis.io/commands/BLPOP

[7] http://jsfiddle.net/andunai/kcdtzdww/: http://jsfiddle.net/andunai/kcdtzdww/

[8] https://bitbucket.org/AndrewDunai/nobackend-chat-dirty: https://bitbucket.org/AndrewDunai/nobackend-chat-dirty

[9] jsfiddle.net/andunai/kcdtzdww/embedded/result/: http://jsfiddle.net/andunai/kcdtzdww/embedded/result/

[10] VPS: https://www.reg.ru/?rlink=reflink-717

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