понедельник, 5 ноября 2012 г.

Антикэш на хэшах ресурсов: Вступление

Преамбула:
  • Есть некое приложение, которое в ходе своей работы использует множество внешних ресурсов/файлов;
  • Приложение работает в среде интернет-браузера;
  • Всего внешних ресурсов/файлов более 3000 единиц;
  • От релиза к релизу часть этих ресурсов может изменятся (например, художник перерисовал изображения некоторых айтемов или фоновых изображений, добавлены новые строковые, нормализованы по уровню громкости фоновые звуки, и прочее).

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

Данная тема затронута в докладе на встрече RAFPUG, прошедшей в мае этого года в г.Челябинске (см. презентацию).

Проблема:
Алгоритм определения факта "протухания" файла в кэше браузера напрямую зависит от самого браузера и настроек отдающего файлового сервера.

Для уверенности в том, что клиентское приложение загрузит последнюю версию ресурса, необходимо принудительно сбросить кэш для всех ресурсов.
Сброс кэша осуществлялся добавлением в конец URL ресурса случайного параметра, например:
http://mysite.ru/images/bg.png?<random string>
Зачастую в качестве рандомного параметра используется номер версии приложения.

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

Следовательно, требуется добиться следующих результатов:
  1. Уверенность в том, что клиентское приложение будет использовать последнюю версию ресурса;
  2. Независимость от алгоритма поиска ресурса в кэше браузера;
  3. Снижение нагрузки на отдающий файловый сервер.

Решение:
Способ №1:
  1. Сформировать словарь соответствий относительных URL файлов с их хэшами, например: файлу
    ./images/bg.png
    соответствует хэш
    bb74f737c85c996cc2ec29c63698bf80
  2. Использовать в качестве антикэш-параметра значение хэша из словаря при формировании URL для загрузки файла, например:
    http://mysite.ru/images/bg.png?bb74f737c85c996cc2ec29c63698bf80
    Если соответствия не найдено — использовать строку с номером последней версии приложения.

Способ №2:
Данный способ был подсказан Михаилом Антипиным (Nox Noctis), за что ему огромное чистосердечное спасибо.
  1. Сформировать словарь соответствий оригинальных имен файлов с их новыми именами, например: оригинальному имени файла
    bg.png
    соответствует новое имя
    00240736ec53d1f23366122e8d12db5f9f06f7c70404000304d8c000
    В новое имя файла может быть "зашит" не только его хэш, но и дополнительные данные, например: размер файла в байтах, тип (png, jpg, ...), дату последнего изменения и прочее.
  2. Переименовать файлы в соответствии со словарем и переместить в общую для внешних ресурсов папку, например:
    http://mysite.ru/files/
  3. Определять его новое имя файла при формировании URL, по оригинальному имени используя словарь, например:
    http://mysite.ru/files/00240736ec53d1f23366122e8d12db5f9f06f7c70404000304d8c000

P.S.: Оба решения успешно протеситрованны и используются в боевых условиях.
В дальнейшем в данном посте будут размещены ссылки на описания реализаций каждого способа, а так же рассмотрены преимущества и недостатки каждого из них.

вторник, 30 октября 2012 г.

navigateToURL в цикле (Action Script)

Преамбула:
Возникла необходимость последовательно открывать в браузере несколько ссылок на ресурсы.

Проблема:
При последовательном или в цикле вызове функции navigateToURL отрабатывает всего лишь один вызов.

Решение:
  1. Приложение работает в standalone flashplayer-е или в среде flash-плагина браузера:
    const URLS:Array = ["http://ya.ru", "http://r0.ru"];
    stage.addEventListener(Event.ENTER_FRAME, function (e:Event):void {
        navigateToURL(new URLRequest(URLS.shift()), "_blank");
        if (URLS.length == 0) {
            e.target.removeEventListener(e.type, arguments.callee);
        }
    });
    
    Прим.: В качестве источника события Event.ENTER_FRAME можно использовать любой экземпляр класса Sprite или его наследников в независимости добавлен ли этот экземпляр в список отображения или нет.
  2. Приложение работает в среде flash-плагина браузера, доступен и разрешен вызов javascript-методов, то можно обойтись без подписки на событие Event.ENTER_FRAME:
    const URLS:Array = ["http://ya.ru", "http://r0.ru"];
    if (ExternalInterface.available) {
        for each (var url:String in URLS) {
            ExternalInterface.call("window.open", url);
        }
    }
    

пятница, 26 октября 2012 г.

As3/Flex offline-документация на русском (Action Script, Flex)

