четверг, февраля 28, 2008

GWT RPC

Вплотную работаю сейчас с GWT RPC. Решил поделится приятными обнаруженными вещами пока не забыл :) , а забыть могу быстро, так как проекты с серверной частью на Java для меня скорее исключение из правил...

Для начала вкратце о том зачем оно(GWT RPC) нужно.
Все наверное слышали про AJAX (те кто не слышал могут зачитать здесь или на русском здесь). В принципе способов реализации много, но обычно(ну или часто, а не обычно - я не бог весть какой AJAX-гуру сейчас, но когда-то много чего пробовал и в итоге приходил к передаче форматированных данных, а не например html для вставки - что само по себе плохо так как стандарт на это вроде смотрит косо. Да и вообще зачем на сайте генерить то что можно переложить на клиентскую часть? Отдал данные - пусть яваскрипт работает и вставляет куда нужно что надо. При этом как правило увеличивается скорость работы, т.к. трафик получается без оверхеда - только данные без оформления гоняются.) данные передают в формате XML или JSON. Т.е. если у вас на сайте есть например объект класса BigInfo, то вы должны его преобразовать в XML или JSON и передать клиенту, а на клиенте должны соответственно обработать полученный кусок данных (часто это значит преобразовать уже в объект класса JS и сделать с ним чего-то).

Вроде всё просто но минусы тоже очевидны:
  • трудоёмкое и нудное отслеживание корректности получаемых данных(речь естественно не о простых случаях);
  • асинхронность разработки усложняет отладку и повышает вероятность ошибки(поменяв формат в серверной части и неправильно поменяв клиентскую можно получить волшебные неуловимые баги).
В принципе можно конечно написать свою волшебную библиотечку которая из класса написанного на серверном языке с некоторыми закоменченными декларациями будет генерить этот же класс но уже с методом сериализующим его например в JSON и этот же класс но уже на яваскрипте с методом десериализации из JSON. В принципе в этом есть смысл если приложение большое...

Так вот GWT RPC нужно чтобы избавить вас от всех этих проблем. Ну избавить настолько насколько это возможно. Естественно бесплатно ничего не бывает - поэтому выбора серверного языка вы лишаетесь, им автоматически становится Java. Итак вкратце как этот работает...

Для тех кто не знаком с GWT сообщу что сам GWT (безо всяких RPC) уже по сути и есть та волшебная библиотечка которая преобразует код написанный на Java в код написанный на JavaScript. Т.е. по сути нам осталось добиться того, чтобы мы могли в GWT написать серверную часть используя общие классы с клиентской частью.

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

Собственно интерфейс нашего сервиса:
public interface CustomService extends RemoteService {
public String myMethod(String s);
}
Асинхронная версия нашего сервиса, получается путём добавления к имени предыдущего интерфейса "Async", заменой возвращаемого значения на void и добавления в параметры метода аргумента типа AsyncCallback:
interface CustomServiceAsync {
public void myMethod(String s, AsyncCallback callback);
}
Ну и наконец класс который будет исполняться на серверной части, к названию основного интерфейса прибавляем "Impl" и наследуемся от RemoteServiceServlet:
public class CustomServiceImpl extends RemoteServiceServlet implements CustomService {

public String myMethod(String s) {
return s+" oO";
}
}
По сути CustomServiceImpl это сервлет умеющий делать сериализацию(т.к. наследуется от RemoteServiceServlet а не от HttpServlet).

Чтобы всё это заработало надо конечно настроить mapping для сервлета, сделать вызов с помощью асинхронного интерфейса, обработать результат в созданном callback'е. Mapping прописывается в web.xml (для Eclipse и NetBeans вроде бы по разному - так что смотрите как вам нужно)... Допустим мы замапили сервлет на url "customURL". Вызов делается примерно так:

public void testMyMethod() {
CustomServiceAsync custService = (CustomServiceAsync) GWT.create(CustomService.class);

// указываем урл по которому отзывается наш "сервлет"
ServiceDefTarget endpoint = (ServiceDefTarget) custService;
String moduleRelativeURL = GWT.getModuleBaseURL() + "customURL";
endpoint.setServiceEntryPoint(moduleRelativeURL);

// создаём callback
AsyncCallback callback = new AsyncCallback() {
public void onSuccess(Object result) {
// радуемся
}

public void onFailure(Throwable caught) {
// страдаем
}
};

// Делаем вызов нашего сервиса. Вызов будет асинхронный - программа продолжится сразу,
// а когда придёт ответ вызовется callback
custService.myMethod(someString, callback);
}
Вобщем понятно - мы получим результат в onSuccess, далее надо его преобразовать к нужному типу и обработать. Но этот пример по сути даёт только одно преимущество по сравнению со стандартным AJAX-подходом - мы может отлаживать серверную часть без отрыва от клиентской. Но тут не отображена проблема передачи не примитивов а например коллекций или объектов наших классов.

Что если мы захотим такой интерфейс, myMethod вернёт нам список строк например:
public interface CustomService extends RemoteService {
public List myMethod(String s);
}
(для тех кто не в курсе - на данный момент под GWT можно писать только в режиме совместимости с явой 1.4, поэтому List не типизирован)

Это работать не будет - потому что угадывать что за объекты лежат в List GWT не умеет, и соответственно сериализовать/десериализовать не сможет. Но для типизирования коллекций в GWT сделали специальную javadoc-аннотацию, если вы хотите получить список строк то надо переписать интерфейс так:
public interface CustomService extends RemoteService {
/**
* @gwt.typeArgs <java.lang.String>
*/
public List myMethod(String s);
}
Тут мы указываем что будет возвращён список строк а не чего-то ещё. Если надо типизировать аргумент, то пишем так:
public interface CustomService extends RemoteService {
/**
* @gwt.typeArgs listArg <java.lang.String>
* @gwt.typeArgs <java.lang.String>
*/
public List myMethod(String s,List listArg);
}
Так мы указали что listArg это список Integer, а возвращается список строк.

Итак выяснили что примитивные типы(ну и обёртки над ними) и коллекции примитивных типов сериализуемы и передавать/получать их через GWT RPC можно. Чуть сложнее дело с пользовательскими классами. Для того чтобы класс можно было сериализовать автоматически необходимо выполнение следующих условий:
  1. класс должен реализовывать интерфейс com.google.gwt.user.client.rpc.IsSerializable либо сам, либо иметь базовый класс который этот интерфейс реализовал;
  2. все поля(кроме final и transient) должны быть сериализуемы;
  3. класс должен иметь публичный конструктор по умолчанию
Если ваш класс не удовлетворяет какому-либо из этих условий, либо не устраивает стандартный сериализатор (например по производительности), то вы можете написать свой сериализатор.

Для того чтобы создать свой сериализатор для класса MyClass надо создать класс MyClass_CustomFieldSerializer (именно такое построение имени важно) такого вида:

import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.client.rpc.SerializationStreamReader;
import com.google.gwt.user.client.rpc.SerializationStreamWriter;

public class MyClass_CustomFieldSerializer
{
public static ServerStatusData instantiate(
SerializationStreamReader reader)
throws SerializationException
{
}

public static void serialize(
SerializationStreamWriter writer,MyClass instance)
throws SerializationException
{
}

public static void deserialize(
SerializationStreamReader reader,MyClass instance)
throws SerializationException
{
}
}
Метод instantiate() опциональный и нужен для того чтобы породить объект класса который не имеет публичного конструктора по умолчанию.

Метод serialize() позволяет записывать в поток данные которые вы хотите сериализовать. У SerializationStreamWriter(объект которого передаётся в аргументах вызова serialize()) есть методы writeBoolean, writeInt, writeObject и т.д. для этого.

