Pull to refresh

Разработка IFrame приложения для ВКонтакта

Reading time 9 min
Views 18K
Несмотря на то, что данная социалка «горячо любима» аудиторией хабра, я всё же решил рискнуть и опубликовать небольшие заметки посвящённые разработке приложений на Джанге под неё.

Предупреждаю, некоторые из них вполне капитанские, а некоторые вообще не имеют отношения к Вконтакте или даже Джанге, в общем, не обессудьте.

Под катом:
  • django-vkontakte-iframe и vkontakte
  • Загрузка приложения на стену
  • Флеш-заглушка
  • Сессия в горячо любимом IE
  • Все «flashVars» передаются GET запросом
  • Работа во фрейме
  • Доступ к информации пользователя
  • Проверка валидности вёрстки
  • JSLint ваш друг и товарищ
  • Генерирование миниатюр
  • Модерация через прокси-модель
  • Импорт настроек
  • Оптимизация

На правах рекламы: Приложение называется «Коллекционер» и предназначено для людей занимающихся коллекционированием монет, марок, карт и других предметов. В данный момент наполнен лишь каталог карт MTG, но в будущем будут созданы и наполнены каталоги для других предметов. Разумеется, мы с радостью выслушаем все ваши предложения и конструктивную критику.

django-vkontakte-iframe и vkontakte


Данные модули позволяют сразу приняться за разработку полезного функционала не разбираясь в документации API ВКонтакта.

Первый берёт на себя авторизацию пользователей, позволяя вам работать через привычных джанговских юзеров, впрочем он на хабре уже упоминался самим автором.

Однако, через какое-то время приложение стало отказываться идентифицировать пользователей. Проблема оказалась в поле refferer, в vk_iframe/forms.py оно прописано следующим образом:

referrer = forms.ChoiceField(REFERRER_CHOICES, required=False)

Из документации можно сделать вывод что REFERRER_CHOICES прописанные в модуле устарели, да и к тому же фиксированный список нам не подходит из-за ad_{AD_ID}. Поэтому логичная замена решит наши проблемы:

referrer = forms.CharField(required = False)

Разумеется REFERRER_CHOICES нам больше не нужо и его можно смело удалять.

Второй модуль представляет обёртку под API, но во-первых не все методы возможно использовать с сервера, а во-вторых, на мой взгляд, в большинстве случаев удобней и оптимальней обращаться к API через джаваскрипт со страницы пользователя. В разрабатываемом приложении я использовал данный модуль в небольшой мидлвари для периодического обновления профилей пользователей созданных django-vkontakte-iframe и для получения друзей пользователя для определения, есть ли у зашедшего право смотреть принадлежащие пользователю страницы для которых выставлено «Показывать только мне и друзьям».

При этом стоит отметить забавный момент, 16 июня при попытке зайти на страницу приложение внезапно стало показываться… ничего. Просмотр запросов показал, что на единственный запрос возвращается 500-я ошибка. Т.е. прошу заметить это возвращало не приложение, а сам ВКонтакт! Недолгое расследование показало что дело было в первом запросе к API, впрочем через некоторое время ситуация нормализовалась.

Загрузка приложения на стену


Для раскрутки приложения зачастую бывает полезно добавить возможность пользователю размещать на стенах друзей сгенерированное приложением сообщение, при клике на которое люди будут заходить в приложение. В нашем случае это было «хвастанье» предметом в коллекции пользователя.

Последовательность публикации простая:
  1. Получаете id пользователя на стену которого будет производится постинг
  2. Получаете адрес сервера для загрузки фото с помощью wall.getPhotoUploadServer
  3. Формируете на указанный адрес POST запрос с полем photo содержащим нужное фото
  4. Вызываете wall.savePost с полученными данными фотографии и указав сообщение с адресатом
  5. И показываете пользователю окно подтверждения с помощью метода JS API saveWallPost

