barbitoff programmer`s blog

Здесь я публикую заметки из программерской жизни: грабли, на которые мне случилось наступить, проблемы, для которых было найдено элегантное (или не очень) решение, а также все, с чем мне пришлось столкнуться и чем хотелось бы поделиться =)
PS Если хотите меня поблагодарить - на странице есть 3 места, чтобы это сделать =)

пятница, 28 октября 2011 г.

js-ctypes в Firefox 3.6

В Firefox 3.6 и Gecko 1.9.2 js-stypes ещё был немного недоразвит, но всё же работоспособен.
Для передачи C-функции параметров-строк (или для возврата строк), использующих Юникод (т.е. wchar на стороне C), можно использовать ctypes.ustring вместо появившегося позже типа ctypes.jschar.ptr.
Также в 3.6 нет вообще указателей, т.е. нельзя получить из типа указатель на него, обратившись к полю ptr, и мне кажется, что обойти это ограничение нельзя.
Для типов ctypes.int и ctypes.long имеются аналоги ctypes.int16_t и ctypes.int32_t.

Копирование из BSTR в wchar_t


Ниже приведен пример, демонстирующий, как можно скопировать данные из строки типа BSTR (полученной, например, после вызова метода COM-объекта, возвращающего строку) в строку типа wchar_t*:
UINT length = SysStringLen(myBstr);        // определяем длину BSTR
wchar_t *myString = new wchar_t[lenght+1]; // используем "+1" т.к. SysStringLen не учел nul в конце строки
wcscpy_s(myString ,wcslen(myString ), myBstr); // копируем, используя безопасную wcscpy_s вместо wcscpy, помеченной как "deprecated"
SysFreeString(myBstr); // скопированную уже BSTR-строку теперь можно корректно удалить

четверг, 27 октября 2011 г.

Обращение к COM-объектам из C++

Для того, чтобы в коде C++ обращаться к методам некоторого COM-объекта, нужно следующее:
1) Импортировать tlb COM-объекта, с которым будем работать (импорт этот статический, выполняется на этапе компиляции):
#import "someCom.tlb" raw_interfaces_only
2) Перед началом работы с COM-объектом инициализировать COM, после окончания - деинициализировать:
HRESULT hr = CoInitialize(NULL);
if(SUCCEEDED(hres = CoInitialize(NULL)))
     {
     ... // тут идет создание COM-объекта, вызов его методов
     CoUninitialize();
     }
3) Создать указатель на COM-объект для последующей работы. Пусть COM-объект, с которым мы хотим работать, имеет пространство имен COMObjectNamespace, имя интерфейса COMInterfaceName и имя класса, реализующего этот интерфейс - COMClassName. Тогда создается ссылка на объект так:
     COMObjectNamespace::COMInterfaceNamePtr myComObj(COMObjectNamespace::COMClassName);
Далее полученную ссылку myComObj можно использовать как указатель для вызова методов, VisualStudio даже будет подсказывать объявления методов.

Информация взята отсюда: http://support.microsoft.com/kb/828736, где рассмотрен пример взаимодействия по технологии COM между C++-программой и модулем на C#.

среда, 26 октября 2011 г.

Создание BSTR из nsAString и наоборот

BSTR - тип, используемый для строк в COM.
nsAString - тип, используемый для строк в XPCOM от мозиллы.

bstr_string = SysAllocString(nsastring_string.BeginReading());

И наоборот:

nsastring_string.Assign((wchar_t*)bstr_string ,wcslen(bstr_string ));

(предполагается, что в качестве nsAString::char_type используется PRUnichar, который, в свою очередь, является wchar_t).

вторник, 25 октября 2011 г.

Создание строк типа BSTR

BSTR  - тип данных, применяемый в С/С++ для строк при работе с COM. Создается строка типа BSTR вызовом:
BSTR bstrString = SysAllocString(new OLECHAR(L'Hello world'));
или
wchar_t *someStr; ...
BSTR bstrString = SysAllocString(someStr);

Подробнее про тип BSTR прочитать можно тут: http://blogs.msdn.com/b/ericlippert/archive/2003/09/12/52976.aspx

Создание расширения Firefox на C++ c использованием XPCOM

Внимание: использование XPCOM уже не рекомендуется разработчиками Mozilla (как минимум из-за того, что придется перекомпилировать бинарные компоненты с выходом новой версии FF, которые теперь будут выходить часто). В версиях FF 4+ можно использовать гораздо более простой способ - js-ctypes (https://developer.mozilla.org/en/js-ctypes/Using_js-ctypes). js-ctypes, с небольшими изменениями, работает даже в FF 3.6.20 (например, там нет ctypes.char, но есть ctypes.ustring).

Уф, один рабочий день и наконец-то получилось понять, как же все-таки писать расширения для FF с использованием XPCOM. MDN оказался куда хуже чем я ожидал - туториалы все древние и не работают с новыми версиями xulrunner-sdk, а то, что удалось накопать по новому движку Gecko2 - толком не описано, поэтому пришлось искать решение практически методом тыка.

Задача для начала простая - написать расширение, к которому можно было обратиться из JavaScript`а на веб-странице  вызвать метод echo, принимающий на входе строку и возвращающий её же.

