Pages: [1]   Go Down
Send this topic | Print
Author Topic: [How to] Ajax and handling concurrent requests  (Read 962 times)
tpog
Super Authority member
******
Online Online

Posts: 1538


WWW
« on: February 14, 2009, 08:50:01 PM »

I recently started looking at Ajax (Asynchronous JavaScript and XML). Initially I wanted to use javascript on the client to look at the headers of web pages, however my mis-understanding soon became apparent in that the client browser running Ajax can only link back to the same server.

Whilst there is aready an Ajax tutorial on 110mb (http://www.110mb.com/forum/ajax-application-tutorial-t18180.0.html;msg138230#msg138230), I thought I'd add my own experiences if anyone's interested, and specifically, one way to handle concurrent requests.

Briefly, Ajax is a method for the client browser to access information from the server, without having to refresh the browser page.

Firstly, within javascript, you need to create the object for the XML request:
Code:
<script>
// For single requests, assign a variable depending on IE / Firefox
if (window.XMLHttpRequest) xmlhttp = XMLHttpRequest(); // Firefox
else xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); // IE

// For multiple concurrent requests, this can be done via a function
var xmlhttpTime = initialiseXML();       // create XMLHttpRequest object
var xmlhttpRate = initialiseXML();       // create XMLHttpRequest object

function initialiseXML() {
  if (window.XMLHttpRequest) return XMLHttpRequest(); // Firefox
  else return new ActiveXObject("Microsoft.XMLHTTP"); // IE
}
</script>

Next, we need to create the request to POST or GET data to the server (there is also an option to extract HEADER information from a web page on the server). For GET, specify the url passing any required variables and specify "true" to execute the request asynchronously (i.e. to run in parallel). For POST, again specify the url however the parameters are passed in the send. Set an event handler to process the returned request, then send the request to the server.
Code:
<script>
// GET method
  xmlhttp.open("GET","/poc/xmlServerGet.php?return=time",true);
  xmlhttp.onreadystatechange = checkData; // function to call when event triggered
  xmlhttp.send(null);

// POST method
  xmlhttp.open("POST","/poc/xmlServerPost.php",true);
  xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
  xmlhttp.onreadystatechange = checkData; // function to call when event triggered
  xmlhttp.send("return=time");
</script>

Note that the "checkData" function handles the event and therefore will have the event passed as the parameter. In order to handle concurrent events, this function can be adapted to call a separate function, passing the appropriate XML request object:
Code:
<script>
// Call POST or GET function, passing XML request object and server request
xmlRequestPost(xmlhttpTime,'time');
xmlRequestGet(xmlhttpRate,'rate');

function xmlRequestGet(xmlhttp,option) {
  xmlhttp.open("GET","/poc/xmlServerGet.php?return="+option,true);
  xmlhttp.onreadystatechange = function(){checkData(xmlhttp)};
  xmlhttp.send(null);
}
function xmlRequestPost(xmlhttp,option) {
  xmlhttp.open("POST","/poc/xmlServerPost.php",true);
  xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
  xmlhttp.onreadystatechange = function(){checkData(xmlhttp)};
  xmlhttp.send("return="+escape(option));
}
</script>

This request will be passed back to the server, as per the url, which needs to take appropriate action and return data to the javascript client, triggering the event handler. Headers, text, html et cetera can be returned, however XML allows you to return the data in a structured way, for interpretation by the client:
Code:
<?php
$return
= $_GET['return'];  // or $_POST
$parameter = date('H:i:s');
header('Content-Type: text/xml');
header("Cache-Control: no-cache, must-revalidate");
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
echo <<<END
<?xml version="1.0" ?>

<response>
<action>$return</action>
<parameter>$parameter</parameter>
</response>
END;
?>
 

