Skip to main content

The World's Slowest Web Service

Posted by campbell on April 26, 2006 at 5:03 PM PDT

The Set Up


Every so often a bug will creep into Java 2D that causes a particular rendering operation to be so ridiculously slow that it is almost comical. (Only graphics geeks would find humor in something so mundane, but I digress.) Back in the day, like 7 years ago, we would joke in our staff meetings along the lines of:

   Person:   This rendering op is so slow...
   Audience: How slow is it?
   Person:   It's so slow that it would be faster to air mail each
             individual pixel to Timbuktu and back.

A few years ago when the phrase "web services" became all the rage we had to update the punchline:

   Person:   It's so slow that it would be faster to implement this
             op as a web service and host the server in Timbuktu.

Okay, so you're not laughing, but I warned you earlier, I did. Plus, it doesn't help that our collective senses of humor have all been addled recently by Chet and his killer puns (Minneapplet anyone?)...

But seriously forks, all the web service yumminess being integrated into Mustang has peaked my interest, especially from the desktop Java standpoint. I've seen a few JAX-WS 2.0 tutorials recently (see "Useful Resources" section below), but most are written from the server-side perspective, and therefore start out with "download the latest GlassFish build" or "grab the latest JWSDP release" or something similar. Already that's one too many steps for my lazy self. I'm a minimalist at heart, so what I really wanted to know was how quickly one could get up and running with JAX-WS using only the latest Mustang binaries. And what better way to learn than to bring that old "joke" to life, so I now present to a truly contrived example: a pixel blender service (and client).


Step 1: Write and compile the endpoint


First we'll create our Blender endpoint. JAX-WS 2.0 makes this a breeze. All we need to do is define our Blender class and annotate it with @WebService, and then implement a single method (the one that does all the work) annotated with @WebMethod:

package blend.endpoint;

import javax.jws.WebMethod;
import javax.jws.WebService;
import javax.xml.ws.Endpoint;

@WebService(serviceName="BlenderService")
public class Blender {

    @WebMethod
    public int blend(int src, int dst) {
        int srcA = src >>> 24;
        int srcR = (src >> 16) & 0xff;
        int srcG = (src >>  8) & 0xff;
        int srcB = (src      ) & 0xff;
        int dstA = dst >>> 24;
        int dstR = (dst >> 16) & 0xff;
        int dstG = (dst >>  8) & 0xff;
        int dstB = (dst      ) & 0xff;
        int resA = (int)((srcA * 0.5) + (dstA * 0.5));
        int resR = (int)((srcR * 0.5) + (dstR * 0.5));
        int resG = (int)((srcG * 0.5) + (dstG * 0.5));
        int resB = (int)((srcB * 0.5) + (dstB * 0.5));
        return (resA << 24) | (resR << 16) | (resG << 8) | resB;
    }
}

The "serviceName" parameter on @WebService is optional, as are many of the web service related annotation parameters, but here I just wanted to be a bit more explicit about the name of the service being generated. (And again, this isn't meant to be an all-encompassing tutorial; I'm just trying to show the minimal number of steps to get "something" up and running.)

As the final part of this step, we will simply compile this source file:


% javac -d build src/blend/endpoint/Blender.java


Step 2: Generate the endpoint "portable artifacts"


Now that we have the source code and class file for our web service endpoint, we can use the wsgen tool (part of the JDK) to generate "portable artifacts", or behind-the-scenes implementation, for our web service:


% wsgen -cp build -d build -wsdl blend.endpoint.Blender

We pass the -wsdl argument to indicate that we want wsgen to generate the WSDL descriptor file, which we will need later. In addition to the WSDL file, wsgen will generate a bunch of class files under the build directory. Optionally you can pass the -keep argument indicating that you'd like it to generate the source files as well for those generated classes, but again, we're not concerned with what wsgen generates. That's the benefit of the annotations here; as long as the tool conforms to the specifications of the various annotation types, we shouldn't need to worry too much about what gets generated under the hood.


Step 3: Generate the client "portable artifacts"


In Step 2 we used wsgen to generate a WSDL file from our Java endpoint class. In this step, we will essentially reverse the process and use that WSDL file and the wsimport tool to generate our Java client class:


% wsimport -p blend.client -d build -wsdllocation http://127.0.0.1:8084/blend?WSDL build/BlenderService.wsdl