Для начала качаем Gecko SDK, он же xulrunner-sdk: http://releases.mozilla.org/pub/mozilla.org/xulrunner/releases/7.0.1/sdk/xulrunner-7.0.1.en-US.win32.sdk.zip. Затем - проект-пример: https://developer.mozilla.org/samples/xpcom/xpcom-test.zip. Проект создан для старой версии xulrunner, поэтому компилироваться не будет, но всё же удобнее использовать его чем начинать с нуля. 
Пусть xulrunner-sdk распакован в "O:\xulrunner-sdk", а проект-пример - в O:\xpcom-test.

Во-первых, нужно описать интерфейс будущего расширения на языке idl (это не майкросовтовский IDL, а мозилловский собственный). Создаем файл (пусть он будет называться echo.idl, его нужно положить в O:\xpcom-test) со следующим содержимым (uuid можно сгенерировать любым генератором UUID):
#include "nsISupports.idl"
[scriptable, uuid(27110427-209a-4f4b-b829-8ca5ebe356a6)]
interface ISpecialThing : nsISupports
{
 AString echo(in AString srcStr);
};
В проект нужно внести следующие изменения:
  • Открываем xpcom-test/xpidl-build.bat и меняем его содержимое на:
    ..\xulrunner-sdk\bin\xpidl.exe -m header -I..\xulrunner-sdk\idl echo.idl
    ..\xulrunner-sdk\bin\xpidl.exe -m typelib -I..\xulrunner-sdk\idl echo.idl
    Запускаем батник, находясь в O:\xpcom-test (создадутся файлы echo.xpt и echo.h).
  • Открываем проект xpcom-test (я использовал MS VS 2005: во-первых, другого под рукой не нашлось, да и проект подойдет для него без конвертации). 
    • Заходим в свойства проектаC/C++ > General > Additional Include Directories, меняем на "..\xulrunner-sdk\include".
    • Затем - в Linker > General > additional Library Directories, ставим "..\xulrunner-sdk\lib"
    • Linker > Input > Additional Dependencies, "nspr4.lib xpcom.lib xpcomglue_s.lib mozalloc.lib"
  • Исключаем из проекта все файлы, кроме созданного нами idl. В свойствах idl-файла отменяем его компиляцию ("Exclude from build" = Yes). Включаем в проект созданный в пункте 1 h-файл с заголовком интерфейса. В комментариях этот файл содержит заготовку для класса, реализующего этот интерфейс.
  • Создаем класс, реализующий описанный в h-файле интерфейс. Пусть он будет называться echoImpl. Ниже приведен код заголовочного файла этого класса:
