Stateful Web Services Practical Contents

advertisement
Stateful Web Services Practical
Contents
2Introduction .................................................................................................................. 3
2.1Formatting Conventions............................................................................................ 3
3The FileStore Service ................................................................................................... 4
3.1Interface Definition ................................................................................................... 4
3.2Resource Management .............................................................................................. 6
3.2.1Preamble ................................................................................................................ 6
3.2.2Generating resourceIDs ......................................................................................... 7
3.2.3Persisting State ....................................................................................................... 8
3.2.4Opening Resources .............................................................................................. 11
3.2.5Creating Resources .............................................................................................. 12
3.2.6Destroying Resources .......................................................................................... 13
3.2.7Further Issues ....................................................................................................... 14
3.3Uploading Files ....................................................................................................... 15
3.4Downloading Files .................................................................................................. 16
3.5Listing and Deleting Files ....................................................................................... 18
3.6Compile and Deploy ............................................................................................... 19
3.6.1Compilation.......................................................................................................... 19
3.6.2Configuration ....................................................................................................... 20
4The FileStore Client ................................................................................................... 23
4.1Stub Generation ...................................................................................................... 23
4.1.1Configuration ....................................................................................................... 23
4.1.2wscompile ............................................................................................................ 23
4.2Preamble ................................................................................................................. 24
4.3Resource Management ............................................................................................ 25
4.4Uploading Files ....................................................................................................... 26
4.5Downloading Files .................................................................................................. 27
4.6Listing and Deleting Files ....................................................................................... 28
4.7User Interface .......................................................................................................... 29
4.8Testing..................................................................................................................... 31
4.9Runtime service selection ....................................................................................... 32
5Enhancements ............................................................................................................ 34
5.1Securing the Service ............................................................................................... 34
1
5.1.1Never Trust a User ............................................................................................... 34
5.1.2Man-in-the-middle ............................................................................................... 35
6Development Environment ........................................................................................ 38
6.1Directory Structure.................................................................................................. 40
1 Introduction
The purpose of this tutorial is to build upon the foundation of the “Quote of the Day”
introductory web services tutorial, and guide you through the development of a much
more “real-world” web services application. Basic resource management, data
transfer and security will be covered, and by the end of the tutorial you should have
all the basic knowledge you need to develop and deploy useful, production web
services.
We will continue to use Java 2 Enterprise Edition (J2EE), Sun’s Java Web Services
Developers Pack (JWSDP), and the JWSDP version of the Tomcat applications server
(Tomcat-jwsdp). This suite of software was chosen due to it’s free availability,
stability, and backing by a reputable corporate vendor.
1.1 Formatting Conventions
Before we begin the tutorial, we will specify the formatting conventions that we will
use in the text in order to make it easier for you to read.
 Normal text is in 12 point Times New Roman

Code and other text that is quoted from files that you will
be working with is presented in 10 point Courier New, with a
light grey background and bars above and below

Important points and issues that you should be aware of will be
highlighted as bold text
2 When a line in a code quotation has to be split to fit
the page, the continuation of that line will begin
with a “»” symbol.The FileStore Service
The service and client that we will be writing in this tutorial is a very simple remote
file repository – a kind of Web Services FTP. Although simple, it will demonstrate
basic resource management, data transfer, and security techniques.
A user of the service will be able to create new resources that are password protected.
The user can then upload, download and delete files from the service, and also list the
contents of the resource.
The client will present a simple interactive text-mode interface, and expanding the
client with a GUI is trivial (but beyond the scope of this tutorial).
2.1 Interface Definition
The first step that we will take in creating our web service is to define the interface
that the service will provide and the client will expect.
2
The service is to provide to users a resource in which they can store their files.
Therefore we must give the user the means by which they can create, open, and
destroy these resources, giving us our first operations:



createResource – This will take a username and password, and return an
opaque resource identifier after it has created a new directory on disk in which
to hold the uploaded files, and made a record of the resourceID that has been
issued. This is the state that the web service will maintain.
openResource – This will also take a username and password, and returns a
resourceID, but only if the resource already exists. Akin to the “open folder”
operation in a normal file manager.
destroyResource – This takes the resourceID of an already open resource, and
destroys all trace of the state associated with that resource.
The use of a resourceID by our web service mimics the functionality of WSAddressing, and our service will follow the WS-RF resource usage pattern.
Open the file called “FileStoreService.java” under your
“tutorial\service\src\FileStore” folder. It should contain an empty interface definition,
and imports the two classes that are required for “remote method invocation”, i.e. web
service calls.
Now we define the methods that our service will expose to the world. We’ll start with
the “createResource” method. This should take a username and password, and return
the resourceID for the newly created resource. To keep things simple, our resourceIDs
will be simple strings. A WS-RF service would return a WS-Address object. Add the
following line of code inside our FileStoreService interface definition:
public String createResource(String username, String password)
» throws RemoteException;
The “openResource” method has an identical signature. Add this after the
“createResource” definition:
public String openResource(String username, String password)
» throws RemoteException;
The “destroyResource” takes a resourceID as its only parameter:
public void destroyResource(String resourceID)
» throws RemoteException;
Now that we have defined the methods by which a client can manage resources, we
need to define ways in which these resources can be used. As each resource is a mini
file repository, it is obvious that we will need to provide a means by which files can
be up and down loaded from the resource. It would also be nice to allow the client to
see what their resource contains, and to give them the ability to delete files from the
resource that are no longer needed. Therefore we come up with the following
additional methods:
 putFile – Requires a resourceID, filename, and data array.
 getFile – Requires a resourceID and a filename, and returns an array of bytes