For the single request above, the "checkData" function will be called on an event. For the concurrent requests, it will be called from the event function, passing the XML object variable. The function will be triggered for different events in the request, therefore it needs to interrogate the "readyState" to determine when it is complete (1=Loading 2=Loaded 3=Interactive 4=Complete). The "status" will also be returned corresponding to http status codes (e.g. 301, 404), the normal status is 200.
Code:
<script>
// handling single request
function checkData() {
  if (xmlhttp.readyState != 4)   return; // 1=Loading 2=Loaded 3=Interactive 4=Complete
  if (xmlhttp.status     != 200) return; // 200=normal 301/302/403/404 et cetera
  xmlDoc  = xmlhttp.responseXML.documentElement;
  if (!xmlDoc) return;
// take any appropriate action required
// (excuse the dodgy newline code, I use 110mb File Manager for maintaining code)
  alert("readyState ="+ xmlhttp.readyState   +String.fromCharCode(0x0A)+
            "status ="+ xmlhttp.status       +String.fromCharCode(0x0A)+
        "statusText ="+ xmlhttp.statusText   +String.fromCharCode(0x0A)+
      "responseText ="+ xmlhttp.responseText +String.fromCharCode(0x0A)+
       "responseXML ="+ xmlhttp.responseXML  );
}
</script>
Code:
<script>
// handling concurrent requests
function checkData(xmlhttp) {
  if (xmlhttp.readyState != 4)   return; // 1=Loading 2=Loaded 3=Interactive 4=Complete
  if (xmlhttp.status     != 200) return; // 200=normal 301/302/403/404 et cetera
  xmlDoc  = xmlhttp.responseXML.documentElement;
  if (!xmlDoc) return;
// Take appropriate action, depending on returned data
  action = xmlDoc.getElementsByTagName("action")[0].childNodes[0].nodeValue
  parameter = xmlDoc.getElementsByTagName("parameter")[0].childNodes[0].nodeValue
  switch(action) {
    case "time": displayTime(parameter); break;
    case "rate": displayRate(parameter); break;
    default: alert("Invalid function");
  }
}
</script>

Putting all of this together, the following example will set up two javascript timers starting off separate functions to request information from the server at specified intervals. These functions will in turn set up the required XML request and handle the returned event. Separate code on the server will pass back the requested information, which is then updated on the screen, highlighted in red.

There is a third timer in the javascript code to monitor how long it's been since the data was last returned and updated.

ajaxClient.php
Code:
<html dir="ltr">
    <head>
        <title>XML Request</title>
    </head>
    <body>
        <h1>XML Request</h1>
        <p>Time from server, updated every 15 seconds : <span id="serverTime" style="color: red"><?php echo date("H:i:s") ?></span>&nbsp;(last update <span id="serverTimeInterval">0</span> seconds ago)</p>
        <p>Exchange rate from Yahoo, updated every 60 seconds : <span id="dollarPound" style="color: red">$ to &pound;</span>&nbsp;(last update <span id="dollarPoundInterval">0</span> seconds ago)</p>
        <script>
var xmlhttpTime = initialiseXML();       // create XMLHttpRequest object
var xmlhttpRate = initialiseXML();       // create XMLHttpRequest object

// timer for Time request every 15 seconds
setInterval("xmlRequestPost(xmlhttpTime,'time')",1000*15);

// timer for Rate request every 60 seconds
setInterval("xmlRequestGet(xmlhttpRate,'rate')",1000*60);

// timer to track last updated
setInterval("increment()",1000);         

function initialiseXML() {
  if (window.XMLHttpRequest) return XMLHttpRequest(); // Firefox
  else return new ActiveXObject("Microsoft.XMLHTTP"); // IE
}
function xmlRequestGet(xmlhttp,option) {
// initialise request with specified url, asynchronously
  xmlhttp.open("GET","/poc/xmlServerGet.php?return="+option,true);
// set event handler, will execute on event.type=readystatechange
// With one request, this function can handle the response
// For concurrent requests, need to call a separate function passing the object
  xmlhttp.onreadystatechange = function(){checkData(xmlhttp)};
// send HTTP request to the server
  xmlhttp.send(null);
}
function xmlRequestPost(xmlhttp,option) {
// initialise request with specified url, asynchronously
  xmlhttp.open("POST","/poc/xmlServerPost.php",true);
  xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
// set event handler, will execute on event.type=readystatechange
// With one request, this function can handle the response
// For concurrent requests, need to call a separate function passing the object
  xmlhttp.onreadystatechange = function(){checkData(xmlhttp)};
// send HTTP request to the server
  xmlhttp.send("return="+escape(option));
}
function checkData(xmlhttp) {
//  alert("readyState="+xmlhttp.readyState+String.fromCharCode(0x0A)+
//        "status="+xmlhttp.status+String.fromCharCode(0x0A)+
//        "statusText="+xmlhttp.statusText+String.fromCharCode(0x0A)+
//        "responseText="+xmlhttp.responseText+String.fromCharCode(0x0A)+
//        "responseXML="+xmlhttp.responseXML);
  if (xmlhttp.readyState != 4)   return; // 1=Loading 2=Loaded 3=Interactive 4=Complete
  if (xmlhttp.status     != 200) return; // 200=normal 301/302/403/404 et cetera
// other variables include xmlhttp.statusText xmlhttp.responseText xmlhttp.responseXML
  xmlDoc  = xmlhttp.responseXML.documentElement;
  if (!xmlDoc) return;
// here, we're only interested in the first occurrence
  action = xmlDoc.getElementsByTagName("action")[0].childNodes[0].nodeValue
  parameter = xmlDoc.getElementsByTagName("parameter")[0].childNodes[0].nodeValue
  switch(action) {
    case "time": displayTime(parameter); break;
    case "rate": displayRate(parameter); break;
    default: alert("Invalid function");
  }
}
function displayTime(time) {
  document.getElementById('serverTime').innerHTML = time;
  document.getElementById('serverTimeInterval').innerHTML = 0;
}
function displayRate(rate) {
  document.getElementById('dollarPound').innerHTML = rate;
  document.getElementById('dollarPoundInterval').innerHTML = 0;
}
function increment() {
  ++document.getElementById('serverTimeInterval').innerHTML;
  ++document.getElementById('dollarPoundInterval').innerHTML;
}
</script>
    </body>