#include "echo.h"
#include "nsStringAPI.h"
#define ECHO_CONTRACTID "@some.domain.com/echosample;1"
#define ECHO_CLASSNAME "echosample"
#define ECHO_CID { 0xe99152f7, 0x73f7, 0x45d3, { 0x8e, 0x94, 0x26, 0x3e, 0x21, 0x10, 0x63, 0x57 } }
static const nsCID kEchoCID = IACSIGN_CID;
class echoImpl: public ISpecialThing
{
public:
  NS_DECL_ISUPPORTS
  NS_DECL_ISPECIALTHING
  echoImpl();
  ~echoImpl();
};
Define`ами в данном файле задает 3 важных параметра будущего компонента:  
    • ECHO_CONTRACTID - contract id компонента, имеющий вид домен/модуль/компонент;версия и используемый для идентификации компонента в массиве Components.classes при обращении из JavaScript.
    • ECHO_CLASSNAME - имя класса компонента (не нашел пока, где оно используется при эксплуатации компонента)
    •  ECHO_CID - уникальный GUI класса компонент. Сгенерировать его можно любым средством генерации GUID (например, http://www.guidgenerator.com/), после чего представить в указанном виде.
Теперь - само тело класса:
#include "echoImpl.h"
#include "nsStringAPI.h"
#include "nsIClassInfoImpl.h"
#include "nsMemory.h"
NS_IMPL_CLASSINFO(echoImpl, NULL, 0, ECHO_CID)
NS_IMPL_ISUPPORTS1_CI(echoImpl, ISpecialThing) 
echoImpl::echoImpl(){}
echoImpl::~echoImpl(){} 
NS_IMETHODIMP echoImpl::echo(const nsAString & srcStr, nsAString & _retval NS_OUTPARAM)
{
return NS_StringCopy(_retval,srcStr);
}
Тут все предельно просто - пара макросов и собственно метод echo.
  • Теперь необходимо описать модуль - код, который будет сообщать FF о написанном нами модуле. Создаем файл echoModule.cpp со следующим наполнением (код взят из примера http://mxr.mozilla.org/mozilla-central/source/xpcom/sample/, который, правда, не является 100% рабочим):
#include "echoImpl.h"
#include "mozilla/ModuleUtils.h"
#include "nsIClassInfoImpl.h"
////////////////////////////////////////////////////////////////////////
// With the below sample, you can define an implementation glue
// that talks with xpcom for creation of component nsSampleImpl
// that implement the interface nsISample. This can be extended for
// any number of components.
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
// Define the contructor function for the object nsSampleImpl
//
// What this does is defines a function nsSampleImplConstructor which we
// will specific in the nsModuleComponentInfo table. This function will
// be used by the generic factory to create an instance of nsSampleImpl.
//
// NOTE: This creates an instance of nsSampleImpl by using the default
// constructor nsSampleImpl::nsSampleImpl()
//
NS_GENERIC_FACTORY_CONSTRUCTOR(echoImpl);
// The following line defines a kNS_SAMPLE_CID CID variable.
NS_DEFINE_NAMED_CID(ECHO_CID);
// Build a table of ClassIDs (CIDs) which are implemented by this module. CIDs
// should be completely unique UUIDs.
// each entry has the form { CID, service, factoryproc, constructorproc }
// where factoryproc is usually NULL.
nsresult myF (nsISupports* aOuter,
                 const nsIID& aIID,
                 void** aResult) {
*aResult = new echoImpl();
return NS_OK;
}
static const mozilla::Module::CIDEntry kSampleCIDs[] = {
{ &kEchoCID , false, NULL, myF },
    { NULL }
};
// Build a table which maps contract IDs to CIDs.
// A contract is a string which identifies a particular set of functionality. In some
// cases an extension component may override the contract ID of a builtin gecko component
// to modify or extend functionality.
static const mozilla::Module::ContractIDEntry kSampleContracts[] = {
    { ECHO_CONTRACTID , &kEchoCID },
    { NULL }
};
// Category entries are category/key/value triples which can be used
// to register contract ID as content handlers or to observe certain
// notifications. Most modules do not need to register any category
// entries: this is just a sample of how you'd do it.
// @see nsICategoryManager for information on retrieving category data.
static const mozilla::Module::CategoryEntry kSampleCategories[] = {
    //{ "my-category", "my-key", ECHO_CONTRACTID },
    { NULL }
};
static const mozilla::Module kSampleModule = {
    mozilla::Module::kVersion,
    kSampleCIDs,
    kSampleContracts,
    kSampleCategories
};
// The following line implements the one-and-only "NSModule" symbol exported from this
// shared library.
NSMODULE_DEFN(nsSampleModule) = &kSampleModule;
// The following line implements the one-and-only "NSGetModule" symbol
// for compatibility with mozilla 1.9.2. You should only use this
// if you need a binary which is backwards-compatible and if you use
// interfaces carefully across multiple versions.
// NS_IMPL_MOZILLA192_NSGETMODULE(&kSampleModule)

Здесь стоит обратить внимание на функцию myF - она возвращает объект созданного ранее класса echoImpl. В примере с MDN её не было (точнее имя-то функции было, а вот тела - нет).

Всё, проект можно компилировать. Если всё пройдет ок, то на выходе получим dll-файл, и представляющий собой компонент (к совокупности с полученным ранее xpt-файлом, описывающим его интерфейс).

Теперь дополнение необходимо добавить в FF. Для этого понадобиться xpi-файл со следующей структурой:
  • components/ - директория, куда нужно положить колченные xpt и dll файлы
  • chrome.manifest - файл реестра FF, описывающий компонент, со следующим содержимым:
binary-component components/echo.dll
interfaces components/echo.xpt
component {99152f77-3f74-5d38-e942-63e21106357} components/echo.dll
Здесь в фигурных скобках указан GUID, установленный в коде компонента с помощью константы ECHO_CID.

  •  install.rdf - файл, описывающий компонент и необходимый для его установки. Минимальное содержимое файла следующее:
<?xml version="1.0" encoding="UTF-8"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:em="http://www.mozilla.org/2004/em-rdf#">
  <Description about="urn:mozilla:install-manifest">
    <em:id>echo@demo.com</em:id>
    <em:type>2</em:type>
    <em:name>Echo echo</em:name>
    <em:version>0.2.1</em:version>
    <em:creator>barbitoff</em:creator>
    <em:contributor/>
    <em:description>Blablabla echo</em:description>
    <em:aboutURL/>
<em:unpack>true</em:unpack>
    <em:targetApplication>
      <Description>
        <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> <!--FF-->
        <em:minVersion>3.0</em:minVersion>
        <em:maxVersion>7.*</em:maxVersion>
      </Description>
    </em:targetApplication>
  </Description>
</RDF>
Такое содержимое  install.rdf позволит установить xpi на версии FF от 3.0 до 7.* (т.е. любой 7ой версии).
Всё, теперь пакуем созданную структуру в zip-файл, меняем расширение на xpi и перетаскиваем его на окно FF. Компонент должен успешно установиться, перезапускаем FF. Вызвать наш echo-метод из JS можно:
var a = Components.classes["@some.domain.com/echosample;1"].createInstance();
alert(a.echo("HELLO WORLD!"));
Примечание - на эту операцию требуется разрешение. Читал, что оно устанавливается вызовом netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect") (естественно, использовать его можно только в тестовых целях, а не на боевых веб-страницах), но у меня почему-то и с ним не работает. Приведенный выше вызов работает из js-консоли Firebug, и должен также работать при вызове из js-кода расширения.

понедельник, 24 октября 2011 г.

PRBool

PRBool - мозилловская вариация на тему обычного булева типа. По сути является enum`ом с 2 значениями: PR_FALSE = 0 и PR_TRUE = 1.

Пример использования:

NS_IMETHODIMP myModule::CanUnload(nsIComponentManager *aCompMgr, PRBool *_retval NS_OUTPARAM)
{
*_retval = PR_TRUE;
return NS_OK;
}

