27 декабря 2009 г.

GWT. Взаимодействие с Django

Вся работа в GWT имеет подробную документацию. В ней все понятно и прозрачно. Но наше дело тормозится тем, что в качестве бекенда у нас выступает Django. То есть стандартный GWT RPC нам не подходит. Встает вопрос о том, как обмениваться данными между GWT и Django.

Я долго раздумывал, какой интерфейс взаимодействия выбрать. Технология-то одна - ajax, а вот какой инструмент более удобен? Можно попробовать использовать RequestBuilder, но этот метод не совсем удобен и не совсем универсален. Поэтому я выбрал json-rpc. Для него уже все создано, как на стороне django, так и на стороне GWT.

Json-rpc, это легкий протокол удаленного вызова процедур, использующий для своего функционирования формат Json.
Пример запроса и ответа:

--> { "method": "echo", "params": ["Hello JSON-RPC"], "id": 1}
<-- { "result": "Hello JSON-RPC", "error": null, "id": 1}

Начнем с django. Добавляем поддержку json-rpc в наш проект.

Импортируем django-json-rpc и добавляем точку входа в urls.py.
 

from jsonrpc import jsonrpc_site
import myproj.myapp.views # подключаем файл, в котором буду храниться rpc функции

urlpatterns += patterns('',
url(r'^json/', jsonrpc_site.dispatch, name="jsonrpc_mountpoint"),
)

Возьмем пример с readme:
 

from jsonrpc import jsonrpc_method

@jsonrpc_method('myapp.sayHello')
def whats_the_time(request, name='Lester'):
return "Hello %s" % name

Запускаем сервер, запускаем шел django
 

./manage.py runserver 8080
./manage.py shell

И тестируем:
 

>>> from jsonrpc.proxy import ServiceProxy
>>> s = ServiceProxy('http://localhost:8080/json/')
>>> s.myapp.sayHello('Sam')
{u'error': None, u'id': u'jsonrpc', u'result': u'Hello Sam'}

В общем, в документе все написано. Также в приложении есть своей браузер, зайти на него можно по адресу http://localhost:8080/json/browse/ , добавив в urls.py, например так:
 

if settings.DEBUG:
urlpatterns += patterns('',
url(r'^json/browse/', 'jsonrpc.views.browse', name="jsonrpc_browser"), # for the graphical browser/web console only, omissible
)

urlpatterns += patterns('',
url(r'^json/', jsonrpc_site.dispatch, name="jsonrpc_mountpoint"),
)


Я нашел несколько неточностей в документации, так, там name="jsonrpc_browser" без буквы "r" на конце, плюс url браузера
идет после r'^json/', который перекрывает его вызов.

Итак, rpc в django у нас теперь есть. Переходим к клиентской части rpc в gwt.

Проделываем все операции, указанные в readme и... у меня не заработало.
 

09:36:42.942 [ERROR] [myapp] Errors in 'jar:file:/lovely.gwt.jsonrpc-0.7.jar!/lovely/gwt/jsonrpc/client/JSONServiceBase.java'
09:36:42.957 [ERROR] [myapp] Line 29: The type JSONServiceBase must implement the inherited abstract method ServiceDefTarget.setRpcRequestBuilder(RpcRequestBuilder)


Судя по всему, в GWT 2.0 изменились какие-то механизмы в сервисах JSON. Наверное класс JSONServiceBase раньше не требовал реализации метода ServiceDefTarget.setRpcRequestBuilder.
Что ж, давайте взглянем на исходники.
Действительно, функции setRpcRequestBuilder нет.

Проект не обновляется, разбираться времени нет, так что пробуем еще один вариант gwt-json-rpc.
По инструкции подключаем библиотеку. В инструкции написано что библиотека использует свой JSON кодер/декодер, поэтому можно использовать простые типы java: String, int, boolean, Array, HashMap, ArrayList, Vector, вместо классов GWT. В общем, она даже проще, чем lovely-gwt-jsonrpc.
У меня получился следующий код.

 

//Create a new JsonRpc instance
JsonRpc jsonRpc = new JsonRpc();

//Create a callback handler
AsyncCallback callback = new AsyncCallback() {

public void onFailure(Throwable caught) {
SC.say(caught.toString());
};

public void onSuccess(Object result) {
SC.say(result.toString());
};
};
jsonRpc.request(
"http://localhost:8080/json/",
"myapp.sayHello",
null,
callback);

Что такое SC смотрим здесь.

После запуска мы обнаруживаем, что все результаты попыток вызова функции попадают в onFailure. Обусловлено это скорее всего тем, что сервер Django и сервер gwt находятся на разных портах, а это противоречит Same Origin Policy.
Далее, я скомпилировал проект, чтобы скопировать его в статический путь в проекте Django, и запустить под сервером Django, чтобы не нарушать SOP. И обнаружил, что CsrfMiddleware не дает скомпилированному gwt приложению делать POST запросы. Но мы знаем, что фрейморки, вроде jQuery позволяли это с легкостью делать. А все потому, что gwt-json-rpc при создании RequestBuilder не создает заголовок вида "X-Requested-With: XMLHttpRequest", поэтому django (точнее middleware) считает, что запрос сделан с другого домена, и возвращает ошибку 403. Значит надо сообщить ему об этом. AJAX запросы считаются безопасными и не нуждаются в проверке, так как современные браузеры придерживаются SOP. Придется добавить данный заголовок в код библиотеки и пересобрать ее. Разработчику проблему описал. Может быть на момент прочтения статьи в ней будет прописан данный заголовок (функция JsonRpc.request):

builder.setHeader("X-Requested-With", "XMLHttpRequest");

Я его добавил сам и сделал новый jar. Компилируем проект, копируем в путь для статики в django, и проверяем - все должно работать.
Остался один ньюанс - работа в дебаг режиме и ajax запросы. Не будем же мы каждый раз компилировать проект, тем более терять все прелести дебага. Воспользуемся HTTP proxy servlet.
Качаем сервлет, копируем в папку WEB-INF/lib, правим web.xml, у меня примерно следующее:


HttpProxy
com.jsos.httpproxy.HttpProxyServlet

host
http://localhost:8080/json/




HttpProxy
/json/


Меняем путь запроса:

jsonRpc.request(
"/json/",
"myapp.sayHello",
null,
callback);


и проверяем работу в дебаг режиме.

Вот и все. Удачной разработки.

Комментариев нет:

Отправить комментарий