Преамбула:
Взбрендило мне заиметь документацию по as3 оффлайн - на тот случай когда интернета нет, а очень надо.

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

Решение:
Нашлась программка Doc, написанная на AIR-е, в которой можно добавить путь до "книги" (в моем случае он http://help.adobe.com/ru_RU/FlashPlatform/reference/actionscript/3/). Затем она очень долго её выкачивала (~800 MB), индексировала и писала все это в локальную SQLite-базу.

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

НО! Главное, что эта программка скачала требуемую документацию пригодную для offline-использования. Спасибо ей за это. Забираем скачанное, анинсталим программку, пользуемся докой.

На случай ядерной войны выложил плотно упакованный 7-zip-ом архив с документацией:
Версия документации по состоянию на октябрь 2012.
As3DocumentatonOffline.7z (~11 MB, MD5: 891d9c8d6d4ba211fbd9087a2adf8f4e)

среда, 24 октября 2012 г.

Доступ к ресурсам защищенным Basic authentication (ActionScript, Basic Authorization)

Преамбула:
При разработке десктопного приложения на AIR столкнулся с проблемой доступа к ресурсам защищеным Basic access authentication или по-простому базовой авторизацией. Причем ресурсы расположены на разных серверах, условно site1.ru и site2.ru.
  • site1.ru – серверная часть приложения
  • site2.ru – хранилище статических ресурсов (картинки, флешки и прочая мультимедия)
При работе аналогичного приложения в среде flash-плагина браузера проблем нет, прошел авторизацию на каждом сервере один раз за сеанс работы браузера и шпарь себе дальше.

Проблема:
Для базовой авторизации необходимо в HTTP-запросе передавать заголовок Authorization с данными по логину и паролю.
Добавить нужный заголовок в URLRequest не представляется возможным из-за ограничений URLRequestHeader.
Нельзя, а хочется, воспользоватся такими средствами как загрузчик LoaderMax.

Решение:
Способ №1
Есть отличная библиотека as3httpclient с реализацией на сокетах аналога URLLoader-а и URLRequest-а которые позволяют добавить необходимые заголовки в HTTP-запрос.
var loader:SocketURLLoader = new SocketURLLoader();
loader.addEventListener(Event.COMPLETE, onComplete);

var request:SocketHTTPRequest = new SocketHTTPRequest();
request.url = "http://site1.ru";
request.requestHeaders = [new URLRequestHeader("Authorization", "Basic dXNlcjpwYXNzd29yZA==")];
loader.load(request);
dXNlcjpwYXNzd29yZA== 
это сериализованная в base64 строка "user:password".
Этот способ хорош для выполнения запросов обращеных к серверу бизнес-логики приложения и не удобен для загрузки статических ресурсов (в частости изображений) которые придется грузить в бинарном виде, заталкивать их в экземпляр класса Loader или другой класс для использования загруженного ресурса.

Способ №2
Как оказалось все уже придумано самими адобами.
Надо по-внимательней смотреть в документацию!
Встречайте URLRequestDefaults.
Достаточно один раз указать данные для авторизации, скажем в конструкторе главного класса приложения:
URLRequestDefaults.setLoginCredentialsForHost("site1.ru", "user", "password");
URLRequestDefaults.setLoginCredentialsForHost("site2.ru", "user", "password");
URLRequestDefaults.authenticate = true;

вторник, 22 мая 2012 г.

RAFPUG в Челябинске (19.05.2012)

Двинул речь вот тут про это.

Особое спасибо Михаилу Антипину (техническому директору mimimigames.ru ) за помощь по теме антикеша на хешах ресурсов.

P.S.: Спасибо тем, кто прослушал мою "тираду". Объясняльщик из меня еще тот ;)

пятница, 27 апреля 2012 г.

Подстановка параметров в шаблон строки (ActionScript, string template)

Преамбула:
Имеются множество шаблонов строк как для вывода сообщений об ошибках, так и просто каких то фраз в диалоговых окнах.
Шаблон строки представляет из себя следующее: "Мама мыла {0} и гладила {1}, а потом еще разок помыла {0}. Наша мама {2}!".

Проблема:
Заменить параметры в шаблоне строки реальными значениями.

Решение:
Способ №1
/**
 * Подстановка параметров в шаблон строки
 * @param  source - шаблон строки
 * @param  ...params - параметры, если первый параметр массив то параметры берутся от туда
 * @return
 */
public function replaceParams(source:String, ...params):String {
  if (!source || !params || !params.length)
    return source;
  
  if (params[0] is Array)
    params = params[0];
    
  var regexp:RegExp = null; 
  for (var i:int = 0; i < params.length; i++) {
    regexp = new RegExp("\\{" + i + "\\}", "g"); 
    source = source.replace(regexp, params[i]);
  }
  
  return source;
}

