Article
Take Command with Ajax
Working with XML
So far, we haven’t discussed how to request and use XML documents—the X in Ajax! We were using the responseText property of the XMLHTTP object. Requesting the document is no different than what we’ve already seen: we just need to instruct our reusable request function to return responseXML, as opposed to responseText. We do that by setting the third parameter to true:
makeHttpRequest(the_url, ‘printResult’, true);
Then, we need to change our exec.php script to return valid XML, instead of plain text. Here’s the source code of the new script (exec_xml.php):
<?php
// $allowed_commands same as previous example
header(‘Content-Type: text/xml’);
echo ‘<?xml version="1.0" ?>‘ . "\n";
echo ‘<exec>‘ . "\n";
echo ‘<command>‘ . htmlentities($_GET[‘command’]) . ‘</command>‘ . "\n";
echo ‘<result>‘;
if (!empty($_GET[‘command’]) && in_array($_GET[‘command’], $allowed_commands)) {
$result = array();
exec($_GET[‘command’], $result);
if (!empty($result)) {
$result = array_map(‘htmlentities’, $result);
echo ‘<line>‘;
echo implode("</line>\n<line>", $result);
echo ‘</line>‘;
} else {
echo ‘<line>No output from this command. A syntax error?</line>‘;
}
} else {
echo "<line>This demo version lets you execute shell commands only from a predefined list:</line>\n";
echo ‘<line>‘;
echo implode("</line>\n<line>", $allowed_commands);
echo ‘</line>‘;
}
echo ‘</result>‘ . "\n";
echo ‘</exec>‘;
?>
This way, if we execute the command ‘ls test.html’, the new server-side script will return the following:
<?xml version="1.0" ?>
<exec>
<command>ls test.html</command>
<result>
<line>test.html</line>
</result>
</exec>
If we execute a command that returns more lines (like ‘ls -la’), every line in the response will be wrapped in <line> tags.
We’ll navigate the above XML document using the JavaScript DOM functions, in order to process the <result> and display it in our result <div>.
Here’s the body of the new WebConsole.printResult() method:
WebConsole.printResult = function(xmldoc)
{
var result_div = document.getElementById(‘result’);
var result_collection = xmldoc.getElementsByTagName(‘line’);
var new_command = xmldoc.getElementsByTagName(‘command’)[0].firstChild.nodeValue;
result_div.appendChild(document.createTextNode(new_command));
result_div.appendChild(document.createElement(‘br’));
var number_of_items = result_collection.length;
var result_wrap, line;
for (var i = 0; i < number_of_items; i++) {
if (result_collection[i].hasChildNodes()) {
result_wrap = document.createElement(‘pre’);
line = document.createTextNode(result_collection[i].firstChild.nodeValue);
result_wrap.appendChild(line);
result_div.appendChild(result_wrap);
}
result_div.appendChild(document.createElement(‘br’));
}
result_div.appendChild(document.createTextNode(‘:-> ‘));
result_div.scrollTop = result_div.scrollHeight;
document.getElementById(‘command’).value = ‘‘;
};
In order to update the result <div> with the data from the XML document, we follow the procedure:
- Access a node from the source XML.
- Get its value.
- Create a new node.
- Append it to the
<div>target tree.
As you see in the code xmldoc.getElementsByTagName (‘command’) is used, and it returns a collection (an array-like list object) of all <command> nodes. In our case, there’s only one such node. We access its value with the following:
xmldoc.getElementsByTagName(‘command’)[0].firstChild.nodeValue;
We take the node value, and create a new text node to append to the <div>, like this:
var new_command = xmldoc.getElementsByTagName(‘command’)[0].firstChild.nodeValue;
result_div.appendChild(document.createTextNode(new_command));
We do the same with the <result> tag of the XML document. First, we get all <line>s:
var result_collection = xmldoc.getElementsByTagName(‘line’);
Then, we loop through each element in the result_collection. Again, we wrap each line of result in <pre> tags.
As you see, working with the XMLDocument is not much more difficult than working with the plain text response. You can test the XML version of the WebConsole yourself.
Using jQuery
jQuery is a popular JavaScript library. Let’s try using it for our Ajax functionality, instead of the reusable makeHttpRequest() function.
First you need to download the latest version of the library from here (I suggest the minified version) and include it in the page:
<script type="text/javascript" src="jquery-1.2.3.min.js"></script>
There was a part where we used to call makeHttpRequest() like this:
the_url = ‘exec.php?command=‘ + escape(the_shell_command);
makeHttpRequest(the_url, WebConsole.printResult);
Using jQuery’s Ajax() method you can now do:
var xhr = $.Ajax({
url: ‘exec.php’,
data: {‘command’: the_shell_command},
success: WebConsole.printResult
});
Let’s see what we’ve got here:
$is a quick name for jQuery; you can also do this instead:jQuery.Ajax- We call the
Ajax()method and pass an object containing a URL to request, a data object (which will be escaped and converted to a query string by jQuery), and a callback function to call once the response arrives.
A working example of the web console using jQuery is here.
There are more ways to Ajax with jQuery, as a look at the documentation will confirm. For example, an often-repeated task of updating a <div> (with id mydiv) using the contents of the file (test.html) could be as simple as:
$("#mydiv").load("test.html");
See an example here.
Using YUI
Another popular JavaScript library is YUI (Yahoo Interface Library). Let’s see how we can make our web console work with YUI’s Ajax functionality.
We don’t need to download YUI files because they are already hosted for free by Yahoo and can be used from their current location. The Ajax functionality is provided by the Connection Manager utility, which you include in your pages like so:
<script type="text/javascript" src="http://yui.yahooapis.com/2.5.1/build/yahoo/yahoo-min.js"></script>
<script type="text/javascript" src="http://yui.yahooapis.com/2.5.1/build/event/event-min.js"></script>
<script type="text/javascript" src="http://yui.yahooapis.com/2.5.1/build/connection/connection-min.js"></script>
Now, to take advantage of YUI we replace the call to makeHttpRequest() with:
// YUI’s Ajax
YAHOO.util.Connect.asyncRequest(
‘GET’,
‘exec.php?command=‘ + escape(the_shell_command),
{
success: function(xhr){
WebConsole.printResult(xhr.responseText)
}
}
);
You can see that the asyncRequest() method takes:
- request method (GET, POST, HEAD, DELETE, and so on)
- URL
- an object that contains functions to handle success and failure scenarios
YUI passes XMLHttpRequest objects to the handler functions, so in this case we just take the contents of the responseText and forward it to printResult().
You can see how the URL was created by concatenating and escaping strings. In this case, there’s only one value we want to pass through the query string. But if there are more, it becomes pretty inconvenient. YUI helps you deal with such situations easily, by providing a setForm() method. Once you set the form, YUI will take the values from the form and take care of escaping and stitching together the query string:
YAHOO.util.Connect.setForm(document.forms[0]);
YAHOO.util.Connect.asyncRequest(
‘GET’,
‘exec.php’,
{
success: function(xhr){
WebConsole.printResult(xhr.responseText)
}
}
);
Here’s a working example of the web console using YUI.
Using JSON
JSON (JavaScript Object Notation) is a popular data exchange format. It gives you another alternative to plain text or XML when it comes to communicating data from the server to the browser. JSON is extremely simple; in essence, it’s just JavaScript.
In JavaScript you can define an array and an object like this:
var a = new Array(1,2,3);
var o = new Object();
o.property = ‘value’;
o.property2 = 2;
You can do the same but using array and object literals, like so:
var a = [1,2,3];
var o = {‘property’: ‘value’, ‘property2’: 2};
This literal notation is what JSON uses to pass data. The quotes around properties in JavaScript proper are not required most of the time, but by convention they are required in JSON.
Let’s change our console so it uses the JSON format to transfer data. The flow would be:
- PHP on the server side creates an associative array with the result, then converts it to JSON using the built-in function
json_encode()which is PHP5-only, but it would be trivial to encode the response even manually. The JSON string is returned. - JavaScript in the browser receives the JSON string and transforms it into a native JavaScript object. An unsecure way to do so is by using the
eval()function. The better way is to use the free JSON library.
For example, if the command we execute is ls, the JSON response from the server will be something like the following (formatted and abridged for readability):
{
"command":"ls",
"result":[
"exec.php",
"exec_json.php",
"exec_xml.php",
"httprequest_example.html",
"httprequest_test.html"
// ... and so on
]
}
As you can see, JSON is more lightweight than XML, as there are no closing tags, XML document tags, or root nodes.
Changing our server-side script to return JSON results in something like this:
exec_json.php:
<?php
// $allowed_commands same as before
$return = array(‘command’ => $_GET[‘command’]);
if (!empty($_GET[‘command’]) && in_array($_GET[‘command’], $allowed_commands)) {
$result = array();
exec($_GET[‘command’], $result);
if (!empty($result)) {
$return[‘result’] = $result;
} else {
$return[‘result’] = array(‘No output from this command. A syntax error?’);
}
} else {
$return[‘result’] = $allowed_commands;
array_unshift(
$return[‘result’],
‘This demo version lets you execute shell commands only from a predefined list:’
);
}
echo json_encode($return);
?>
In JavaScript, the part of WebConsole.printResult that accepts the data would become:
WebConsole.printResult = function(json_string)
{
var data = eval(‘(‘+ json_string +’)’);
var result_array = data.result;
// ... same as before
}
You can see how after the eval(), data becomes a normal JavaScript object and you can access its properties, such as data.result and data.command. As mentioned already, eval() is an less-than-secure way of transforming a JSON encoded string into an object. A better way is by using the JSON library which helps us replace the eval() call with this:
var data = JSON.parse(json_string);
A working JSON example is here.
Security Reminder
For the purposes of the demonstration of this application, I allow only a predefined set of harmless commands to be executed on my web server. If you expand the list of commands or allow any command whatsoever, don’t forget to protect the directory on your server in which you’ll install application. Leaving this application accessible to strangers can have devastating results. It’s pretty powerful: it will allow the user to execute any command, including, but not limited to, deleting everything on your web server!
Conclusion
We’ve come to the end of our example Ajax application. You know the basics, you saw the action, and you’re armed with enough knowledge to start experimenting yourself. You can warm up by modifying and playing around with this article’s code—it’s all included in the downloadable code archive—then move to projects of your own.
These are exciting times: the face of the Web is undergoing big changes, thanks to remote scripting. We’ve passed the phase of early adopters (Google, Amazon, Flickr, Yahoo) and now remote scripting with Ajax is becoming more common when creating responsive and user-friendly web pages. Your visitors nowadays are already spoiled by using GMail and Flickr, and you cannot afford to insult them with static Web 1.0-style pages!