Server-side XML-RPC

If you have read and understood the previous document about the Apache XML-RPC client, then the server isn't too much news.

First of all, there is an object, called the XmlRpcServer. This objects purpose is to receive and execute XML-RPC calls by the clients. The XmlRpcServer can be embedded into a servlet container, or another HTTP server (for example, the minimal web server, that comes with XML-RPC), but it doesn't need to. Take the local transport as an example: In that case the XML-RPC server is simply embedded into the client application.

Like the XmlRpcClient, the XmlRpcServer needs a configuration, which is given by the XmlRpcServerConfigImpl object.

The XML-RPC Servlet

The easiest way to create an XML-RPC Server is the XmlRpcServlet, which has an automatically embedded instance of XmlRpcServer. This servlet allows you to create a server within 10 minutes or so:

  1. Create a class, or a set of classes, which are implementing the remote procedure calls. Here's an example of such a class:
        package org.apache.xmlrpc.demo;
        public class Calculator {
                    public int add(int i1, int i2) {
                            return i1 + i2;
                    }
                    public int subtract(int i1, int i2) {
                            return i1 - i2;
                    }
        }
    

    This class has two public, non-static methods, which should be available to the clients.

  2. Create a property file, which contains at least one property. The property name is arbitrary, and the property value is the fully qualified name of the Calculator class. For example, like that:
        Calculator=org.apache.xmlrpc.demo.Calculator
    

    The property file must be called XmlRpcServlet.properties, and it must be located in the package org.apache.xmlrpc.webserver. In other words, you would typically put it into the directory org/apache/xmlrpc/webserver and add it to your jar file.

  3. Add entries like the following to your war files web.xml:
        <servlet>
            <servlet-name>XmlRpcServlet</servlet-name>
            <servlet-class>org.apache.xmlrpc.webserver.XmlRpcServlet</servlet-class>
            <init-param>
              <param-name>enabledForExtensions</param-name>
              <param-value>true</param-value>
              <description>
                Sets, whether the servlet supports vendor extensions for XML-RPC.
              </description>
            </init-param>
        </servlet>
        <servlet-mapping>
            <servlet-name>XmlRpcServlet</servlet-name>
            <url-pattern>/xmlrpc</url-pattern>
        </servlet-mapping>
    

That's it! You have just created your first XML-RPC server. :-)

The Server configuration

Unlike in the case of the clients configuration, there isn't much to configure on the server. The reason is, that most things depend on the client and the HTTP headers, which are received by the client. There is one very important property to configure, though:

Property Name Description
enabledForExceptions If the server catches an exception, and this
property is set, then the server will convert
the exception into a byte array (by using an
ObjectOutputStream) and return the exception to
the client. Note, that this may have privacy
or even security implications, because Exceptions
may contain arbitrary Java objects, which you
possibly do not want to be sent to the client.
enabledForExtensions Whether the vendor extensions of Apache XML-RPC
should be enabled. By default, Apache XML-RPC is
strictly compliant to the XML-RPC specification.
Enabling this property doesn't indicate, that the
server is unable to serve requests by standard
clients: In contrary, the servers behaviour
depends on the client. Setting this property to
true will only advice the server, that it may
accept requests, which ask for vendor extensions.
For example, if a client sends a content-length
header, then the server assumes, that the client
wants a content-length header in the request
and disables the streaming mode.

Basic Authentication

Basic authentication is frequently used to authenticate and authorize users. Within Apache XML-RPC, basic authentication is done by the XmlRpcHandler. The handler receives an instance of XmlRpcRequest. This object has a method getConfig(), which returns an instance of XmlRpcRequestConfig.

If you are running within a HTTP server, then the request configuration may be casted to an instance of XmlRpcHttpRequestConfig. This object has methods getBasicUserName(), and getBasicPassword(), which provide the necessary details.

In other words: Your task is to provide your own instance of XmlRpcHandlerMapping, which creates your own handlers. And your own handlers are responsible to validate the basic authentication details.

Here's an example servlet, which overrides the default PropertyHandlerMapping.

  public class MyServlet extends XmlRpcServlet {
          private boolean isAuthenticated(String pUserName, String pPassword) {
              return "foo".equals(pUserName) && "bar".equals(pPassword);
          }
          protected XmlRpcHandlerMapping newXmlRpcHandlerMapping() throws XmlRpcException {
              PropertyHandlerMapping mapping
                  = (PropertyHandlerMapping) super.newXmlRpcHandlerMapping();
              AbstractReflectiveHandlerMapping.AuthenticationHandler handler =
                  new AbstractReflectiveHandlerMapping.AuthenticationHandler(){
                          public boolean isAuthorized(XmlRpcRequest pRequest){
                              XmlRpcHttpRequestConfig config =
                                  (XmlRpcHttpRequestConfig) pRequest.getConfig();
                              return isAuthenticated(config.getBasicUserName(),
                                  config.getBasicPassword());
                          };
                  };
              mapping.setAuthenticationHandler(handler);
              return mapping;
          }
  }

The WebServer class

The WebServer is a minimal HTTP server, that might be used as an embedded web server.

Use of the WebServer has grown very popular amongst users of Apache XML-RPC. Why this is the case, can hardly be explained, because the WebServer is at best a workaround, compared to full blown servlet engines like Tomcat or Jetty. For example, under heavy load it will almost definitely be slower than a real servlet engine, because it does neither support proper keepalive (multiple requests per physical connection) nor chunked mode (in other words, it cannot stream requests).