</html>

xmlServerGet.php
Code:
<?php
$return
= $_GET['return'];
switch (
$return) {
  case
"time": $parameter = date('H:i:s'); break;
  case
"rate":
    
$curl = curl_init();
    
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
    
curl_setopt($curl,CURLOPT_URL,"http://download.finance.yahoo.com/d/quotes.csv?s=GBPUSD=X&f=sl1d1t1c1");
    
$gbpusd = curl_exec($curl);
    
curl_close($curl);   
    
$dollar = explode(",",$gbpusd);
    
$parameter = $dollar[1];
    break;
  default: die(
'Variable not set');
}

header('Content-Type: text/xml');
header("Cache-Control: no-cache, must-revalidate");
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
echo <<<END
<?xml version="1.0" ?>

<response>
<action>$return</action>
<parameter>$parameter</parameter>
</response>
END;
?>
Note: xmlServerPost.php is identical except it uses $_POST.

Example at http://tpog.110mb.com/poc/ajaxClient.php.

Whilst Ajax has been around for a while, this is my first foray into the code. I would appreciate any comments or feedback. Any questions, please feel free to ask.
Logged
paulwratt
Advanced Authority Member
*****
Offline Offline

Posts: 1247


« Reply #1 on: February 17, 2009, 03:38:37 AM »

nice, some peeps will find this useful

suggestion: xmlServer.php uses $_REQUEST. (instead of two pages that do the same)

have you had a look at antimatters vxCore

have you had a look at AjaxRequest has some interesting things on the example page

apart from that, welcome to the world of dynamic page content and interaction ..

Paul
Logged
tpog
Super Authority member
******
Online Online

Posts: 1538


WWW
« Reply #2 on: February 17, 2009, 06:47:49 PM »

Thanks for the comments, it's nice to know someone reads it!

Thanks for the pointer to $_REQUEST, I've not seen that before and will have a look (although, the above was an example, not sure I'd generally mix the methods).

I have seen the vX thread, but to be honest, it looks a bit daunting and over my head. May look at it when I've got a bit more time.

I did do quite a bit of searching looking for handling concurrent requests, most seemed to duplicate code. I've not seen the above link before so will take a look, I found something similar at http://www.hunlock.com/blogs/Concurrent_Ajax but most seemed overly complicated.

Again, thanks for taking the time.
Logged
antimatter15
Loyal 110MB Member
*******
Offline Offline

Posts: 4075


WWW
« Reply #3 on: February 18, 2009, 10:11:28 AM »

vX is relatively simple. To port your functions to use the vXCore library it's

Code:
function xmlRequestGet(url,opt){
_.ajax("/poc/xmlServerGet.php?return="+option, checkData)
}
Code:
function xmlRequestPost(url,opt){
_.ajax("/poc/xmlServerPost.php", checkData, escape(opt))
}
Code:
function checkData(text, xhr){
  xmlDoc  = xhr.responseXML.documentElement;
  if (!xmlDoc) return;
  action = xmlDoc.getElementsByTagName("action")[0].childNodes[0].nodeValue
  parameter = xmlDoc.getElementsByTagName("parameter")[0].childNodes[0].nodeValue
  switch(action) {
    case "time": displayTime(parameter); break;
    case "rate": displayRate(parameter); break;
    default: alert("Invalid function");
  }
}

vX Ajax is mostly more flexible than yours (you can have GET parameters and POST ones at the same time), it's condensed to a single function (and one of the more memorizable, compared to the rest of the confusing library). One big difference though is that vX Ajax is oriented toward text. But vX has the second callback argument which contains the raw XHR object.
Logged

Ajax Animator, a web-based, collaborative animation authoring environment with Flash, Silverlight, and GIF export.
Pages: [1]   Go Up
Send this topic | Print
Jump to: