2. easyAPI. Удаленная загрузка XML.

Автор: Вячеслав Гринин | веб-мастеру, веб-программирование | 30 Янв 2010 1:00 пп

И вот, наконец, долгожданная, вторая статья из раздела easyAPI.

Сегодня мы узнаем, как загрузить XML-данные с удаленного домена. Подобная задача возникает, например, когда владелец сайта на бесплатном (а значит – без серверного кода) хостинге хочет вставить в страницу погодный XML-информер. Именно на этом примере мы и рассмотрим сегодня удаленную загрузку XML-файлов.

Итак, для фоновой загрузки данных мы привыкли использовать так называемый AJAX, то есть объект XMLHttpRequest, про эту технологию я уже не раз писал в статьях блога. Однако, эта технология в данном случае никуда не годится. Почему? А потому что объект XMLHttpRequest не позволяет делать запросы к доменам, иным, чем тот, в котором работает скрипт, создающий этот объект.

И здесь к нам на помощь приходит технология, получившая название JSONP, что значит JavaScript Object Notation with Padding. Про сам JSON можео почитать вот здесь JSON, а JSONP представляет собой объект в нотации JSON обернутый в вызов функции, что-то вроде этого:

onSuccess('JSON-object')

А теперь объясню, зачем это нужно. Хоть XMLHttpRequest и не позволяет делать запросы к удаленным доменам, однако все браузеры позволяют нам загружать JavaScript’ы с любых доменов, то есть страница, загруженная с домена localdomain.ru, позволяет выполнить в ее рамках следующую инструкцию:

<script type='text/javascript' src='http://remotedomain.ru'>
</script>

А теперь смотрите, что происходит. Наша страница загружает скрипт с удаленного домена, а скрипт этот возвращает нам JSONP-конструкцию, которая после загрузки скрипта тут же и выполнится, а значит, вызовет некую предопределенную заранее функцию onSuccess, которая получит в качестве аргумента тот самый XML-файл. И теперь мы можем делать с ним все, что захотим!

Осталось только одно – завладеть таким remotedomain.ru, который вернет нам то, что нам нужно. Скажу, что домен этот – http://easyapi.ru. Да-да, именно на этом, предусмотрительно приобретенном не так давно домене, и будут жить все наши полезные, жизненно необходимые почти каждому веб-разработчику, функции. :) Скромненько и со вкусом…

Итак, в рамках домена easyapi.ru я уже создал ответную часть для удаленной загрузки XML-документов. Живет она вот здесь: http://easyapi.ru/xml/get.php и принимает на входе два GET-параметра: url и callback. Первый из них – адрес, с которого будет загружаться XML-файл, а второй – имя JavaScript-функции, которая будет вызвана после загрузки, и которая должна будет обработать полученный XML-документ.

Не откладывая дело в долгий ящик, приведу ниже содержимое страницы, которая, используя наш сервис easyAPI, загружает данные об XML-погоде в городе Москва и при помощи XSLT-шаблона преобразует эти данные в красивый информер, который вы, кстати, изучив основы XSLT-преобразований, сможете оформить по своему усмотрению.

Вот исходный код страницы:

<html>
<head>
<script type="text/javascript">
function getXMLFromString(s) {
    if(window.ActiveXObject)
    {
        var xml;
        xml=new ActiveXObject("Microsoft.XMLDOM");
        xml.async=false;
        xml.loadXML(s);
        return xml;
    }
    else if(window.DOMParser)
    {
      var parser = new DOMParser();
      return parser.parseFromString(s,'text/xml');
    }
    else
    {
        alert("Загрузка XML не поддерживается браузером");
        return null;
    }
}

function getXMLDocument(url)
{
    var xml;
    if(window.XMLHttpRequest)
    {
        xml=new window.XMLHttpRequest();
        xml.open("GET", url, false);
        xml.send("");
        return xml.responseXML;
    }
    else
        if(window.ActiveXObject)
        {
            xml=new ActiveXObject("Microsoft.XMLDOM");
            xml.async=false;
            xml.load(url);
            return xml;
        }
        else
        {
            alert("XML loading not supported");
            return null;
        }
}

