现在的位置: 首页 > 综合 > 正文

AJAX and scripting Web services with E4X, Part 1

2013年10月02日 ⁄ 综合 ⁄ 共 17096字 ⁄ 字号 评论关闭

Get an introduction to ECMAScript for XML (E4X), a simple extension to JavaScript that makes XML scripting very simple. In this paper, the authors demonstrate a Web programming model called Asynchronous JavaScript and XML (AJAX) and show you how some new XML extensions to JavaScript can make it very simple.

ECMAScript for XML

You might not have come across the term ECMAScript before. It is actually the official name for JavaScript. The European Computer Manufacturers Association (ECMA) is the standards body where JavaScript is standardized (it is also the place where C# and the CLR are standardized). The ECMAScript standard is freely available from the ECMA Web site.

E4X is an extension of JavaScript that adds direct support for XML to the language. It's also an ECMA standard (see Resources -- ECMA-357). So what is direct support and why is it valuable? If you are a JavaScript programmer, you might already use technology such as Netscape LiveConnect or Rhino (a freely available JavaScript library that runs under Java™) to use Java libraries with your JavaScript. That means that you can already create, manipulate and use XML with the help of an XML library. Similarly, if you use Microsoft® Internet Explorer, you have XML support through the Microsoft MSXML2 library. Well, if you have used those libraries, prepare for a big change -- E4X is much simpler and easier.

Before we look at examples, a word on trying this out: E4X is available in two implementations at the moment. Both are available from Mozilla. One is the C JavaScript engine that the browser uses (it's code-named SpiderMonkey), and it's available in the latest Mozilla builds -- we used Mozilla 1.8a6. E4X is also available in Rhino. Rhino is a JavaScript interpreter and compiler built in Java, which we will demonstrate as both stand-alone and running inside Axis. Both are available from Mozilla.

In these examples we start off with using E4X on a command-line with Rhino, and then we move to using it inside a browser with Mozilla, demonstrating the AJAX model. In the second paper, we will show you how you can use E4X inside a server by embedding Rhino inside the Apache Axis Web services engine. But before we move on to Web services, we will show you the basics of XML programming in E4X.

Back to top

A simple example

Let's begin with a simple example. We parse and manipulate an XML that represents some information about the authors. The XML we want looks like this:

Listing 1. The authors

<people>
   <person gender="male">
      <name>Ant</name>
      <hair>Shaggy</hair>
      <eyes>Blue</eyes>
      <height measure="metric">176</height>
   </person>
   <person gender="male">
      <name>Paul</name>
      <hair>Spiky</hair>
      <eyes>Grey</eyes>
      <height measure="metric">178</height>
   </person>
</people>

If we had this in a string, we could "parse" the string by simply doing the following:

var x = new XML(peopleXMLString);

Alternatively, we can simply "in-line" the XML in the code:

var x = 
<people>
   <person gender="male">
      <name>Ant</name>
      <hair>Shaggy</hair>
      <eyes>Blue</eyes>
      <height measure="metric">176</height>
   </person>
   <person gender="male">
      <name>Paul</name>
      <hair>Spiky</hair>
      <eyes>Grey</eyes>
      <height measure="metric">178</height>
   </person>
</people>;

Yes -- that's right -- the XML becomes a direct part of the language.

Back to top

Using E4X with Rhino

If you are anything like we are, you'll want to try this by now. E4X is supported by the latest version of Rhino 1.6R1, in conjunction with the XMLBeans library from Apache. Simply grab the packages, extract them and add js.jar and xbean.jar to your classpath, and then start up the JavaScript shell.

java -cp js.jar;xbean.jar org.mozilla.javascript.tools.shell.Main

You can now try out the examples that follow. You can either cut-and-paste them from here, or they are included in a file, examples1.js, which is in the zip file (ws-ajax1code.zip) which you can download by clicking on the code icon at the top or bottom of this paper.

You can refer to any part of the XML simply using JavaScript properties. For example:

print(x.person[0].name);
Ant
print(x.person[1].hair);
Spiky

Have you noticed yet that we aren't using XML APIs like DOM or SAX? XML has simply become one of the native types that JavaScript understands.

Use the following to print out the heights for both people:

for each (var h in x..height) { print(h) };
176
178

The .. syntax is very useful. It returns all of the child elements, at any depth, that match the following tagname. So x..height returns the values of the height tags.

Another useful syntax is the following:

print(x.person.(name=="Ant").hair);
Shaggy

This makes it very simple to do look-ups inside the tree.

Back to top

A more complex example

Suppose you want to change the heights from metric to imperial measurements (that's what we British folks call feet and inches).

First here's the conversion from centimeters to inches...

function metricToImperial(cms) {
	var totalinches = Math.round(cms/2.54); 
	var inch = totalinches%12;
	var ft = (totalinches-inch)/12; 
	var response = <height/>;
	response.feet = ft;
	response.inches = inch + 2; // we sounded a bit short
	response.@measure = "imperial";
	return response;
}

The first interesting line is this:

var response = <height/>;

This line allows you to "in-line" XML in JavaScript. There are two benefits to this syntax. Firstly, it makes it really easy to use XML. Secondly, the meaning of the code is very clear.

Now you can add further children to this element:

response.feet = ft;

This creates a child element of <height> with the tag name set to be "feet" and the value set to the value of the variable ft.

You can also manipulate attributes this way:

response.@measure = "imperial";

Now let's use this function to update the XML:

for each (var p in x.person) { 
	if (p.height.@measure=="metric") 
		p.height=metricToImperial(p.height);  
}

print (x);

And here is the output:

<people>
  <person gender="male">
    <name>Ant</name>
    <hair>Shaggy</hair>
    <eyes>Blue</eyes>
    <height measure="imperial">
      <feet>5</feet>
      <inches>11</inches>
    </height>
  </person>
  <person gender="male">
    <name>Paul</name>
    <hair>Spiky</hair>
    <eyes>Grey</eyes>
    <height measure="imperial">
      <feet>5</feet>
      <inches>12</inches>
    </height>
  </person>
</people>

XML Namespaces in E4X

If you are an XML guru, at this point you are probably wondering how you can manage XML Namespaces with this syntax. There are three ways of doing this:

Firstly, you can use the in-line XML syntax:

var soapMsg = <s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"/>;
print(soapMsg.namespace());

http://www.w3.org/2003/05/soap-envelope

The next way is to set the default XML Namespace before creating elements:

default xml namespace = new Namespace("http://www.w3.org/2003/05/soap-envelope");

You can reset the default namespace by setting it to an empty string:

default xml namespace = ""

The final way is to use the :: operator

var f = new Namespace("http://fremantle.org");
soapMsg.Body.f::GetStockQuote="IBM";
print(soapMsg);
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
  <s:Body>
     <frem:GetStockQuote xmlns:frem="http://fremantle.org">
        IBM
     </frem:GetStockQuote>
  </s:Body>
</s:Envelope>

XML element ordering

One significant benefit that E4X has is that it fully supports XML, including ordering. Many mappings from XML directly into language objects end-up only supporting a subset of XML semantics that matches normal object semantics -- thereby missing the ability to represent not only objects but also documents. While it isn't appropriate for this paper, a quick look at the specification will show that the built-in functions that E4X XML objects have allow the order of the XML to be carefully crafted.

Using Javascript expressions within XML

There is one last thing we would like to tell you about before moving on to Web services -- the curly braces. Above we covered "in-lining" XML. E4X also allows you to re-enter the world of JavaScript and include evaluated expressions. So for example:

var num = 36;
var p = <paul><age>{num}</age></paul>
print(p);
<paul>
  <age>36</age>
</paul>

We've now covered the basics of E4X, so let's do some work with it.

Back to top

Using E4X to call Web services

In this section we describe how to use E4X in the following two environments:

  1. Mozilla 1.8 with XMLHttpRequest
  2. Java/Rhino

You can use E4X very easily to call Web services in a browser. There is an issue though! The only browser that supports E4X so far is the developer release of Mozilla 1.8.

While we aren't recommending this yet as a portable cross-browser solution, the following example demonstrates how E4X can be used to call Web services in a simple fashion. In the next section we will look at another approach that works in the Rhino Javascript engine.

AJAX

The simple example shows the browser sending and receiving SOAP messages to a SOAP server. In order to do this, we use E4X with the XMLHttpRequest object. This useful object (which is supported in Mozilla and Internet Explorer) allows scripts running in the browser to make HTTP requests in the background. In fact, this is how Google's GMail does almost everything. This architecture has recently been named Asynchronous JavaScript + XML (AJAX). You can read more about AJAX in the article "Ajax: A New Approach to Web Applications" (see Resources).

Fundamentally, the idea of AJAX is to improve the responsiveness and usability of Web pages by interacting with the server in a more flexible manner than the "page" model of standard HTML and HTTP. A great example of this is the Google Maps beta, which is considerably more interactive than previous mapping Web sites.

The great news is that AJAX combined with E4X is even better! We will show two versions of our browser apps. The first demonstrates the workings of the interactions, while the second version hides the buttons and inner workings of the Web page to show the interactivity and asynchrony.

Browser security

In order to demonstrate this, we use a standard service available over the Web from xmethods.net. However, there is a catch! Browser security rules do not in general allow scripts or Java applets to create network connections except to the server where the page came from. Otherwise you could have a spyware page that copied your keystrokes to another server.

You can, however, get around this. To do so, you need to do two steps. First, you have to enable enhanced privileges for scripts in the config for Mozilla. (Assuming you've downloaded and installed the Mozilla 1.8 beta).

Type about:config into the browser address bar, and then update the value of signed.applets.codebase_principal_support from false to true . (For security reasons please remember to set this back after you have finished.)

Then, in the script, you can ask for enhanced privileges. When the script runs, the user will be prompted to allow these. The code line is:

netscape.security.PrivilegeManager.enablePrivilege( "UniversalXPConnect UniversalBrowserAccess");

The other alternative is to deploy a service and the Web page into a Web application server like IBM® WebSphere® Application Server or Tomcat. For example, this works with Apache Axis and the default stock ticker sample that comes with Axis (see Resources).

The stock quote client sample

The script is part of stockclient.html. If you download the ws-ajax1code.zip file from this paper, extract the zip contents, and then open stockclient.html with Mozilla you should see the following:

Figure 1. stockclient.html in Mozilla
stockclient.html in Mozilla

To try it out, first click Update URL. This uses the XMLHttpRequest to fetch the WSDL file from http://services.xmethods.net/soap/urn:xmethods-delayed-quotes.wsdl (or whatever URL you type in the WSDL box) and then uses E4X to grab the port address URL from there. Now click Send, and you should see the SOAP request fill in. A second or two later, the SOAP response should update as well as the result field. Let's look at the code.

The stock quote client script

The script calls the URL with a request for the IBM stock price. If you are using the Axis server, we recommend using the ticker symbol XXX, which is a special symbol -- the deployed service will always return a fixed response for this ticker rather than making a Web request to find the real price - therefore it's better for testing with.

The first thing you need to do is define that you wish to use E4X:

<script type="text/javascript;e4x=1">

When you push the send button, the script does the following:

    var s = new Namespace(
       "s",
       "http://schemas.xmlsoap.org/soap/envelope/");
    var envelope = <s:Envelope xmlns:s={s}/>;
    envelope.s::Body="";
    var body = envelope.s::Body;

This is generic to any SOAP request. It simply creates a SOAP envelope with an empty body.

An equivalent way of doing this would be the following:

var envelope = 
      <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
         <s:Body/>
      </s:Envelope>

However, the previous code is simpler and also gives you a pointer to the body element.

The next step is to create the Body of the message:

   var x = new Namespace("x","urn:xmltoday-delayed-quotes");
   body.x::getQuote = <x:getQuote xmlns:x={x}/>;

Finally you need to add the correct symbol:

   var symbol = document.getElementById("symbol").value;	
   var getQuote = body.x::getQuote;
   getQuote.symbol=symbol;

You now have a fully-formed SOAP request. If you were to evaluate the envelope, you would get the following:

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Body>
    <x:getQuote xmlns:x="urn:xmethods-delayed-quotes">
      <symbol>XXX</symbol>
    </x:getQuote>
  </s:Body>
</s:Envelope>

To send this, you need to use the XMLHTTPRequest object. We created a simple helper function to support using the XMLHttpRequest object to invoke services using E4X. The function execService supports both asynchronous and synchronous usage.

function execService(url, xml, callback) {
   var xmlhttp = new XMLHttpRequest();
   var async=false;
   if (arguments.length==3) async=true;
      xmlhttp.open("POST", url, async);
      xmlhttp.setRequestHeader("SOAPAction", "/"/"")
      xmlhttp.setRequestHeader("Content-Type", "text/xml")
      if (async) { 
         var f = function() {
         if (xmlhttp.readyState==4) {
            callback(new XML(xmlhttp.responseText));
         }
      }

      xmlhttp.onreadystatechange = f;
   }
   xmlhttp.send(xml.toString());
   if (!async) return new XML(xmlhttp.responseText);
}

Let?s look at the code in more detail. Firstly, the code supports two ways of calling. You can call either:

XML execService(String url, XML envelope);

or

void execService(String url, XML envelope, function callback);

in which case the callback function should be void callback(XML x).

So either you can use this to invoke the XML service directly and wait for the response, or you can pass in a function which will be invoked with the response XML.

The function decides if it is async or sync based on the number of parameters (3 is async), and then simply uses the XMLHttpRequest object to POST the XML to the URL.

We set a couple of HTTP headers -- SOAPAction and Content-Type -- and then send the envelope parameter using xmlhttp.send(xml.toString()).

If the invocation is async, then it waits until the readyState is 4 (complete) before invoking the callback with the XML created from the response.

So the code to use this is as follows:

   var url = document.getElementById("url").value;

   var callback = function(resp) {
      alert(resp..*::getQuoteReturn);
   }

   execService(url, envelope, callback);

In this situation, we use the asynchronous model. Web browsers typically do not block while talking to servers, and we don?t want to either. For example, if we did, the browser window might end up "Not Responding" to Windows prompting the user to kill it.

resp..*::getQuoteReturn

This syntax takes a second to digest if you are new to E4X. The .. means search down the tree for the named element. The *:: means any Namespace, and so the value will be the value of any element called getQuoteReturn in any Namespace in the response SOAP envelope.

The actual sample code stockclient.html also displays the request and response SOAP envelopes. Try it out -- you should see the following:

Figure 2. stockclient.html displaying the request and response SOAP envelopes
stockclient.html displaying the request and response SOAP envelopes

The stockclient.html looks like a traditional Web page with a submit button, although it isn?t really (the page that the browser is viewing never changes). We built it that way to let you understand the interaction. However, the real AJAX version of the page is much nicer. The stockclientAjax.html has no buttons. As you type, it automatically updates the stock price. Try it out.

Figure 3. AJAX version of the Web page -- stockclientAjax.html
AJAX version of the Web page -- stockclientAjax.html

Now the page has no buttons, but instead, it automatically makes requests as soon as you stop typing (it waits 0.6 seconds before making the request in order to "sense" when you've stopped).

Back to top

Making Web services requests from Rhino

Rhino doesn?t support the XMLHTTPRequest object, but don't worry. Since Rhino runs in a Java environment, you can use the Java capabilities to make the Web service request. To demonstrate this, we wrote a simple Java implementation of the XMLHttpRequest object. Rhino allows Java programmers to extend their JavaScript environment issuing Java language. To use the XMLHttpRequest object in the Rhino shell, you simply make sure that the e4xutils.jar is in your classpath, and then you use a shell command, defineClass, to add it to your environment:

>set classpath=./js.jar;./xbean.jar;./e4xutils.jar;.
>java org.mozilla.javascript.tools.shell.Main
Rhino 1.6 release 1 2004 11 30H
js> defineClass('xmlhttp.XMLHttpRequest');

Here is a simple script to test this:

>test.js
defineClass("xmlhttp.XMLHttpRequest");
var xh = new XMLHttpRequest();
xh.open("GET",
"http://services.xmethods.net/soap/urn:xmethods-delayed-quotes.wsdl",
 false);
xh.send(null);
var wsdl = new XML(xh.responseText);
print(wsdl..*::address.@location);

>java org.mozilla.javascript.tools.shell.Main test.js

http://64.124.140.30:9090/soap

The result is that you can now use the same sort of scripts that you have written so far (E4X + XMLHttpRequest) both in Mozilla and in Rhino. In the second part of this paper, we will use this in some interesting scenarios.

Back to top

Conclusion

So far you have seen how to use E4X and Javascript to initiate Web services requests. In the next paper, we will show you how to provide Web services using E4X.

Back to top

Download

Description Name Size Download method
Source code for this article ws-ajax1code.zip 75 KB FTP
Information about download methods Get Adobe® Reader®


Back to top

Resources

抱歉!评论已关闭.