Статьи

Джерси с участием JQuery + JSONP

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

Когда JSONP является хорошим вариантом?

Спор вокруг JSONP заключается в том, что при использовании JSONP ваше приложение обходит Единую Политику Происхождения , способствуя страшному межсайтовому скриптингу (XSS) [ 1 , 2 ]. Несмотря на этот очевидный риск, существуют определенные сценарии, в которых JSONP имеет смысл и решает множество проблем, которые требуют более дорогостоящего решения без использования межсайтовых сценариев.

Учтите, что вам необходимо интегрировать некоторые данные между двумя веб-приложениями, размещенными сторонними компаниями. Предположим, что у этих компаний нет предыдущего соглашения, поэтому для обмена данными между компаниями у вас есть два варианта:

  1. Чтобы установить безопасный канал между серверами (подумайте о коммерческих расходах здесь).
  2. Чтобы предоставить данные приложения с помощью JSONP, а затем одно приложение может использовать данные другого без каких-либо изменений в сети (технические усилия по поддержке формата JSONP минимальны).

Другим аспектом, который следует учитывать при использовании традиционного, является проблема
узких мест в сети . Итак, если сервер является единственным источником данных своего веб-приложения, действительно ли он проверяет данные сторонних серверов перед отправкой клиентам? Если нет, то это просто ненужное узкое место, замедляющее работу сети без какой-либо выгоды (и не более и не менее безопасное, если сервер просто пересылает данные третьих сторон) — подумайте об этом пару минут.

 

 

Эта тема гораздо более противоречива [ 1 , 2 , 3 ] и более детальна, чем мои простые аналогии, но я надеюсь, что вы сможете увидеть преимущества использования JSONP в контролируемых средах. В противном случае, просто подумайте, что Google Adsense был бы невозможен без JSONP — хороший пример того, как JSONP развязывает сеть. В итоге:

 

«
JSONP — это лучший выбор, когда вы доверяете или когда вы можете контролировать серверы, используемые вашими веб-приложениями ».

 

 

JSONP на практике с Jersey & Glassfish V3

В качестве примера того, как включить JSONP в ваших веб-сервисах, я продемонстрирую некоторые особенности проекта Arena PUJ . Одна из целей проекта Arena состоит в том, чтобы облегчить получение публичной информации о соревнованиях и конкурентах. Итак, вопрос в том, как позволить разработчикам быстро собрать клиент сервера Arena? , Цель состоит в том, чтобы сделать HTTP-сервер Arena таким же дружелюбным, как, например, сервер Twitter . К счастью, среда Jersey рассматривает JSONP как первоклассную технологию и позволяет чрезвычайно легко предоставлять ресурсы HTTP в таком формате.

Поддержка JSONP в Джерси

Чтобы включить JSONP в веб-приложении на основе Jersey Framework , вам нужно позаботиться о трех основных шагах:

  1. Чтобы изменить код на стороне сервера, чтобы он возвращал данные, обернутые в атрибуты GenericEntity типа JSONWithPadding.
  2. Чтобы настроить Джерси для использования естественной нотации JSON вместо формата JAXB по умолчанию.
  3. Чтобы изменить код клиента для понимания вашей структуры данных, отформатированной как JSONP.

Первые два шага — это чистый код Джерси, и на самом деле это довольно простые шаги. Третий шаг может быть сделан с простым javascript, но для облегчения моей жизни я буду использовать функции JSONP библиотеки jQuery. Итак, начнем со стороны сервера.

Загрузка домашних заданий конкурса PUJCE-09.

PUJ — это академический конкурс, в котором студенты сдают свои домашние задания для оценки старшими специалистами по Java. Основной вариант использования веб-приложений, раскрывающих данные PUJ, заключается в предложении загрузки домашних заданий конкурентов. Вы можете увидеть такую ​​функцию в действии у одного из следующих клиентов PUJ:

Вышеуказанные веб-приложения используют информацию с одного и того же сервера, веб-службы Arena PUJ . Изображение конкурентов ссылается на их Java-код, просто нажмите на него, чтобы загрузить домашнее задание. Несмотря на то, что все они размещены в одном домене, приложения DWR и HTML5 используют JSONP и при необходимости готовы к развертыванию на другом сервере. Приложение JSF 2.0 подключается непосредственно на уровне персистентности — наборе @Stateless EJB, внедренном в JSF ManagedBeans. Таким образом, у нас с одного и того же сервера есть два разных типа доступа к данным: обычные HTTP-запросы с клиентом, развернутым в том же домене, что и сервер, и JSONP HTTP-запрос, выполняемый из приложений, развертываемых в любом месте (больше свободы, больше ориентируется на облако).

