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

    Данная статья — так называемый «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: репозиторий теперь публичный, случайно сделал его приватным при создании.

    Similar posts

    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 21

      0
      Самое интересное тут было бы — авторизация пользователей. JSONP — тривиальщина, чатик на Redis — тоже…
      А почему русский не работает?
        0
        Забыл о кириллице совсем. Сейчас исправлю.
        Насчёт авторизации — в принципе, она сейчас есть, но без пароля — лишь логин. Можно без проблем дописать ещё одно поле.
        +4
        Чисто интересно: почему бы не использовать CORS вместо JSONP?
          +5
          Особенно учитывая то, что RESTful API требуют не только GET запросов, но и POST, PUT, DELETE, которые не получится сделать с помощью JSONP.
            0
            Факт. Для данного метода нужно будет использовать некую клиентскую прослойку для завертания REST-запросов в GET.
              0
              не надо, это вектор атаки дополнительный будет
            0
            Это уже было бы неинтересно ;)
              0
              CORS запросы почему-то часто обрываются в ослах (9+). Лично я в проектах юзаю CORS для нормальных браузеров, а JSONp для ослов. Хотя сейчас назревает решение вообще отказаться от CORS, так как он требует два запроса, если использовать GET: OPTIONS и, собственно, GET, которые, в итоге, выполняются дольше (в 2-3 раза). JSONp — кроссбраузерное, быстрое, небажное решение для GET запросов на другой сервер.
              +2
              Если я правильно понимаю, то в Firebase уже давно предоставили такое решение, которые вы описываете, только без велосипедов и головной боли. Он позволяет делать CRUD с моментальным обновление данных на клиенте с помощью Web Sockets.
                +2
                По идее для вещей типа чата больше подходит redis.io/topics/pubsub чем BLPOP. Так же, тема транзакций в Redis не раскрыта — без них не видно ни атомарности ни изоляции в БД, что чревато.
                  0
                  ошибся
                    0
                    В понедельник постараюсь начать цикл статей про свое полноценное музыкальное backendless приложение, работающее поверх трех (если бы last.fm не были м***ми и сделали бы поддержку CORS — было бы четырех, видимо, для них придется городить-таки бэкэнд) различных апей.
                    Трафик жрет — мама не горюй, чисто запросов за данными (при повторной загрузке после уже заготовленного кэша indexedDB) — больше 20 Мб данных за первые пару минут. К сожалению, избыточность данных хрен понизишь.

                    А у вас фигня какая-то, а не backendless приложение. Абсолютно обычный web app поверх сделанного для себя самих же api, почти все приложения так работают, начиная от gmail и заканчивая кучей мелочевки на github.io. Просто с кроссдоменными запросами

                    Да, вот картинка того как я отлаживаю свою балалайку. Ну, то есть все эти запросы только от меня.
                      0
                      Поздравляю, вы придумали SPA!)))
                        0
                        Того кто минуснул, я бы попросил объяснить почему. Прочитав заголовок, я действительно не ожидал увидеть здесь довольно примитивное SPA приложение, и уж точно не ожидал, что большая часть статьи будет посвящена бекенд-технологиям вроде Redis и Python. Как минимум фраза «Веб-приложение — ну почти без бек-энда», вызвала у меня ощущение, что я прочитаю здесь про хоть немного «толстый» клиент, который как минимум имеет offline-режим работы и действительно требует минимум бекенда. Уж простите.
                          0
                          Хотите «толстый» клиент, копайте в сторону asm.js
                            0
                            Простите, а как именно «толстота» клиента соотносится с asm.js?
                              0
                              Так же как и ваши претензии по поводу SPA. Даже kate в javascript будет являться SPA.
                                0
                                А где вы увидали мои претензии к SPA? Максимум в заголовку статьи. Первый же мой комментарий носил легкий саркастический характер, касательно того с каким вдохновением и в каких эпитетах автор статьи описывает свою поделку, когда все вокруг уже не только делают полноценные клиентские веб-приложения на основе веб-сервисов, но и делают их действительно backend-less, как например Jabher.
                                  0
                                  Статья мало соответствует своему заголовку.
                                  Я упомянул asm.js как хороший инструмент переноса или создания приложений, которые вы хотели бы увидеть под таким заголовком. SPA, как мне думается, предполагает более широкий функционал, чем представлен в статье.
                                    0
                                    О том и речь.
                      • UFO just landed and posted this here

                        Only users with full accounts can post comments. Log in, please.