function transformXslt(source,style) {
    if(window.ActiveXObject)
    {
        return source.transformNode(style);
    }
    else if(window.XSLTProcessor)
    {
        var xsltProcessor=new XSLTProcessor();
        xsltProcessor.importStylesheet(style);
        var resultDocument = xsltProcessor.transformToDocument(source);
        var xmls = new XMLSerializer();
        return xmls.serializeToString(resultDocument);
    }
    else
    {
        alert("Преобразование XML не поддерживается браузером");
        return null;
    }
}

</script>

</head>
<body>
<div id="res"></div>
<script type="text/javascript">
function onSuccess(res)
{
    var xslt = getXMLDocument("http://easy4web.ru/samples/easyxml/gis.xsl");
    var xml = getXMLFromString(res.result);
    var res = transformXslt(xml, xslt);
    document.getElementById("res").innerHTML=res;
}
</script>
<script src="http://easyapi.ru/xml/get.php?url=http://informer.gismeteo.ru/xml/27612_1.xml&callback=onSuccess" type="text/javascript"></script>
</body>
</html>

В первой части страницы мы видим три функции, все они – кроссбарузерные:

  • getXMLFromString(s) – преобразует валидную строку s в XML-документ
  • getXMLDocument(url) – загружает XML-документ с адреса url
  • transformXslt(source,style) – преобразует XML-документ source при помощи XSLT-шаблона style

В нижней части исходного кода мы видим функцию onSuccess – ока как раз и выполнится после загрузки xml-документа с удаленного домена. Она отрисует загруженный и полученный в переменной res.result XML-Документ при помощи XSLT-файла, хранящегося по адресу http://easy4web.ru/samples/easyxml/gis.xsl
И в самой-самой нижней части исходного кода мы видим вот такую конструкцию:

<script src="http://easyapi.ru/xml/get.php?url=http://informer.gismeteo.ru/xml/27612_1.xml&callback=onSuccess" type="text/javascript"></script>

Вот эта часть и представляет собой загружалку XML-документов с удаленных доменов. Как видим в атрибуте src тега SCRIPT мы указали, кто будет заниматься загрузкой (http://easyapi.ru/xml/get.php), что он будет загружать (url=http://informer.gismeteo.ru/xml/27612_1.xml) и какая функция займется обработкой документа (callback=onSuccess)

Здесь вы можете скачать исходные коды приведенного примера. А вот здесь – посмотреть, как это работает.

Прочтите еще:

Кроссбраузерное XSLT-преобразование в JavaScript

Автор: Вячеслав Гринин | веб-программирование | 25 Окт 2009 3:00 пп

Столкнулся я однажды с необходимостью отобразить XML-данные погодного информера gismeteo посредством XSLT-шаблона. Причем возникла острая необходимость сделать это через JavaScript, так как на предлагаемом бесплатном хостинге не было поддержки PHP, а значит отпарсить XML на сервере не представлялось возможным.

В результате этой острой необходимости родился код на JavaScript, который я и предлагаю на ваше рассмотрение.

function transformXslt(source,style) {
    if(window.ActiveXObject)
    {
        return source.transformNode(style);
    }
    else if(window.XSLTProcessor)
    {
        var xsltProcessor=new XSLTProcessor();
        xsltProcessor.importStylesheet(style);
        var resultDocument = xsltProcessor.transformToDocument(source);
        var xmls = new XMLSerializer();
        return xmls.serializeToString(resultDocument);
    }
    else
    {
        alert("Преобразование XML не поддерживается браузером");
        return null;
    }
}

Что мы здесь видим?

А видим мы здесь кроссбраузерное решение данной задачи. XSLT-преобразование в IE производится посредством метода transformNode XML-документа. Во всех остальных браузерах(FF, Chrome, Safari, Opera) XSLT-преобразование делается совсем по-другому. И делается оно посредством объекта XSLTProcessor, в который методом importStylesheet загружается файл стилей XSLT, а результирующий документ генерируется методом transformToDocument(xml). И напоследок мы поручим объекту XMLSerializer сгенерить строковое представление результирующего XML-документа.

И напоследок еще одна полезная, и такая же кроссбраузерная, функция, которая преобразует строковое представление XML (и XSLT)-документа собственно в сам документ, готовый для его дальнейшего использования.

function getXMLFromString(s) {
    if(window.ActiveXObject)
    {
        var xml;
        xml=new ActiveXObject("Microsoft.XMLDOM");
        xml.async=false;
        xml.loadXML(s);
        return xml;
    }
    else if(window.DOMParser)
    {
      var parser = new DOMParser();
      return parser.parseFromString(s,'text/xml');
    }
    else
    {
        alert("Загрузка XML не поддерживается браузером");
        return null;
    }
}

Этот код я собираюсь оставить без комментариев, так как в нем и так все понятно. :)

Скачать файл с этими функциями вы можете здесь

Прочтите еще:

Кросдоменный JavaScript (JSONP)

Автор: Вячеслав Гринин | веб-программирование | 10 Окт 2009 4:41 пп

Итак, мы уже научились использовать объект XMLHttpRequest для фоновой загрузки данных с сервера, этот подход подробно описан в статье AJAX. 1 – Что это такое?. Однако, приведенный метод обладает одним недостатком – он не позволяет делать кроссдоменные запросы, то есть скрипт расположенный на домене easy4web.ru не может обратиться к домену vision4web.ru, а иногда этого так хочется. И здесь нам на помощь придет метод динамически подгружаемых скриптов <SCRIPT>.

Как известно, HTML-элемент <SCRIPT> может загружаться и с чужого домена, то есть аттрибут src у него может указывать вообще на любой домен. При этом, сразу после загрузки в нашу страницу, скрипт начнет выполняться. Таким образом, вставив в этот скрипт вызов некой заранее известной функции, мы можем организовать оповещение страницы о том, что скрипт загружен и готов к выполнению работы. А данные для для скрипта можно упаковать в пакет JSON. Такой комбинированный подход носит имя JSONP (JSON with padding).

На клиентской стороне мы выполняем такой код:

  var script = document.createElement("script");
  script.src = 'http://mydomain.ru/script.php?callback=func1';
  script.type = 'text/javascript';
  document.body.appendChild(script);

А по адресу  http://mydomain.ru/script.php поселим вот такой код:

<?php
echo($_REQUEST['callback'].'({"result":"Успешно!"})');
?>

На клиентской стороне также существует такой callback (то есть функция, которая сработает после загрузки скрипта):

function func1(response)
{
  alert(response.result);
}

Протестировать рабочий пример можно здесь: http://easy4web.ru/samples/jsonp/index.html, а скачать код вышеприведенного примера можно здесь: http://easy4web.ru/images/jsonp.zip

В тестовом примере, как можно увидеть, не происходит кроссдоменной загрузки – мы загружаем с того же домена, где расположен скрипт. Но вы можете заменить URL в этом месте кода:

<input type="button" value="Получить JSONP"
 onclick="getJSONP('http://easy4web.ru/samples/jsonp/handler.php',onSuccess)"/>

