пятница, 11 марта 2011 г.

eBay Auth + Erlang

Некоторое время назад меня попросили поковыряться с таким монстром как eBay. Вот, решил немного подытожить всё прочитанное и испытанное по данной теме. Заодно показать на примере как можно сделать аутентификацию на своём сайте с помощью eBay.


Cначала поговорим об API.


Существует несколько категорий eBay API, например Trading API, Finding API, Shopping API и т.д. Эти категории, как понятно из названия, служат разным целям, и требуют разных входных данных для выполнения вызова и предоставляют результат в разных форматах.
Все вызовы осуществляются через один из заранее заданных форматов обмена данными, например XML, SOAP, JSON.


Каждый разработчик должен сгенерировать пачку ключей в разделе для разработчиков. Причем эти ключи делятся на две категории - боевые(production) и холостые(sandbox). Холостые ключи нужны для отладки приложения, например что бы отладить покупку какого-нибудь фиктивного товара на фиктивные деньги. Тройка ключей состоит из DevId, AppId и CertId.


Ещё один важный пункт, так называемая "eBay User Consent Form". С помощью этой формы вы налаживаете обратную связь между пользователем и eBay`ем. Об этом чуть дальше. Главное здесь нужно сгенерировать два RuName`а, один с настройкой Authorization Type: ID Verification , другой с настройкой Authorization Type: Authorization.


Поехали дальше, как я писал выше - есть несколько способов общения с eBay, мы выбираем не самый легкий и очевидный - SOAP. Почему не самый легкий? В идеале есть WSDL файл, на основании которого генерится API вашего приложения, и все счастливы. Практически вся куча примеров с eBay Documentation Center и API Test Tool ориентированы на XML. Плюс обязательно почитайте как правильно выбрать и составить адресс API GateWay`я (практика показала что это https://api.sandbox.ebay.com/wsapi?callname=API_CALL_NAME для холостого набора ключей и вместо API_CALL_NAME подставьте название того метода, который вы вызываете, иначе всё валится).

Итак, наметим цель - аутентификация пользователя на своем сайте, с помощью eBay
Наметим шаги:

  1. Получаем SessionID с помощью вызова GetSessionID(здесь и далее речь идёт о Trading API)
  2. Отправляем пользователя по специально сформированной ссылке на eBay
  3. eBay предлагает пользователю залогинится
  4. После успешной аутентификации, он говорит что такое-то приложение хочет знать это реально вы или кто-то прикалывается? Вы конечно соглашаетесь
  5. eBay уведомляет сайт что пользователь залогинился и согласился
  6. Вы с помощью метода ConfirmIdentity узнаете его идентификатор
  7. ???
  8. PROFIT


Немного кода




Первым делом когда я погуглил erlang + soap мне вывалилась ссылка на yaws + soap, вот на этом примере мы и будем тренироваться. Для эксперементов нам потребуется yaws(я использовал версию 1.76) - веб сервер на erlang, библиотека erlsom(1.2.1), библиотека для http запросов ibrowse(2.1.3) и WSDL файл с ebay.com
Устанавливаем yaws, erlsom, ibrowse и убеждаемся что они прописаны в пути(можно сделать из шелла erl с помощью функции code:get_path())
запускаем шелл erl



AppId = "YourAppID",
DevId = "YourDevId",
AuthCert = "YourCertId",
RuName = "RuName with AuthType: ID Verification",
Version = "709",
WsdlURL = "/path/to/wsdl/ebaySvc.wsdl",
ssl:start(),

%% делаем файл с рекордами для дальнейшего инклуда
yaws_soap_lib:write_hrl(WsdlURL, "soap.hrl"),

%% подключаем рекорды в шелл
rr("soap.hrl""),

Wsdl = yaws_soap_lib:initModel(WsdlURL),

{ok, _, [#'p:GetSessionIDResponseType'{'SessionID' = SessionID}]} = yaws_soap_lib:call(
  Wsdl, "GetSessionID", [
    #'p:CustomSecurityHeaderType'{'Credentials' = DevCredentials}], [#'p:GetSessionIDRequestType'{'RuName' = RuName, 'Version' = Version}
    ]
).

%% далее посылаем пользователя по ссылке https://signin.sandbox.ebay.com/ws/eBayISAPI.dll?SignIn&RuName=RuName="RuName с настройкой AuthType: ID Verification"&SessID="SessionID, который вернулся в предыдущем обращении к API"

%% после того, как пользователь нажал accept на сайте eBay, вызываем следующий метод
yaws_soap_lib:call(Wsdl, "ConfirmIdentity", [#'p:CustomSecurityHeaderType'{'Credentials' = DevCredentials}], [#'p:ConfirmIdentityRequestType'{'SessionID' = SessionID, 'Version' = Version}]).


Вуаля! как просто ;)
Второй RuName, который мы генерили в начале нужен нам, что бы получить AuthToken - такая штука, которая нужна для всех запросов
функция для получения этого токена - FetchToken
вот пример на erlang:



{ok, _, [#'p:GetSessionIDResponseType'{'SessionID' = SessionID2}]} = yaws_soap_lib:call(

  Wsdl, "GetSessionID", [
    #'p:CustomSecurityHeaderType'{'Credentials' = DevCredentials}], [#'p:GetSessionIDRequestType'{'RuName' = RuName2, 'Version' = Version}
    ]
).

yaws_soap_lib:call(Wsdl, "FetchToken", [#'p:CustomSecurityHeaderType'{'Credentials' = DevCredentials}], [#'p:FetchTokenRequestType'{'SessionID' = SessionID2, 'Version' = Version}]).

суббота, 18 сентября 2010 г.

thread pools in glassfish v3

Пришло время переводить свои приложения с glassfish v2+ на glassfish v3+, при это естественно возникла куча проблем.
одна из них это то, что я использовал thread pool`ы GFv2. Эта часть не является частью стандарта javaee как скажем time services, и по этому api легко и непренужденно изменилось

В своем предыдущем посте я описывал как это делается в GFv2


...
        WorkManager workManager = WorkManagerFactory.getWorkManager("threadpoolname");
...
Поковырявшись в исходниках GFv2.1, посмотрим что же происходило на самом деле:
appserv-core/src/java/com/sun/enterprise/connectors/work/WorkManagerFactory.java:

...
    public static WorkManager getWorkManager(String poolName)
                              throws ConnectorRuntimeException {

        String className = null;
        String methodName = "getInstance";
        Class cls = null;
        WorkManager wm = null;

        try {
            className = System.getProperty(WORK_MANAGER_CLASS, DEFAULT);

            // Default work manager implementation is not a singleton.
            if (className.equals(DEFAULT)) {
                return new CommonWorkManager(poolName);
            }

            cls = Class.forName(className);
            if (cls != null) {
                Method method = cls.getMethod("getInstance", new Class[]{});
                wm = (WorkManager) method.invoke(cls, new Object[] {});
            }
        } catch (Exception e) {
            String msg = localStrings.getString("workmanager.instantiation_error");
            logger.log(Level.SEVERE, msg, e);
        }

        return wm;
    }
...

В последней версии GFv3, которую я смотрел [v3.1-b20-09_15_2010], в WorkManagerFactory нет такого метода, по этому можно просто руками сделать то же самое, хотя скорее всего это неправильно, но вот как это выглядит:

...
import com.sun.enterprise.connectors.ConnectorRuntime;
import com.sun.enterprise.connectors.work.CommonWorkManager;
...
workManager = new CommonWorkManager("threadpoolname", ConnectorRuntime.getRuntime(), "resourceAdapterName", resourceAdapter.getClass().getClassLoader());
...

эти классы находятся в библиотеках GLASSFISHv3_DIR/work-management.jar и GLASSFISHv3_DIR/connectors-runtime.jar

p.s. почитав код, не нашел как используются resourceAdapterName и classLoader, по этому в качестве resourceAdapterName использовал просто "fffuuu", а в качестве classLoader - this.getClass().getClassLoader()