8 августа 2011 г.

Кросдоменный ajax с помощью javascript


В прошлом посте был описан способ передачи данных на сервер с помощью тега img на странице. Но он не единственный способ.
Для этих целей можно приспособить тег script, котороый так же будет передовать данные на сервер и еще асинхронно передавать ответ обратно.
html код:
Copy Source | Copy HTML
  1. <input type="text" id="textField"/>
  2. <input type="button" id="sendMessage" value="Отправить IMG"/>
  3. <img id="imaGet" src="picture.php?add=hello" style="width:30px;height:30px;"/>
javascript код:
Copy Source | Copy HTML
  1. function ScriptDataSend(urlEncoded_str,callBack_func){
  2.     ////generate uniq id for elements (I don't check it, only random!!)
  3.     var uniqId=(Math.random()*10000000).toFixed( 0);
  4.     //console.log("uniq: %s",uniqId);
  5.     ////add script element and encoded data
  6.     var th = document.getElementsByTagName("body")[ 0];
  7.     var s = document.createElement('script');
  8.     s.setAttribute('type','text/javascript');
  9.     s.setAttribute('src',"script.php?uid="+uniqId+"&add="+urlEncoded_str);
  10.  
  11.     s.setAttribute('id',uniqId);
  12.     ////auto remove script element
  13.     s.onload=function(){
  14.         //console.log("I'm loaded");
  15.         s.parentNode.removeChild(s);
  16.         delete window.responceCollection[uniqId];
  17.     }
  18.     th.appendChild(s);
  19.     delete s;
  20.     //// add callback in globalScope!
  21.     if (window.responceCollection==undefined)
  22.         window.responceCollection={};
  23.     window.responceCollection[uniqId]=callBack_func;
  24. }
  25.  
  26. var messageField=document.getElementById("textField");
  27. var sendButton2=document.getElementById("sendMessage2");
  28. sendButton2.addEventListener("click",function(){
  29.         ScriptDataSend(encodeURIComponent(messageField.value),function(time){
  30.             console.log("Get Javascript response: %s",time);
  31.         });
  32.     },false);
Функция ScriptDataSend создает тег script с заранее определенным url адресом и уникальным id (uid),  чтобы используя этот uid задать уникальный обработчик события для этого соединения в глобальном хэше обработчиков window.responceCollection (другого "красивого" варианта реализации пока не придумал ...).
код файла script.php:
Copy Source | Copy HTML
  1. <?php
  2. header('Content-type: text/javascript');//Делаем вид что javascript
  3. $time=date("H:i:s");
  4. $uid=$_GET["uid"];
  5. // отправляем ответ
  6. echo "window.responceCollection[\"".$uid."\"]('".$time."');";
  7. //- сохраняем полученные данные
  8. $fileName="script_data.txt";
  9. if (strlen($_GET["add"])> 0){
  10.     $fh = fopen($fileName, "a+");
  11.     fwrite($fh, urldecode($_GET["add"])." ".date("H:i:s")."\n");
  12.     fclose($fh);
  13. }
  14. ?>
Как видите, script.php формирует ответ в виде набора данных и передает их в обработчик с уникальным id (uid).
Повозможности никакого управляющего кода на js в php скрипте не генирируется, чтобы было удобнее отлаживать.

Недостатки метода отправки данных сервер с помощью тега script:

  1. только запросы get
  2. вся отладка на плечи кодера
  3. добавление и удаление элемента из DOM
  4. обработчик в глобальной области видимости 

Кросдоменный ajax с помощью тега IMG

С помощью тега img можно отправить данные на сервер. Таким же образом действует и счетчик Яндекс Метрики, когда отправляет данные о кликах посетителей на страницах сайтов.
Создавать фейковую картинку 1px x 1px необязательно, картинка может быть обычной и может служить элементом декора на странице.
И при этом, главное, что, может передавать данные на сервер.
html код:
Copy Source | Copy HTML
  1. <input type="text" id="textField"/>
  2. <input type="button" id="sendMessage" value="Отправить IMG"/>
  3. <img id="imaGet" src="picture.php?add=hello" style="width:30px;height:30px;"/>