Больше всего пришлось промучатся с загрузкой фотографии, в первую очередь из-за собственной невнимательности. В документации надо было следовать инструкции «Загрузка приложения на стену пользователя», а я упорно пытался следовать «Загрузка фотографий на стену пользователя».

Советую обратить внимание на неточность в инструкции, в ответ на загрузку фотографии POST запросом приходит объект с полем photo, а не photos. Плюс учитывайте, что ВКонтакт ресайзит фотографию до 75 пикселей, поэтому посылать сильно большую картинку смысла нет.

Ну и наконец как формировать этот самый POST запрос. В теории это можно сделать средствами джаваскрипта, но я не стал мучиться и сделал отправку сервером. Здесь снова есть варианты, можно формировать запрос ручками, а можно воспользоваться готовым модулем. Лично я остановился на втором варианте.

И да, не забудьте при вызове wall.savePost указать post_id, по нему вы потом сможете определить что отобразить пользователю зашедшему в приложение через сообщение на стене.

Кому интересно, вот немного подправленная версия джаваскрипта, реализующего описанный алгоритм.

Флеш-заглушка


Но разумеется не могло обойтись и без капли дёгтя. Если пользователь отправит сообщение вышеописанным методом и зайдя на стену адресата кликнет по нему, то ему откроется… пустое окошко.
image
Происходит это потому что ВКонтакт пытается отобразить пользователю флеш файл и ему плевать, что у нас вообще-то iframe. Не спрашивайте почему это так, самому интересно.

Что ж, поэтому придётся ещё возиться и с флешем (а лучше просто отдать на аутсорс знакомым) создав заглушку размером 607x412, как-минимум надо оповестить пользователя, что нужно открыть сообщение на стене в новой вкладке, как максимум отобразить информацию о предмете хвастанья ориентируясь на post_id. Что бы залить заглушку измените в настройках тип вашего приложения на Flash, залейте файл и меняйте тип обратно.

Хоть возможно эта информация уже устарела, но заглушка возможно ещё пригодится на этапе одобрения приложения, т.к. администрации по умолчанию отображается именно флеш, а значит они просто могут не увидеть ваше творение.

Сессия в горячо любимом IE


Ослик разумеется не мог не выделиться, поэтому придётся проделать некоторые телодвижения специально для него.

Проблема с P3P-политиками, из-за чего приложение не может установить куки, а это приводит к фейлу авторизации. К счастью, это исправляется достаточно просто дописыванием к ответу поля P3P с следующим содержанием: CP=«IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT»

В приложении, я решил проблему ещё одной мидлварью.

UPD: В этой статье вопрос с iframe куками рассматривается более подробно и приводятся возможные решения для потенциальных проблем в других браузерах.

Все «flashVars» передаются GET запросом


При заходе пользователя к нам в приложение, передаётся небесполезная информация. Так вот, всё это передаётся в обычном GET запросе при заходе на главную. Соответсвенно, если вы хотите большей интерактивности, то не ленитесь использовать её.

Самое банальное, это перенаправлять пользователя пришедшего в приложение с чьей-нибудь стены с главной на нужную страницу ориентируясь по request.GET['post_id'].

Работа во фрейме


Не забывайте, что вы делаете не обычный сайт, а работаете во фрейме. А это значит что нужно озаботиться изменением размера окна с помощью resizeWindow (берёте высоту body или вашего обёрточного div-а), при этом максимальная ширина окна 827 пикселей (807 с отступами и элементами управления), при этом следует учитывать что при ширине больше 607 пикселей интерфейс ВКонтакта начинает растягиваться.

Кроме того что бы у пользователя работали кнопки браузера «вперёд» и «назад» стоит использовать setLocation, а для изменения названия родительской страницы стоит использовать setTitle.

Плюс можно столкнуться с тем, что сторонние фронт-энд приложения перестанут работать нормально, например мне пришлось немного допилить lytebox, что бы заставить его нормально открываться внутри фрейма.

Доступ к информации пользователя


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