четверг, 20 октября 2011 г.

WSO2ESB и endpoint из реестра WSO2GREG

При добавлении WSDL в реестр WSO2GREG он создает в ветке реестра "/_system/governance/trunk/endpoints/..." ресурс, соответствующий конечной точке данной WSDL. 
При добавлении прокси-сервиса на WSO2ESB есть опция выбора конечной точки из реестра. Поэтому и появилась идея - почему бы не выбрать там конечную точку, волшебным образом созданную при импорте WSDL в реестр? Оказалось как бы не так: прокси сервис создается, однако при попытке отправить на него запроса возвращается лишь HTTP-загловок "202 Accepted", в логах - тишина, до конечного сервиса запрос также не доходит. При создании faultSequence с логированием, можно лишь увидеть, что возникающая ошибка имеет код 0 и текст "Configuration is not in proper format":
Synapse ERROR_CODE : 0 ERROR_MESSAGE : Configuration is not in proper format.
Оказывается дело в следующем: конечная точка, создаваемая в реестре при добавлении WSDL содержит лишь URL, тогда как ESB ожидает увидеть в реестре полноценную xml-конфигурацию конечной точки.
Так что единственный вариант использовать реестр для хранения конечных точек WSO2ESB - это вручную загружать туда xml-ки с их конфигурациями.
Источник: http://wso2.org/forum/thread/11705 

Error processing POST request: Transport level information does not match with SOAP Message namespace URI

WSO2ESB (а точнее, лежащий в её основе Apache Axis2) при поступлении входящего сообщения пытается определить используемую сообщением версию протокола SOAP (1.1 или 1.2). Делает он это 2 способами:
  1. На основании пространства имен SOAP envelope, которое равно "http://schemas.xmlsoap.org/soap/envelope/" для вресии 1.1 и "http://www.w3.org/2003/05/soap-envelope" для 1.2
  2. На основании HTTP-заголовка в случае, если нижележащим протоколом является HTTP. Для SOAP 1.1 должен присутствовать заголовок SOAPAction, для 1.2 может присутствовать параметр SOAPAction внутри заголовка Content-type.
В случае, если полученные в результате этих двух проверок протоколы не совпадают, генерируется ошибка "Transport level information does not match with SOAP Message namespace URI".
Похожая проверка выполняется для ответов, получаемых от внешних сервисов. Выполняется сопоставление пространства имен SOAP-конверта и HTTP-заголовка Content-Type. Для пространства имен SOAP 1.1 ожидается Content-Type "text/xml", для SOAP 1.2 - "application/soap+xml". В случае несовпадения также генерируется ошибка "Transport level information does not match with SOAP Message namespace URI".

среда, 19 октября 2011 г.

apache 2.2.x и "MaxClients of xxx would require yy servers, and would exceed the ServerLimit value of 16."

Проблема:
При установке в конфигурации Apache версии 2.2.х параметра MaxClients большим, чем 400 (например, 1000), получаем при перезапуске ошибку:

WARNING: MaxClients of 1000 would require 40 servers,
 and would exceed the ServerLimit value of 16.
 Automatically lowering MaxClients to 400.  To increase,
 please see the ServerLimit directive.
Директивы же  ServerLimit в конфиге нету, и её добавление туда ситуации не меняет.

Причина:
В новых версиях Apache параметр ServerLimit  больше нельзя установить через конфигурацию.