javascript код:
Copy Source | Copy HTML
  1. var messageField=document.getElementById("textField");
  2. var sendButton=document.getElementById("sendMessage");
  3. var imageElement=document.getElementById("imaGet");
  4. //Отправка данных на сервер методом get:
  5. sendButton.addEventListener("click",function(){
  6.     //определяем куда в аддресс вставлять сообщение по позиции фрагмента:
  7.     var pos= imageElement.src.indexOf("add=");
  8.     var newLink= imageElement.src.substring( 0,pos)+"add="+encodeURIComponent(messageField.value);
  9.     imageElement.src=newLink;//Присваиваем новый адресс - пофакту отправляем сообщение на сервер
  10. },false);
код файла picture.php:
Copy Source | Copy HTML
  1. <?php
  2. header('Content-type: image/jpeg');
  3. // Можно вывести картинку
  4. $content = implode ( "", file ( 'image.jpg' ) );
  5. echo $content;
  6. // Полученные данные можно отлажить куда-нибудь
  7. $fileName="data.txt";
  8. if (strlen($_GET["add"])> 0){
  9.     $fh = fopen($fileName, "a+");
  10.     fwrite($fh, urldecode($_GET["add"])." ".date("H:i:s")."\n");
  11.     fclose($fh);
  12. }
  13. ?>
Недостатки метода отправки данных сервер с помощью тега img:
  1. только запросы get;
  2. вся отладка на плечи кодера.

31 июля 2011 г.

Html5 Workers

WebWorkers - делают возможным динамическую загрузку JS файлов и запуска их в фоновом потоке.
Пояснительный пример, основная страница:
Copy Source | Copy HTML
  1. <!DOCTYPE HTML>
  2. <html>
  3. <head>
  4.     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  5. <script>
  6. window.onload = function () {
  7.     var worker = new Worker("message.js");// Creates a Web Worker
  8.     worker.onmessage = function (e) {// Triggered by postMessage in the Web Worker
  9.         document.write('Worker said: ', e.data);// e.data is the values from the Web Worker
  10.     };
  11.     worker.onerror = function (e) {// If the Web Worker throws an error
  12.         document.write(e.data);
  13.     };
  14.     worker.postMessage('World!'); // Send data to our worker.
  15. };
  16. </script>
  17. </head>
  18. <body>
  19. </body>
  20. </html>
Для запуска фонового процесса создается экземпляр объекта Workers с указанным путем до скрипта. Сообщение  между основной запускаемой страницей и фоновым потоком происходит при помощи метода postMessage.
Пример message.js:
Copy Source | Copy HTML
  1. onmessage = function (evt) {
  2.     postMessage("hello "+evt.data+"!");
  3. };
В запускаемом файле есть допуск к объекту контекста this или self, который можно использовать:
Copy Source | Copy HTML
  1. self.addEventListener('message', function(e) {
  2.     var rez="hello "+e.data+"!";
  3.     self.postMessage(rez);
  4. }, false);

Возможности доступные в Workers