Метод deserialize() соответственно позволяет считать из потока сериализованные данные и как-то привязать их к объекту. Считывать надо в том же порядке в котором вы писали в методе serialize(). У SerializationStreamReader есть методы readBoolean, readInt, readObject и т.д.

В итоге на относительно сложных проектах это всё очень удобно писать, дебагить и поддерживать.

Ссылки по теме:
GWT RPC
GWT

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

суббота, февраля 23, 2008

Выкат GWT-RPC приложения на Tomcat из-под Eclipse

Решил написать краткую инструкцию по выкату GWT-RPC приложения под Томкат. Стандартный способ который даже вроде бы упоминается в доках, гласит примерно следующее - скомпиляйте клиентскую часть в яваскрипт, отдельно скомпиляйте серверную часть и выкатите куда вам надо.

Всё правильно конечно, но это можно делать с разной эффективностью. Особенно компилять серверную часть(те кто юзал GWT давно должны помнить хаки к которым приходилось прибегать)... На самом деле есть вполне конкретные удобные варианты быстрого deploy'я приложения GWT-RPC на Tomcat. Один из них заточенный под Eclipse я и распишу. Соль этого варианта - быстро сделать WAR-файл и выкатывать уже его.

Итак то что нужно сделать для начала(только один раз):
  • открыть {PF}\www\com.domain.{Project}
  • создать новую директорию WEB-INF
  • открыть {PF}\www\com.domain.{Project}\WEB-INF
  • создать файл web.xml и прописать туда что нужно
  • создать новую директорию lib
  • открыть {PF}\www\com.domain.{Project}\WEB-INF\lib
  • скопировать gwt-servlet.jar из дистрибутива GWT в {PF}\www\com.domain.{Project}\WEB-INF\lib
Это надо делать каждый раз для получения WAR-файла:
  • запустить скрипт {PF}\{Project}-compile
  • в Eclipse выбрать папку src
  • нажать Export (в выпадающем меню или в меню "File")
  • выбрать экспорт в "JAR file"
  • отметьте "Export generated class files and resources"
  • отметьте "Export java source files and resources"
  • установите export destination в {PF}\www\com.domain.{Project}\WEB-INF\lib\{Project}.jar
  • отметьте "Compress the contents of the JAR file"
  • дважды нажмите Next
  • выберите "Generate the manifest file" (если не выбран)
  • не отмечайте "Save the manifest in the workspace"
  • "Seal the contents" - должно быть выбрано "Seal some packages" ... "Nothing sealed"
  • оставьте поле "Main class" пустым
  • жмём "Finish"
  • открываем {PF}\www\com.domain.{Project}
  • упаковываем все файлы и директории в zip-файл с именем {Project}.war
  • кидаем {Project}.war томкату внутрь (зависит от того куда именно вы хотите выкатить проект)
Обозначения:
{PF} - путь к проекту (например /home/gwt_project1)
{Project} - название проекта (например Calendar)

Вобщем лично мне такой подход сильно экономит время. Те кто использают не Eclipse - могут модифицировать всё тут описанное в скрипт для ant ну или ещё во что-то, никакой особой магии тут не используется...

В принципе идея взята отсюда но там автор (видимо после множества редакций) перевирает с путями местами.

пятница, февраля 22, 2008

Про ломку капч

Давно я не писал про спам и спамботов. А тут и повод есть.

Mail.ru ввела новую капчу на реге. Сначала она выглядела весьма психоделично:
Потом сменили на менее мозголомающий вариант:

При кажом нажатии на цифру кнопки перемешиваются заново.

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

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

В принципе из интервью вебпланете видно что Ильичев (директор по проектам mail.ru) вполне в теме вопроса и нету у него ощущения что он отлил серебрянную пулю против спамеров. Просто он обеспечил себе перевес в борьбе. И путь к победе он видит верно: "Наша задача - не сделать лже-регистрации невозможными в принципе (это утопия), а сделать их как можно более дорогостоящими. Чтобы бизнес спамеров с использованием нашей системы стал неэффективным.". Вобщем рад что первыми ощутимо двинулись в правильном направлении мои соотечественники (хотя самое правильное направление конечно у Gmail - не помню когда я там спам в инбоксе видел последний раз).

