University of Oxford |
|
Computing Services |
|
Web Services using Axis |
|
Author: Barry Cornelius Date: 6th May 2003; first created: 17th April 2003 |
A Web Service is the provision of one or more methods that can be invoked by some external site. The external site contacts the webserver of the site providing the Web Service to get a method of the Web Service executed. It is usually a program running on the external site that wants the result of a call of the method. A website might be providing Web Services in several different areas.
Although some people assume that Web Services requires you to use Microsoft's .NET Framework, there are many rival products. In this document, we will look at Axis, a Web Services product from Apache. This product is targetted at Java programmers.
The main aim of this document is to demonstrate how easy it is for any Java programmer:
Although the instructions given in this document assume you are using Windows, Linux could be used instead. The document also assumes that the Java 2 SDK has been installed.
In their book [44] about the .NET Framework, Thai and Lam provide the following answer to the question How can software be viewed as services?:
For a simple example of a servlet, suppose we want a WWW page that delivers the current date and time. In Java, we can easily use java.util.Date to output the current date and time:
0001: Date tDate = new Date(); 0002: String tDateString = tDate.toString(); 0003: System.out.println(tDateString);
How can we convert this code to code that can be executed as a servlet? One way is to produce a class that is derived from the HttpServlet class (that is in the javax.servlet.http package). If our class overrides HttpServlet's doGet method, the webserver program will arrange for our doGet method to be executed when someone visits the appropriate WWW page.
Here is the code of a getDateAndTime servlet:
0004: import java.util. Date; // getDateAndTime.java 0005: import javax.servlet.http. HttpServlet; 0006: import javax.servlet.http. HttpServletRequest; 0007: import javax.servlet.http. HttpServletResponse; 0008: import java.io. IOException; 0009: import java.io. PrintWriter; 0010: import javax.servlet. ServletException; 0011: public class getDateAndTime extends HttpServlet 0012: { 0013: public void doGet(HttpServletRequest request, 0014: HttpServletResponse response) 0015: throws IOException, ServletException 0016: { 0017: Date tDate = new Date(); 0018: String tDateString = tDate.toString(); 0019: response.setContentType("text/html"); 0020: PrintWriter tResponsePrintWriter = response.getWriter(); 0021: StringBuffer tStringBuffer = new StringBuffer(); 0022: tStringBuffer.append("<html>\n" ); 0023: tStringBuffer.append("<title>Clock</title>\n" ); 0024: tStringBuffer.append("It is now " + tDateString + "\n" ); 0025: tStringBuffer.append("</html>\n" ); 0026: tResponsePrintWriter.println(tStringBuffer); 0027: tResponsePrintWriter.close(); 0028: } 0029: }
When the doGet method gets called, it is passed two arguments (request and response):
Later, we will be installing Axis. The Axis Installation Guide at:
http://cvs.apache.org/viewcvs.cgi/~checkout~/xml-axis/java/docs/install.html
says that you should get the latest 4.1.x version
of tomcat.
It also says that you should get the
full distribution rather than the
LE version as the LE version omits the
Xerces XML parser.
The Axis Installation Guide does not cover the installation
of tomcat.
However,
there are installation notes for tomcat at:
http://jakarta.apache.org/tomcat/tomcat-4.1-doc/RUNNING.txt
These installation notes refer to downloading a
binary distribution of tomcat from:
http://jakarta.apache.org/builds/jakarta-tomcat-4.0/nightly/
However, this directory is empty.
So, instead go to:
http://jakarta.apache.org/builds/jakarta-tomcat-4.0/release/
and look for a subdirectory containing the latest 4.1.x release.
For example, I downloaded from:
http://jakarta.apache.org/builds/jakarta-tomcat-4.0/release/v4.1.18/bin/The notes advise you to download a .zip file, a file with a name like jakarta-tomcat-4.1.18.zip. This file can be unzipped (perhaps using the jar command). I think it only writes into the directory that you choose.
However, although it is not mentioned in the installation notes, for Windows you can instead download a self-installation binary. This is in a file with a name like jakarta-tomcat-4.1.18.exe. This file was 8.45MB. Using a modem on a 33Kbps line, this took about 40 minutes to download.
After this .exe file has been downloaded, double click on jakarta-tomcat-4.1.18.exe whilst using Windows Explorer. An Apache Tomcat 4.1.18 Setup screen appears. It will first look for a Java 2 SDK directory. It found mine in:
E:\j2sdk1.4.1_02Click on OK. A License Agreement form appears. Read it and click on I Agree. An Installation Options screen appears. The default is a Normal installation which it said requires 28.8MB of disc space. I took this and clicked on Next. It then wants to know where you want the files to be put. For me it defaulted to:
E:\Program Files\Apache Group\Tomcat 4.1Elsewhere this is called the CATALINA_HOME directory. Click on Install. This installs most of the files, installing them into this directory. The only other directory I noticed it write into was:
E:\Documents and settings\barry\Start Menu\Programs\Apache Tomcat 4.1Later, it displays a Test Install Setup screen. This defaults to a port of 8080, a username of admin and leaves it to you to choose a password. After typing something appropriate into the password box, click on Next. This installs a few more files, and then says Completed. Click on Close.
If you click on Start | Programs, you should find a menu item called Apache Tomcat 4.1. This menu allows you easily to start and stop tomcat.
So go to Start | Programs | Apache Tomcat 4.1 and click on Start Tomcat. A Command Prompt window will appear, and this will be used to log the activities of the tomcat webserver program. So this window will be present whilst tomcat is running.
An alternative way of starting tomcat is to use a Command Prompt window and execute commands like:
0030: set CATALINA_HOME=E:\Program Files\Apache Group\Tomcat 4.1 0031: set JAVA_HOME=e:\j2sdk1.4.1_02 0032: "%CATALINA_HOME%\bin\startup"If your PC needs to use a proxy server to get to the internet, before executing these commands you will also need to execute a command like:
0033: set CATALINA_OPTS=-Dhttp.proxyHost=proxy.site.com -Dhttp.proxyPort=8080
Start a browser
(such as Netscape Navigator or Internet Explorer)
and go to:
http://localhost:8080/
You should get a WWW page announcing that you have successfully
setup tomcat.
On the left hand side of this WWW page, you will see a link to
Servlet Examples.
If you click on this link, the page at:
http://localhost:8080/examples/servlets/index.html
appears.
This is a page containing some
Servlet Examples with Code.
If you wish, click on the Execute
and Source links
that are on this WWW page.
For example, click on the Execute link
next to Hello World.
This will take you to:
http://localhost:8080/examples/servlet/HelloWorldExample
Because of the way in which tomcat is configured,
visiting this URL will cause tomcat
to execute the .class file that has been
stored in:
%CATALINA_HOME%\webapps\examples\WEB-INF\classes\HelloWorldExample.class
Suppose we want tomcat to be able to execute the getDateAndTime servlet that was given earlier. Although we could add the files for our own servlets to the examples directory, suppose we want to store our servlets in a separate directory. Suppose this directory is called mytomcat.
We will need to create the mytomcat directory as a subdirectory of:
%CATALINA_HOME%\webappsWe actually have to create a directory tree like this:
webapps mytomcat WEB-INF classes libYou can do this in Windows Explorer, or execute the following commands in a Command Prompt window:
0034: cd %CATALINA_HOME%\webapps 0035: mkdir mytomcat 0036: cd mytomcat 0037: mkdir WEB-INF 0038: cd WEB-INF 0039: mkdir classes lib
We now need somewhere to store the servlet's source code. Although tomcat does not require the source code of a servlet to be stored in the %CATALINA_HOME% directory tree, one possibility is to create a subdirectory of:
%CATALINA_HOME%\webapps\mytomcatcalled getDateAndTime and store the text of the getDateAndTime servlet in the file:
%CATALINA_HOME%\webapps\mytomcat\getDateAndTime\getDateAndTime.java
Then execute the following commands from a Command Prompt window:
0040: cd %CATALINA_HOME%\webapps\mytomcat\getDateAndTime 0041: set classpath=..\..\..\common\lib\servlet.jar;. 0042: javac -d ..\WEB-INF\classes getDateAndTime.java 0043: dir /od ..\WEB-INF\classesThis will compile the file getDateAndTime.java. The -d option will arrange for the .class file to be put into a directory where tomcat will find it. So this javac command creates the file:
%CATALINA_HOME%\webapps\mytomcat\WEB-INF\classes\getDateAndTime.class
By convention, a servlet is accessed using a URL like:
http://localhost:8080/mytomcat/servlet/getDateAndTimeWhen it gets a URL like this, tomcat knows from the first part of the URL that the files are in:
%CATALINA_HOME%\webapps\mytomcatHowever, we have to tell tomcat that there is a servlet called getDateAndTime. We can do this by putting the following text into a file called web.xml in the:
%CATALINA_HOME%\webapps\mytomcat\WEB-INFdirectory:
0044: <?xml version="1.0" encoding="ISO-8859-1"?> 0045: <!DOCTYPE web-app 0046: PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN" 0047: "http://java.sun.com/j2ee/dtds/web-app_2.2.dtd"> 0048: <web-app> 0049: <display-name>mytomcat</display-name> 0050: <servlet> 0051: <servlet-name>getDateAndTime</servlet-name> 0052: <display-name>getDateAndTime Servlet</display-name> 0053: <servlet-class> 0054: getDateAndTime 0055: </servlet-class> 0056: </servlet> 0057: <servlet-mapping> 0058: <servlet-name>getDateAndTime</servlet-name> 0059: <url-pattern>/servlet/getDateAndTime</url-pattern> 0060: </servlet-mapping> 0061: </web-app>
Having done that, restart tomcat,
and then use a browser to go to:
http://localhost:8080/mytomcat/servlet/getDateAndTime
This will get tomcat to execute the bytecodes of the file:
%CATALINA_HOME%\webapps\mytomcat\WEB-INF\classes\getDateAndTime.class
If you now alter the text of the servlet getDateAndTime.java, and recompile it placing the file getDateAndTime.class in the classes directory, you will find that this new version will not be executed unless you restart tomcat.
If you wish, you can alter the configuration of tomcat so that it frequently looks at the modification time of .class files in all of the classes directories and reloads its copy of any .class files that have been updated. This can be done by altering the file:
%CATALINA_HOME%\conf\server.xmladding the line:
0062: <DefaultContext debug="0" reloadable="true"/>between the line:
0063: </Context>and the line:
0064: </Host>You will have to restart tomcat for this change to server.xml to have any effect.
0065: <HTML> 0066: <BODY> 0067: <FORM METHOD="POST" ACTION="http://localhost:8080/mytomcat/servlet/toFahrenheit"> 0068: Type in a Centigrade value 0069: <INPUT TYPE="text" NAME="centigrade"> 0070: <BR> 0071: <INPUT TYPE="submit" VALUE="Get Fahrenheit"> 0072: </FORM> 0073: </BODY> 0074: </HTML>
0075: import javax.servlet.http. HttpServlet; // toFahrenheit.java 0076: import javax.servlet.http. HttpServletRequest; 0077: import javax.servlet.http. HttpServletResponse; 0078: import java.io. IOException; 0079: import java.io. PrintWriter; 0080: import javax.servlet. ServletException; 0081: public class toFahrenheit extends HttpServlet 0082: { 0083: public void doPost(HttpServletRequest request, 0084: HttpServletResponse response) 0085: throws IOException, ServletException 0086: { 0087: String tCentigradeString = request.getParameter("centigrade"); 0088: double tCentigrade = Double.parseDouble(tCentigradeString); 0089: double tFahrenheit = 32 + tCentigrade*9/5; 0090: response.setContentType("text/html"); 0091: PrintWriter tResponsePrintWriter = response.getWriter(); 0092: StringBuffer tStringBuffer = new StringBuffer(); 0093: tStringBuffer.append("<html>\n" ); 0094: tStringBuffer.append("<head>\n" ); 0095: tStringBuffer.append("<title>Reply</title>\n" ); 0096: tStringBuffer.append("</head>\n" ); 0097: tStringBuffer.append("<body>\n" ); 0098: tStringBuffer.append("<p>\n" ); 0099: tStringBuffer.append("In Fahrenheit, this is " + tFahrenheit + "\n" ); 0100: tStringBuffer.append("</p>\n" ); 0101: tStringBuffer.append("</body>\n" ); 0102: tStringBuffer.append("</html>\n" ); 0103: tResponsePrintWriter.println(tStringBuffer); 0104: tResponsePrintWriter.close(); 0105: } 0106: }
0087: String tCentigradeString = request.getParameter("centigrade");
The code of a servlet is a mix of Java source code doing some work and Java source code that generates HTML instructions. Because a lot of the code often consists of Java statements generating HTML, the code is not easy to read.
A JavaServer page is a WWW document that is written in a mix of elements of a markup language (e.g., HTML) and elements that will be dynamically generated by executing some Java code. As with servlets, the webserver program will either itself transform, or instead arrange for another program to transform, the JavaServer page into Java source code (a servlet). Behind the scenes, the servlet is then compiled and executed, and finally its output (HTML) is passed to the webserver program.
Here is a WWW page that has a button that will cause a JavaServer page to be executed:
0107: <HTML> 0108: <BODY> 0109: <FORM METHOD="POST" ACTION= 0110: "http://localhost:8080/mytomcat/jtoFahrenheit/toFahrenheit.jsp"> 0111: Type in a Centigrade value 0112: <INPUT TYPE="text" NAME="centigrade"> 0113: <BR> 0114: <INPUT TYPE="submit" VALUE="Get Fahrenheit"> 0115: </FORM> 0116: </BODY> 0117: </HTML>
And here is an appropriate JavaServer page (which is stored in the file toFahrenheit.jsp):
0118: <%@page language="java" %> 0119: <% 0120: String tCentigradeString = request.getParameter("centigrade"); 0121: double tCentigrade = Double.parseDouble(tCentigradeString); 0122: double tFahrenheit = 32 + tCentigrade*9/5; 0123: %> 0124: <html> 0125: <head> 0126: <title>Reply</title> 0127: </head> 0128: <body> 0129: <p> 0130: In Fahrenheit, this is <%= tFahrenheit %> 0131: </p> 0132: </body> 0133: </html>
The Netcraft survey points out that ‘over the last year JSP has been the fastest growing scripting technology after ASP.NET. JSP sites are often bigger, more complex, and better funded and run by larger organisations than sites using the more common scripting technologies’ [33]. Some examples of JSP sites are www.theaa.com, www.bt.com, www.explore.co.uk and www.opodo.co.uk.
0134: Publisher: dpchiesa 0135: Service Name: ZipToCityState 0136: Description: Retrieves valid City+State pairs for a given 0137: US Zip Code, or longitude/latitude for a zipcode. 0138: Also retrieves zipcodes for city/state pairs 0139: Implementation: MS .NET
http://www.winisp.net/cheeso/zips/ZipService.asmx?WSDL
The above URL ends in .asmx?WSDL.
The .asmx is a sure sign that this Web Service
has been implemented using Microsoft's .NET Framework.
One of the benefits of using .NET for Web Services
is that WWW pages that can be used
to test a Web Service are dynamically generated.
You can go to the first of these WWW pages
by using the above URL without the last 5 characters,
i.e., by going to:
http://www.winisp.net/cheeso/zips/ZipService.asmx
You should get a WWW page saying:
0140: The following operations are supported. For a formal definition, 0141: please review the Service Description. 0142: * ZipToLatLong 0143: * CityToZip 0144: * ZipToCityAndState 0145: * CityTo1Zip 0146: * ZipTo1CityAndState
This
is a list of the names of methods provided by the Web Service.
If you now click on one of these names, e.g.,
ZipTo1CityAndState,
another WWW page is dynamically generated.
It is the WWW page at:
http://www.winisp.net/cheeso/zips/ZipService.asmx?op=ZipTo1CityAndState
This WWW page provides a textbox
and an Invoke button.
If you type a zipcode like 94042 into the textbox and click on the Invoke button, the browser will contact the webserver at www.winisp.net to get it to execute the ZipTo1CityAndState method. After a little time, your browser will give you the following WWW page:
0147: <?xml version="1.0" encoding="utf-8"?> 0148: <string xmlns="http://dinoch.dyndns.org/webservices/"> 0149: MOUNTAIN VIEW CA 0150: </string>
So these WWW pages provide a way in which you can test the methods provided by a .NET Web Service.
If an external site wishes to contact a Web Service in order to get some method executed, it needs to indicate to the Web Service the name of the method and the arguments to be passed to the method. And the external site is expecting the Web Service to return a value to the external site.
As a Web Service is being hosted on a webserver, the obvious way of contacting a Web Service is to send an HTTP request to the webserver. In fact, the usual way of contacting a Web Service is to send the webserver an HTTP POST request where the body of the request is coded using an XML language called SOAP (Simple Object Access Protocol) [50]. The reply from the Web Service is sent as an HTTP reply usually with a body coded in SOAP.
If you return to
the WWW page containing the
zipcode textbox and the Invoke button,
i.e. to:
http://www.winisp.net/cheeso/zips/ZipService.asmx?op=ZipTo1CityAndState
you will see this WWW page also gives an example of
the HTTP request that can be used to contact the Web Service
and an example of the HTTP reply
that comes back from the Web Service.
An example of an HTTP request to this Web Service is:
0151: POST /cheeso/zips/ZipService.asmx HTTP/1.1 0152: Host: www.winisp.net 0153: Content-Type: text/xml; charset=utf-8 0154: Content-Length: XXXX 0155: SOAPAction: "http://dinoch.dyndns.org/webservices/ZipTo1CityAndState" 0156: 0157: <?xml version="1.0" encoding="utf-8"?> 0158: <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 0159: xmlns:xsd="http://www.w3.org/2001/XMLSchema" 0160: xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> 0161: <soap:Body> 0162: <ZipTo1CityAndState xmlns="http://dinoch.dyndns.org/webservices/"> 0163: <zip>94042</zip> 0164: </ZipTo1CityAndState> 0165: </soap:Body> 0166: </soap:Envelope>
The body of this HTTP POST request is written using the notation of the XML language called SOAP. Hiding in all this detail is the fact that we want to contact a webserver on the computer www.winisp.net to visit the page at /cheeso/zips/ZipService.asmx to execute the ZipTo1CityAndState method with an argument having the name zip and value 94042.
The reply from the webserver (after the appropriate method has been executed) is likely to have a body coded using SOAP. An example of a reply from www.winisp.net is:
0167: HTTP/1.1 200 OK 0168: Content-Type: text/xml; charset=utf-8 0169: Content-Length: YYYY 0170: 0171: <?xml version="1.0" encoding="utf-8"?> 0172: <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 0173: xmlns:xsd="http://www.w3.org/2001/XMLSchema" 0174: xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> 0175: <soap:Body> 0176: <ZipTo1CityAndStateResponse 0177: xmlns="http://dinoch.dyndns.org/webservices/"> 0178: <ZipTo1CityAndStateResult>MOUNTAIN VIEW CA</ZipTo1CityAndStateResult> 0179: </ZipTo1CityAndStateResponse> 0180: </soap:Body> 0181: </soap:Envelope>Hidden away in this XML is the fact that the result of the call is the value MOUNTAIN VIEW CA.
Even though the communication between the external site and the Web Service is coded in SOAP, normally we need not be concerned with the hard work of encoding and decoding the request and the reply: we will find that all of this is done by supporting software.
There are currently two products from the Apache Software Foundation for Web Services: there is version 2.3.1 of Apache SOAP and version 1.1 of a newer product called Axis.
The WWW pages for Axis are at http://ws.apache.org/axis/. The FAQ says that ‘Axis is essentially Apache SOAP 3.0. It is a from-scratch rewrite, designed around a streaming model (using SAX internally rather than DOM). The intention is to create a more modular, more flexible, and higher-performing SOAP implementation (relative to Apache SOAP 2.0).’
You can download Axis from http://ws.apache.org/axis/releases.html. Currently, the latest release of Axis is Release Candidate 2 of Axis 1.1. So I downloaded a file called axis-1_1rc2.zip. This 10.1MB .zip file would take about 50 minutes to download through a 33Kbps modem. The file contains one directory called axis-1_1RC2. I unzipped this to:
e:\so all the files are in the directory:
e:\axis-1_1RC2As the .zip file is a 10.1MB file, the unzipping takes a long time.
The details about how to get Axis to work are in the
Axis Installation Guide.
This is available at:
http://cvs.apache.org/viewcvs.cgi/~checkout~/xml-axis/java/docs/install.html
The first thing to do is to copy the axis subdirectory of:
e:\axis-1_1RC2\webappsto:
%CATALINA_HOME%\webapps
The Axis Installation Guide then suggests that you copy some XML parser files. I found that I did not have to do this step. However, if you need to do this, the Axis Installation Guide says that the XML parser files need to be put into the directory:
%CATALINA_HOME%\webapps\axis\WEB-INF\libAs the full distribution of tomcat comes with an XML parser, you could copy those files: so you could copy the files xercesImpl.jar and xmlParserAPIs.jar from:
%CATALINA_HOME%\common\endorsedto:
%CATALINA_HOME%\webapps\axis\WEB-INF\lib
Use a browser to go to the WWW page http://localhost:8080/axis/. You should get the WWW page welcoming you to Apache-Axis. The Axis Installation Guide now suggests that you click on the link:
Validate the local installation's configurationThis will run http://localhost:8080/axis/happyaxis.jsp.` You should examine the text of the resulting WWW page carefully. The Axis Installation Guide says ‘if any of the needed libraries are missing, Axis will not work’.
When I ran happyaxis.jsp, it gave me a WWW page with lots of information. However, towards the middle of the page I got:
The core axis libraries are present. 1 optional axis library is missing.So that is OK.
In the following sections of this document, we will be using many of the programs that come with Axis. We will need an appropriate classpath to execute these programs. An appropriate very long classpath will be set up in a Command Prompt window if the following commands are executed:
0182: set AXIS_HOME=E:\axis-1_1RC2 0183: set CLASSPATH=. 0184: set CLASSPATH=%AXIS_HOME%\lib\axis.jar;%CLASSPATH% 0185: set CLASSPATH=%AXIS_HOME%\lib\commons-discovery.jar;%CLASSPATH% 0186: set CLASSPATH=%AXIS_HOME%\lib\commons-logging.jar;%CLASSPATH% 0187: set CLASSPATH=%AXIS_HOME%\lib\jaxrpc.jar;%CLASSPATH% 0188: set CLASSPATH=%AXIS_HOME%\lib\saaj.jar;%CLASSPATH% 0189: set CLASSPATH=%AXIS_HOME%\lib\log4j-1.2.4.jar;%CLASSPATH% 0190: set CLASSPATH=%AXIS_HOME%\lib\wsdl4j.jar;%CLASSPATH%
Even though we have not yet produced a Web Service, we can use the Axis that we have just installed in order to access an external Web Service. We will suppose that we want to produce a program that contacts the Web Service (used earlier) that translates a zipcode into a place name.
Earlier, we stored the WSDL document of this Web Service in the file ZipService.wsdl. We now execute a program called WSDL2Java on this ZipService.wsdl file:
java org.apache.axis.wsdl.WSDL2Java ZipService.wsdlAs this is a program that comes with Axis, in order to execute the above command you will need to set up the very long classpath that was given earlier.
The WSDL2Java program looks at the WSDL document to discover things about the Web Service. It then generates a proxy class (aka a stub class). For each method of the Web Service, the proxy class will have a method that has the same parameters and the same return type. The idea is that if a client wants to call a method of the Web Service it instead calls the method of the proxy class that has the same name and parameters. Behind the scenes, the call of a method of the proxy class creates the HTTP request (with its complicated SOAP); sends it to the webserver providing the Web Service; waits for the reply; and then decodes the SOAP that is returned by the Web Service.
If, having executed the above WSDL2Java command, you then execute the command:
dir org\dyndns\dinoch\webservicesyou will see that the WSDL2Java program has created the files:
ArrayOfString.java Latitude.java LatLong.java Longitude.java ZipcodeLookupService.java ZipcodeLookupServiceLocator.java ZipcodeLookupServiceSoap.java ZipcodeLookupServiceSoapStub.javaThe file ZipcodeLookupServiceSoapStub.java contains the proxy class; the other files are supporting files.
The interface ZipcodeLookupService declares headers for get methods for each port that is listed in the service element of the WSDL document. In our example, it declares a header for a method called getZipcodeLookupServiceSoap. The class ZipcodeLookupServiceLocator is an implementation of this interface, and so it implements the getZipcodeLookupServiceSoap method. This method returns an instance of the proxy class ZipcodeLookupServiceSoapStub. This class implements the interface ZipcodeLookupServiceSoap: this interface just declares headers for each method that is provided by the Web Service.
Now we can produce a Java program that is going to be a client of this Web Service:
0191: import java.rmi. RemoteException; // MyTestClient.java 0192: import javax.xml.rpc. ServiceException; 0193: import org.dyndns.dinoch.webservices. ZipcodeLookupService; 0194: import org.dyndns.dinoch.webservices. ZipcodeLookupServiceLocator; 0195: import org.dyndns.dinoch.webservices. ZipcodeLookupServiceSoap; 0196: public class MyTestClient 0197: { 0198: public static void main(String[] pArgs) throws RemoteException, ServiceException 0199: { 0200: String tZipcodeString = pArgs[0]; 0201: ZipcodeLookupService tZipcodeLookupService = 0202: new ZipcodeLookupServiceLocator(); 0203: ZipcodeLookupServiceSoap tZipcodeLookupServiceSoap = 0204: tZipcodeLookupService.getZipcodeLookupServiceSoap(); 0205: String tCityAndStateString = 0206: tZipcodeLookupServiceSoap.zipTo1CityAndState(tZipcodeString); 0207: System.out.println(tCityAndStateString); 0208: } 0209: }
MyTestClient.java can be compiled and run using a particular zipcode by the commands:
javac MyTestClient.java java MyTestClient 94042The program should contact the Web Service and then output MOUNTAIN VIEW CA. Note: if your PC needs to use a proxy server to get to the internet, you will need a java command like:
java -Dhttp.proxyHost=proxy.site.com -Dhttp.proxyPort=8080 MyTestClient 94042
We can have a clearer idea of what information is being transferred if we run the tcpmon program (that comes with Axis). Instead of the client program contacting the Web Service directly we arrange for it to go through the tcpmon program. So the client program contacts tcpmon and tcpmon contacts the Web Service.
We need to make a few changes for this to occur. First, modify the WSDL file so that the URL of the Web Service refers to a port on the local computer. For example, the three occurrences of:
<http:address location="http://www.winisp.net/cheeso/zips/ZipService.asmx" />in the file ZipService.wsdl could be changed to:
<http:address location="http://localhost:8081/cheeso/zips/ZipService.asmx" />where 8081 is an arbitrarily chosen port. Then run the WSDL2Java program again in order to generate new proxy classes. Now, when you want to run MyTestClient, first type the following command in a Command Prompt window:
start java org.apache.axis.utils.tcpmon 8081 www.winisp.net 80As tcpmon is a part of Axis, it requires the very long classpath given earlier. Then, after the tcpmon window appears, type the following command in a Command Prompt window:
java MyTestClient 94042The tcpmon window should show you the HTTP request that goes to www.winisp.net and the HTTP response that comes back from that site.
There are many issues that need to be considered if you wish to secure Web Services. These are examined by the papers at [13], [18], [20] and [46].
Of course, writing a program in terms of a Web Service means that the program is dependent on a connection to the internet, the availability of the computer and the webserver of the Web Service, whether the Web Service still maintains the same contract as when the program was written, ... . Some of these reliability issues are addressed by the papers at [19] and [22].
We first look at the easiest way in Axis of providing a Web Service.
First, produce a Java class declaration containing the methods that you wish to be made available. For example, we could produce:
0210: public class Convert { 0211: public double toFahrenheit(double pCentigrade) { 0212: return 32 + pCentigrade*9/5; 0213: } 0214: }Store this text in a file with a .jws extension. In the above example, the text would be stored in a file called Convert.jws.
Put this file in the directory:
%CATALINA_HOME%\webapps\axisAnd that's it! You have a Web Service available at:
There is a Web Service here Click to see the WSDLIf you do click on Click to see the WSDL, your browser will go to:
Having provided a Web Service, you can now follow the steps given earlier if you want to provide a client that accesses this Web Service.
The Axis User's Guide says ‘JWS web services are intended for simple web services. You cannot use packages in the pages, and as the code is compiled at run time you can not find out about errors until after deployment’. The Axis User's Guide gives details of alternative ways of deploying Web Services which it says should be used for ‘production quality web services’.
In my view, the best way of deploying an Axis Web Service is to use the following six steps. It is one of the ways described in the Axis User's Guide.
The first step is to provide and then compile an interface declaration or a class declaration giving details of the methods that you wish to make available as a Web Service.
So we could provide the following interface declaration:
0215: package Pack; // Convert.java 0216: public interface Convert { 0217: public double toFahrenheit(double pCentigrade); 0218: }storing it in the file Convert.java in some subdirectory called Pack. We could then compile it:
javac Pack\Convert.javaThis produces the file Convert.class in the subdirectory Pack.
However, if we want our choice of the names of the parameters of methods to prevail, it is better to provide a class declaration and compile it with javac's debug option (-g). The class declaration could consist of method declarations with nearly empty bodies. For example, we could store:
0219: package Pack; // Convert.java 0220: public class Convert { 0221: public double toFahrenheit(double pCentigrade) { 0222: return 42; 0223: } 0224: }in the file Convert.java in the subdirectory Pack and then compile it using:
javac -g Pack\Convert.javato produce the file Convert.class in the subdirectory Pack.
Having produced a .class file, the Java2WSDL program can be used to create a WSDL file. Once again, this program is only accessible if the very long classpath that was given earlier has been set up.
As well as indicating the name of the class (Pack.Convert) and the name of the WSDL file (Convert.wsdl), the command line that is used to run the Java2WSDL program also needs to mention the namespace to be used in the WSDL file and the URL to be used as the endpoint of the Web Service. Here is a typical use of the program:
java org.apache.axis.wsdl.Java2WSDL -o Convert.wsdl ^ -l"http://localhost:8080/axis/services/Convert" ^ -n "urn:Convert" -p"Pack" "urn:Convert" Pack.Convert
The Java2WSDL program will ensure that the appropriate message, portType, binding and service elements are included in the WSDL file. If the Java interface/class refers to other interfaces/classes (that we have written), the WSDL file will also contain XML that can be used to represent those types. The Axis User's Guide points out that the Java2WSDL program supports bean classes, arrays and holder classes.
In this step, the WSDL2Java program is used again. Earlier we used it to create proxy files (aka stub files) for a client. It can also be used to create files needed to support the code of a Web Service.
Here is a typical use of the WSDL2Java program for this purpose:
java org.apache.axis.wsdl.WSDL2Java -o . -s -S true -Nurn:Convert Pack Convert.wsdl
When the WSDL2Java program is used with these options, it will examine the WSDL file (that is named as the final parameter) and generate eight or more files. In our example, these files will be created in the Pack subdirectory. There are three kinds of files:
Convert.java ConvertSoapBindingImpl.java ConvertSoapBindingSkeleton.java
deploy.wsdd undeploy.wsdd
ConvertService.java ConvertServiceLocator.java ConvertSoapBindingStub.java
One of the files created by the WSDL2Java program is a file containing an interface declaration that declares the methods of the Web Service. For example, given the above command line it would produce a file called Convert.java that contains:
0225: package Pack; // Convert.java 0226: public interface Convert extends java.rmi.Remote { 0227: public double toFahrenheit(double pCentigrade) throws java.rmi.RemoteException; 0228: }
It also produces a stab at an implementation of the Web Service in the file ConvertSoapBindingImpl.java:
0229: package Pack; // ConvertSoapBindingImpl.java 0230: public class ConvertSoapBindingImpl implements Pack.Convert { 0231: public double toFahrenheit(double pCentigrade) throws java.rmi.RemoteException { 0232: return -3; 0233: } 0234: }This file needs to be altered. In our example, the:
0232: return -3;needs to be replaced by:
0235: return 32 + pCentigrade*9/5;
The files of the Web Service need to be compiled and put into their correct places. I do this using commands like:
0236: cd Pack 0237: javac *.java 0238: mkdir "%CATALINA_HOME%\webapps\axis\WEB-INF\classes\Pack" 0239: copy Convert.class "%CATALINA_HOME%\webapps\axis\WEB-INF\classes\Pack" 0240: copy ConvertSoapBindingImpl.class "%CATALINA_HOME%\webapps\axis\WEB-INF\classes\Pack" 0241: copy ConvertSoapBindingSkeleton.class "%CATALINA_HOME%\webapps\axis\WEB-INF\classes\Pack" 0242: cd .. 0243: dir "%CATALINA_HOME%\webapps\axis\WEB-INF\classes\Pack"
One of the files resulting from running the WSDL2Java program is a file called deploy.wsdd. In our example, the WSDL2Java program produces a deploy.wsdd file containing:
0244: <deployment 0245: xmlns="http://xml.apache.org/axis/wsdd/" 0246: xmlns:java="http://xml.apache.org/axis/wsdd/providers/java"> 0247: <service name="Convert" provider="java:RPC" style="rpc" use="encoded"> 0248: <parameter name="wsdlTargetNamespace" value="urn:Convert"/> 0249: <parameter name="wsdlServiceElement" value="ConvertService"/> 0250: <parameter name="wsdlServicePort" value="Convert"/> 0251: <parameter name="className" value="Pack.ConvertSoapBindingSkeleton"/> 0252: <parameter name="wsdlPortType" value="Convert"/> 0253: <parameter name="allowedMethods" value="*"/> 0254: </service> 0255: </deployment>
Essentially this WSDD (Web Services Deployment Descriptor) file is describing:
Once this file has been created (by the WSDL2Java program), we can use the AdminClient program to get the Web Service deployed.
Here is a typical use of the AdminClient program:
java org.apache.axis.client.AdminClient Pack\deploy.wsdd
Note: you can check what Web Services are deployed either by executing the following command in a Command Prompt window:
java org.apache.axis.client.AdminClient listor by visiting the WWW page at:
Note: the following command can be used to undeploy a Web Service:
java org.apache.axis.client.AdminClient Pack\undeploy.wsdd
Microsoft's .NET Framework can also be used to produce Web Services and to access them. We now look at an example that illustrates how easy it is to interoperate Axis and .NET for Web Services. So we want the answers to the following questions:
The example we will look at provides a Web Service that accumulates the sum of a series of values. The Web Service is to provide a method called inc that has one parameter, and, each time a client calls inc, the value of the argument is added to a variable stored somewhere in the Web Service. So this Web Service is different from those we have looked at so far: it requires the Web Service to retain a value between successive calls.
Here is the code of the implementation of an Axis Web Service (called ATotal) that can be used to accumulate the sum of a series of values:
0256: package Pack; // ATotalSoapBindingImpl.java 0257: public class ATotalSoapBindingImpl implements Pack.ATotal{ 0258: private double iTotal = 0.0; 0259: public double inc(double pIncrement) throws java.rmi.RemoteException { 0260: iTotal = iTotal + pIncrement; 0261: return iTotal; 0262: } 0263: }If we proceed to use the six steps (described earlier) to produce an Axis Web Service, then each call of inc will use a different ATotalSoapBindingImpl object. What we want is for all calls of inc to access a specific ATotalSoapBindingImpl object. Besides getting a different object (called Request), Axis permits two other possibilities: either every call gets the same object (called Application) or a call can give the name of the object it wishes to use (called Session).
Step 3 of the six steps uses the WSDL2Java program. When executing this program, a -d option can be used to indicate which kind of Web Service is to be generated. So if a:
-d Sessionoption is supplied, as in:
java org.apache.axis.wsdl.WSDL2Java -o . -s -S true -d Session ^ -Nurn:ATotal Pack ATotal.wsdlthe line:
<parameter name="scope" value="Session"/>will appear in the file deploy.wsdd.
If this is done, the HTTP response from the first call of a method of the Web Service will include a header line like:
Set-Cookie: JSESSIONID=595B7DAE454938844EEB49EFFE67C6B3; Path=/axisIf a client wishes to access the object used in this call in later calls, it needs to capture this value and provide it in the header of the HTTP requests of subsequent calls to the Web Service, e.g.:
Cookie: JSESSIONID=595B7DAE454938844EEB49EFFE67C6B3
Here is an Axis client of the ATotal Web Service:
0264: import ATotal_pkg. ATotal; // MyTestClient.java 0265: import ATotal_pkg. ATotalService; 0266: import ATotal_pkg. ATotalServiceLocator; 0267: import ATotal_pkg. ATotalSoapBindingStub; 0268: import java.io. BufferedReader; 0269: import java.io. InputStreamReader; 0270: import java.io. IOException; 0271: import javax.xml.rpc. ServiceException; 0272: public class MyTestClient 0273: { 0274: public static void main(String[] pArgs) throws IOException, ServiceException 0275: { 0276: ATotalService tATotalService = new ATotalServiceLocator(); 0277: ATotal tATotal = tATotalService.getATotal(); 0278: ATotalSoapBindingStub tATotalSoapBindingStub = (ATotalSoapBindingStub)tATotal; 0279: tATotalSoapBindingStub.setMaintainSession(true); 0280: BufferedReader tKeyboard = new BufferedReader(new InputStreamReader(System.in)); 0281: while (true) 0282: { 0283: System.out.print("Increment: "); 0284: String tLine = tKeyboard.readLine(); 0285: if ( tLine.equals("") ) break; 0286: double tIncrement = Double.parseDouble(tLine); 0287: double tNewTotal = tATotal.inc(tIncrement); 0288: System.out.println("NewTotal is now: " + tNewTotal); 0289: } 0290: } 0291: }The client sits there waiting for the user to type some value. It thens calls inc which will add the value to the total being stored by the Web Service. The client then waits for another value to be typed. An empty line is used to terminate the execution of the client.
The code of this client is similar to the code of the Axis Web Service client given earlier. The only new idea is contained in the statements:
0278: ATotalSoapBindingStub tATotalSoapBindingStub = (ATotalSoapBindingStub)tATotal; 0279: tATotalSoapBindingStub.setMaintainSession(true);The call of setMaintainSession ensures that the session id is collected from the first HTTP response and is used in the HTTP requests of any subsequent calls of methods of the Web Service.
We could use Microsoft's Visual Studio.NET to create a client of the ATotal Web Service [12, 25]. For example, we could use the wizard that creates a client that is a Windows Form Application. The form could have a textbox to obtain the increment, a button to submit the query, and a label that can be used to report back the latest value of the total. By default, the wizard would create a class called Form with private variables called textbox1, button1 and label1. The wizard also generates a skeleton for a method called button1_Click and we could supply the following body for this method. (This code is given in C#.) This method would be executed whenever there is a click of the button:
0292: private void button1_Click(object sender, System.EventArgs e) 0293: { 0294: double tIncrement = double.Parse(textBox1.Text); 0295: double tNewTotal = iATotalService.inc(tIncrement); 0296: label1.Text = tNewTotal.ToString(); 0297: }This code assumes that iATotalService is declared as a private variable of the Form class:
private ATotalService iATotalService;and that it is pointing to an object of the proxy class for the Web Service. This object can be created using the statement:
iATotalService = new ATotalService();The client needs to store the cookie it receives from the first call of inc and pass it in the headers of the HTTP requests of subsequent calls. This can be done using the statements:
CookieContainer tCookieContainer = new CookieContainer(); iATotalService.CookieContainer = tCookieContainer;where CookieContainer is a class declared in the System.Net namespace. The last three statements need to be executed once. So they can go in the constructor of the Form class.
The above code assumes the existence of a
proxy class called ATotalService.
Visual Studio.NET has a wizard
(called
Add Web Reference)
that can be used to generate a proxy class.
It needs you to supply the URL of a WSDL document,
e.g.:
http://localhost:8080/axis/services/ATotal?WSDL
If you supply a URL (like this one)
that includes a port number,
you may find that
Visual Studio.NET ignores the port number when generating
the proxy class.
If this is the case,
you will need to edit the constructor of the proxy class
to include the port number:
public ATotalService() { this.Url = "http://localhost:8080/axis/services/ATotal"; }
We now suppose that, instead, the .NET Framework is being used to provide the Web Service [12, 24]. Visual Studio.NET has a wizard that makes it easy to create a Web Service. It generates most of the code for you in a class that is derived from a class called WebService (which is declared in the System.Web.Services namespace). All you need to do is to include in this class the code of the methods that you wish to make available. You will have to declare each of these methods with a WebMethod attribute.
You will need to do two things to get a value retained from one call of a method to the next:
Here is the full code (in C#) for the web method inc:
0298: [WebMethod(EnableSession=true)] 0299: public double inc(double pIncrement) 0300: { 0301: double tNewTotal; 0302: object tObject = Session["MTotal"]; 0303: if ( tObject == null ) 0304: tNewTotal = pIncrement; 0305: else 0306: tNewTotal = (double)tObject + pIncrement; 0307: Session["MTotal"] = tNewTotal; 0308: return tNewTotal; 0309: }
The HTTP response from the first call of a method of this Web Service will include a header line like:
Set-Cookie: ASP.NET_SessionId=50jlsti3jqa5gg45321sbkia; path=/A client needs to ensure that it captures this value and uses it in the header of the HTTP requests of subsequent calls to the Web Service, e.g.:
Cookie: ASP.NET_SessionId=50jlsti3jqa5gg45321sbkia
A WSDL document for accessing the Web Service provided by
www.amazon.com/www.amazon.co.uk
is at:
http://soap.amazon.com/schemas2/AmazonWebServices.wsdl
If you run the WSDL2Java program on this document,
it will produce a lot of .java files in
the directory:
com\amazon\soap
I compiled these files, produced a jar file of the resulting .class files, and then moved this jar file to the directory:
%CATALINA_HOME%\webapps\mytomcat\WEB-INF\libI ensured that this directory also included all the jar files needed to use Axis.
I then produced the following JavaServer page. You need to register with Amazon to use these facilities. For this reason, in the following code, my registration id has been replaced by XXXXXXXXXXXXXX. Setting the locale to uk is meant to get it to yield information from amazon.co.uk rather than amazon.com but this does not seem to be the case for some pieces of information. It seems it is part of the design of Amazon's Web Service to return null if a value is not available in time: it seems that their view is that it is better to be fast in providing some of the information rather than hanging around for all of it to become available.
0310: <html> 0311: <body> 0312: <%@page language="java" import="com.amazon.soap.*" %> 0313: <% 0314: try 0315: { 0316: String tIsbn = "0201711079"; 0317: AmazonSearchService tAmazonSearchService = new AmazonSearchServiceLocator(); 0318: AmazonSearchPort tAmazonSearchPort = tAmazonSearchService.getAmazonSearchPort(); 0319: AsinRequest tAsinRequest = new AsinRequest(); 0320: tAsinRequest.setTag("webservices-20"); 0321: tAsinRequest.setDevtag("XXXXXXXXXXXXXX"); 0322: tAsinRequest.setAsin(tIsbn); 0323: tAsinRequest.setLocale("uk"); 0324: tAsinRequest.setType("heavy"); 0325: ProductInfo tProductInfo = tAmazonSearchPort.asinSearchRequest(tAsinRequest); 0326: Details[] tDetailsArray = tProductInfo.getDetails(); 0327: String tProductName = tDetailsArray[0].getProductName(); 0328: String tReleaseDate = tDetailsArray[0].getReleaseDate(); 0329: String tOurPrice = tDetailsArray[0].getOurPrice(); 0330: String tListPrice = tDetailsArray[0].getListPrice(); 0331: String tUsedPrice = tDetailsArray[0].getUsedPrice(); 0332: String tSalesRank = tDetailsArray[0].getSalesRank(); 0333: String tAvailability = tDetailsArray[0].getAvailability(); 0334: String tImageUrlSmall = tDetailsArray[0].getImageUrlSmall(); 0335: String tImageUrlMedium = tDetailsArray[0].getImageUrlMedium(); 0336: String tImageUrlLarge = tDetailsArray[0].getImageUrlLarge(); 0337: %> 0338: <table><tr><td><img src="<%= tImageUrlMedium %>"></td><td> 0339: My book <em><%= tProductName %></em> is on sale at 0340: <a href="http://www.amazon.co.uk/exec/obidos/ASIN/0201711079/">www.amazon.co.uk</a> 0341: for <%= tOurPrice %>. 0342: <% 0343: if ( ! tOurPrice.equals(tListPrice) ) 0344: { 0345: %> 0346: <br>Its list price is <%= tListPrice %>. 0347: <% 0348: } 0349: %> 0350: <br>Grimly, it's also available there with a used price of <%= tUsedPrice %>! 0351: <br>Currently, its sales rank is <%= tSalesRank %>. 0352: <br>It was published on <%= tReleaseDate %>. 0353: </td></tr></table> 0354: <% 0355: } 0356: catch (Exception pException) 0357: { 0358: pException.printStackTrace(); 0359: } 0360: %> 0361: </body> 0362: </html>
Axis is not the only product for Web Services: there are many others that can be used.
As has already been mentioned, it is possible to provide a Web Service and to access Web Services using Microsoft's .NET Framework. For more details, see [12].
Sun provide a Java Web Services Developer Pack (Java WSDP) [43]. This assumes you have no existing specialist software (other than the Java 2 SDK), and it allows you to get started in a number of different areas besides Web Services.
Sun's WWW page says that the Java WSDP ‘is a free integrated toolkit that allows Java developers to build, test and deploy XML applications, Web services, and Web applications. The Java WSDP provides Java standard implementations of existing key Web services standards including WSDL, SOAP, ebXML, and UDDI as well as important Java standard implementations for Web application development such as JavaServer Pages and the JSP Standard Tag Library. These Java standard implementations allow developers to send and receive SOAP messages, browse and retrieve information in UDDI and ebXML registries, and quickly build and deploy Web applications based on the latest Java standard implementations’.
There is a lot of software (70MB) in the Java WSDP including Java Architecture for XML Binding (JAXB), Java API for XML Messaging (JAXM), Java API for XML Processing (JAXP), Java API for XML Registries (JAXR), Java API for XML-based RPC (JAX-RPC), SOAP with Attachments API for Java (SAAJ), JavaServer Pages Standard Tag Library (JSTL), Java WSDP Registry Server, Ant Build Tool and Apache Tomcat. The size of the file containing the download of the Java WSDP is 34.5Mb.