Из того что можно запустить в основоном потоке в  Workers доступны,
navigator object
location object (read-only)
XMLHttpRequest
setTimeout()/clearTimeout() and setInterval()/clearInterval()
The Application Cache
Импорт внешних скриптов при помощи вызова метода importScripts()
Запуск других фоновых объектов
Workers не имеет доступа к:
DOM (it's not thread-safe)
window object
document object
parent object

В Workers  есть возможность загрузки внешних скриптов при помощи вызова метода importScripts(). Метод importScripts может принимать несколько параметров с указанием путей до файлов:
importScripts('script1.js', 'script2.js');
Почитать больше можно на - www.html5rocks.com

Ajax Hello world! пример использования

Ajax наиболее кроссбраузерный способ передать данные на сервер и получить ответ обратно без перезагрузки страницы.
Приведу небольшой пример в стиле "Hello world!" по применению этой техники:

Всю рутиную работу с Ajax, обработкой событий можно свести в одну библиотеку которую написать самому или использовать одну из самых ходовых (например с JQuery), чтобы не мучатся с поиском ошибок.
Для демонстрации возможностей приведу примитивные варианты на js и php  без каких либо сторонних библиотек.

Свой велосипед за 5 минут:
Copy Source | Copy HTML
  1. //Небольшая подборка объектов которая может пригодится при использовании AJAX
  2. //На самом деле код немного излишний - необходимости в детектирования IE 5 уже наверное нету 
  3. //Код на скорую руку!
  4. function AjaxObject(){
  5.     var browser=detectBrowser();
  6.     if (typeof XMLHttpRequest=="undefined"){
  7.         //window.XDomainRequest
  8.         XMLHttpRequest=function(){
  9.             return new ActiveXObject(browser.isMsie5? "Microsoft.XMLHTTP":"Msxml2.XMLHTTP");
  10.         };
  11.     }
  12.     this.xhr=new XMLHttpRequest();
  13. }
  14. //property:
  15. //.postOrGet =true if post, false if get
  16. //.onResponce =function(xhr_object)
  17. //.url
  18. //.data - data for send
  19. //.type - dataType: application/x-www-form-urlencoded or text/xml
  20. AjaxObject.prototype.start=function(property){
  21.     var self=this;
  22.     if(self.xhr){
  23.         self.xhr.onreadystatechange = function(){
  24.             if (self.xhr.readyState == 4){
  25.                 property.onResponce(self.xhr);
  26.             }
  27.         };
  28.         self.xhr.open((property.postOrGet ? "POST" : "GET"),property.url,true);//true- asynchrone, false -synchrone
  29.         if (property.type && property.postOrGet){
  30.             self.xhr.setRequestHeader("Content-Type", property.type);
  31.             if(self.xhr.ovverideMimeType)
  32.                 self.xhr.setRequestHeader("Connection","close");
  33.         }
  34.         self.xhr.send((property.postOrGet ? property.data : null));
  35.     }
  36. };
  37. // Events fix
  38. function events(o, event_name, handler){
  39.     if (o.addEventListener != undefined) {
  40.         o.addEventListener(event_name, handler, false);
  41.     }else if (o.attachEvent != undefined) {
  42.         o.attachEvent(event_name, handler);
  43.     }
  44. }
  45. // IE or not IE
  46. function detectBrowser(){
  47. var _ua = navigator.userAgent;
  48. return {
  49.   isMsie: (/msie/i.test(_ua) && !/opera/i.test(_ua)),
  50.   isMsie5: (/msie 5/i.test(_ua) && !/opera/i.test(_ua)),
  51.   isMsie6: (/msie 6/i.test(_ua) && !/opera/i.test(_ua)),
  52.   isMsie7: (/msie 7/i.test(_ua) && !/opera/i.test(_ua)),
  53.   isMsie8: (/msie 8/i.test(_ua) && !/opera/i.test(_ua)),
  54.   isMsie9: (/msie 9/i.test(_ua) && !/opera/i.test(_ua)),
  55. };
  56. }

Пример #1 Необходимо передать что-то на сервер методом GET:


Copy Source | Copy HTML
  1. <!DOCTYPE HTML>
  2. <html>
  3. <head>
  4. <title>Ajax with get method</title>
  5. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  6. <script src="ajaxObject.js"></script>
  7. <script>
  8. window.onload=function(){
  9.     //DOM elements:
  10.     var textField1=document.getElementById("textField1");
  11.     var sendButton1=document.getElementById("sendButton1");
  12.     var p_buf=document.getElementById("buf");
  13.     //Ajax objects & properties:
  14.     var ajObj= new AjaxObject();
  15.     var options={
  16.         postOrGet: false, //use Get method
  17.         onResponce:function(xhr){
  18.             if (xhr.status >= 200 && xhr.status<300){
  19.                 //Ok!
  20.                 if (p_buf.innerText!=undefined)
  21.                     p_buf.innerText=xhr.responseText;
  22.                 else
  23.                     p_buf.textContent=xhr.responseText;//in FF
  24.             }
  25.             else
  26.                 if (p_buf.innerText!=undefined)
  27.                     p_buf.innerText="trouble on recieve!";
  28.                 else
  29.                     p_buf.textContent="trouble on recieve!";//in FF
  30.  
  31.         },
  32.     };
  33.     //Event handlers:
  34.     var clickHandler=function(e){
  35.         options.url="ajax_get.php?name="+encodeURIComponent(textField1.value);
  36.         ajObj.start(options);
  37.     };
  38.     events(sendButton1, "click",clickHandler);
  39. }
  40. </script>
  41. </head>
  42. <body>
  43.     <p>Enter name:</p>
  44.     <input type="text" id="textField1"/>
  45.     <input type="button" id="sendButton1" value="Send name"/>
  46.     <p id="buf"></p>
  47. </body>
  48. </html>
Код ajax_get.php:
Copy Source | Copy HTML
  1. <?php
  2.     if ($_GET["name"]!="")
  3.         echo "hello ",$_GET["name"],"!";
  4.     else
  5.         echo "Please, Enter name and try again! ";
  6. ?>

Пример #2 Необходимо передать что-то на сервер методом POST (urlEncoded data):


Copy Source | Copy HTML
  1. <!DOCTYPE HTML>
  2. <html>
  3. <head>
  4. <title>Ajax with post method</title>
  5. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  6. <script src="ajaxObject.js"></script>
  7. <script>
  8. window.onload=function(){
  9.     //DOM elements:
  10.     var textField1=document.getElementById("textField1");
  11.     var sendButton1=document.getElementById("sendButton1");
  12.     var p_buf=document.getElementById("buf");
  13.     //Ajax objects & properties:
  14.     var ajObj= new AjaxObject();
  15.     var options={
  16.         postOrGet: true, //use Get method
  17.         onResponce:function(xhr){
  18.             if (xhr.status >= 200 && xhr.status<300){
  19.                 //Ok!
  20.                 if (p_buf.innerText!=undefined)
  21.                     p_buf.innerText=xhr.responseText;
  22.                 else
  23.                     p_buf.textContent=xhr.responseText;//in FF
  24.             }
  25.             else
  26.                 if (p_buf.innerText!=undefined)
  27.                     p_buf.innerText="trouble on recieve!";
  28.                 else
  29.                     p_buf.textContent="trouble on recieve!";//in FF
  30.         },
  31.         type:"application/x-www-form-urlencoded",
  32.     };
  33.     //Event handlers:
  34.     var clickHandler=function(e){
  35.         options.url="ajax_post.php";
  36.         options.data="name="+encodeURIComponent(textField1.value);
  37.         ajObj.start(options);
  38.     };
  39.     events(sendButton1, "click",clickHandler);
  40. }
  41. </script>
  42. </head>
  43. <body>
  44.     <p>Enter name:</p>
  45.     <input type="text" id="textField1"/>
  46.     <input type="button" id="sendButton1" value="Send name"/>
  47.     <p id="buf"></p>
  48. </body>
  49. </html>
Код ajax_post.php:
Copy Source | Copy HTML
  1. <?php
  2.     if ($_POST["name"]!="")
  3.         echo "hello ",$_POST["name"],"!";# send data on page
  4.     else
  5.         echo "Please, Enter name and try again! ";
  6. ?>

Пример #3 Необходимо передать что-то на сервер методом POST (xml data):

Copy Source | Copy HTML
  1. <!DOCTYPE HTML>
  2. <html>
  3. <head>
  4. <title>Ajax with post method and XML</title>
  5. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  6. <script src="ajaxObject.js"></script>
  7. <script>
  8. window.onload=function(){
  9.     //DOM elements:
  10.     var textField1=document.getElementById("textField1");
  11.     var sendButton1=document.getElementById("sendButton1");
  12.     var p_buf=document.getElementById("buf");
  13.     //Ajax objects & properties:
  14.     var ajObj= new AjaxObject();
  15.     var options={
  16.         postOrGet: true, //use Get method
  17.         onResponce:function(xhr){
  18.             if (xhr.status >= 200 && xhr.status<300){
  19.                 //Ok!
  20.                 if (p_buf.innerText!=undefined)
  21.                     p_buf.innerText=xhr.responseText;
  22.                 else
  23.                     p_buf.textContent=xhr.responseText;//in FF
  24.             }
  25.             else
  26.                 if (p_buf.innerText!=undefined)
  27.                     p_buf.innerText="trouble on recieve!";
  28.                 else
  29.                     p_buf.textContent="trouble on recieve!";//in FF
  30.         },
  31.         type:"text/xml",
  32.     };
  33.     //Event handlers:
  34.     var clickHandler=function(e){
  35.         options.url="ajax_xml_post.php";
  36.         //this is xml data for server
  37.         options.data="<data><name>"+encodeURIComponent(textField1.value)+"</name></data>";
  38.         ajObj.start(options);
  39.     };
  40.     events(sendButton1, "click",clickHandler);
  41. }
  42. </script>
  43. </head>
  44. <body>
  45.     <p>Enter name:</p>
  46.     <input type="text" id="textField1"/>
  47.     <input type="button" id="sendButton1" value="Send name"/>
  48.     <p>That ypur have send:</p>
  49.     <textarea id="buf" style="width:400px; height:200px;"></textarea>
  50. </body>
  51. </html>
Код ajax_xml_post.php:
Copy Source | Copy HTML
  1. <?php
  2. # input xml data
  3. $xml=@file_get_contents('php://input');
  4. #send back what have you send
  5. echo $xml;
  6. ?>
Скачать все одним архивом

24 июля 2011 г.

Заметки про класс URI из Ruby stdlib

Тем кто работает с языком ruby должно быть знаком класс URI из стандартной библиотеке классов. С этим классом мне пришлось немного повозится. И зная то, что  какие-нибудь сведения про класс URI трудно найти, в этом посте приведу свои сведения.

1. Странное результат парсера:
Пример:
require 'uri'


demo_url=["http://www.host.ru","http://www.host.ru/"]
demo_url.each{|url|
_uri=URI.parse(url)
puts "\nhost: '#{_uri.host}', path: '#{_uri.path}' query: '#{_uri.query}'"
puts "path:"
p _uri.path
puts "host:"
p _uri.host
puts "query:"
p _uri.query


В этом примере на входе два url адреса один с указанием пути, другой без, но оба без указания параметров запроса (?var1=pae1&var2=pp && e.t.c.).
можно было бы подумать путь первого url адреса будет равняться nil. Но

host: 'www.host.ru', path: '' query: ''
path:
"" # <= !path.nil? == true, path.emty? ==true
host:
"www.host.ru"
query:  
nil # <= query.nil? == true , Ok!


host: 'www.host.ru', path: '/' query: ''
path:
"/"
host:
"www.host.ru"
query:
nil

В данном случае поведение парсера не логично и не понятно

2. Свойство opaque экземпляра класса URI, темное пятно в вашем url:
С английского термин можно перевести как темный, непрозрачный.
При попытка распарсить ссылку на почту (mailto:login@host) парсер выдавал ошибку opaque or fragment not null.
В документации http://www.ruby-doc.org/stdlib/libdoc/uri/rdoc/ написано:
# File uri/generic.rb, line 568
    def opaque=(v)
      check_opaque(v)
      set_opaque(v)
      v
    end
Ребята делавшие документацию ruby не тот фрагмент привели - check_opaque() ??
Метод opaque - возвращает фрагмент url, который не может быть распарсен!(Ruby cookbook стр 537 )

9 июля 2011 г.

поиск гиперсылок регулярными выражениями или поиск значения атрибута html тега

Для автоматического составителя robots.txt написал регулярное выражение которая выдирает гипперссылки из html страничек (атрибут href из тега  )
Возможно кому-то будет полезным.
Код парсера с пояснением:
/<\s*a#Тег Гиперссылки
 (?:\s+#название аттрибутов и их значения одной строкой не сохраняем
  (?:href\s*=\s*#Выискиваем атрибут хранящий url
   (?:# возможны три варианта записи url в Гиперссылки
    "([^"^']*)"|# "ww.tetrapolis.ru"
    '([^'^']*)'|# 'www.tetrapolis.ru'
    ([\w\d\.=\-_\,\?\/:#]+)# www.tetrapolis.ru, в этом выражении по возможности указаны все возможные символы -
# альтернатива [\S]+ здесь не подходит так как не остановится у ">"
   )
  |\w+\s*=\s*# Обрабатываем все остальные аттрибуты сохранять бессмысленно так как сохраняется значение последнего аттрибута
   (?:
   "[^'^"]*"|# значение аттрибута html тега в двойных кавычках
   '[^'^"]*'|# значение аттрибута html тега в одинарных кавычках
   [\S]+)# записанный без кавычек - это значение не сохраняем поэтому о достоваерности значения можно не беспокоится
  |\w+)#аттрибут гипперссылки без значения (а вдруг такой будет)
 )+# подразумеваем что в гипперссылке как минимум один аттрибут (иначе он нам не нужен)
\s*>/i# ищем без учета регистра
Небольшой пример на ruby использующий этот парсер:

require 'net/http'
require 'uri'
require 'ping'

class RopotsKiller
 def initialize(url_str,proxy_ip_str, proxy_port_int)
  @url=URI.parse(url_str)
  @proxy_addr=proxy_ip_str
  @proxy_port=proxy_port_int
 end
 def internet_connection?
  Ping.pingecho @url.host, 1, 80
 end
 def start
  # если инет через прокси то использыем прокси
  str = ((self.internet_connection?) ?
  (Net::HTTP):
  (Net::HTTP::Proxy(@proxy_addr, @proxy_port))).start(@url.host, @url.port)   
                {|http|
   http.get('/index.php').body
   }
  str #возвращаем код страницы
 end
 
end
rk = RopotsKiller.new("http://www.tetrapolis.ru/index.php","192.168.255.6",8080)
text=rk.start
pattern=/<\s*a(?:\s+(?:href\s*=\s*(?:"([^"^']*)"|'([^'^"]*)'|([\w\d\.=\-_\,\?\/:#]+))|\w+\s*=\s*(?:"[^"^']*"|'[^'^"]*'|[\S]+)|\w+))+\s*>/i
i=0
text.scan(pattern){ |match|
 p match
 i=i+1
}
puts "Length: #{i}"


23 июня 2011 г.

Ошибка 2104

Если Вы когда-нибудь будете плотно работали с Silverlight то вполне можете столкнуться с ошибкой 2104:
Uncaught Error: Неуправляемая ошибка в приложении Silverlight
Код: 2104    
Категория: InitializeError       
Сообщение: 2104 Произошла ошибка.  
В частности если зайдете в директорию где лежат исполняемые файлы вашего проекта и запустите xap файл во всех браузерах кроме Internet Explorer.
Проблема в URL пути до того самого xap файла - в нем все пробелы и символы кириллицей будут перекодированы (.../Мои Документы/...), url адрес до xap файла не должен содержать пробелы или кириллицы.