Так вы не только не будете каждому пользователю выставлять окошко «А разреши-ка мне позыркать всю твою информацию, да и ключи от квартиры где деньги лежат не забудь», а будете постепенно предлагать расширить права, при этом желательно объясняя зачем вам собственно эти права нужны. getUserSettings и showSettingsBox вам в этом помогут.

Ах да, не забывайте разместить на видном месте кнопку приглашения друзей, которая работает с помощью showInviteBox.

Проверка валидности вёрстки


Настоятельно рекомендую работать только с полностью валидной вёрсткой. Это может сэкономить вам кучу времени, да и просто принесёт эстетическое удовлетворение проделанной работой, например я потратил около часа разбираясь почему у меня неправильно ресайзится фрейм, а дело оказалось в шальном div-е.

Для того что бы упростить процесс проверки я поместил в футере ссылку и повесил на неё следующий джаваскрипт. Он не только позволит вам следить за валидностью вёрстки, но и позволит ею хвастаться. Своеобразный баннер от W3C, только наглядней. Конечно никто не отменял встроенные в браузеры инструменты (например, пункт контекстного меню Оперы «Соблюдены ли веб-стандарты»), но, на мой взгляд, ссылка по которой можно кликнуть нагляднее и удобнее, не говоря уже о кроссбраузерности.

<thinking_aloud>А ведь возможность подгружать в родительскую страницу свою страницу это практически реализация дырки, т.е. можно из iframe приложения подгрузить другой сайт, который будет похож на вконтакт, а дальше уже по накатанной.</thinking_aloud>

И между делом, не стесняйтесь использовать новые теги из html5, для применения стилей к ним в IE есть достаточно простое решение состоящее в создании нужных тегов через джаваскрипт, просто погуглите «html5 fix». Лично я стараюсь придерживаться такой парадигмы: «Пользователи устаревших и альтернативно одарённых браузеров увидят страницу, но более примитивную».

JSLint ваш друг и товарищ


При разработке приложения, хочешь-не хочешь, а придётся писать джаваскрипт. А учитывая, что это не является обычно у Джангистов основной сферой деятельности, высок шанс понаделать ошибок. JSLint не только позволит сразу отлавливать глупые ошибки, но и позволит держать код в очень опрятном виде. Впрочем, на хабре о нём уже достаточно подробно писали.

Генерирование миниатюр


Несмотря на то что существуют достаточно солидные решения, например широко известные sorl-thumbnail и aino-convert, на мой взгляд, они слишком тяжёлые.

Поэтому я взял на вооружение django-thumbs. Благо состоит он всего из одного файла предоставляющего расширение стандартного ImageField. Разобраться в нём и допилить до нужного состояния не составит труда. Например, для приложения я добавил приделывание ватермарков (хоть я и против них, но таковы были требования), сохранение оригинальных изображений в отдельную папку и перегенерацию миниатюр на случай изменения настроек.

Модерация через прокси-модель


Довольно интересная техника попалась мне (к сожалению ссылку на блог найти уже нет возможности), когда я искал как добавить модерацию для тегов добавленных пользователями, учитывая что для их отображения в админке уже использовался mptt-admin, поэтому обычным фильтром тут было не обойтись. Суть её состоит в том, что мы создаём прокси-модель, прописывая левое приложение (например «Moderating»), а затем в admin.py руками задаём что именно нужно отображать (т.е. только неотмодерированные теги) определив метод queryset (который почему-то в документации отсутствует). По желанию ещё можно добавить метод для модерирования тегов пачками.

Результат получается примерно таким. О применении прокси-моделей, правда для несколько иных целей, можно почитать тут.

Импорт настроек


По началу я постоянно использовал такой импорт настроек, как и думаю большинство начинающих:
from settings import foo

Но правильно импортировать вот так:
from django.conf import settings

Почему именно так, лучше почитать в документации.

Оптимизация


