python

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

  • воскресенье, 19 октября 2014 г. в 03:10:58
http://habrahabr.ru/post/240787/

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

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

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



Цель статьи — поделится своим сегодняшним опытом с двух сторон:
  • Имплементацией JSONP + Long Polling
  • Работой с замечательной Redis


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


Что у нас есть


Итак, у нас есть:
  • Некий собственный сервер с Python 2.x на борту
  • Браузер и доступ к JSFiddle.net
  • Желание построить 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:X с данными {id: X, name: Y, email: Z}, а также несколько массивов с обратной связкой, например, nicknames со значениями {andrew: 1, john: 2, mike: 3, ...}

Почему Redis?

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


Структура БД системы хранения ключей-значений

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


Long polling


Средствами Redis можна легко реализовать long polling. Примерный алгоритм таков:
  • Запрашиваем в Redis (командой LLEN), есть ли прямо сейчас в списке сообщений для клиента сообщения, если есть — возвращаем сообщения и чистим список через DEL
  • Если сообщений нет, запрашиваем их опять, но на сей раз команой BLPOP, которая заблокирует активный поток, пока не появлятся данные либо не истечет таймаут. По разблокировке возвращаем клиенту результат от Redis, в котором будет либо только что пришедшее сообщение, либо ничего.


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


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

Post Scriptum



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

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