// Пример:
var source:String = "Мама мыла {0} и гладила {1}, а потом еще разок помыла {0}. Наша мама {2}!";
trace(replaceParams(source, "раму", "штаны", "молодец"));
// or
trace(replaceParams(source, ["раму", "штаны", "молодец"]));
// выведет: Мама мыла раму и гладила штаны, а потом еще разок помыла раму. Наша мама молодец!
Прим.: как оказалось такого рода функционал по подмене параметрами реализован в методе StringUtil.substitute(), о чем я узнал немного позже. Ну да ладно, свой велос ближе к телу :)

Способ №2 (более монструозный и менее производительный)
/**
 * Вставка массива параметров
 * @param str - шаблон строки
 * @param params - массив с параметрами
 * @return строка заполненная параметрами
 */
public static function insertParametersIntoString(str:String, params:Array):String {
  if (str == "" || str == null) 
    throw new Error("Insert parameter in 'null' string. Target string = " + str + ". Number of parameter = " + params.length + ". Parameters = " + params.toString());
  
  for (var i:int = 0; i < params.length; i++) {
    try {
      str = strInsertParameter(str, i, params[i].toString());
    } catch (err:Error) {
      throw new Error("Insert null parameter in string. Target string = " + str + ". Number of parameter = " + i + ". Parameter = " + params[i].toString() + "Error: " + err);
    }
  }
  
  return str;
}

/**
 * Вставка параметра в указанную позицию
 * @param str - шаблон строки
 * @param paramIndex - индекс параметра в шаблоне
 * @param paramValue - значение параметра
 * @return строка заполненная параметрами
 */
public static function strInsertParameter(str:String, paramIndex:int, paramValue:String):String {
  var resultStr:String;
  var searchStr:String;
  var i:int;
  searchStr = "{" + paramIndex + "}";
  i = str.lastIndexOf(searchStr);
  if (i == -1)
    return str;

  while (i != -1) {
    str = str.slice(0, i) + paramValue + str.slice(i + searchStr.length, str.length);
    i = str.indexOf(searchStr);
  }

  return str;
}

// Пример:
var source:String = "Мама мыла {0} и гладила {1}, а потом еще разок помыла {0}. Наша мама {2}!";
trace(insertParametersIntoString(source, "раму", "штаны", "молодец"));
// выведет: Мама мыла раму и гладила штаны, а потом еще разок помыла раму. Наша мама молодец!

воскресенье, 8 апреля 2012 г.

Имитация ячейки scratch-лотереи (ActionScript, GameDev)



Преамбула:
Понадобилось как то раз в игре сделать такую вещь:
  1. Дан массив ячеек со скрытым содержимым
  2. Пользователь юлозя мышкой по ячейке открывает содержимое
Проблема:
Сделать процесс открытия ячейки похожим на процесс стирания в scratch-лотерее.

Решение:
Проблема сводится к решению двух задач:
  1. Отобразить результат стирания
    • накладываем на скрытый контент маску
    • "рисуем" на маске "стиралкой", тем самым открывая контент
  2. Проверить факт стирания достаточной площади ячейки для её открытия
    • при отпускании кнопки мыши снимаем битмапу со спрайта где отображается результат работы маски
    • смотрим какой процент прозрачных пикселей остался и принимаем решение открыть ячейку или нет

ScratchSlot

Alternative content

Get Adobe Flash player



Исходник (FlashDevelop project, 201 кб)

вторник, 6 марта 2012 г.

Пакетная проверка xml-файлов на валидность (ant)

Преамбула:
  • Приложение подгружает языковой xml-файл (соответствующий переданной локале) со строковыми в стандарте xliff.
  • Архитектура приложения такова, что если загруженный файл не валидный то приложение "прекращает работу".
  • Языковых файлов около 15 штук.
  • Загрузкой файлов на сервер занимается ant-build-скрипт.

Проблема:
Не допустить загрузку не валидных xml-файлов.

Решение:
Воспользуемся консольной утилитой xmllint, которая есть в *nix системах, а так же в комплекте cygwin для windows.
Например, файл для русского языка находится по пути: ${projectDir}\bin\ru\client_ru.xlf
Вот таким таском проверим перед загрузкой нужные нам языковый файлы:

   Validating lang files...
      
         
         
         
         
         ...
   
   OK

P.S.: Можно еще более ужесточить проверку подсунув xmllint дополнительно DTD описывающего структуру xml-файлов.