Да-да, «Преждевременная оптимизация — это корень всех бед», но ведь во-первых эта фраза по словам автора (или не автора, не суть важно) верна только в 97 процентах случаев, а во-вторых описанные ниже меры лучше предпринять сразу во время разработки.

Обычно самым узким местом всех веб-приложений и, в частности, основанных на Django являются запросы к базе данных, что ещё обычно усугубляется не очень правильным использованием ORM. Впрочем растекаться по дереву особого смысла нет, ибо в документации данный вопрос раскрыт достаточно подробно. Для отображения запросов производимых к базе данных и времени их выполнения можно использовать django-debug-toolbar или же более деревянное решение.

Но с другой стороны, пользователю важнее скорость отображения страницы, а не насколько там тяжело серверу, поэтому нужно оптимизировать и саму загрузку. Кстати поэтому я и написал выше, что «оптимальней обращаться к API через джаваскрипт со страницы пользователя», т.к. задержка ответа сервера вконтакта может сильно варьироваться, поэтому все запросы лучше делать по возможности уже после того как страница загрузилась и отобразилась пользователю.

Очень полезным помощником в деле оптимизации является Page Speed. Чаще всего в первую очередь нужно минифицировать всё и вся.

В случае с html вопрос решается шаблонными тегами {% spaceless %} {% endspaceless %}, при этом стоит учитывать, что тег удаляет пробелы только между тегами, поэтому надо либо оборачивать весь текст в теги (даже между шаблонными тегами, если они идут в несколько строк), либо писать в таких местах всё в одну строчку, а так же забыть как страшный сон о написании атрибутов тегов в отдельных строчках. Кроме того если вы раньше полагались на пробельные отступы между тегами, вам придётся исправляться и делать это целиком стилями. Разумеется подобная минификация не является верхом совершенства, но думаю 5-10 процентами, получаемых от удаления кавычек и прочих извращений, можно спокойно пренебречь. Кроме того не стоит пренебрегать и gzip сжатием, для чего предусмотрена соответствующая мидлваря.

Теперь насчёт css и js. Лично я использовал django-compress. Из явных плюсов можно назвать:
  • Версионирование склеенных файлов (а это значит вам не придётся мучаться с кешем для отображения изменений)
  • Возможность использовать разные компрессоры (очень пригодилось, т.к. на сервере не получилось запустить бинарник CSSTidy, поэтому я взял YUI Compressor)
  • Поддержка внешних файлов, так что CDN приделать очень просто (что такое CDN и для чего оно собственно надо, можно почитать тут)

Конечно этот модуль по сути нарушает MVC, но я считаю это не смертельно и очень удобно.

Плюс стоит отметить django-media-bundler. Кроме сжатия css и js он ещё позволяет автоматически создавать CSS спрайты, при этом сразу проводя их минификацию через pngcrush, но к сожалению отсутствует версионирование как у django-compress. По причине небольшого количества иконок в приложении, я решил оставить его для будущих проектов.

Кроме того, очень полезно ставить точку с запятой в конце каждого js-файла, т.к. её отсутствие может привести после компрессии к нерабочему коду.

Ну и наконец стоит протестировать наше изделие на прочность. Load Impact нам в этом деле очень поможет. Если вам интересно, то вот статья рассказывающая о нём чуть более подробно. Конечно доступно бесплатное тестирование (до 50 юзеров), но я рекомендую не поскупиться и купить хотя бы базовый пакет (до 250 юзеров) стоимостью $9.

Тесты приложения выдали следующие результаты:
image
image

Видно, что приложение достаточно хорошо справляется с нагрузкой, но тем не менее после 70-и юзеров всё упирается в ширину предоставляемого канала. Так что в конечном счёте телодвижения с минификацией html, css и js, а так же использование CDN оказались очень кстати.

P.S.: После меня над вёрсткой работал другой человек, так что часть советов в приложении может не выполняться.
Tags:
Hubs:
+26
Comments 16
Comments Comments 16

Articles