środa, 26 października 2011

XMLHttpRequest

W kolejnym poście zajmę się podstawowym elementem aplikacji internetowej, tym, dzięki któremu strony HTML ożyły. Chodzi o możliwość wykonywania asynchronicznych odwołań do serwera, inicjowanych przez skrypty w JavaScript. Bez takiej funkcji przeglądarka może jedynie prezentować strony, ładując kolejne wtedy i tylko wtedy gdy użytkownik nacisnął na odnośnik. Dodatkowo to program decyduje, co ma zrobić z otrzymaną odpowiedzią, jak ją zinterpretować. Odpowiedź nie musi już być pełną stroną HTML, nawet nie musi zawierać kodu HTML. Może być zakodowana w XML, JSON lub dowolnie innym formacie, byle tylko program był na to przygotowany.

var http = getHTTPObject();

function getHTTPObject() {
  var xmlhttp;
  /*@cc_on
  @if (@_jscript_version >= 5)
    try {
      xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
    } catch (e) {
      try {
        xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
      } catch (E) {
        xmlhttp = false;
      }
    }
  @else
  xmlhttp = false;
  @end @*/
  if (!xmlhttp && typeof XMLHttpRequest != 'undefined') {
    try {
      xmlhttp = new XMLHttpRequest();
    } catch (e) {
      xmlhttp = false;
    }
  }
  return xmlhttp;
}

function httpget(aUrl){
 http.open("GET",aUrl,true);
 http.onreadystatechange = handleHttpResponse;
 http.send(null);
 return false;
}




Do realizacji zapytań należy utworzyć obiekt http;


Zapytania sekwencyjne


Zapytania wysyłane przez przeglądarkę nie są realizowane natychmiast. A dokładniej to wysyłając zapytanie program nie czeka na odpowiedź, ta przychodząc wywołuje wskazane zdarzenie. Pomiędzy tymi dwoma momentami upływa pewien okres czasu. Jeżeli czas ten jest stosunkowo długi (np. z powodu wolnej sieci albo przeciążonego serwera albo po prostu przetworzenie zajmuje czas) to może się zdarzyć że nastąpi kolejna inicjacja zapytania zanim poprzednie zostanie zakończone. Przeglądarka domyślnie porzuca (przerywa) poprzednie zapytanie i rozpoczyna realizację nowego. Jest to podyktowane naturalnym schematem przeglądania internetu. Jeżeli jakaś strona ładuje się zbyt długo albo została wywołana przez pomyłkę to naciśnięcie kolejnego odnośnika spowoduje rozpoczęcie kolejnego zapytania z jednoczesnym przerwaniem poprzedniego. Takie przerwanie nie powoduje większych problemów, gdyż odpowiedź zazwyczaj stanowi pełną całość i wystarcza do wygenerowania nowego obrazu.

O ile podczas przeglądania internetu ignorowanie niektórych zapytań nie powoduje skutków ubocznych, to w przypadku aplikacji internetowych sprawa ma się inaczej. Każde zapytanie, a dokładniej każda uzyskana odpowiedź ma wpływ na końcowy rezultat. Szczególnie to jest widoczne przy obsłudze klawiatury. Kolejne zdarzenia (tj. naciśnięcia klawiszy) mogą następować bardzo szybko po sobie, szybciej niż typowy czas odpowiedzi serwera. Kilkadziesiąt milisekund, dzielące kolejne uderzenia w klawisz jest niewystarczające na efektywne skomunikowanie się z serwerem, do którego przeciętny czas samego przesłania informacji (mierzony usługą ping) wynosi 100 i więcej milisekund. Wymuszenie kolejnego zapytania przerywa oczekiwanie na odpowiedź, co też wymaga poinformowania serwera o tym fakcie. Do analizy tego typu zjawisk przydatny jest firebug.com. Wszystkie przerwane zapytania są oznaczone statusem aborted.

Współbieżność

Ponieważ zablokowanie możliwości wysyłania zapytań przed powrotem poprzedniej odpowiedzi znacząco zmniejszyło by wygodę działania programu należało poszukać innych rozwiązań. Ponieważ do obsługi połączeń wykorzystywane jest opisany powyżej obiekt http oczywiste jest że ograniczenie leży w jego strukturze wewnętrznej. Ponieważ nie można zmusić go w prosty sposób do obsługi wielu połączeń jednocześnie to może można utworzyć wiele takich obiektów i w ten sposób uzyskać współbieżność. Okazało się to skutecznym rozwiązaniem (kod poniżej).


var http2 = getHTTPObject();
var http3 = getHTTPObject();


function httpswap(aUrl){
   httpswapid+=1;
 if ((httpswapid  %  3) == 0 )
   {return  httpget(aUrl);}
 else if ((httpswapid  %  3) == 1 )
   {return  httpget2(aUrl);}
 else
   {return  httpget3(aUrl);};
}



function httpget2(aUrl){
 http2.open("GET",wjakie_id,true);
 http2.onreadystatechange = handleHttpResponse2;
 http2.send(null);
 return false;
}

function handleHttpResponse() {
  if (http.readyState == 4) {    handleResponse(http.responseText);  }
}

function handleHttpResponse2() {
   if (http2.readyState == 4) {   handleResponse(https.responseText); }
 }

Zostały utworzone jeszcze dwa takie obiekty (łącznie 3),
funkcje wołania zapytań z httpget zamieniono na httpswap, która rozdzielała kolejno zapytania do poszczególnych obiektów. Każdy z nich samodzielnie czekał na odpowiedź, gdy ją otrzymał wywoływał funkcję przetwarzającą.

Rezultat



Uzyskano znacznie lepszą użyteczność programu. Mimo realnie istniejącego znacznego opóźnienia odpowiedzi (porównując aplikację webową do stacjonarnej, desktopowej) sposób obsługi znacząco się upodobnił. Prawdopodobnie wynika to z możliwości szybkiego wprowadzania poleceń bez oczekiwania na wynik, tzn. czas oczekiwania rzędu kilkuset milisekund, przy pewności że wszystkie wysłane w tym czasie polecenia zostały przetworzone jest akceptowalny. Natomiast pomyłki wynikające z pominięcia niektórych poleceń przez przeglądarkę przeszkadzają w intensywnej pracy na aplikacji

2 komentarze:

  1. a teraz mozna uzywac zamiast tego jquery.
    Wybieramy diva do ktorego chcemy dodac tresc i wywolujemy odpowiednio .get albo .post i mozemy podac parametry wywolania strony albo funkcje dla pewnego zdarzenia.

    OdpowiedzUsuń
  2. Oczywiście że jest jquery (wraz z wieloma mutacjami) Ale to "tylko" opakowane XMLHttpRequest. Czasami dobrze jest wiedzieć co jest pod maską a nie tylko używać zaklęć. Osobiście praktycznie nie używam jquery bo może dużo, ale niekoniecznie to co potrzebuję i nie w taki sposób jak bym chciał

    OdpowiedzUsuń