Привет, %username%!
Этот пост я посвящаю в первую очередь своим друзьям, а так же тем, кто считают фреймворки верхом совершенства, а все остальные велосипеды — маст дай.
Столкнувшись с чем то новым я предпочитаю написать велосипед, чтобы понять, как оно работает, чтобы потом, например, при использовании ajax-фреймворка не говорить, что загрузка файлов происходит в формате xml, и поэтому ajax-upload не рулит!
Месяц назад я начал писать систему заявок для одной конторы в нашем городе.
Началось это с того, что шеф этой конторы неожиданно захотел как то автоматизировать обработку заявок, поступающих от потенциальных клиентов. Контора занимается продажей билетов, каких — не суть важно ;)
Схема работы:
Сейчас это работает так: потенциальный клиент заходит на сайт, заполняет форму, и содержимое формы уходит на емаил кассиру. Кассир проверяет(иногда) почту, ну и если есть заявки, то что то с ними делает. Шеф захотел как то повысить производительность труда, одновременно облегчить жизнь кассирам, что и поручил мне. Кроме того, в перспективе была интеграция с платежными терминалами…
И тут разгулялась моя фантазия:
Поднял сервер, на нем крутится база данных, и вебморда, написанная на пхп. В базе данных хранятся все заявки от клиентов, а обработка и осуществляется через вебморду.
Кроме того, чтобы девочки постоянно не проверяли почту, написал на своем любимом C# небольшую программку, которая периодически проверяет заявки, и если таковые есть, показывает красиво моргающее окошко с уведомлением.
Осталось придумать, как клиентскую программку, назовем ее информером взаимодействовать с БД? Прямой доступ к БД давать не хотелось, поэтому я снова подгрузил модуль Fantasy.dll
Первое что пришло на ум — написать скрипт, который бы выдавал информеру циферку, которая характеризует количество необработанных заявок, но такой вариант мне показался слишком простым. И тут я вспомнил о веб-сервисах — о всяких там xml-rpc, soap, etc… Посовещавшись с другом решил взяться за использование soap. Ах, как же я был наивен, что с ним все будет так просто :) Для использования SOAP оказалось нужно генерить какой то хитрый xml c описанием функций, которые предоставляет вебсервис. как этот WSDL генерить я тогда еще не знал, а руками писать не хотелось. Спустя некоторое время эту функцию я нашел в IDE, которую использую для разработки, но к тому времени я решил отказаться от SOAP, т.к. мне не нравилось количество мусорного трафика, которым эти ваши soap-вебсервисы сопровождались. Кроме того, поговорить с другим знакомым, который был знаком с технарями из местной платежной системы, сказал что с соапом меня там пошлют куда подальше :(
Я придумал свой вариант:
написал скрипт, которому первым параметром передается команда, вторым параметром передаются аргументы к этой команде(блин, как же их передать? :() ну и третий аргумент — ключ, по которому скрипт идентифицирует того, кто выполняет конкретную команду, а если не идентифицирует — говорит что «сюда низя, здесь кролики!»
Как передавать аргументы:
Сразу же подумал об XML.
Когда гуглил про SOAP, наткнулся в википедии на клевую аббревиатуру WDDX. Погуглил еще немножко, и нашел класс написанный на C#, который сможет обеспечить требуемый функционал для информера. Принялся за работу. Написал на пхп пебольшую обертку над wddx, чтоб красивее использовать:
Данные
Данные в базе хранятся в кодировке utf-8, соединение с БД пхп скрипт поддерживает в кодировке Utf-8, вебморда клиентам отдается в utf-8, скрипты хранятся в кодировке utf-8.
Написал обработчик команды get_new_tickets_count, которая понятно дело, возвращает количество новых не обработанных заявок информеру. Все работает, супер! Уже был готов показать шефу как красиво моргает окошко с уведомлением, и чтобы совсем было красиво, фамилию, имя кассира, хранящиеся в БД из транслита решил написать русскими буквами. Открыл пхпмайадмин, заменил значение соответствующего поля, запустил информер, а он мне показывает что-то, но что именно — непонятно. Открыл в браузере этот урл, вроде бы все правильно, страничка отдается в юникоде, но данные почему то портятся. Начал искать проблему. Проблема оказалась в wddx. функции этого расширения зачем-то меняют кодировку, и соответственно, это дело отдается уже и не в UTF-8. Решение искал довольно долго, в итоге нашел — помогла волшебная функция iconv(«UTF-8», «Волшебная кодировка, название которой забыто на веки», xml_пакет_который_делает_функция_wddx_serialize_value).
Работает! Круто! Но я не понял, почему страница отдается в юникоде, а текст из юникода кодируется в какую то загадочную кодировку, чтобы корректно отображаться в окне браузера. Ну да фиг с ним.
Запускаю информер, и вижу «Имя кассира: ВсЯкИеТаМиЕрОгЛиФы».
Как информер пытался узнать имя кассира:
Имя кассира узнавалось при старте программы
Из за своей невнимательности проблему иероглифов я заметил не сразу, А из за того, что пхпшный wddx оказался каким то странным я решил изобрести очередной велосипед. Велосипед по типу wddx, и полностью своей реализации. Передавать в скрипт требовалось простые структуры по типу одномерных ассоциативных массивов (ключ = значение). В итоге пришел к такому виду пакета:
За 5 минут написал пхп парсер этого дела:
Это позволило на выходе получать обычный ассоциативный массив на выходе при передаче не менее обычного xml на входе:
Что-ж, наш «веб-сервис» умеет генерировать xml в своем фирменном формате, осталось немного — научить информер парсить этот несложный xml. Про тип данных Hashtable в C# я узнал совсем недавно — как раз тогда, когда прикручивал WddxDeserializer :) Кода в информере очень мало, но кардинально менять его не хотелось, и я решил написать аналог метода d.Deserialize():
В методе GetManagerName меняем
на
Компилим, запускаем, и на выходе у нас опять все те же иероглифы. И только после 15 минут передышки и спокойных раздумий до меня дошло, что я не знаю, в какой кодировке мой WebDownloader принимает ответ скрипта. Иду в конструктор формы и добавляю следующий код:
Этим я сообщил даунлоадеру что данные нужно принимать в юникоде.
Компилю, запускаю — It's works!..
Все, система функционирует, информер работает, все необходимое показывает правильно, можно продолжать наращивать функционал API тикетов для дальнейшей интеграции с платежными системами.
Надо сказать, что из этого я сделал несколько полезных выводов, и это послужило мне хорошим уроком => XP++ :)
Этот пост я посвящаю в первую очередь своим друзьям, а так же тем, кто считают фреймворки верхом совершенства, а все остальные велосипеды — маст дай.
Столкнувшись с чем то новым я предпочитаю написать велосипед, чтобы понять, как оно работает, чтобы потом, например, при использовании ajax-фреймворка не говорить, что загрузка файлов происходит в формате xml, и поэтому ajax-upload не рулит!
Месяц назад я начал писать систему заявок для одной конторы в нашем городе.
Началось это с того, что шеф этой конторы неожиданно захотел как то автоматизировать обработку заявок, поступающих от потенциальных клиентов. Контора занимается продажей билетов, каких — не суть важно ;)
Схема работы:
Сейчас это работает так: потенциальный клиент заходит на сайт, заполняет форму, и содержимое формы уходит на емаил кассиру. Кассир проверяет(иногда) почту, ну и если есть заявки, то что то с ними делает. Шеф захотел как то повысить производительность труда, одновременно облегчить жизнь кассирам, что и поручил мне. Кроме того, в перспективе была интеграция с платежными терминалами…
И тут разгулялась моя фантазия:
Поднял сервер, на нем крутится база данных, и вебморда, написанная на пхп. В базе данных хранятся все заявки от клиентов, а обработка и осуществляется через вебморду.
Кроме того, чтобы девочки постоянно не проверяли почту, написал на своем любимом C# небольшую программку, которая периодически проверяет заявки, и если таковые есть, показывает красиво моргающее окошко с уведомлением.
Осталось придумать, как клиентскую программку, назовем ее информером взаимодействовать с БД? Прямой доступ к БД давать не хотелось, поэтому я снова подгрузил модуль Fantasy.dll
Первое что пришло на ум — написать скрипт, который бы выдавал информеру циферку, которая характеризует количество необработанных заявок, но такой вариант мне показался слишком простым. И тут я вспомнил о веб-сервисах — о всяких там xml-rpc, soap, etc… Посовещавшись с другом решил взяться за использование soap. Ах, как же я был наивен, что с ним все будет так просто :) Для использования SOAP оказалось нужно генерить какой то хитрый xml c описанием функций, которые предоставляет вебсервис. как этот WSDL генерить я тогда еще не знал, а руками писать не хотелось. Спустя некоторое время эту функцию я нашел в IDE, которую использую для разработки, но к тому времени я решил отказаться от SOAP, т.к. мне не нравилось количество мусорного трафика, которым эти ваши soap-вебсервисы сопровождались. Кроме того, поговорить с другим знакомым, который был знаком с технарями из местной платежной системы, сказал что с соапом меня там пошлют куда подальше :(
Я придумал свой вариант:
написал скрипт, которому первым параметром передается команда, вторым параметром передаются аргументы к этой команде(блин, как же их передать? :() ну и третий аргумент — ключ, по которому скрипт идентифицирует того, кто выполняет конкретную команду, а если не идентифицирует — говорит что «сюда низя, здесь кролики!»
Как передавать аргументы:
Сразу же подумал об XML.
Когда гуглил про SOAP, наткнулся в википедии на клевую аббревиатуру WDDX. Погуглил еще немножко, и нашел класс написанный на C#, который сможет обеспечить требуемый функционал для информера. Принялся за работу. Написал на пхп пебольшую обертку над wddx, чтоб красивее использовать:
class Wddx
{
function MakePacket($value)
{
return @wddx_serialize_value($value);
}
function ParsePacket($packet)
{
return @wddx_unserialize($packet);
}
}
Данные
Данные в базе хранятся в кодировке utf-8, соединение с БД пхп скрипт поддерживает в кодировке Utf-8, вебморда клиентам отдается в utf-8, скрипты хранятся в кодировке utf-8.
Написал обработчик команды get_new_tickets_count, которая понятно дело, возвращает количество новых не обработанных заявок информеру. Все работает, супер! Уже был готов показать шефу как красиво моргает окошко с уведомлением, и чтобы совсем было красиво, фамилию, имя кассира, хранящиеся в БД из транслита решил написать русскими буквами. Открыл пхпмайадмин, заменил значение соответствующего поля, запустил информер, а он мне показывает что-то, но что именно — непонятно. Открыл в браузере этот урл, вроде бы все правильно, страничка отдается в юникоде, но данные почему то портятся. Начал искать проблему. Проблема оказалась в wddx. функции этого расширения зачем-то меняют кодировку, и соответственно, это дело отдается уже и не в UTF-8. Решение искал довольно долго, в итоге нашел — помогла волшебная функция iconv(«UTF-8», «Волшебная кодировка, название которой забыто на веки», xml_пакет_который_делает_функция_wddx_serialize_value).
Работает! Круто! Но я не понял, почему страница отдается в юникоде, а текст из юникода кодируется в какую то загадочную кодировку, чтобы корректно отображаться в окне браузера. Ну да фиг с ним.
Запускаю информер, и вижу «Имя кассира: ВсЯкИеТаМиЕрОгЛиФы».
Как информер пытался узнать имя кассира:
Имя кассира узнавалось при старте программы
// В конструкторе формы было написано
WebDownloader = new WebClient();
d = new WddxDeserializer();
this.GetManagerName(SystemConfig.ManagerId); // SystemConfig.ManagerId - персональный ключ, который присваивается каждой учетной записи в системе
// дальше в коде программы
private void GetManagerName(string hash)
{
try
{
string dataPacket = WebDownloader.DownloadString(SystemConfig.TicketApiUrl + "net_get_manager_name&manager=" + SystemConfig.ManagerId); // формуруем УРЛ и посылаем запрос, в результате которого хотим получить содержимое wddx пакета
this.ServiceState = ConnectionState.ServiceAvailable; // Переключатель, которым регулируется поведение программы
Hashtable resultTable = (HashTable)d.Deserialize(dataPacket); // десериализируем
if (resultTable["error"] != null) // если есть ошибка, в трее будет написано соответствующее уведомление
{
TrayIco.Text = "Не удалось получить имя кассира\nКод: " + resultTable["error"].ToString();
}
else // иначе выведем имя кассира
{
SystemConfig.ManagerName = resultTable["manager_name"].ToString();
TrayIco.Text = "Кассир: " + resultTable["manager_name"].ToString();
}
}
catch { }
}
Из за своей невнимательности проблему иероглифов я заметил не сразу, А из за того, что пхпшный wddx оказался каким то странным я решил изобрести очередной велосипед. Велосипед по типу wddx, и полностью своей реализации. Передавать в скрипт требовалось простые структуры по типу одномерных ассоциативных массивов (ключ = значение). В итоге пришел к такому виду пакета:
/>
/>
/>
За 5 минут написал пхп парсер этого дела:
class DataPacket
{
public static $data;
public static function ParseXmlString($xmlstr)
{
$doc = new DOMDocument();
@$doc->loadXML($xmlstr);
$xpath = new DOMXPath($doc);
$nodes = @$xpath->query("/DataPacket/Param");
foreach($nodes as $node)
{
self::$data[@$node->getAttribute('Key')] = @$node->getAttribute('Value');
}
return self::$data;
}
public function AddData($key, $value)
{
$this->data[$key] = $value;
return $this;
}
public function Clear()
{
$this->data = null;
}
public function GenerateXml()
{
$str = '';
foreach($this->data as $k => $v)
{
$str .= '/>';
}
$str .= "";
return $str;
}
}
Это позволило на выходе получать обычный ассоциативный массив на выходе при передаче не менее обычного xml на входе:
$packet = DataPacket::ParseXmlString($xmlstring);
Что-ж, наш «веб-сервис» умеет генерировать xml в своем фирменном формате, осталось немного — научить информер парсить этот несложный xml. Про тип данных Hashtable в C# я узнал совсем недавно — как раз тогда, когда прикручивал WddxDeserializer :) Кода в информере очень мало, но кардинально менять его не хотелось, и я решил написать аналог метода d.Deserialize():
private Hashtable ParseResponse(string xml)
{
XmlDocument xdoc = new XmlDocument();
xdoc.LoadXml(xml);
XmlNodeList nodes = xdoc.SelectNodes("/DataPacket/Param"); // xpath прекрасен ;)
Hashtable table = new Hashtable();
foreach (XmlNode node in nodes)
{
table.Add(node.Attributes["Key"].Value, node.Attributes["Value"].Value);
}
return table;
}
В методе GetManagerName меняем
Hashtable resultTable = (HashTable)d.Deserialize(dataPacket);
на
Hashtable resultTable = ParseResponse(dataPacket);
Компилим, запускаем, и на выходе у нас опять все те же иероглифы. И только после 15 минут передышки и спокойных раздумий до меня дошло, что я не знаю, в какой кодировке мой WebDownloader принимает ответ скрипта. Иду в конструктор формы и добавляю следующий код:
WebDownloader.Encoding = Encoding.UTF8;
Этим я сообщил даунлоадеру что данные нужно принимать в юникоде.
Компилю, запускаю — It's works!..
Все, система функционирует, информер работает, все необходимое показывает правильно, можно продолжать наращивать функционал API тикетов для дальнейшей интеграции с платежными системами.
Надо сказать, что из этого я сделал несколько полезных выводов, и это послужило мне хорошим уроком => XP++ :)