containing the contents of the requested file.
3

listFiles – Takes a resourceID, and returns an array of strings containing the
names of all the files stored in the resource.
 deleteFile – Requires a resourceID and a filename.
These methods are all very self-explanatory. Note that every one requires a
resourceID, which is used by the service to determine which set of state the operation
should act upon. This is the approach that is being standardised with WS-RF.
The corresponding Java code looks like this, and be added to the FileStoreService
interface body:
public void putFile(String resourceID, String filename,
» byte[] data) throws RemoteException;
public byte[] getFile(String resourceID, String filename)
» throws RemoteException;
public String[] listFiles(String resourceID)
» throws RemoteException;
public void deleteFile(String resourceID, String filename)
»
throws RemoteException;
2.2 Resource Management
Now that we have defined our interface, it is time to start writing the implementation.
Open the file called “FileStoreServiceImpl.java” in your
“tutorial\service\src\FileStore” folder. The file contains a partially complete class
which has a number of convenience functions to make it easy and faster to write your
service.
2.2.1 Generating resourceIDs
ResourceIDs are values that we associate with resources. Each resource has its own
unique ResourceID, and the ResourceID is “opaque” to the client – i.e. the client can
infer no information from the ResourceID. From the client’s point of view, the
ResourceID is a “magic key” that allows it to access a remote resource. In order to
access a resource, the client must either create one, or open an existing resource. To
do this, a username and password must be supplied, which we use to generate a
ResourceID. We do this by combining the username and password, and then
generating the MD5 “digest” of the result. MD5 is the name of the algorithm. It takes
a sequence of bytes and produces a 64-byte value. Generating an MD5 digest is quite
fast, however it is computationally very expensive to run the algorithm in reverse, so
the username and password combination are relatively secure (although the service as
a whole isn’t – more on that later). If two sets of usernames and passwords differ by
only a single character, their respective MD5 digests will be vastly different, making
it impossible to guess a username and password from the difference between two
ResourceIDs. Here is the method that you need to enter into your
FileStoreServiceImpl class:
private String makeResourceID(String username, String password)
» throws java.security.NoSuchAlgorithmException,
» java.io.UnsupportedEncodingException
{
String digest;
4
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(username.getBytes());
md.update(password.getBytes());
digest = new String(md.digest());
return digest;
}
This small method will happily turn a username and password of any length into a 64
character MD5 digest. However, there is a problem with this method. The digest may
contain characters that are not XML safe – i.e. they are characters that have a special
meaning in XML, such as “<” and “>”. Using this method as-is in your service will
result in illegal character exceptions being raised. In order to make this XML safe we
need to encode it, and the easiest way of doing that is by encoding it like a URL.
Replace the line that generates the digest in the above code with this line:
digest = URLEncoder.encode(new String(md.digest()),
» "UTF-8");
2.2.2 Persisting State
The biggest difference between the FileStore service and the quote of the day service
is that we now have to maintain state. Files that are uploaded by clients will be stored
on the server’s hard disk. Each resource will be associated with a single directory
which contains all uploaded files, and the resource directory name will by the same as
the username supplied by the client when that resource was created.
We also have to maintain a mapping between ResourceID and username, so that the
service can locate the correct resource directory when a client invokes a method. We
will do this with a hash table, using ResourceIDs as keys.
Before we start writing the code for handling the service’s state, there is one last issue
that we must deal with. What happens when the server is restarted, given the service
as currently described? All of the uploaded resources are safely stored on the hard
disk. However, the hash table that maps ResourceID to resource directory name will
have been destroyed, so there is no way in which a user can open an existing resource,
and creating a new resource with the same name as a previous resource that has been
forgotten about could provoke strange behaviour, or even be a security risk. There
fore we must ensure that the hash table persists between server restarts. We will do
this by writing it out to a file on disk. When ever the service restarts, it will look for
this file to see if it exists, and if so it will use its contents to initialise the hash table,
thus persisting that last bit of state that we need.
The constructor for the FileStoreServiceImpl class will be responsible for restoring
the services' state. First the constructor must check to see if the directory that will
contain all of our repositories exists and is accessible by the service. Add the
following to the constructor method body:
File root = new File(rootDirectory);
// If our root directory does not exist, create it
if(root.exists() != true) root.mkdir();
if(root.exists() && !root.isDirectory())
5
» throw new RemoteException(rootDirectory + " not a directory");
if(root.exists() && root.isDirectory() &&
»
(!root.canRead() || !root.canWrite()))
throw new RemoteException("Read and write
» permission is needed on directory " + rootDirectory);
So now our service will try and access its “root directory” when it starts up. If the
directory doesn’t exist, it creates it. We raise an exception if the directory exists but
can’t be read from, can’t be written to, or if it isn’t a directory.
The remainder of the constructor handles the loading of the resourceID table from a
text file:
resourceFile = new File(rootDirectory, "resources.dat");
resourceList = new HashMap();
try {
loadResourceData();
} catch (Exception e){
throw new RemoteException(e.getMessage());
}
The “loadResourceData” method performs the data file parsing, and is separate from
the constructor to improve code reuse, readability, etc. Note the try…catch block
around the loadResourceData method call – this is so that any locally generated
exceptions, such as IO exceptions, and caught and then redirected to the remote client.
The loadResourceData method has been provided for you.
Of course, now that we have a means of loading saved state into our service, we also
need a method which will save, or “serialise”, the ResourceID hash table. This
method is very simple, straight forward Java code with no Web Service bits, and has
been provided for you.
Your service should now have the ability to load and save the state of its resource list.
Next we’ll give the client the means by which to manipulate those resources.
2.2.3 Opening Resources
When a client asks to open a resource, the client is really asking the service “here is
the username and password of my user – can I access his resource please?” Our
“openResource” method will use the username and password to create a ResourceID.
If that ResourceID exists in the services resource list, then the ResourceID is returned.
If it doesn’t, then an exception is thrown. The method is very straight forward, and
should be added to your FileStoreServiceImpl class:
public String openResource(String username, String password)
» throws RemoteException {
String resourceID = "";
6
try{
resourceID = makeResourceID(username, password);
} catch(Exception e){
throw new RemoteException(e.getMessage());
}
if(!resourceList.containsKey(resourceID))
throw new RemoteException("No such resource");
return resourceID;
}
Note that we are using another try…catch block around our helper function so that we
can intercept any unexpected errors and send them on to the client.
2.2.4 Creating Resources
Create a new resource is a little more involved than merely opening an existing one.
Like openResource, we have to generate a resourceID from the supplied username
and password. Once we have that, we have to check that it doesn’t already exist in the
resource hash table; if it does, then we know that someone else is using the same
username and password, and we send an exception back to the client. We also check
to make sure that the username alone has not already been used, and again we send a
descriptive remote exception back to the client if it has. The code to do all this is here:
public String createResource(String username, String password)
» throws RemoteException {
String resourceID = "";
try{
resourceID = makeResourceID(username, password);
} catch(Exception e){
throw new RemoteException(e.getMessage());
}
if(resourceList.containsKey(resourceID))
throw new RemoteException("ResourceID " +
» resourceID + " already exists");
if(resourceList.containsValue(username))
throw new RemoteException("Username " + username +
» " already exists");
}
7
Now we have to do the real work. The first task is to create a new directory under the
repository’s root directory, using the username as the directory name. Add this code
to the end of the createResource method:
File userDirectory = new File(rootDirectory +
» File.separator + username);
if(userDirectory.exists())
throw new RemoteException(userDirectory.getPath() +
» " already exists!");
userDirectory.mkdir();
Now, we have to add a resourceID to username mapping to the resource list hash table
and return the new resourceID to the client:
resourceList.put(resourceID, username);
return resourceID;
Now, we could leave the method as is, however when the server is restarted, the new
resource will be lost – we didn’t save the resource list after modifying it. Add the
following code before the return statement in the createResource method:
try{
saveResourceData();
} catch(Exception e){
throw new RemoteException(e.getMessage());
}
This calls the method we wrote earlier to save the resource list to a file on disk. Note
again that we intercept local exceptions and pass them on to the client to think about.
When your service modifies persistent state, it should make every attempt to commit
those changes to stable storage as soon as possible. In our case, we are vulnerable to a
fault killing the server after the resource list has been updated, but before it is saved to
disk, which could lead to a client believing that a resource has been created for them
when in fact it hasn’t. Using a proper database table to manage the resourceID list
would avoid this issue, but is beyond this tutorial.
2.2.5 Destroying Resources
The final piece of the resource management functionality is the destruction of
resources. This is basically the reverse of the createResource method. Add the
following method to your FileStoreServiceImpl class:
public void destroyResource(String resourceID)
» throws RemoteException {
}
The first thing that this method must do is check that the supplied resourceID actually
exists; if it doesn’t, then throw an exception:
if(!resourceList.containsKey(resourceID))
8
throw new RemoteException("Invalid resourceID");
Next, we build the path to the resource directory that will be deleted, then we remove
the reference to the resourceID from the resource list:
File resourceDir = new File(rootDirectory, (String)
» resourceList.get(resourceID));
resourceList.remove(resourceID);
Immediately after removing the resourceID from the resource list, we save the
resource list to disk:
try{
saveResourceData();
} catch(Exception e){
throw new RemoteException(e.getMessage());
}
Next, we iterate through the contents of the resource, and delete each file that the user
has uploaded:
String fileList[] = resourceDir.list();
for(int i = 0; i < fileList.length; i++)
{
File target = new File(resourceDir, fileList[i]);
if(target.exists() && target.isFile())
» target.delete();
}
And finally, we remove the resource directory from the root directory:
resourceDir.delete();
There should now be no trace of the resource left in the system.
2.2.6 Further Issues
There are some things that you should bear in mind when developing a production
system that deals with resource management. The first, mentioned earlier, is that your
system must be prepared to recover from a crash at the most inappropriate moment,
such as when you’ve updated an in-memory resource, but have not yet mirrored the
change to disk. Databases such as MySQL, PostGreSQL, Oracle, DB2 etc. are
designed to handle this sort of eventuality, and using them to store critical, persistent
data is usually a very good idea and can save a lot of development effort (use
whatever transaction mechanism they provide – thus your data is either updated or it
isn’t, it will never be left in an unknown, inconsistent state).
Another issue of concern for production systems is that of thread safety. Most web
servers, and hence web services containers, use a pool of process threads to service
incoming requests. This means that your client may not be talking to the same
instance of the service for every method call that it makes. This is a problem if your
9
service has local variables that persist between calls, like our resourceList. Because
local variables are private to each instance, a change made by one thread may not
propagate to the other threads. The way to avoid this, if it is an issue with your
container suite, is to either use a shared memory mechanism which allows all of the
threads to access the same memory (but which introduces all sorts of locking and
access headaches), or use a decent database to store the shared data. The database will
handle all concurrent access issues, again saving you a lot of effort, although you will
suffer a performance penalty due to the extra layers of software you have to deal with.
2.3 File Handling
The final set of functionality that we need to implement in the server is the file
handling. The user must be able to upload, download, list and delete files from his or
her repository. The methods have very little web-service specific code in them, and
have been provided for you. You should read the methods to make sure you
understand what they do. The only real difference between these methods and those
you would use with a stand-alone application is that all exceptions are caught and repackaged as remote exceptions so that they can be sent to the client.
The methods all take a resourceID which they use to resolve the directory on disk that
the operation is to be performed in, and all are declared capable of throwing a remote
exception.
It should be noted that transferring data by passing it as a method call parameter is a
rather inefficient way to do it. The byte array is encoded into XML using base-64
encoding, where only 64 values are used to represent binary data (thus making it safe
for embedding in XML). The problem with this is that it bloats the data to four times
its original size, slowing down both client and service, and clogging the network for
other users. The solution to this problem is to send the data “out of band”, using the
web service.
10
2.4 Compile and Deploy
The service should now be complete, and all we have left to do is to compile it and
deploy it to a web services container.
2.4.1 Compilation
The first step is to compile the interface and the implementation into Java class files.
Make sure that you are in your tutorial\service\src\ directory, and run the following
command:
javac –d ..\war\WEB-INF\classes\ filestore\*.java
This compiles your Java source files into executable class files, which are placed in
the “tutorial\service\war\WEB-INF\classes\” directory. This command will exit
silently if it succeeds, other wise it will display a list of errors that it encountered, and
where it encountered them.
2.4.2 Configuration
Next we have to write a couple of XML files that configure the service during
deployment.
2.4.2.1 web.xml
The first that you’ll write is called “web.xml”, and it should be created in the
“tutorial\service\war\WEB-INF\” directory. It is very simple, and is used by the
Tomcat server software:
<?xml version="1.0" encoding="UTF-8" ?>
<web-app>
<display-name>FileStore service</display-name>
<description>File storage example service</description>
</web-app>
This simply gives Tomcat a name and description with which to associate the service
in its administration interfaces.
2.4.2.2 jaxrpx-ri.xml
The other XML file that you must write is a Sun JAX-RPC dependent file, that is used
by the “wsdeploy” tool (which you will use in a little while), to convert the code that
you have written into a deployable archive.
Create a new file called “jaxrpc-ri.xml” in your “tutorial\service\war\WEB-INF\”
directory. The first thing that we must do inside this file, is declare the root
“webServices” tag and its various namespace declarations:
<?xml version="1.0" encoding="UTF-8" ?>
<webServices
xmlns="http://java.sun.com/xml/ns/jax-rpc/ri/dd"
version="1.0"
targetNamespaceBase="http://nesc.ed.ac.uk/wsdl"
typeNamespaceBase="http://nesc.ed.ac.uk/types"
11
urlPatternBase="/ws">
</webServices>
The “targetNamespaceBase” and “typeNamespaceBase” will be used when generating
the WSDL for your service. Next we must define an “endpoint” for your service,
inside the “webServices” tag:
<endpoint
name="FileStore"
displayName="Simple Remote File Storage Service"
description="A simple web service that stores files"
interface="FileStore.FileStoreService"
implementation="FileStore.FileStoreServiceImpl"/>
Together with the user-friendly “displayName” and “description”, this tag also tells
wsdeploy the name of the interface and implementation of your service.
Finally, we have to tell wsdeploy how the service is going to be mapped to a URL on
the application server (in our case Tomcat). This is done with an “endpointMapping”
tag, again inside the “webServices” tag, immediately following the “endpoint” tag:
<endpointMapping
endpointName="FileStore"
urlPattern="/filestore"/>
This will expose your service at the address “http://your_machine/FileStore/filestore”.
2.4.2.3 Create the WAR archive
The next stage is to package up everything that you have written so far in to a
temporary Web Application Resource, or WAR, file. First, make sure that you are in
the “tutorial\service\war” directory. Now run the following command at your
command prompt:
jar cfv temp.war *
The “jar” command is a standard Java utility for packaging up Java programs and
resources. (Type “jar” and press return to see a list of command line options). The
output from the command should look very similar to this:
added manifest
adding: WEB-INF/(in = 0) (out= 0)(stored 0%)
adding: WEB-INF/classes/(in = 0) (out= 0)(stored 0%)
adding: WEB-INF/classes/FileStore/(in = 0) (out= 0)(stored 0%)
adding: WEB-INF/classes/FileStore/FileStoreService.class(in = 664)
» (out= 279)(deflated 57%)
adding: WEB-INF/classes/FileStore/FileStoreServiceImpl.class(in =
» 5784) (out= 2870)(deflated 50%)
adding: WEB-INF/jaxrpc-ri.xml(in = 665) (out= 288)(deflated 56%)
adding: WEB-INF/web.xml(in = 203) (out= 138)(deflated 32%)
12
The final task that you must complete before you can deploy your service is the
conversion from the platform-independent WAR file that you have just created, to one
that is specific to Sun’s Java WSDP system. We do this with the “wsdeploy”
command, supplied by the JWSDP. We run it in your “tutorial\service\war” directory,
again from the command prompt:
wsdeploy –o FileStore.war temp.war
This command will complete silently if it succeeds. The “-o” flag and parameter tell
wsdeploy the name of the platform-specific WAR file that you are trying to create.
The other parameter is the name of the source WAR file. You should now have a
WAR file that is ready to deploy on the JWSDP version of Tomcat that is installed on
your machine. The simplest way to do this is to direct your web browser to
http://localhost:8080/ (note that the port number may be different on your machine –
the instructors will inform you of the correct port for your installation – port 8080 is
typical, and will be used throughout this tutorial), and click on the “Web Application
Manger” link under the “Tomcat Tools” title (the username and the password will be
given to you by the instructor), you should see a list of services that are installed on
the server. At the bottom of the page is a “Deploy” box. In the “WAR file to deploy”
subsection, use the “browse” button to find and select your “FileStore.war” file (it
should be under “My Documents\FileStore tutorial\service\war”). Click the deploy
button. When the page refreshes you should be able to see a “FileStore” entry in the
list of deployed services, and a message box at the top of the page saying:
OK - Deployed application at context path /FileStore
If you enter the following address in your browser, you should be able to see the
information about your service:
http://localhost:8080/FileStore/filestore
Following the WSDL link, or entering:
http://localhost:8080/FileStore/filestore?WSDL
3 This will display the WSDL that wsdeploy
generated for your service. Now we just need to
write the client.The FileStore Client
Compared to the FileStore service, the client is extremely simple, and is little more
than a user interface to web services method call converter. The user interface will be
a simple, interactive text mode interface. Writing a full-blown windowed graphic
interface would work in a similar manner, but the additional work has nothing to do
with web services and so won’t be covered here.
3.1 Stub Generation
Before we start to implement the code needed by the client, we must first generate the
classes that will allow our client to talk to the web service. These classes, called
“stubs”, are generated by a tool “wscompile” from the WSDL of a service. A stub
class has the same interface as your web service, i.e. it has all of the same method
calls available, so your client code can “talk” to you service as though it were a
locally implemented class. Behind the scenes, the stub converts the method call and
parameters into a SOAP request that it sends off to your service. The SOAP response
13
that it receives back from the service is converted back into native Java, and the result
returned to the client code.
3.1.1 Configuration
The first thing we must do is write the configuration file that will tell wscompile the
information that it needs to generate the stubs. Create a new file called “config.xml”,
in your “tutorial\client\” directory. It is very short, and should contain the following:
<?xml version="1.0" encoding="UTF-8" ?>
<configuration xmlns="http://java.sun.com/xml/ns/jax-rpc/ri/config">
<wsdl location="http://localhost:8080/FileStore/filestore?WSDL"
packageName="filestore" />
</configuration>
The “location” attribute of the “wsdl” tag tells wscompile where to get the WSDL
from. This is a URL, so it can download a WSDL file from the internet, a local
service (as in this case), or a file on disk. You should set it to the address of the
WSDL file that you examined earlier. The “package” name attribute tells wscompile
the name of the Java package which you would like the generated classes to be
contained within.
3.1.2 wscompile
Once you have your “config.xml” file written, you need to run wscompile. Run the
following command at the command prompt in your “tutorial\client\” directory:
wscompile –gen:client –keep –d . config.xml
The “-gen:client” parameter tells wscompile that you want it to generate the client
side stub classes. “-keep” tells it not to delete the generate Java source files after it has
compiled them to class files. This is so you can read them to find out the names of the
stub classes that you will need to import into your client. wscompile will exit silently
upon successful completion. Assuming that wscompile ran without errors, you should
now have a “tutorial\client\filestore\” directory, which contains a long list of autogenerated Java and class files. The two class that we are interested in are called
“FileStoreService_Stub” and “FileStore_Impl”. It is probably a good idea that you
have a quick look inside these files to see the kind of things that the auto-generated
classes have to do to interface your code with a web service via SOAP.
3.2 Client code
All of the code that you write for your client will be contained in a single Java file,
called FileStoreClient.java, located in your “tutorial\client\” directory. A large part of
this code has been provided for you, as it is standard Java with no web service related
functionality. Please open this file for editing.
First, we have to import the two auto-generated classes that were mentioned earlier.
Remember that we had wscompile place them in the “filestore” package:
import filestore.FileStoreService_Stub;
import filestore.FileStore_Impl;
The FileStore_Impl class is responsible for connecting your client to the service,
while the FileStoreService_Stub is the actual stub class that has all the same method
calls as your web service.
14
The “main” method of the client will do all of the work, and is where you will be
adding code. Our method must keep track of the currently open resource, and it keeps
a record of the current username for user interface purposes. You should see these
variable declarations at the start of the “main” method:
String currentResourceID;
String username;
FileStoreService_Stub stub;
The “currentResourceID” will store the resourceID of the resource that is currently
open and being operated on. “username” is just used to remember the name of the
resource that is currently open, and is displayed as part of the interactive prompt to
make the users life a little easier. The “stub” variable is where we store the active stub
class that lets us talk to the web service. All method calls will go through this.
Next we must setup the stub class so we can communicate with the remote service.
Add the following to the main method:
FileStore_Impl factory = new FileStore_Impl();
stub =
» (FileStoreService_Stub)factory.getFileStoreServicePort();
The first line creates a new FileStore_Impl class. This can be thought of as a stub
“factory”, responsible for the creation and initialisation of stub classes for this
particular service. The second line uses the “factory” to create a stub class, which we
store in our “stub” variable so that we can use it later. This is the only real web
services specific code in the entire client.
15
3.3 Resource Management
Because the remainder of the code has very little to do with web services, we will
discuss it only briefly. We’ll deal with resource management first. The following
should be added to the location labelled “createResource” in the command-processing
loop in the main method:
username = commandBits[1];
currentResourceID = stub.createResource(username,
» commandBits[2]);
System.out.println("Resource created");
The method for opening an existing resource is functionally identical to the method
for creating them. Add this to under the label “openResource”:
username = commandBits[1];
currentResourceID = stub.createResource(username,
» commandBits[2]);
System.out.println("Resource opened");
Finally we must be able to destroy resources. Add the following under the
“deleteResource” label:
username = "No Open Resource";
stub.destroyResource(currentResourceID);
System.out.println("Resource destroyed");
3.4 File Handling
Two helper methods have been supplied which handle the loading and saving of files,
as this is straight forward Java code.
You should add this code under the “putFile” label:
byte[] data = readFile(commandBits[1]);
stub.putFile(currentResourceID, commandBits[1], data);
The code for downloading files is the mirror image of the code for uploading files.
Under the “getFile” label add:
byte[] data = stub.getFile(currentResourceID,
» commandBits[1]);
writeFile(commandBits[1], data);
16
3.5 Listing and Deleting Files
The code for listing files is also very simple. Once we have our string array from the
web service via the stub class, we iterate through it displaying a nice list for the user
to read. Add this code under the “listFiles” label:
String[] fileList = stub.listFiles(currentResourceID);
System.out.println("Available Files:");
for(int i = 0; i < fileList.length; i++)
System.out.println(" - " + fileList[i]);
Deleting files from a resource is about as simple as we get in this client, with only two
lines, one of which is purely for the user’s information. Add this under the
“deleteFile” label:
stub.deleteFile(_currentResourceID, filename);
System.out.println(filename + " deleted");
3.6 Testing
You should now have completed your FileStore web service, deployed it to a web
services container (Tomcat-JWSDP in our case), and written your client to interact
with it. The last thing you need to do now before you can start using your client and
service, is to compile the client. Use the following command at a command prompt in
your “tutorial\client\” directory:
javac FileStoreClient.java
This command will exit silently if successful. If not, it will give you a list of errors
that you need to correct before trying again.
You can now run your client:
java FileStoreClient
You should be presented with the user help that you wrote and a prompt. Here is an
example interaction:
…\FileStore Tutorial\client>java FileStoreClient
FileStore Web services tutorial
------------------------------Commands:
new <username> <password>
- Create a new resource
open <username> <password> - Open an existing resource
destroy
- Destroy the current resource
list
- List all your uploaded files
get <filename>
- Download the named file
put <filename>
- Upload the local file
del <filename>
- Delete file from the resource
quit
- Exit the client
No Open Resource >new ojm wibble
17
Resource created
ojm >put config.xml
Uploaded 183 bytes
ojm >put FileStoreClient.java
Uploaded 5140 bytes
ojm >put FileStoreClient.class
Uploaded 4803 bytes
ojm >list
Available Files:
- config.xml
- FileStoreClient.class
- FileStoreClient.java
ojm >del FileStoreClient.class
FileStoreClient.class deleted
ojm >list
Available Files:
- config.xml
- FileStoreClient.java
ojm >destroy
Resource destroyed
No Open Resource >quit
The next section of this tutorial will discuss some issues and enhancements that can
be made to this service and client.
3.7 Runtime service selection
The client that we have written so far works well enough. However, it can only talk to
one service, the one that originated the WSDL that you used to generate the stub
classes. It would be much more practical and useful if the client could talk to any
service that provided the FileStore interface. Fortunately, it is very easy to modify
your client to do this.
First, open your “tutorial\client\FileStoreClient.java” file for editing.
All we need to do is tell our stub that we want to use a different end point. Add the
following line after you initialise the stub, in your “main” method:
_stub._setProperty(Stub.ENDPOINT_ADDRESS_PROPERTY, args[0]);
Now all you have to do is save your changes and recompile your client. Test your
changes first by starting the client using your machine as the service:
java FileStoreClient http://localhost:8080/FileStore/filestore
Assuming that works, you can now try connecting to another student’s machine. The
tutor will tell you exactly what the URL should be.
18
4 Enhancements
There are a number of enhancements that should be considered before deploying our
FileStore service on a live, public server. These enhancements are left as areas for
students to explore if they finish the tutorial with time to spare, or wish to learn more
after the tutorial.
4.1 Securing the Service
The service that you have written so far has a number of security vulnerabilities that a
malicious user or third party could exploit in order to disrupt your service, or
commandeer the server itself.
4.1.1 Never Trust a User
The first issue to be aware of is that you can never trust a user. Be it an innocent
newbie or a vindictive employee, users can and will find holes in your service and
exploit them. Our service trusts the user in several ways. Firstly, we use usernames as
directory names with out doing any kind of validity checking. Start up your client and
try the following:
FileStore Web services tutorial
------------------------------Commands:
new <username> <password>
- Create a new resource
open <username> <password> - Open an existing resource
destroy
- Destroy the current resource
list
- List all your uploaded files
get <filename>
- Download the named file
put <filename>
- Upload the local file
del <filename>
- Delete file from the resource
quit
- Exit the client
No Open Resource >new ..\wibble bob
Resource created
Now have a look at your home directory. There is a “wibble” folder – the user has
managed to escape the directory that we are using to isolate all of the FileStore
resources and files. Luckily our service checks to see whether or not a directory or file
exists before attempting to create it, so we should have any problems with users overwriting critical system files and folders, but it is still a security threat when users can
get out of the “sandbox” that our service is attempting to maintain.
Another example of this kind of risk is using user-supplied values in an SQL database
statement. If the data isn’t properly escaped and tested for safety, then the user could
potentially run any arbitrary SQL command they please.
The solution to this is to always check a users input for validity before using it. In our
case, it should be enough to ensure that all slash characters are removed from
usernames and filenames before they are used. The service could either remove the
characters and continue regardless using a name the user didn’t expect, or the service
could raise an exception to inform the user that they have entered illegal characters
(this is probably the best solution).
19
You should implement this security fix on your service, and test it and other student’s
services to see if this security hole has been filled.
4.1.2 Man-in-the-middle
Our service uses username and password combinations to access resources, and
resourceIDs to manipulate resources. Both the username and password combinations
and the resourceIDs that are derived from them should be very hard to guess,
assuming sensible choice of password. However, these values are sent “across the
wire” between the client and service in plain sight of anyone on the network path with
a “packet sniffer” – a piece of software that intercepts TCP/IP packets as they move
through a network. Thus our service is wide open to this kind of attack. The way to
solve this insecurity is to encrypt the data transport link between the client and the
server. Although the man-in-the-middle can still see our network traffic, it will all
appear to be random characters.
The most widely used transport encryption protocol is SSL – the Secure Socket Layer.
This uses public key encryption technology to ensure that only the server and client
can read the data (a discussion about encryption technology is beyond the scope of
this tutorial).
The best part about SSL is that, at least with Tomcat, we need make no changes to
either our client or our service code. All the setup we have to do is in the Tomcat
configuration then we simply use a different URL with our client to take into account
the new protocol and port number. Refer to the tomcat website on how to setup SSL
(http://jakarta.apache.org/tomcat/tomcat-4.0-doc/ssl-howto.html).
4.2 Data Transfer
As mentioned earlier, transferring data between client and service via method
parameters is generally a bad idea. The XML encoding of the data both bloats the
data, and slows down the code. There are several ways to remedy the situation. The
first is to use SOAP attachments, which are similar to email attachments. The SOAP
message contains several parts, each with its own MIME type. This way, your data
can be added as a binary attachment to the SOAP message. However, this does
require more work on both client and service as they must manipulate the SOAP
message directly.
5 Another option is to use web service method calls
to co-ordinate an “out of band” data transfer, using
common methods such as SSH or
FTP.Development Environment
This practical guided you through the creation and deployment of a stateful Java web
service and client. The following software packages are needed:
 Java 2 Enterprise Edition (J2EE) 1.4
(http://java.sun.com/j2ee/index.jsp)
 Java Web Services Developer Pack (JWSDP) 1.1
(http://java.sun.com/web services/jwsdp/index.jsp)
 Tomcat-JWSDP
(http://java.sun.com/web services/containers/tomcat_for_JWSDP_1_4.html)
20
There are versions of these packages available for most operating systems.
These software packages should already be installed on your tutorial machines. If not,
or you are installing all the software on your own machine, please ensure that you
install the JWSDP package last, and that when asked to select an application server
you make sure that the JWSDP version of Tomcat that you have installed is in the list
and selected (if not, use the browse button).
Developing and using Java web services requires a relatively complex set of
environment variables. For occasional developers, it is convenient to set these
variables in a batch file or script that you run every time you open a commandprompt. Alternatively, these settings can be permanently added to the commandprompt shortcut under windows, or your shell setup under Unix. Here is what you
should add to a “classpath.bat” file in your tutorial directory (note that only “set”
commands are in this listing, any line that doesn’t start with “set” is a
continuation of the previous line):
set WS_HOME=\Sun
set JWSDP_HOME=\tomcat-jwsdp-1.4
set PATH=%WS_HOME%\Appserver\bin;
» %WS_HOME%\Appserver\jdk\jre;
» %WS_HOME%\Appserver\jdk\bin;
» %JWSDP_HOME%\apache-ant;
» %JWSDP_HOME%\apache-ant\bin;
» %JWSDP_HOME%\jwsdp-shared\bin;
» %JWSDP_HOME%\bin;
» %JWSDP_HOME%\jaxr\bin;
» %PATH%
set JAVA_HOME=%WS_HOME%\Appserver\jdk
set J2EE_HOME=%WS_HOME%\Appserver
set JAXRPC_HOME=%JWSDP_HOME%\jaxrpc
set JAXR_HOME=%JWSDP_HOME%\jaxr
set ANT_HOME=%JWSDP_HOME%\apache-ant
set JAVA_XML_HOME=%JWSDP_HOME%
set JAXM_HOME=%J2EE_HOME%\imq\demo
set JAXM_LIB=%J2EE_HOME%\imq\lib
set JAXP_HOME=%JWSDP_HOME%\jaxp
set JAXP_LIB=%JWSDP_HOME%\jaxp\lib
set JAXRPC_LIB=%JWSDP_HOME%\jaxrpc\lib
set XERCES_JAR=%JAXP_HOME%\lib\
» endorsed\xercesImpl.jar
21
set JSSE_HOME=%J2EE_HOME%\jdk\jre\lib
set SAAJ_HOME=%JWSDP_HOME%\saaj
set CLASSPATH=.;
» %WS_HOME%\Appserver\jdk\lib\tools.jar;
» %WS_HOME%\Appserver\jdk\lib\rt.jar;
» %JAVA_HOME%\jre\javaws\javaws.jar;
» %J2EE_HOME%\lib\activation.jar;
» %JWSDP_HOME%\jwsdp-shared\lib\mail.jar;
» %JAXRPC_HOME%;
» %JAXR_HOME%;
» %JAXRPC_HOME%\lib\jaxrpc-api.jar;
» %JAXRPC_HOME%\lib\jaxrpc-impl.jar;
» %JAXRPC_HOME%\lib\jaxrpc-spi.jar;
» %SAAJ_HOME%\lib\saaj-api.jar;
» %SAAJ_HOME%\lib\saaj-impl.jar;
» %JAXP_HOME%;
» %JAXP_LIB%\jaxp-api.jar;
» %JAXP_LIB%\endorsed\xalan.jar;
» %JAXP_LIB%\endorsed\sax.jar;
» %JAXP_LIB%\endorsed\dom.jar;
» %J2EE_HOME%\lib\j2ee.jar;
» %XERCES_JAR%
It should be obvious to the Linux/Unix user how to convert this into a shell script.
This environment setup is specific to the application server and tool kit that you are
using, although any Java-based web services setup will likely suffer from the same
problem. If you get errors during compilation or deployment, the first thing to check
is usually that your CLASSPATH variable is correctly configured in the command
prompt that you are currently using.
5.1 Directory Structure
This tutorial assumes the following directory structure inside the “My Documents”
folder if you are using Windows, or your home directory if using some other OS:
22
The “FileStore Tutorial” folder should contain a file called “classpath.bat”, which
contains the environment settings described previously. Every time that you run a new
command prompt, you should run “classpath.bat” to ensure that your environment is
correctly configured. Note that every command prompt instance (or window) has its
own independent environment, so “classpath.bat” must be run in each one.
23
Download