Previous Page
Next Page

Hack 73. Use a Dynamic script Tag to Make Web Services Requests

Use a dynamic script tag and a special JSON-related JavaScript class for easy, XML-less web services.

Making requests to third-party web services from an Ajax application is a pain, but new web services that offer the option of returning results in JSON format [Hack #7] instead of XML can provide significant relief. In fact, if you make web services requests using the dynamic script tag approachand the web service lets you specify a JavaScript callback functionyou can have unfettered access to the web service in a seamless, cross-domain, cross-browser fashion.

Here is what you need to try out this dynamic script tag request:

  • My JSONscriptRequest class

  • Access to a web service that returns JSON-formatted results and lets you specify a callback function

To create the JSONscriptRequest class, I distilled a lot of existing information, and then adapted it to the second requirement above. Until recently, finding a web service that met that requirement was, well, darn near impossible, unless you wrote one yourself. Fortunately, Yahoo! has recently begun to offer the option on many of its REST-ish web services. Notably, Yahoo!'s many search-related web services, as well as its geocoding, map image, and traffic web services, now can return JSON values wrapped in a callback function.

Using the Geocoding Web Service

Compared to using the XMLHttpRequest object and a proxy, this stuff is easy. The JSONscriptRequest class does the messy work of creating the script tag; this tag dynamically makes the actual web service request. For a quick example, I'll do some geocoding, turning a zip codein this case, 94107into a latitude/longitude pair, using Yahoo!'s Geocoding web service:

<html>
<body>
//Include the JSONscriptRequest class
<script type="text/javascript" src="jsr_class.js"></script>
<script type="text/javascript">

//Define the callback function
function getGeo(jsonData) {     
    alert('Latitude = ' + jsonData.ResultSet.Result[0].Latitude +
          ' Longitude = ' + jsonData.ResultSet.Result[0].Longitude);
    bObj.removeScriptTag(  ); 
}

//The web service call
var req = 'http://api.local.yahoo.com/MapsService/V1/geocode?appid=YahooDemo
          &output=json&callback=getGeo&location=94107'; 
//Create a new request object
bObj = new JSONscriptRequest(req); 
//Build the dynamic script tag
bObj.buildScriptTag(  ); 
//Add the script tag to the page
bObj.addScriptTag(  );
</script>

</body>
</html>

Running this application makes a request to Yahoo!'s Geocoding web service and yields the alert box shown in Figure 9-5. The alert box displays the latitude and longitude of the zip code 94107.

Figure 9-5. A coordinate from Yahoo's Geocoding web service


The web service requestthe req variable in the previous scriptspecifies that the web service should return JSON-encoded data (output=json) and that the data should be wrapped in a callback function named getGeo( ) (callback=getGeo). You can cut and paste the URL in the code into your browser to see the output of the web service. The output looks like this:

getGeo({"ResultSet":{"Result":[{"precision":"zip","Latitude":"37.7668"
,"Longitude":"-122.3959","Address":"","City":"SAN 
FRANCISCO","State":"CA","Zip":"94107","Country":"US"}]}});

That is a valid JavaScript statement, so it can be the target of a script tag that returns JavaScript (raw JSON data, without the callback function, is not a valid JavaScript statement, so it will fail to load if it is the target of a script tag). For comparison, look at the XML version of the output of this call (formatted for the book):

<?xml version="1.0" encoding="UTF-8"?>
<ResultSet xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns=
"urn:yahoo:maps" xsi:schemaLocation=
"urn:yahoo:maps http://api.local.yahoo.com/MapsService/V1/GeocodeResponse.xsd">
<Result precision="zip"><Latitude>37.7668</Latitude>
<Longitude>-122.3959</Longitude><Address></Address>
<City>SAN FRANCISCO</City><State>CA</State><Zip>94107</Zip>
<Country>US</Country></Result>
</ResultSet>

The buildScriptTag method of the JSONscriptRequest object builds a script tag that looks like this:

<script src="getGeo({"ResultSet":{"Result":[{"precision":"zip",
"Latitude":"37.7668","Longitude":"-122.3959","Address":"","City":"SAN 
FRANCISCO","State":"CA","Zip":"94107","Country":"US"}]}});"
 type="text/javascript">

To actually execute the web service request, the script tag has to be added to the page. The addScriptTag method attaches the script tag to the HTML page that is already loaded in your browser window. That action causes the getGeo( ) function to be called and the JSON-encoded data to be passed to that function. Now comes the magic part of the script; it's a side effect of using JSON-encoded data instead of XML. When a string of JSON-encoded data is used as an argument to a JavaScript function, the JavaScript interpreter automatically turns the JSON return value into a JavaScript object. Essentially, the parsing step is done automatically, and you can reference the data immediately:

alert('Latitude = ' + jsonData.ResultSet.Result[0].Latitude +
      ' Longitude = ' + jsonData.ResultSet.Result[0].Longitude);

Pros and Cons

The HTML script tag is the last frontier of unfettered access for browser-based applications. Depending on your viewpoint, it is either a gaping security hole, or a tool to make rich clients even richer. Its most common use, though, is by Internet advertisers who use it to pull their colorful ads into your web pages.

For the average Ajax or Ajaj (Asynchronous JavaScript and JSON) developer, the dynamic script tag approach can make life easier in certain scenarios. The XMLHttpRequest object, however, is still a more reliable, flexible, and secure request mechanism (see Table 9-1).

Table XMLHttpRequest compared to the dynamic script tag
 XmlHttpRequestDynamic script tag
Cross-browser compatible?NoYes
Cross-domain browser security enforced?YesNo
Can receive HTTP status codes?YesNo (fails on any HTTP status other than 200)
Supports HTTP GET and POST?YesNo (GET only)
Can send/receive HTTP headers?YesNo
Can receive XML?YesYes (but only embedded in a JavaScript statement)
Can receive JSON?YesYes (but only embedded in a JavaScript statement)
Offers synchronous and asynchronous calls?YesNo (asynchronous only)


The script tag's main advantages are that it is not bound by the web browser's cross-domain security restrictions and that it runs identically on more web browsers than XMLHttpRequest. Further, if your web service happens to offer JSON output and a callback function, you can nimbly access web services from within your JavaScript applications without having to parse the returned data.

XMLHttpRequest is available in all the latest browsers, but IE's implementation is somewhat different from that of the other major browsers and requires a compatibility layer (such as Sarissa) to make it work across all browsers. XMLHttpRequest can receive raw JSON data as well as XML, plain text, and HTMLin fact, it handles any non-binary data easily. It also can send and receive individual HTTP headers, can do both HTTP GETs and POSTs, and supports both synchronous and asynchronous calls. In short, if there's a problem with your web services request, such as invalid XML or a server error, XMLHttpRequest gives programmers tools to handle the situation.

In contrast, the script tag offers few of XMLHttpRequest's capabilities. The most notable downside is that it cannot handle errors gracefully. If the web service returns an invalid JavaScript statement to the script tag, a JavaScript error is generated. If the web service returns invalid JSON data wrapped inside a callback function, a JavaScript error is returned when the invalid JSON data is passed to the callback function. Also, if your web service returns an HTTP return code other than 200 (successful), the script tag will silently fail.

To be fair, script tag requests don't actually work exactly the same way across all browsers. The event handlinghow you wait for the tag to loadis a bit different. Technically, dynamically generated script tags load asynchronously, but there is no reliable, cross-platform way to wait for a script tag to load. Microsoft's IE uses one method described here, while the HTML 4.0 specification suggests the onload event handler (although it doesn't seem to work across all browsers).

The security issues surrounding the script tag cannot be ignored, either. Malicious scripts downloaded into your browser run with the same authority as other scripts in your page, so a villainous script can steal your cookies or misuse any authorization that you may have with a server. These villainous scripts can more easily send and receive stolen data using the script tag. For this reason, applications using the dynamic script tag approach need to be carefully vetted.

With all of these drawbacks, it is unlikely that programmers will flock to implement or reimplement web services requests using script tags. Still, it's a useful technique for scripting applications where noncritical data needs to be retrieved from third-party sources easily.

Resources

The JSONscriptRequest class: http://www.xml.com/2005/12/21/examples/jsr_class.zip; Yahoo! Web Services; Geocoding API: http://developer.yahoo.net/maps/rest/V1/geocode.html.

Jason Levitt


Previous Page
Next Page