на ваш собственный или на наш тестовый handler, расположенный на другом домене, (вот здесь http://vision4web.ru/api/handler.php) и убедиться в том, что кроссдоменная загрузка прекрасно работает.

Ниже приведен скриншот сетевого взаимодействия:
screen

Как видно, при нажатии на кнопку “Получить JSONP” происходит создание элемента <SCRIPT> с атрибутом src=http://easy4web.ru/samples/jsonp/handler.php, при этом браузер тут же начинает загружать этот скрипт с удаленного сервера. А загруженный скрипт представляет собой вызов функции onSuccess() с параметром {”result”:”Успешно!”}. После загрузки скрипта он тут же начинает выполняться, тем самым выполняя функцию OnSuccess, которая, получив в качестве аргумента JSON-объект, обрабатывает его и совершает все необходимые действия, обусловленные логикой приложения.

Этот подход я собираюсь использовать при реализации многих полезных сервисов в рамках проекта easyAPI.

Прочтите еще:

Начинаем создавать набор сервисов easyAPI

Автор: Вячеслав Гринин | веб-мастеру | 09 Окт 2009 9:00 пп

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

Собственно, этой статьей я начинаю публикацию на нашем ресурсе сервисов под общим названием easyAPI.

Что это будет?

EasyAPI это по большей части JavaScript-библиотеки, реализующие ту или иную функциональность, я пока до конца не знаю, какие именно сервисы я буду предоставлять и в каком виде. Но точно знаю, что эти сервисы помогут, например, владельцу сайта, поднятого на бесплатном хостинге, отобразить погодный информер, настроенный полностью под требования веб-мастера. Или, например, позволят определить географию посетителей определенного сайта и отобразить ее на карте страны. Или, например, отобразить посетителю сайта погодный информер именно его города. И все это, повторюсь, чаще всего будет возможно даже на хостинге без поддержки PHP.

Короче говоря, от слов к делу!

И сегодня на сайте easy4web.ru появляется первый такой сервис – GeoIP, позволяющий по адресу посетителя определить его приблизительное местоположение.

Он живет здесь http://easy4web.ru/api/geoip/get.php

Поддерживаемые параметры:
p – формат отдаваемых данных:

  • p=j – результат – JSON
  • p=a – результат – массив вида cc2=RU;cc3=RUS;cty=Moskow
  • p=x – результат – XML
  • p=ax – результат – массив обернутый в XML, вида <easy>cc2=RU;cc3=RUS;cty=Moskow</easy>
  • p=jx – результат – JSON обернутый в XML, вида <easy>cc2=RU;cc3=RUS;cty=Moskow</easy>

f – длина имен полей:

  • f=f – длинные имена, вида country_code
  • f=s – короткие имена, вида cc2

ip – IP-адрес, если он не задан, то в качестве адреса берутся координаты сервера, с которого пришел запрос REMOTE_ADDR.

Если я желаю получить информацию по собственному местоположению в формате JSON с длинными именами полей, я вызову URL http://easy4web.ru/api/geoip/get.php?p=j&f=f

В следующей статье на эту тему я расскажу о том, как можно применить данный сервис при вызове его из PHP-программы, и из JavaScript на удаленном сервере. Пока сервис easy.GeoIP находится в стадии тестирования, возможны некоторые изменения в его работе, о которых я буду предупреждать на сайте.

Прочтите еще:

AJAX с помощью jQuery и ASP.NET web-сервисов

Автор: Вячеслав Гринин | веб-программирование | 21 Сен 2009 5:14 пп

Если вы создаете сайты под ASP.NET, то возможно вам уже довелось использовать AJAX-framework, предоставляемый Microsoft. Тот самый, что использует ScriptManager, rfк в примере, приведенном ниже:

1) Содержимое страницы Default.aspx:

<asp:ScriptManager ID="MainSM" ScriptMode="Debug" runat="server">
 <Services>
 <asp:ServiceReference Path="~/usersrv.asmx" InlineScript="false" />
 </Services>
 </asp:ScriptManager>

2) Содержимое файла script.js:

// вызов ajax-метода GetPoint
usersrv.GetPoint(id,OnUSGetPointSucc,OnUSGetPointErr);
// callback успешного вызова
function OnUSGetPointSucc(res)
{
 alert('success');
}
// callback ошибки
function OnUSGetPointErr(err)
{
 alert('success');
}

3) Содержимое файла usersrv.asmx:

<%@ WebService Language="C#" CodeBehind="~/App_Code/usersrv.cs" %>

4) Содержимое файла usersrv.cs:

[WebService(Namespace = "http://tempuri.org/")]
[System.Web.Script.Services.ScriptService]
public class usersrv : System.Web.Services.WebService
{
[WebMethod]
public Dictionary<string, object> GetPoint(long p_id)
 {
 Dictionary<string, object> res = new Dictionary<string, object>();
 try
 {
   // готовим возвращаемые параметры и сохраняем их в res
 }
 catch (Exception)
 {
 res["Error"] = U.GTFR("userpoints_err");
 }
 return res;
 }
}