Решение:
Установить MaxClients в 400 или перекомпилировать Apache (как, написано тут: http://onaxer.com/blog/blog/2010/04/11/compiling-serverlimit-in-apache-2-x/).


Apache2 mod_proxy: ProxyPass, использование balancer и проблемы с передачей оставшейся части пути backend-серверу

Проблема:
Используется mod_proxy для балансировки нагрузки (проксирования https запросов на 2 backend-сервера). В качестве пути для балансировки используется путь "/load-balancer/", при обращении по которому запрос отправляется на "https://192.168.0.101:8000/services/" или "https://192.168.0.101:8001/services/". Соответствующая настройка httpd следующая:

SSLProxyEngine on
ProxyPass /load-balancer/ balancer://myhttpsbalancer/
ProxyPassReverse /load-balancer/ https://192.168.0.101:8000/services/
ProxyPassReverse /load-balancer/ https://192.168.0.101:8001/services/
<Proxy balancer://myhttpsbalancer>
BalancerMember https://192.168.0.101:8000/services/ loadfactor=50
BalancerMember https://192.168.0.101:8001/services/ loadfactor=50
</Proxy> 

Вроде бы всё верно, но почему-то все запросы на поддиректории директории "/load-balancer/" приводят к обращению всё к той же корневой директории "/services/" backend-сервера. Т.е. при выполнении запроса на путь "/load-balancer/echo?wsdl" видим все равно тоже, что и для запроса "/load-balancer/".

Решение:
В директиве BalancerMember в конце URL убрать слеш. Т.е. получится конфигурация:

SSLProxyEngine on
ProxyPass /load-balancer/ balancer://myhttpsbalancer/
ProxyPassReverse /load-balancer/ https://192.168.0.101:8000/services/
ProxyPassReverse /load-balancer/ https://192.168.0.101:8001/services/
<Proxy balancer://myhttpsbalancer>
BalancerMember https://192.168.0.101:8000/services loadfactor=50
BalancerMember https://192.168.0.101:8001/services loadfactor=50
</Proxy> 

вторник, 18 октября 2011 г.

Linux: просмотр списка прослушиваемых портов

sudo netstat -anp --udp --tcp | grep LISTEN

WSO2ESB: localhost в ссылках на WSDL сервисов и в конечных точках внутри WSDL

Проблема:
В списке сервисов, развернутых на WSO2ESB, ссылка на WSDL содержит вместо ip-адреса или сетевого имени хоста просто "localhost". HTTP и HTTPS - конечные точки внутри WSDL также содержат вместо хоста "localhost".

Решение:
Хост, используемый при формировании ссылки на WSDL, а также хост-порт, используемые в ссылках на конечные точки внутри WSDL, настраиваются в файле repository/conf/axis2.xml следующим образом:

    <transportReceiver name="http" class="org.apache.synapse.transport.nhttp.HttpCoreNIOListener">
      ...

        <parameter name="bind-address" locked="false">192.168.0.101</parameter>
        <parameter name="WSDLEPRPrefix" locked="false">http://192.168.0.101:8280</parameter>
      ...
    </transportReceiver>
    <transportReceiver name="https" class="org.apache.synapse.transport.nhttp.HttpCoreNIOSSLListener">
      ...

<parameter name="bind-address" locked="false">192.168.0.101</parameter>
<parameter name="WSDLEPRPrefix" locked="false">https://192.168.0.101:8243</parameter>
      ...
    </transportReceiver>


java.net.UnknownHostException при вызове java.net.InetAddress.getLocalHost()

Проблема:
При вызове метода java.net.InetAddress.getLocalHost() для получения IP-адреса локальной машины генерируется исключение "java.net.UnknownHostException: ... ".

Причина:
Имя, указанное как hostname компьютера (в Debian это устанавливается в /etc/hostname), неразрешимо в IP-адрес.

Решение:
Возможно 2 варианта:
  • изменить /etc/hostname на имя, которое преобразуемо в IP либо с помощью /etc/hosts, либо с помощью нелокального DNS
  • добавить hostname в /etc/hosts



Проверка целостности данных при передаче из Windows в Linux с помощью md5

Во-первых, необходимо посчитать md5-суммы передаваемых файлов в Windows. Для этого существует утилита fsum (http://www.fastsum.com/). Например, команда
fsum.exe D:\transfer D:\transfer\transfer.md5 /R 
посчитает md5-суммы всех файлов из директории "D:\transfer", включая вложенный папки, и создаст в этой директории файл transfer.md5 со списком этих контрольных сумм.
В Linux (по крайней мере, в Debian) для проверки контрольных сумм существует утилита md5sum, однако формат входного файла для неё несколько иной, поэтому с полученным md5-файлов нужно выполнить некоторые преобразования:

  1. Заменить CR LF окончания строк на просто LF
  2. " *" между контрольной суммой и именем файла заменить на "  ./" (два пробела и обозначение текущей директории)
  3. Удалить 3 строки с комментариями в начале файла (начинающиеся с ";")
Всё, теперь, после передачи файлов директории "D:\transfer" на Linux-машину, необходимо перейти в директорию с переданными файлами и выполнить:
md5sum transfer.md5 -c 
Если же проверять нужно тоже на винде, то проверяется тем же fsum следующим образом (при этом менять ничего в md5-файле, естественно, не надо):
fsum.exe D:\downloads D:\downloads\transfer.md5 /R /V

Распаковка многотомных zip-архивов в Linux

Утилита p7zip (http://www.7-zip.org/download.html), можно скачать binaries, не требующие установку в систему. Сама распаковка:
p7zip_9.20.1/bin/7z x some_arch.zip.001
Программа сама найдет остальные части архива.

пятница, 14 октября 2011 г.

Публикация данных с WSO2ESB 4.0.0 на WSO2BAM 1.3.0 и производительность ESB

По результатам тестирования приходится сделать неутешительный вывод - установка модулей публикации данных с ESB на BAM (http://barbitoff.blogspot.com/2011/08/wso2esb-400-wso2bam.html) приводит к падению пиковой производительности шины более чем в 50 раз и её нестабильности уже при 20 запросах в секунду (без модулей шина в моём случае выдерживает около 500 запросов без превышения временем обработки запроса порога в 1 сек). Причем наблюдается это даже в случае, если модули установлены, но публикация выключена в их настройках. Модули вызывают неэффективное использование памяти и возникновение исключений "ERROR - NativeWorkerPool Uncaught exception java.lang.OutOfMemoryError: unable to create new native thread", после чего сервер становится неработоспособным до перезагрузки.
Всё описанное выше актуально для Windows, в Linux зафиксировано лишь падение производительности медиации в 7-8 раз.

четверг, 13 октября 2011 г.

bash: определение абсолютного пути скрипта

Для определения пути к скрипту изнутри него самого можно воспользоваться нулевым атрибутом скрипта ($0), однако его значение зависит от того, каким образом вызван скрипт - по относительному пути или по абсолютному. Поэтому всё выходит немного сложнее:

WDIR=`pwd`
testAbsolutePath=`echo $0 | sed -e '/^\//d'` # delete string, starting with "/"
if [ -n "$testAbsolutePath" ]; then # rel path
THISEXEC="$WDIR/$0"
else
THISEXEC="$0" # abs path
fi

Результат будет в переменной THISEXEC.

среда, 12 октября 2011 г.

WSO2: настройка узлов кластера интеграционных шин на использование единого LDAP хранилища пользователей

Для того, чтобы не заниматься управлением пользователями на всех экземплярах WSO2ESB в кластере (впрочем, это относится не обязательно к кластеру, и вообще даже не только к ESB, а и к другим продуктам WSO2), удобно использовать единое хранилище пользователей. Вариантов здесь несколько: можно использовать JDBC-коннектор к единой БД, а можно - встроенный в Carbon LDAP-сервер.
Пусть всеми узлами будет использоваться LDAP-сервер узла-менеджера кластер-группы, слушающий порт 10386 (напомню, порт этот можно настроить в repository/conf/carbon.xml  в теге <LDAPServerPort>).
Тогда на всех остальных экземплярах шины необходимо зайти в  repository/conf/embedded-ldap.xml и установить:
<EmbeddedLDAP>
     <Property name="enable">false</Property>
     ....
</EmbeddedLDAP>
, чтобы они не стартовали свой LDAP, т.к. он больше нужен не будет. Затем идем в  repository/conf/user-mgt.xml и устанавливаем URL LDAP-сервера (в моем случае узел ESB, играющий роль менеджера кластер-группы, а теперь и менеджера пользователей, расположен на том же хосте, что и остальные узлы, т.е. на localhost) :

<UserStoreManager class="org.wso2.carbon.user.core.ldap.ApacheDSUserStoreManager">
     <Property name="ReadOnly">false</Property>
     <Property name="ConnectionURL">ldap://localhost:10386</Property>
</UserStoreManager>
Впрочем, <Property name="ReadOnly"> будет логичнее установить в true, чтобы вносить изменения мог только центральный узел, однако при попытке это сделать WSO2 вылетает с исключением, что, дескать, этот UserStoreManager должен быть Read/Write (возникает правда вопрос, зачем тогда в его настройках есть свойство ReadOnly?). Поэтому, если нужно, чтобы хранилище пользователей было только для чтения, меняем атрибут "class" тега UserStoreManager с org.wso2.carbon.user.core.ldap.ApacheDSUserStoreManager на org.wso2.carbon.user.core.ldap.LDAPUserStoreManager.

Очевидный минус подхода с использованием в качестве хранилища пользователей встроенного LDAP одного из экземпляров шины - необходимость запускать экземпляры в строго определенном порядке, и, более того, к моменту запуска slave-экземпляров LDAP-сервер на master-экземпляре должен успеть запуститься. Это условие затрудняет написание скриптов автоматизации запуска шин.

вторник, 11 октября 2011 г.

Замена favicon.ico в WSO2 Carbon

Файл favicon.ico находится тут:

repository\components\plugins\org.wso2.carbon.ui-3.2.0.jar\web\admin\images\

WSO2: настройка title страниц консоли администрирования и замена copyright в подножии

Title страниц задается в файле:
repository/components/plugins/org.wso2.carbon.ui-3.2.0.jar/web/admin/layout/template.jsp
подножие:
 repository/components/plugins/org.wso2.carbon.ui-3.2.0.jar/web/admin/layout/footer.jsp
При использовании русских символов нужно не забыть перекодировать файлы в UTF-8.

понедельник, 10 октября 2011 г.

Особенности русификации продукции WSO2

По идее, русификация WSO2 - дело достаточно тривиальное. Необходимо извлечь из jar-ников директории repository/components/plugins/ все файлы Resources.properties, перевести их (вместо русских символов необходимо использовать т.н. "Unicode-последовательности") и закатать обратно. Однако не всё оказывается так просто, об особенностях этого процесса я и напишу ниже.

1) Русификация домашней страницы административной консоли (а также других страниц, на которых содержится запрашиваемые по AJAX данные)

Properties-файл для локализации домашней страницы консоли администрирования WSO2 (таблички, отображающей суммарную статусную информацию по серверу) расположен тут:
repository/components/plugins/org.wso2.carbon.server.admin.ui-3.2.0.jar/org/wso2/carbon/server/admin/ui/i18n/Resources.properties
Однако при попытке перевести расположенные там строчки, заменив их Unicode-последовательностями ('\u...'), соответствующими русскому переводу, в админке вместо русских букв появляются знаки вопроса (в отличие от других Resources.properties-файлов, локализация которых не вызывает каких бы то ни было проблем).
Дело в том, что эта табличка запрашивается с помощью AJAX со страницы

Аналогичные манипуляции необходимы также на всех других jsp-страницах, на которых кодировка не установлена корректно и к которым запрос ведется по AJAX.
ИМХО самый простой вариант, чтобы не осуществлять полное тестирование продукта для выявления всех страниц с побитой кодировкой - извлечь из jar-ников repository/components/plugins все файлы по регулярному выражению '/.*ajaxprocessor\.jsp$/', и произвести с ними вышеуказанные манипалуции (во многих из них менять ничего будет не надо, т.к. кодировка там уже задана). При этом важно пропускать файлы, используемые для генерации ява-скрипта или стилей, т.к. после установки типа содержимого "text/html" они перестанут восприниматься браузером!


2) Проблема с квадратиками вместо символов на странице "Статистика медиации", "Features", и, возможно, других в WSO2 ESB 4.0.0 в Mozilla Firefox
Если статистика отсутствует, то в табличках на странице "Статистика медиации" отображаются соответствующие надписи, причем курсивом. Мозилла нарисовать курсив указанным в стилях страницы шрифтом не может, поэтому на странице отображаются квадратики. Выход - либо менять шрифты, либо отображать курсив не курсивом. И то, и другое можно сделать в файле org.wso2.esb.styles-4.0.0.jar\web\admin\css\main.css.

3) Опасные для использования символы при переводе. Переводя ресурсы, необходимо заменять символы, которые могут разрушить html-разметку ("<", ">") или сделать некорректным JavaScript-код (двойные и одинарные кавычки), на соответствующие им кодовые последовательности. Также нельзя использовать переводы строки ("\n"), т.к. они будут разрушать строки в JavaScript (ведь в JS нельзя переходить на новую строку в пределах константной строки).


среда, 5 октября 2011 г.

Пример SOAP-запроса на Security Token Service WSO2 Identity Server

Ниже представлен пример SOAP-запроса, отправляемого на Security Token Service для получения Security Token. Запрос имеет следующие параметры:
  1. Субъект, запрашивающий токен, аутентифицируется парой логин / пароль: admin/admin
  2. Сервис, для работы с которым запрашивается токен: https://192.168.50.102:3443/services/echo/
  3. Тип запрашиваемого токена: SAMLV2.0
  4. Тип ключа: симметричный
  5. Размер ключа: 256
  6. Пользовательская часть комбинированного ключа представлена base64-закодированным значением "g5WVRpUl7bKno8LYFC9JUGLpe1NZpkZ/"
  7. Алгоритм калькуляции ключа: PSHA1
  8. Ответ на запрос возвращается обратно (т.е. в ReplyTo указан "anonymous").
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
   <soapenv:Header>
  <wsse:Security>
   <wsu:Timestamp wsu:Id="Timestamp-17449452" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
     <wsu:Created>2011-10-05T05:03:40.568Z</wsu:Created> 
   </wsu:Timestamp>
  <wsse:UsernameToken>
   <wsse:Username>admin</wsse:Username>
   <wsse:Password Type="wsse:PasswordText">admin</wsse:Password>
  </wsse:UsernameToken>
   </wsse:Security>
<wsa:MessageID>
uuid:6B29FC40-CA47-1067-B31D-00DD010662DF
</wsa:MessageID>

<wsa:To/>
 <wsa:ReplyTo>
<wsa:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:Address>
 </wsa:ReplyTo>
<wsa:Action>http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue</wsa:Action>
 
   </soapenv:Header>
   <soapenv:Body>
<wst:RequestSecurityToken xmlns:wst="http://docs.oasis-open.org/ws-sx/ws-trust/200512">
    <wst:RequestType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue</wst:RequestType>
    <wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
        <wsa:EndpointReference xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing">
            <wsa:Address>https://192.168.50.102:3443/services/echo/</wsa:Address>
        </wsa:EndpointReference>
    </wsp:AppliesTo>

    <wst:TokenType>http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0</wst:TokenType>
    <wst:KeyType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/SymmetricKey</wst:KeyType>
    <wst:KeySize>256</wst:KeySize>
    <wst:Entropy>
        <wst:BinarySecret Type="http://docs.oasis-open.org/ws-sx/ws-trust/200512/Nonce">
            g5WVRpUl7bKno8LYFC9JUGLpe1NZpkZ/
        </wst:BinarySecret>
    </wst:Entropy>
    <wst:ComputedKeyAlgorithm>http://docs.oasis-open.org/ws-sx/ws-trust/200512/CK/PSHA1</wst:ComputedKeyAlgorithm>
</wst:RequestSecurityToken>

 </soapenv:Body>
</soapenv:Envelope>


org.apache.rahas.TrustException: Unsupported WS-Trust version {org.apache.rahas.STSMessageReceiver}

Проблема:
При попытке отправить SOAP-запроса на Security Token Service Apache Rahas (в моем случае, как части WSO2 Identity Server) ответ не приходит, а в логах сервера появляется ошибка:

ERROR {org.apache.rahas.STSMessageReceiver} -  org.apache.rahas.TrustException: Unsupported WS-Trust version {org.apache.rahas.STSMessageReceiver}
Причина:
Видимо, в качестве пространства имен wst указан url "http://docs.oasis-open.org/ws-sx/ws-trust/200802" или другой, вместо необходимого "http://docs.oasis-open.org/ws-sx/ws-trust/200512".

Решение:
Установить пространство имен корректно:
 xmlns:wst="http://docs.oasis-open.org/ws-sx/ws-trust/200512"

Настройка внешнего UserStoreManager для WSO2IS

1) Создать структуру БД из соответствующего СУБД скрипта из директории wso2is-3.2.0/dbscripts
2) В /repository/components/lib положить jar-ник клиента Вашей СУБД
3) Изменить настройки управления пользователями в файле /repository/conf/user-mgt.xml. Вот пример настроек для БД postgers:

<UserManager>
    <Realm>
        <Configuration>
            <AdminRole>admin</AdminRole>
            <AdminUser>
                <UserName>admin</UserName>
                <Password>admin</Password>
            </AdminUser>
            <EveryOneRoleName>everyone</EveryOneRoleName>
            <ReadOnly>false</ReadOnly>
            <MaxUserNameListLength>500</MaxUserNameListLength>
            <Property name="url">jdbc:h2:repository/database/WSO2CARBON_DB;DB_CLOSE_ON_EXIT=FALSE</Property>
            <Property name="userName">wso2carbon</Property>
            <Property name="password">wso2carbon</Property>
            <Property name="driverName">org.h2.Driver</Property>
            <Property name="maxActive">50</Property>
            <Property name="maxWait">60000</Property>
            <Property name="minIdle">5</Property>
        </Configuration>
        <UserStoreManager class="org.wso2.carbon.user.core.jdbc.JDBCUserStoreManager">
<Property name="PasswordJavaScriptRegEx">[\\S]{5,30}</Property>
            <Property name="url">jdbc:postgresql://localhost:5432/wso2is_um</Property>
            <Property name="userName">postgres</Property>
            <Property name="password">postgres</Property>
            <Property name="driverName">org.postgresql.Driver</Property>
            <Property name="maxActive">50</Property>
            <Property name="maxWait">60000</Property>
            <Property name="minIdle">5</Property>
