/home/projects/ubik

HTTP Transport

The HTTP transport layer conveniently adds support for receiving remote method invocations over the internet. It opens the door to rich-client interfaces that can from now on use an object-oriented approach to communicating with servers over the WAN, something that is usually implemented with XML. Of course, this does not invalidate XML for such use, but provides an alternative if clients are in Java, and if getting up and running fast is a must.

Ubik's HTTP Tranport layer is implemented by two transport providers, for two different usages: 1) stand-alone; 2) embedding Ubik within a servlet - for use within a servlet container.

Servlet Transport

In short, the ServletTransportProvider can be used to export remote objects over HTTP, from within a servlet. This means the following:

  • A Ubik server can receive remote method calls over HTTP;
  • A Ubik server can be embedded within an already-running servlet container (Tomcat, WebSphere, WebLogic...);
  • A Ubik server can benefit from existing HTTP infrastructure setups (firewalls, load-balancers, integration with Apache, SSL, etc.);

Server-Side

The code below is taken from the examples that come with Ubik. Although simple and meaningless, it clearly demonstrates the steps necessary to embed Ubik within a servlet:

public class HttpFooServlet extends HttpServlet{
	
  private Foo _foo;
  private ServletTransportProvider _provider;
  
  // this would not be hard-coded like this 
  // normally - but rather configured in servlet 
  // init parameters.
  static final String SERVLET_URL = 
               "http://localhost:8080/ubik";
	
  public void init(ServletConfig conf) 
    throws ServletException {
  	
    _provider = new ServletTransportProvider();
    TransportManager.registerProvider(_provider);
  	
    Properties props = new Properties();
    
    // we would normally get value for this 
    // property from init parameters...
    props.setProperty(
      ServletConsts.SERVLET_URL_KEY, 
      SERVLET_URL);
    
    // this tells ubik "under" which transport 
    // our object will be exported 
    props.setProperty(
      Consts.TRANSPORT_TYPE, 
      ServletConsts.DEFAULT_SERVLET_TRANSPORT_TYPE);
    
    try{
      _foo = new UbikFoo();
      Hub.exportObject(_foo, props);
    }catch(RemoteException e){
      throw new 
        ServletException("Could not export Foo", e);
    }
  }

  protected void service(HttpServletRequest req, 
                         HttpServletResponse res)
                         throws ServletException, 
                                IOException {
    _provider.handleRequest(req, res);
  }
  