If you still insist in using the WebServer, it is recommended to use its subclass, the ServletWebServer instead, which offers a minimal subset of the servlet API. In other words, you keep yourself the option to migrate to a real servlet engine later.

Use of the WebServer goes roughly like this: First of all, create a property file (for example "MyHandlers.properties") and add it to your jar file. The property keys are handler names and the property values are the handler classes. Once that is done, create an instance of WebServer.

  package org.apache.xmlrpc.demo.webserver;

  import java.net.InetAddress;

  import org.apache.xmlrpc.common.TypeConverterFactoryImpl;
  import org.apache.xmlrpc.demo.webserver.proxy.impls.AdderImpl;
  import org.apache.xmlrpc.server.PropertyHandlerMapping;
  import org.apache.xmlrpc.server.XmlRpcServer;
  import org.apache.xmlrpc.server.XmlRpcServerConfigImpl;
  import org.apache.xmlrpc.webserver.WebServer;

  public class Server {
      private static final int port = 8080;

      public static void main(String[] args) throws Exception {
          WebServer webServer = new WebServer(port);
        
          XmlRpcServer xmlRpcServer = webServer.getXmlRpcServer();
        
          PropertyHandlerMapping phm = new PropertyHandlerMapping();
          /* Load handler definitions from a property file.
           * The property file might look like:
           *   Calculator=org.apache.xmlrpc.demo.Calculator
           *   org.apache.xmlrpc.demo.proxy.Adder=org.apache.xmlrpc.demo.proxy.AdderImpl
           */
          phm.load(Thread.currentThread().getContextClassLoader(),
                   "MyHandlers.properties");

          /* You may also provide the handler classes directly,
           * like this:
           * phm.addHandler("Calculator",
           *     org.apache.xmlrpc.demo.Calculator.class);
           * phm.addHandler(org.apache.xmlrpc.demo.proxy.Adder.class.getName(),
           *     org.apache.xmlrpc.demo.proxy.AdderImpl.class);
           */
          xmlRpcServer.setHandlerMapping(phm);
        
          XmlRpcServerConfigImpl serverConfig =
              (XmlRpcServerConfigImpl) xmlRpcServer.getConfig();
          serverConfig.setEnabledForExtensions(true);
          serverConfig.setContentLengthOptional(false);

          webServer.start();
      }
  }

The Calculator class can be found above. The Adder and AdderImpl classes can be found in the proxy example.

Jimisola Laursen, who provided the above example, has also supplied an example for the client:

  package org.apache.xmlrpc.demo.client;

  import java.net.MalformedURLException;
  import java.net.URL;

  import org.apache.xmlrpc.XmlRpcException;
  import org.apache.xmlrpc.client.XmlRpcClient;
  import org.apache.xmlrpc.client.XmlRpcClientConfigImpl;
  import org.apache.xmlrpc.client.XmlRpcCommonsTransportFactory;
  import org.apache.xmlrpc.client.util.ClientFactory;
  import org.apache.xmlrpc.demo.proxy.Adder;

  public class Client {
      public static void main(String[] args) throws Exception {
          // create configuration
          XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl();
          config.setServerURL(new URL("http://127.0.0.1:8080/xmlrpc"));
          config.setEnabledForExtensions(true);  
          config.setConnectionTimeout(60 * 1000);
          config.setReplyTimeout(60 * 1000);

          XmlRpcClient client = new XmlRpcClient();
        
          // use Commons HttpClient as transport
          client.setTransportFactory(
              new XmlRpcCommonsTransportFactory(client));
          // set configuration
          client.setConfig(config);

          // make the a regular call
          Object[] params = new Object[]
              { new Integer(2), new Integer(3) };
          Integer result = (Integer) client.execute("Calculator.add", params);
          System.out.println("2 + 3 = " + result);
        
          // make a call using dynamic proxy
          ClientFactory factory = new ClientFactory(client);
          Adder adder = (Adder) factory.newInstance(Adder.class);
          int sum = adder.add(2, 4);
          System.out.println("2 + 4 = " + sum);
      }
  }

The ServletWebServer class

This is a subclass of the standalone WebServer, which offers a minimal servlet API. It is recommended to use this class, rather than the WebServer, because it offers you a smooth migration path to a full blown servlet engine.

Use of the ServletWebServer goes like this: First of all, create a servlet. It may be an instance of @link XmlRpcServlet or a subclass thereof. Note, that servlets are stateless: One servlet may be used by multiple threads (aka requests) concurrently. In other words, the servlet must not have any instance variables, other than those which are read only after the servlets initialization.

The XmlRpcServlet is by default using a property file named org/apache/xmlrpc/server/webserver/XmlRpcServlet.properties. See the PropertyHandlerMapping for details on the property file.

  package org.apache.xmlrpc.demo.webserver;

  import java.net.InetAddress;

  import org.apache.xmlrpc.common.TypeConverterFactoryImpl;
  import org.apache.xmlrpc.demo.webserver.proxy.impls.AdderImpl;
  import org.apache.xmlrpc.server.PropertyHandlerMapping;
  import org.apache.xmlrpc.server.XmlRpcServer;
  import org.apache.xmlrpc.server.XmlRpcServerConfigImpl;
  import org.apache.xmlrpc.webserver.ServletWebServer;

  public class ServletServer {
      private static final int port = 8080;

      public static void main(String[] args) throws Exception {
          XmlRpcServlet servlet = new XmlRpcServlet();
          ServletWebServer webServer = new ServletWebServer(servlet, port);
          webServer.start();
      }
  }