<Property name="ReadOnly">true</Property>
            <Property name="MaxUserNameListLength">100</Property>
            <Property name="IsEmailUserName">false</Property>
            <Property name="DomainCalculation">default</Property>
            <Property name="PasswordDigest">SHA</Property>
            <Property name="StoreSaltedPassword">true</Property>
            <Property name="UserNameUniqueAcrossTenants">false</Property>
        </UserStoreManager>
        <AuthorizationManager class="org.wso2.carbon.user.core.authorization.JDBCAuthorizationManager"/>
    </Realm>
</UserManager>

За основу взят пример с wso2.org (http://wso2.org/project/solutions/identity/3.2.0/docs/user-core/user-mgt-jdbc.xml), однако PasswordDigest заменен на "SHA", потому что иначе не получается залогиниться под admin/admin.Также добавлен параметр PasswordJavaScriptRegEx, без него через интерфейс карбона не создать пользователя, т.к. он ругается на любой введенный пароль, что он якобы менее 5 символов.
4) Запустить WSO2IS с ключом -Dsetup, который создаст первоначальное заполнение БД (впрочем, п.1 можно было бы и пропустить, -Dsetup вроде бы сам умеет создавать структуру). При этом в БД появятся 2 пользователя - admin и wso2.anonymous.user.