  /**
   * @see javax.servlet.GenericServlet#destroy()
   */
  public void destroy() {
  	try {
      Hub.shutdown(30000);      
    } catch (Exception e) {
      getServletContext()
        .log("Exception caught performing Hub shutdown", 
             e);
    }
  }

Note, in the code above, that passing the URL of the servlet that embeds Ubik when exporting our object is mandated by the underlying transport provider (in this case, the ServletTransportProvider). This URL eventually ends up in the stubs that are received by clients: stubs will perform remote method calls through this URL.

Normally, the servlet URL would not be hard-coded like this (as comments in the code indicate). Rather, it would be passed to the servlet through an initialization parameter. For the constants that are used, see the ServletConsts interface.

Client-Side

On the client-side, the code is straightforward:

public class HttpFooServletClient {
  public static void main(String[] args) {
    try {
      TransportManager.registerProvider(
        new ServletTransportProvider());

      Foo foo = (Foo) Hub.connect(new ServletAddress(
            "http://localhost:8080/ubik"));
      System.out.println(foo.getBar().getMsg());
    } catch (Throwable t) {
      t.printStackTrace();
    }
  }
}

Stand-Alone

The HttpTransportProvider can be used when wishing to export objects over HTTP using an underlying stand-alone HTTP server. In this case, you do not embed Ubik in a servlet, but rather use it as you would normally - to the exception that you must register the appropriate transport provider with the Hub.

Preferrably use this implementation when needing a quick-and-dirty stand-alone HTTP-based Ubik server that does not need a full-fledge infrastructure (security, load-balancing, etc.).

This transport layer builds on the Simple API, and allows to plug in custom Simple services.

Server-Side

The code below (again taken from Ubik's examples) demonstrates how a remote object is exported over HTTP:

public class HttpFoo {
  public static void main(String[] args) {
    try {
      TransportManager.registerProvider(
        new HttpTransportProvider());

      Properties props = new Properties();
      props.setProperty(Consts.TRANSPORT_TYPE, 
                        HttpConsts.DEFAULT_HTTP_TRANSPORT_TYPE);
      props.setProperty(HttpConsts.HTTP_PORT_KEY, "8080");
      Hub.exportObject(new UbikFoo(), props);

      while (true) {
        Thread.sleep(100000);
      }
    } catch (Throwable t) {
      t.printStackTrace();
    }
  }
}

See the HttpConsts interface for the constants that are used in the code.

Note that in the above example, the URL of the server is not specified; the URL that is used then is of the format http://local_ip_address:port/ubik. The port is in this case expected through a property (as shown in the code above); if not specified, the underlying HTTP server will be listening on the default port: 8080. The URL will eventually be used by stubs on the client-side; if this is not what you want (because your server might be behind a firewall/load-balancer, and stubs in this case cannot connect to your server directly), you can specify the URL to which stubs will connect:

  // Do not forget the trailing "/ubik" path
  // when using Ubik HTTP in standalone (not in a
  // servlet container)
  props.setProperty(HttpConsts.SERVER_URL_KEY, 
                    "http://www.somedomain.com/ubik");

Note that the above feature really makes sense only if your HTTP-remoted object hides behind some proxy, and that all clients are expected to originate from beyond such a proxy.

If you wish that the path to which the exported object corresponds be mapped to something else then "/ubik" (the default), and that you are not using the URL substitution scheme - explained previously, then you can specify a different path:

  props.setProperty(HttpConsts.PATH_KEY, "/somePath");

Client-Side

The client corresponding to the above server would look like this:

public class HttpFooClient {
  public static void main(String[] args) {
    try {
      TransportManager.registerProvider(new HttpTransportProvider());

      Foo foo = (Foo) Hub.connect(new HttpAddress(Uri.parse(
              "http://localhost:8080/ubik")));
      System.out.println(foo.getBar().getMsg());
    } catch (Throwable t) {
      t.printStackTrace();
    }
  }
}    

Custom Simple Services

As was mentioned, Ubik's stand-alone HTTP transport layer is built on top of Simple. The Simple API allows implementing custom services that can benefit from the HTTP infrastructure. Ubik makes this feature available to developers that would want to plug their Simple services (see Simple's API for more on how to implement them) in its HTTP layer. A Simple service can be added to a HttpTransportProvider in the following way:

HttpTransportProvider prov = new HttpTransportProvider();
MyService mySvc = new MyService();
prov.getServiceMapper().addService("/myPath", mySvc);

// here register provider with hub and export remote object...

The above is an interesting feature: it allows handling remote method calls and other types of HTTP requests with the same underlying HTTP server.

Conclusion

So there you have it, at last: remote method invocations over HTTP, in two flavors, depending on your needs. This transport layer is especially suited to receive remote method calls from clients across the internet. It also opens the door to rich client interfaces, and frees you from XML if you do not need it.

Using with Firewalls

If you want clients to be able to connect to your Ubik/HTTP servers through firewalls, you must configure the "external" address of your firewall when initializing the transport provider (as illustrated in the examples above). The URL that is given to clients is thus NOT the address of the host/port on which our Ubik server is listening...

Then, of course, at your firewall, you need to forward the calls target at a Ubik server to the host/port corresponding to that server.

Using with Load-Balancers

You should work with the servlet transport provider if you wish to use Ubik with load-balancers. To dispatch to the appropriate servers, load-balancers typically either use cookies, SSL session IDs, or some other sort of string identiers that are part of the request/response cycle between HTTP clients and servers. In the case of Ubik, it is important that once a client has acquired a stub, all remote method calls be performed in the context of a session.

Indeed, when you export an object through HTTP, the stub that the client obtains for that object must connect to that original object whenever a remote method call is performed using the acquired stub.

The best way to ensure that a HTTP session is indeed established, call the getSession(true) method on the HttpServletRequest instance every time your servlet receives a request.

Of course, the above explanation is generic, and you should discuss with your sysadmin in order to figure out what's most appropriate...