Этот метод достаточно прост и удобен, если бы не одно “но” – он весит 132 Кб. И обладает еще одним интересным свойством: если в нашем проекте есть несколько страниц, использующих AJAX, то каждая из них будет заново загружать этот скрипт, потому что при загрузке скрипта на странице используется примерно следующий URL http://domainname/ScriptResource.axd?d=TvjJs2RlM8MX3pIUhEsdnZKUzGh-9Wr9nvNBZJbxf-xzq-Wvzbpj6FfMJqZqOPR8Pku52J2zBqleUlYtVE8XCyfyo3kcbSnhbwzYc2LkdfQ1&t=ffffffffee41303f и для каждой страницы в пределах вашего проекта будет свой ScriptManager, а значит и свой уникальный ID в query-string. А значит скрипт этот для каждой страницы будет браться не из кэша, а запрашиваться заново с сервера.

В общем, мне все это не понравилось и я заинтересовался, как можно сэкономить трафик и перейти на AJAX.jQuery. Тем более, что он уже использовался в этом же проекте для других целей. Сказано – сделано! И вот какое решение я нашел.

Собственно, модификации пришлось подвергнуть как сами клиентские JavaScript-ы, так и методы, вызываемые на стороне сервера. Связано это с тем, что мне хотелось передавать в качестве параметров для web-сервиса сложные объекты, а не просто строки и целые значения. И получать в ответ хотелось тоже сложные объекты, а не просто строки.

Все это можно легко и просто сделать, передавая как в ту так и в другую сторону все же строки, но строки в формате JSON, который является родным для JavaScript. Да к тому же ASP.NET обладает встроенными возможностями сериализации/десериализации JSON.

Правда, для сериализации объекта в JSON на клиентской стороне пришлось использовать еще одну дополнительную библиотеку json2.js. Таким образом, обе библиотеки весят около 67 Кб. Да плюс ко всему в случае нескольких ajax-страниц в проекте, скрипт загрузится всего один раз, и для остальных страниц будет браться из кэша браузера.

Но от слов – к делу!  Вот содержимое скрипта, выполняемого на клиентской стороне:

// готовим передаваемые параметры в переменную par, здесь - всего лишь эмуляция
 var par = new Object();
 par.a = 5;
 par.b = 'data';
 par.c = new Array();
 par.c[0] = 5;
 par.c[1] = 10;
 // сериализуем объект
 var ser = JSON.stringify(par);
 // формируем ajax-запрос
 $.ajax({
 type: "POST",
 url: "usersrv.asmx/GetPoint",
 data: "{'par':'"+ser+"'}",
 contentType: "application/json; charset=utf-8",
 dataType: "json",
 success: function(r) {
 var res=r.d;
 // сериализуем для наглядности полученные данные и выводим их
на страницу
 // в реальном приложении здесь мы просто будем использовать готовые
данные
 // в переменной res без сериализации
 document.getElementById("result").innerHTML=JSON.stringify(r);
 },
 error: function(err) {
 // обработка ошибки
 alert('error: '+err.responseText);
 }
 });
 }

А вот содержимое usersrv.cs:

using System;
using System.Collections;
using System.Linq;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Xml.Linq;
using System.Web.Script.Serialization;

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.Web.Script.Services.ScriptService]
public class usersrv : System.Web.Services.WebService
{
 public usersrv() { }

 [WebMethod]
 public ResponseType GetPoint(string par)
 {
 ResponseType response = new ResponseType();
 RequestType request = new RequestType();
 try
 {
 // десериализуем полученные параметры
 JavaScriptSerializer ser = new JavaScriptSerializer();
 request = ser.Deserialize<RequestType>(par);
 }
 catch (Exception)
 {
 response.isError = true;
 response.message = "Parameters is invalid";
 return response;
 }

 // преобразуем переданные параметры и готовим ответ
 // здесь - всего лишь эмуляция обработки данных
 if (request.c != null && request.a == request.c.Length)
 {
 response.isError = false;
 response.message = "That's good";
 }
 else
 {
 response.isError = true;
 response.message = "Length is not the same";
 }
 return response;
 }
}

А вот описание двух воспомогательных классов:

public class RequestType
{
    public int a;
    public string b;
    public int[] c;
}
public class ResponseType
{
    public string message;
    public bool isError;
}

Разумеется, они могут быть сколь угодно сложными. Думаю, что суть алгоритма ясна из комментариев в тексте программы.

Ссылка на архив с готовым проектом здесь.

Прочтите еще:

Позже »