Изменение параметра innodb_log_file_size InnoDB-движка MySQL

Проблема:
Если файл лога уже присутствует, при попытке поменять параметр innodb_log_file_size при запуске сервера вылезет ошибка вида:

InnoDB: Error: log file .\ib_logfile0 is of different size 0 <xxx> bytes
InnoDB: than specified in the .cnf file 0 <yyy> bytes! 
Причина:
MySQL видит, что размер существующего лог-файла отличается от указанного в настройках.

Решение:
Остановить сервер, сделать резервную копию файлов ib_logfile<x>, после чего удалить их, изменить значение параметра  innodb_log_file_size и запустить сервер. MySQL создаст новый лог-файл указанного в конфигурации размера.

вторник, 4 октября 2011 г.

KILL процесса MySQL, выполняющего ALTER TABLE

Делать KILL процесса, выполняющего модификацию структуры таблицы (ALTER TABLE), можно. По крайней мере, если верить mail-листам mysql.com. Действительно, при выполнении команды ALTER TABLE MySQL создает копию модифицируемой таблицы, а после успешного завершения процесса удаляет исходную таблицу, а скопированную переименовывает. На собственном опыте проверил (после 22 часов выполнения запроса ждать попросту надоело), таблица жива =)

Аналог wget для Windows

Аналог /dev/null в Windows

В Windows вместо "черной дыры" /dev/null можно использовать псевдофайл с именем "NUL", имеющийся в каждой директории. Также он позволяет проверить наличие какой-либо директории (командой "if exist somedir\nul", тогда как проверяя "if exist somedir" мы получим истину даже если somedir вовсе и не директория, а файл).

Запрет форвардинга X11 по ssh

В файле /etc/ssh/sshd_config установить:
X11Forwarding no

суббота, 1 октября 2011 г.

MySQL: #1005 - Can't create table (errno: 150) при попытке создания внешнего ключа

Проблема:
При попытке создать внешний ключ в таблице MySQL командой ALTER TABLE появляется ошибка "#1005 - Can't create table ...#sql-c84_... (errno: 150)".
Причина:
Причин может быть несколько: несовпадение типов поля, являющегося первичным ключом, и поля-внешнего ключа, или же поле, на которое ссылается внешний ключ вообще не является первичным ключом. Для того, чтобы увидеть текст ошибки, нужно выполнить SHOW ENGINE INNODB STATUS, где посмотреть в блок "LATEST FOREIGN KEY ERROR".