Прежде всего, давайте использовать данные

безопасным
традиционным способом, используя чистый HTTP-запрос. Когда вы открываете URL http://fgaucho.dyndns.org:8080/arena-http/homework?competition=PUJCE-09 , вы получаете доступ к приведенному ниже фрагменту кода сервера (вы можете проверить полный код здесь ). Обратите внимание, что поддерживаемыми типами ответов являются XML и JSON, поэтому ваши данные доступны только с клиента, запущенного с сервера, на котором выполняется этот код.

  @GET
@Produces( { MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public Collection<PujHomeworkEntity> readAll(
@QueryParam(EntityFacadeConstants.PARAM_START) @DefaultValue(EntityFacadeConstants.PARAM_START_DEFAULT_VALUE) int start,
@QueryParam(EntityFacadeConstants.PARAM_MAX) @DefaultValue(EntityFacadeConstants.PARAM_MAX_DEFAULT_VALUE) int max,
@QueryParam("acronym") String acronym,
@QueryParam("title") String title,
@QueryParam("competition") String competition) {
Map<String , Serializable> parameters = new ConcurrentHashMap<String, Serializable>();
if (acronym != null) { parameters.put("acronym", acronym); }
if (title != null) { parameters.put("title", title); }
if (competition != null) {
try {
parameters.put("competition", competitionFacade.read(
PujCompetitionEntity.class, competition));
} catch (Exception error) {
return new ArrayList<PujHomeworkEntity>();
}
}

return facade.findByCriteria(parameters, start, max); // <small>using JPA2 criteria API</small>
}

Шаг № 1 — использование JSONWithPadding

Теперь вы хотите предоставить те же данные с помощью JSONP, позволяя любому HTTP-клиенту получать доступ к данным вашего сервера. Единственное изменение, которое вам нужно сделать в своем коде, — это обернуть коллекцию в javax.ws.rs.core.GenericEntity, а затем вернуть com.sun.jersey.api.json.JSONWithPadding вместо самой коллекции. Обратите внимание также на тип MIME ответа, определенный как application / x-javascript, а также как XML и JSON. Это особая подсказка о Джерси: у вас может быть только один метод, возвращающий несколько разных типов MIME. Я решил использовать отдельный метод для JSONP, потому что он экспериментальный, но я полагаю, что скоро я рассмотрю это решение и объединю все методы в одном коде — имейте это в виду, если вы разрабатываете свой собственный API.

  @GET
@Path("jsonp")
@Produces( { "<strong>application/x-javascript</strong>", MediaType.APPLICATION_JSON,
MediaType.APPLICATION_XML })
public JSONWithPadding readAllP(
@QueryParam("jsoncallback") @DefaultValue("fn") String callback,
@QueryParam(EntityFacadeConstants.PARAM_START) @DefaultValue(EntityFacadeConstants.PARAM_START_DEFAULT_VALUE) int start,
@QueryParam(EntityFacadeConstants.PARAM_MAX) @DefaultValue(EntityFacadeConstants.PARAM_MAX_DEFAULT_VALUE) int max,
@QueryParam("acronym") String acronym,
@QueryParam("title") String title,
@QueryParam("competition") String competition) {
Collection<PujHomeworkEntity> competitions = readAll(start, max,
acronym, title, institution, competition);
return new JSONWithPadding(
new GenericEntity<Collection<PujHomeworkEntity>>(competitions) {
}, callback);
}

Вы можете проверить этот метод, используя CURL :

curl -XGET http://fgaucho.dyndns.org:8080/arena-http/homework/jsonp\?competition=PUJCE-09

Шаг № 2 — использование натуральной нотации JSON с Джерси

Jersey — один из самых дружественных API в портфеле Glassfish, но есть кое-что, что беспокоит меня с первого дня, когда я попытался сериализовать аннотированные объекты JAXB в формате JSON. Если вы используете атрибуты в своих объектах JAXB, нотация JSON получит символ @ перед ним, нестандартную нотацию javascript, которая будет тормозить библиотеки, такие как jQuery. Обходной путь предоставляется самим Джерси в формате класса ContextResolver. Итак, чтобы иметь естественную запись JSON в вашем веб-сервисе, вам нужно создать класс, подобный приведенному ниже. Вы можете найти реальный код, используемый на Арене, здесь , а также узнать больше информации о нотации ContextResolver и JSON в Джерси здесь .

import javax.ws.rs.ext.*;
import javax.xml.bind.JAXBContext;
import com.kenai.puj.arena.model.*;
<strong> import com.sun.jersey.api.json.JSONConfiguration; import com.sun.jersey.api.json.JSONJAXBContext;</strong>

@Provider
public class MyJAXBContextResolver implements ContextResolver<JAXBContext> {

private JAXBContext context;
private Class<?>[] types = { PujAdvertisementEntity.class,
PujCompetitionEntity.class, PujLinkEntity.class,
PujHomeworkEntity.class };

public MyJAXBContextResolver() throws Exception {
this.context = new JSONJAXBContext(JSONConfiguration.natural().build(),
types);
}

public JAXBContext getContext(Class<?> objectType) {
for (Class<?> c : types) {
if (c.equals(objectType)) {
return context;
}
}
return null;
}
}

You just need to have a ContextResolver class in the same package of your Jersey resources, and the container will load that. The server side is done, now you need a smart client to consume that.

Step #1 — consuming JSONP with jQuery

The special feature that motivated me to use jQuery for loading the JSONP data of Arena was a function called jQuery.getJSON. It basically returns for your javascript code the content of any JSONP enabled server, and jQuery does that using asynchronous calls (AJAX).

<script type="text/javascript"
src="http://code.jquery.com/jquery-latest.js"></script>
<script>
jQuery.noConflict();
jQuery.ajaxSetup ({ cache: true });
jQuery(document).ready(function(){
loadHomeworks("PUJCE-09"); /* loading three different competitions */
loadHomeworks("PUJCE-08");
loadHomeworks("PUJCE-07");
function loadHomeworks(competition) {
var div = document.createElement('div');
div.className="history";
div.id = competition;
var url = "http://fgaucho.dyndns.org:8080/arena-http/homework/jsonp?competition="+competition+"&jsoncallback=?";
jQuery.getJSON(url,
function(data){
var oUL = document.createElement('ul');
oUL.className="history2";
div.appendChild(oUL);

jQuery.each(data, function(i, fn) {
var avatar = document.createElement('img');
for ( var a in fn.author )
{
var author = fn.author[a];
for ( var r in author.role )
{
if(author.role[r].name == 'STUDENT') {
avatar.src = author.avatar + '?s=48&r=r';
avatar.title = author.name;
break;
}
}
if(avatar.src) {
break;
}
}
var anchor = document.createElement('a');
anchor.href = fn.url;
anchor.appendChild(avatar);
var p = document.createElement('p');
p.appendChild(anchor);
var item = document.createElement('li');
item.appendChild(p);
oUL.insertBefore(item, oUL.firstChild);

});
});
var outterDiv = document.getElementById("homeworks");
outterDiv.appendChild(div);
}

});
</script>

The result of the above script is demonstrated in the below image, a set of images for each of the PUJCE competitions.

Summary

The script presented here can be embedded in any web-application and I suggest you to try that in your local machine as a cloud computing experiment. Just create a webapp containning an HTML page with the above script and deploy it to your preferred web-server, a Tomcat or a Glassfish for example. Access it and notice that it will load the data from my server without any problem. Isn’t it cool? You can consume the data of CEJUG PUJ competitions without any integration effort, without the need of any extra code, you don’t need to ask permission for me to load such data or even make a formal agreement to interchange data between the two servers.

JSONP is a silver bullet?

Not at all, JSONP is just an alternative for scenarios where you can have diverse HTTP clients consuming diverse data from diverse HTTP servers in a free way — no need of contracts or web bottlenecks. The clients consume the data from where it is generated, and the servers expose just part of the data required for the clients to complete the information required by the end users. It is cloud computing oriented and it explores the best of the Internet capabilities.

How to control the security and the dynamic of the data? For security there is only one feature: trust, when you code the client software you should consume data from server you know or from servers you trust. And about the changes in data models, you need just one more special feature from the REST style of architecture: HATEOAS. I will talk about that in another blog, for now I believe we already have a good topic to discuss — I am just waiting your feedback.

From http://weblogs.java.net/blog/felipegaucho