The -p blend.client argument indicates that we'd like wsimport to generate the class files in the blend.client package to keep them separate from our endpoint artifacts. The -wsdllocation argument is not strictly necessary, but it is important if we want our client to pull the WSDL descriptor directly from the web service endpoint (otherwise our client would try to pull the descriptor at runtime from the location on disk, which is not what we want; this was tricky, it took me a while to figure out why this was needed).


Step 4: Write and compile the client application


Now that we have our automagically-generated Blender client classes that know how to communicate with the Blender endpoint, we just need to write an "interesting" client application to consume our even more "interesting" web service. I've written a small Swing client application called BlendClient (source code included in the zip file mentioned later), but it would be easier to illustrate how to use the client code with a simple testcase:

package blend.client;

public class Test {
    public static void main(String[] args) throws Exception {
        BlenderService service = new BlenderService();
        Blender blender = service.getBlenderPort();
        int src = 0xff00ff00;
        int dst = 0xff0000ff;
        int res = blender.blend(src, dst);
        System.out.printf("src=%08x dst=%08x result=%08x\n",
                          src, dst, res);
    }
}

Note that by default this testcase will talk to the web service endpoint running at http://127.0.0.1/blend by virtue of the -wsdllocation parameter specified in Step 3. If we wanted this testcase to communicate with an endpoint published on a different server, we could specify that with some additional parameters to the BlenderService constructor, but for the purposes of this demonstration, the default values will be sufficient.


Step 5: Publish the endpoint


Now it's time to publish our endpoint, which is a fancy way of saying that we'll start our "server". Again, I've written a Swing client to start/stop the endpoint, but it would be easier to demonstrate how easy it is to publish an endpoint with a simple code example:

package blend.endpoint;

import javax.xml.ws.Endpoint;

public class Test {
    public static void main(String[] args) throws Exception {
        Endpoint e = Endpoint.create(new Blender());
        e.publish("http://127.0.0.1:8084/blend");
        System.out.println("Endpoint running");
        try {
            Thread.sleep(300000);
        } catch (InterruptedException ex) {
        }
        e.stop();
        System.out.println("Endpoint stopped");
    }
}

This code will publish the web service endpoint on the local machine on port 8084, and will keep it running for 5 minutes. You can run compile and run this testcase easily:


% java -cp build blend.endpoint.Test

Note that the Endpoint API in Mustang is mainly useful for testing purposes. If you were to publish this for real, you'd probably want to deploy it on one of them application server things the server-side folks like to talk about these days.


Step 6: Run the client application


This would be a good time to unveil my ludicrous attempt at a Swing application to demonstrate this stuff. You'll need the latest version of Mustang installed to run these WebStarted apps. Note that I've had to sign my jar and give the application all permissions since it needs to talk to the network, even though it is running entirely locally on your machine. (A real web service client won't necessarily have such a restriction; it should be possible to deploy a sandboxed WebStart application as long as it talks back to the server from which it was deployed, at least in theory.)

First fire up the endpoint (Blender Endpoint) using this link (thanks to Romain for hosting the binaries):




Then once the endpoint is running, try running the client app (Blender Client):




Click the button and behold, it should look something like this:




If all goes well, you should see the blended image filling in very, very slowly in the middle of the window. Basically, the app is consulting the Blender web service running on your machine, one pixel and one scanline at a time. Scary, and useless! (By the way, the source code for this application demonstrates a couple other Mustang features, namely SwingWorker and the Desktop integration API, in case you're curious.)


Conclusion


Whew! This turned out to be a much longer exercise than I was expecting, but I hope this information will be useful to those getting started with JAX-WS in Mustang, especially from the client side. The minimalist within me winces when I realize how much prose and code it took to explain all this stuff. I'm sure this process is much easier than it would have been in earlier JAX-RPC releases, so I can appreciate the simplicity of the new annotations. However, there seem to be plenty of ways to shoot yourself in the foot when using these annotations, and the current documentation for many of these classes is not entirely clear in my opinion. (Fortunately, I think that once there is more tools support for developing web services, developers will have less opportunity to screw up, and I would hope that the devlopment/deployment experience in those tools will improve things as well.)

At some point I'd like to go into more detail about my issues with JAX-WS, but I've got a plane to catch! I'll be away for a couple weeks, so hopefully you'll understand if I don't respond to any comments right away.


Useful Resources




In my ears: Destroyer, "Destroyer's Rubies"


In my eyes: Lots of blurriness at the moment

Related Topics >>