Applets have always been designed to play in a 'sandbox' in which they can't hurt anything on a user's system, so their security is tighter than that of their server-based application counterparts. For example, a Java application can easily make a network connection to another server to request a Web service response; an applet can, too -- as long as it's talking only to the server on which it was originally hosted. But what if you want an applet that can make arbitrary Web requests?
In this article, I show you how to create a system that uses your browser to request and interact with Web service data from an arbitrary source. First, I create the basic Java applet, then I create the JavaScript code that pulls the data into the Web page. Finally, I create a servlet that acts as a proxy for non-local requests.
This article assumes that you are familiar with Java technology and (to a lesser extent) with XML. In addition to a Java development environment such as J2SE 1.4 or above, you'll need several pieces of software for this article. To send and receive the SOAP messages, you'll need the SOAP with Attachments Application Program Interface (API) for Java, or SAAJ (see "Send and receive SOAP messages with SAAJ" for help in setting it up) and you'll need a servlet engine such as IBM® WebSphere® Application Server or Apache Tomcat to run the servlet. See Resources for links to the various software packages you'll need.
First, take a look at the request you're ultimately going to make from the applet. Although this technique works for any kind of data you can pass through a URL, this article focuses on Web services, so I'll start with the simple SOAP message in Listing 1.
Listing 1. A simple SOAP message
<SOAP-ENV:Envelope
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns1="urn:chaosmagnet-quote"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<ns1:getQuoteResponse>
<return xsi:type="xsd:string">The early bird gets the worm, but it's the
second mouse that gets the cheese...</return>
</ns1:getQuoteResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope> |
To make things simple, I made this message available through a simple GET request at http://www.nicholaschase.com/testsoap.php. Making it available at a URL allows you to create a simple Java application (Listing 2) to retrieve and parse it.
Listing 2. Access a URL through a Java application
import java.net.URLConnection;
import java.net.URL;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
public class SendRequest {
public static void main(String args[]){
try{
URL url = new URL("http://www.nicholaschase.com/testsoap.php");
URLConnection urlconn = url.openConnection();
Document doc = null;
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
doc = db.parse(urlconn.getInputStream());
System.out.println(doc.getDocumentElement()
.getFirstChild().getNextSibling()
.getFirstChild().getNextSibling()
.getFirstChild().getNextSibling().getFirstChild().getNodeValue());
} catch (IOException e) {
System.out.println("can't find the file:"+e.getMessage());
} catch (Exception e) {
System.out.print("Problem parsing the XML.");
}
} catch (Exception e){
e.printStackTrace();
}
}
} |
First, create the actual URLConnection. From there, you can feed its InputStream to the DocumentBuilder as the source from which to build the Document object. I realize the output statement is a bit awkward, but because this article is about accessing the data rather than analyzing it, I'm just working my way down to the actual quotation.
Compile and run the application from the command line, and you should get the expected response:
The early bird gets the worm, but it's the second mouse that gets the cheese... |
You might be wondering why I went to the trouble of dealing with the XML directly rather than using, say, the SAAJ. The answer is that ultimately I'll package this code as an applet that runs on computers over which I have no control, so I want to stay with classes that are part of the Java technology itself.
The applet itself is simple, as Listing 3 shows.
Listing 3. A simple applet
import java.applet.*;
import java.awt.*;
public class SendRequest extends Applet {
public void paint(Graphics g) {
g.drawRect(0, 0, 499, 149);
g.drawString("Printing...", 5, 70);
}
} |
This applet simply draws a rectangle and displays the word "Printing..." inside it each time the applet is displayed. Save the class and compile it, then open a second text file to create the HTML page that displays the applet, as shown in Listing 4.
Listing 4. The HTML page displaying the applet
<HTML>
<HEAD>
<TITLE>A Simple Program</TITLE>
</HEAD>
<BODY>
<CENTER>
<APPLET CODE="SendRequest.class" WIDTH="500" HEIGHT="150">
</APPLET>
</CENTER>
</BODY>
</HTML> |
Notice that this typical HTML page includes an APPLET tag that calls the applet code. Save this HTML page in the same directory as the SendRequest.class file, then open it in your browser. You should see a result similar to Figure 1.
Figure 1. The simple applet
Now, add the code to retrieve the URL.
Access the response from the applet
Adding the URL retrieval to the applet is straightforward, as Listing 5 shows.
Listing 5. Add the URL retrieval to the applet
import java.applet.*;
import java.awt.*;
import java.net.URLConnection;
import java.net.URL;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
public class SendRequest extends Applet {
public void paint(Graphics g) {
g.drawRect(0, 0, 499, 149);
g.drawString(getResponseText(), 5, 70);
}
public String getResponseText(){
try{
URL url = new URL("http://www.nicholaschase.com/testsoap.php");
URLConnection urlconn = url.openConnection();
Document doc = null;
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
doc = db.parse(urlconn.getInputStream());
return (doc.getDocumentElement()
.getFirstChild().getNextSibling()
.getFirstChild().getNextSibling()
.getFirstChild().getNextSibling()
.getFirstChild().getNodeValue());
} catch (Exception e) {
return "Can't get the string.";
}
} catch (Exception e){
return "Problem accessing the response text.";
}
}
} |
Here, I include the code wholesale from the original application, but I've changed the main() method to the getResponseText() method, which I then print to the page when the browser displays the applet.
Everything should work, but if you refresh the browser, you'll see that all is not as it seems -- see Figure 2. (To see the changes, you may have to hold down the Ctrl key while you refresh the page.)
Figure 2. Calling the applet from the local file system
So, what's the problem? As I mentioned earlier, applets are designed with certain security limitations, one of which is the inability to access a server other than the one from which the applet was originally downloaded. So, because I'm trying to call a URL on www.nicholaschase.com, I simply need to upload the applet and the HTML file to that server. Then, I can call the applet and everything will work as advertised, as you can see in Figure 3.
Figure 3. Accessing the applet from the proper server
Now that I have the data, I can access it from within the HTML page.
Access applet data through JavaScript
The ultimate goal of this process is to be able to use JavaScript code to analyze the retrieved data. The key to the process is to treat the applet as an object -- in fact, the APPLET tag will eventually be replaced by the object tag. To replace the tag, you must assign it an id attribute, as shown in Listing 6:
Listing 6. Accessing the applet as an object
<HTML>
<HEAD>
<TITLE>A Simple Program</TITLE>
</HEAD>
<BODY>
<CENTER>
<APPLET CODE="SendRequest.class" WIDTH="500" HEIGHT="150" id="TheApplet">
</APPLET>
</CENTER>
<b>The returned data is:</b><br />
<script type="text/javascript">
document.write(TheApplet.getResponseText());
</script>
</BODY>
</HTML> |
Assigning the applet an id attribute enables you to treat the applet as a simple object, which in turn enables you to make a direct call to the applet's methods. If you save the page and refresh it, you can see that the information gets pulled into the page (Figure 4).
Figure 4: Accessing the applet data through JavaScript
Now all that's left is to access an arbitrary URL.
Everything is now working, but because of security requirements, you can access only the server from which you downloaded the applet. What happens if you want to access a different server?
For example, suppose you wanted to pull this quote live from the Quote of the Day service. Because the applet can connect only to its own server, you can't connect to the applet directly. But the server can connect to anything, which means that instead of connecting to the data directly, you can connect to a servlet that retrieves the data.
The code in Listing 7 creates a servlet that retrieves a response from the Quote of the Day service.
Listing 7. Servlet to retrieve the remote information
import javax.servlet.http.*;
import javax.servlet.*;
import javax.xml.soap.SOAPConnectionFactory;
import javax.xml.soap.SOAPConnection;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPPart;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPElement;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamResult;
public class SendingServlet extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException {
try {
//First create the connection
SOAPConnectionFactory soapConnFactory =
SOAPConnectionFactory.newInstance();
SOAPConnection connection =
soapConnFactory.createConnection();
//Next, create the actual message
MessageFactory messageFactory = MessageFactory.newInstance();
SOAPMessage message = messageFactory.createMessage();
//Create objects for the message parts
SOAPPart soapPart = message.getSOAPPart();
SOAPEnvelope envelope = soapPart.getEnvelope();
SOAPBody body = envelope.getBody();
//Populate the body
//Create the main element and namespace
SOAPElement bodyElement =
body.addChildElement(envelope.createName("getQuote" ,
"ns1",
"urn:xmethods-qotd"));
//Save the message
message.saveChanges();
//Send the message and get a reply
//Set the destination
String destination =
"http://webservices.codingtheweb.com/bin/qotd";
//Send the message
SOAPMessage reply = connection.call(message, destination);
//Check the output
//Create the transformer
TransformerFactory transformerFactory =
TransformerFactory.newInstance();
Transformer transformer =
transformerFactory.newTransformer();
//Extract the content of the reply
Source sourceContent = reply.getSOAPPart().getContent();
resp.setHeader("Content-Type", "text/plain");
//Set the output for the transformation
StreamResult result = new StreamResult(resp.getWriter());
transformer.transform(sourceContent, result);
//Close the connection
connection.close();
} catch(Exception e) {
System.out.println(e.getMessage());
}
}
} |
This script looks long and complicated, but it's really straightforward. First, create the SOAPConnection and the message objects. Next, populate the objects with a single getQuote element, as required by the Quote of the Day service.
After you've finished creating the request message, send it to the service and retrieve the reply. The reply comes back as a SOAPMessage object, but you need to pass the actual text of the message to the servlet's Response object. To do so simply use an identity XSLT transformation with the response as the destination.
If you compile and install this servlet just as you would any other servlet -- see Resources for help -- you can call it directly from the browser to see the results (Figure 5).
Figure 5: The remote response, as retrieved by the local servlet
From here, pulling the remote information into the applet is as simple as calling the servlet, as the code in Listing 8 shows:
Listing 8. Calling the remote data from the applet
...
public void paint(Graphics g) {
g.drawRect(0, 0, 499, 149);
g.drawString(getResponseText(), 5, 70);
}
public String getResponseText(){
try{
URL url = new URL("http://localhost:8080/servlet/SendingServlet");
URLConnection urlconn = url.openConnection();
Document doc = null;
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
doc = db.parse(urlconn.getInputStream());
return (doc.getDocumentElement()
.getFirstChild().getFirstChild().getFirstChild()
.getFirstChild().getNodeValue());
} catch (Exception e) {
return "Can't get the string:"+e.toString();
}
} catch (Exception e){
return "Problem accessing the response text."+e.toString();
}
}
} |
Notice that the code is the same, with two exceptions. First, rather than calling the data directly, I'm calling the servlet that retrieves the data. Second, because the service returns a message without the extra line breaks, I've cleaned up the retrieval a bit.
To see the results, move the applet and the HTML page to the server on which you've installed the servlet. You can then access the applet and view the results (Figure 6).
Figure 6: Seeing the results
In this article, I have explained how to create a system that uses your browser to access an arbitrary Web service. JavaScript code calls a method within an applet, which calls a servlet, which retrieves the remote information. In this way, you bypass restrictions on what an applet can and cannot do.
From here, you can take several routes to clean up the process or enhance its functionality. Because you're pulling the data into the JavaScript code, you no longer have to display the actual applet on the page. You can also modify the applet to retrieve more than one piece of information from the service or pass information back from a single request rather than making a new request every time the user changes windows.
If you want to go all out, you have the option of changing the servlet so that it takes parameters from the applet. Those parameters can determine what service the servlet calls or even parameters to pass to that service. Using methods (as I did here with getResponseText()), you can even write JavaScript code to pass those parameters in to the applet, allowing the user to decide what information is ultimately presented.
- Check out the status of various Web Services related recommendations at the W3C's Web Services Activity page.
- Learn how to create and use SOAP messages by reading "Send and receive SOAP messages with SAAJ" (developerWorks, July 2003).
- Download SAAJ as part of the Java Web Services Developer Pack.
- Download the IBM WebSphere Application Server.
- Download J2SE 1.4.
- For an alternate servlet engine, download Tomcat, from the Apache Project.
- For a complete Web Services toolkit, download the Web Services Toolkit from IBM alphaWorks.
- Learn how to speed-start your Web services, or read our feature for those new to SOA and Web services.
- Find a broad array of articles, columns, tutorials, and tips at the developerWorks XML and SOA and Web services zones. While you're at it, subscribe to the developerWorks newsletter.
- Browse for books on these and other technical topics.
Nicholas Chase, a Studio B author, has been involved in Web site development for companies such as Lucent Technologies, Sun Microsystems, Oracle, and the Tampa Bay Buccaneers. Nick has been a high school physics teacher, a low-level radioactive waste facility manager, an online science fiction magazine editor, a multimedia engineer, and an Oracle instructor. More recently, he was the Chief Technology Officer of an interactive communications firm in Clearwater, Florida, USA, and is the author of several books on XML and on Web development, including XML Primer Plus (Sams). He's currently trying to buy a farm so that he and his wife can raise alpacas and mutant chickens. He loves to hear from readers and can be reached at nicholas@nicholaschase.com.