В принципе давно надо было mail.ru взятся за эту проблему - у меня есть ящик на mail.ru и количество спама которое на него приходит с ящиков на том же mail.ru огромно. Конечно много сразу ложится в spambox ещё на сервере, что-то ещё фильтрами клиента укладывается, но всё же прорывалось иногда письмо-другое.

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

Ссылки по теме(все публикации свежие - этого года):
взлом капчи Gmail
взлом капчи Microsoft
взлом капчи Yahoo

воскресенье, февраля 10, 2008

Что делает gcc/особенности OpenSSL

Решил в виде исключения разродится миниобзором чужого сайта.

Просматривал тут сайт Сысоева (который автор nginx) и наткнулся на древнюю ссылку на англоязычную статью What gcc really does. Оказывается сам gcc в основном занимается тем что вызывает другие программы. Флажок -save-temps весьма интересный - даёт возможность посмотреть что получается на промежуточных стадиях. Надо бы копнуть поглубже потому что с наскоку не всё понятно. Понятно что .i файл получился после препроцессинга, но интересно что за хитрые строчки типа # 325 "/usr/include/sys/cdefs.h" 2 3 4 там натыканы(ну по этому поводу несколько догадок напрашивается, но хотелось бы убедится).

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

Вообще на сайте Сысоева есть не менее интересные заметки и ссылки (например "The C10K problem") - просто они не были для меня новы. Вобщем советую зачитать сайт sysoev.ru - обновляется он редко, так что много времени не уйдёт.

пятница, февраля 08, 2008

Получить список используемых модулей в проекте perl

Вычитал на Mechanix что для того чтобы узнать какие модули используются perl-проектом, достаточно запустить в директории с ним вот такое:
ack -h '^use\s+(\w+(?:::\w+)*).*' --output=\$1 | sort -u

Ну как вобщем я увидел сразу - этого не достаточно. Потому что не учитываются например базовые классы, которые я использую с помощью use base qw(ModuleName), и почему-то не учитываются варианты когда use написан не вначале строки а с отступом поэтому я быстренько(не мудрствуя особо) дополнил эту команду до такой:
ack -h '^\s*use\s+base\s+(?:qw\()*\s*(\w+(?:::\w+)*)|^\s*use\s+(\w+(?:::\w+)*).*' --output=\$+ | sort -u


Тут конечно тоже не всё учтено, но уже лучше по крайней мере. В этом варианте не учитываются классы из @ISA, не учитывается множественное наследование через base - так что это не универсальный вариант. Но кому надо - сам допишет что ему нужно.

P.S. Для тех кто не в курсе что такое ack: http://petdance.com/ack/

суббота, февраля 02, 2008

Perl cheat sheets

Думаю у тех кто использует много языков периодически возникают проблемы с вспоминанием простых казалось бы вещей при переходе на язык который не использовал довольно долго. Вернее даже вроде бы вспомнил но сомневаешься, а хочется проверить быстренько скажем за несколько секунд ну или десятков секунд (я например периодически забываю $+). Для таких случаев я обычно использовал распечатку ключевых моментов языка в виде таблицы(на 1-2 страницы максимум), но со временем потерял и сами таблички и цифровой их оригинал. Поиском нашёл что оказывается что-то такое уже есть.

Ключевые моменты perl (для новичков незаменима, для не новичков - такое забывать стыдно :) ):
http://juerd.nl/site.plp/perlcheat

Предопределённые переменные perl:
http://perldoc.perl.org/perlvar.html,
то же самое но переформатированное более удобно:
http://docs.google.com/Doc?id=dn6sgh3_6fbdspbcg.

Расширенный краткий обзор perl(ссылка на pdf внизу страницы):
http://johnbokma.com/perl/perl-quick-reference-card.html

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