HTTP/WebSocket Clients

../../_images/example-protocol-module.png

HTTP/WebSocket Remoting for a Service

Overview

Baratine provides HTTP remoting based on Java service APIs in a JSON-RPC format.

  • Java API translates to JSON-based calls
  • JSON for flexibility
  • Binary Hessian for performance
  • High performance under load

HTTP variations depending on the client needs:

  • JAMP-RPC (json-rpc)
  • WebSocket with JAMP (subprotocol “jamp”)
  • HTTP long polling with JAMP (jamp-push/jamp-pull)
  • Binary HAMP with WebSockets for Java servers (subprotocol “hamp”)
  • HTTP REST for simple HTTP clients

Languages:

  • Java servlet web-app client
  • Java client
  • JavaScript
  • PHP
  • Python

Java Clients

Sample API

public interface Hello
{
  String hello(String arg);

  void sendHello(String arg);
}

Java Servlet Web-App Client

  • WEB-INF/lib/baratine.jar
  • WEB-INF/baratine.cf

Client code:

{
  ServiceManager manager = ServiceManager.current()

  Hello myHello = manager.lookup("pod://pod/hello")
                         .as(Hello.class);

  out.println("Hello: " + myHello.hello("world"));

  hello.helloSend("data");
}

WEB-INF/baratine.cf:

cluster {
  server 10.0.0.1 8085;
  server 8084;
}

Java Standalone Client

ServiceClient is a standalone Java Baratine client that can be used in any Java system. An application will use BaratineClient to connect to the remote Baratine server, lookup one or more services, and generate proxies to the service. Once the application has a proxy, it can call it as a normal Java proxy:

import io.baratine.core.ServiceClient;

public class Test
{
  public void main(String []args) throws Exception
  {
    String url = "http://127.0.0.1:8085/s/pod";

    try (ServiceClient client = ServiceClient.newClient(url).build()) {
      Hello myHello = client.lookup("/hello-service")
                                  .as(Hello.class);

      System.out.println("Hello: " + myHello.hello("world"));
    }
  }

The first URL is the HTTP address of the Baratine server, “http://127.0.0.1:8085/s/pod”. The client will store the address and connect to it as necessary. If the connection is dropped, it will try to reconnect. If it still fails to connect, then any query will return with a ServiceConnectException. If a send message is dropped from a connection failure, no exception will be produced.

ServiceClient is the general Baratine client API. The most important method in ServiceClient is lookup, which returns a ServiceRef that represents a proxy to the remote service. The proxies are thread-safe.

Application can call lookup many times, reusing the ServiceClient and can use the proxies from multiple threads. In fact, a ServiceClient can be more efficient when used from multiple application thread clients, because the HAMP messages can be more efficiently batched to the Baratine server.

To get an application proxy from a ServiceRef, use the as method with the application’s own API. Baratine services and HAMP are designed to decouple the client proxy from the service implementation. So it’s possible for a client team to write its own proxy in its own independent development repository. The client and server teams still need to agree on the API and the contents of objects. Since Hessian allows for version differences and upgrades, even the value objects can be loosely coupled.

Service Proxies

Client proxies for ServiceClient follow the same rules as other Baratine proxies.

  • void methods queue the call and return immediately and will not return any errors.
  • value returning methods will block on an internal future for the result.
  • value methods using a Result callback will return immediately. The result will callback into the Result in the context of the original service.

The three method types look like the following:

public interface MyServiceApi
{
  // unidirectional message is queued and returns immediately
  void mySend(String arg_1);

  // rpc calls block on an internal future for the result
  String myQuery(String arg_1);

  // completion calls will have the result returned in the callback
  void myQuery(String arg_1, Result<String> result);
}

In general, the completion style is preferable to the RPC/future style because it doesn’t tie up the thread, and can be faster, at a slight cost of convenience.

JavaScript

Baratine Java Script Library implements JAMP communication protocol providing WebSocket and XMLHttpRequest based implementations.

Browsers that support WebSocket should prefer WebSocket based implementation.

The library supports invocation of Baratine methods that return a result (query), and invocation of methods corresponding to void method (send).

(At the moment the library can be obtained from
https://github.com/baratine/baratine-js/blob/master/dist/baratine-js.js )

Please see “examples”:https://github.com/baratine/baratine-js/tree/master/examples for additional sample code using Baratine JavaScript Library

Query

Query methods return results.

Invoke Method:

var client = new Jamp.BaratineClient("http://localhost:8086/s/pod");

client.query("/hello-service", "hello", ["world"], function(data) {
  console.log("success: " + data);
});

Send

Send methods do not return results.

Invoke Method:

var client = new Jamp.BaratineClient("http://localhost:8086/s/pod");

client.query("/hello-service", "sendHello", ["data"]);

PHP

The PHP client library closely follows the Java client library:

<?php

  require_once('baratine-php/baratine-client.php');

  function main()
  {
    $client = new baratine\BaratineClient('http://127.0.0.1:8085/s/pod');

    $hello = $client->_lookup('/hello-service')->_as('HelloService');

    $result = counter->hello("world");

    var_dump($result);

    $client->close();
  }

  abstract class HelloService
  {
    public abstract function hello(/* string */ $value);
    public abstract function sendHello(/* string */ $value);
  }

  main();
?>

The _as() method creates a proxy for the PHP interface CounterService. You then can call that proxy as if you’re calling the concrete CounterService object. The proxy checks for the number of required arguments, serializes them, and then sends the method call to the Baratine service.

BaratineClient API

namespace baratine;

class BaratineClient
{
  /**
   * @returns a new BaratineClient
   */
  public static function create(/* string */ $url);

  /**
   * @returns a Proxy
   */
  public function _lookup(/* string */ $url);

  public function close();
}

Proxy API

namespace baratine;

class Proxy
{
  public function __call($name, $arguments);

  /**
   * @returns another Proxy where $relativeUrl is appended to the original $url
   */
  public function _lookup(/* string */ $relativeUrl);

  /**
   * @returns a ClassProxy
   */
  public function _as(/* string */ $clsName);
}

ClassProxy API

namespace baratine;

class ClassProxy
{
  public function __call($name, $arguments);

  /**
   * @returns another ClassProxy where $relativeUrl is appended to the original $url
   */
  public function _lookup(/* string */ $relativeUrl);

  /**
   * @returns a ClassProxy for this class instead
   */
  public function _as(/* string */ $clsName);
}

JAMP Protocol

JAMP - JSON Async Message Protocol

JAMP (JSON Async Message Protocol) was designed together with Baratine as an asynchronous method call messaging protocol. Historically, JAMP was designed first and Baratine was built to support JAMP services.

JAMP messages:

["send", {headers}, "to", "method", arg_1, ..., arg_n]

["query", {headers}, "from", qid, "to", "method", arg_1, ..., arg_n]

["reply", {headers}, "to", qid, result]

["error", {headers}, "to", qid, {error}]

JAMP WebSockets is asynchronous. Multiple messages can be in flight, and results can return out of order. To correlate queries with result, the query method creates an integer query-id and the result uses the same query-id.

  • “send” - unidirectional fire-and-forget message
  • “query” - request query for an RPC message
  • “reply” - reply to a query
  • “error” - error to a query
  • {headers} - optional headers used for debugging
  • “to” - address of the target service
  • “method” - method name of the target API
  • arg_n - arguments for the method
  • “from” - client’s reply address for a query
  • qid - the correlation id for a query is 63-bit unsigned integer
  • result - the result of an RPC call
  • error - failed query information

Queries must be replied to with either “reply” or “error” with a matching “from” and qid.

examples:

["query", {}, "/my-client", 2712, "/hello-service", "hello", "world"]

["reply", {}, "/my-client", 2712, "Hello[world]"]

JAMP WebSockets

A JAMP WebSocket connection uses the “jamp” subprotocol.

JAMP messages are WebSocket text messages using the syntax give above.

JAMP WebSockets is asynchronous. Multiple messages can be in flight, and results can return out of order. To correlate queries with result, the query method creates an integer query-id and the result uses the same query-id.

HTTP Long Polling

As a fallback to WebSockets, Baratine provides jamp-push and jamp-pull for HTTP long polling. Two connections are used in this technique.

  • jamp-pull waits for messages from the server.
  • jamp-push sends messages from the client

The content-type for jamp-pull is x-application/jamp-pull. The content-type for jamp-push is x-application/jamp-push.

Like JAMP WebSockets, queries are asynchronous and can return out of order. Multiple messages send with jamp-push can be in flight at the same time.

JAMP-RPC (blocking HTTP RPC)

JAMP-RPC is a simplified HTTP call suitable for standard HTTP requests. Unlike the other JAMP subprotocols, JAMP-RPC is a blocking HTTP call with in-order responses to its query.

JAMP-RPC is suitable to traditional or simple web applications that don’t need the full asynchronous protocol.

The content-type for JAMP-RPC is x-application/jamp-rpc.

Send:

[["query", {}, "/from", 2712, "/hello-service", "hello", "world"]]

Result:

[["reply", {}, "/from", 2712, "Hello[World]"]]

REST with HTTP Methods

Baratine’s REST supports the standard HTTP methods GET, PUT, CREATE, DELETE. These methods require matching application methods as follows:

  • GET - get()
  • PUT - put(X value)
  • CREATE - create(X value)
  • DELETE - delete()
curl http://localhost:8085/s/pod/my-service
{"status":"ok", "value":"hello"}

REST URL

Baratine’s REST uses a fixed URL that specifies the service. The path of the URL selects the service address.

A sample URL might look like:

http://localhost:8085/s/my-pod/my-service

The result is a JSON value:

$ curl http://localhost:8085/s/pod/hello-service
{"status":"ok", "value":"Hello[]"}

HelloWorldRest.java:

@Service("public:///hello-service")
public HelloWorldRest
{
  public String get()
  {
    return "Hello[]";
  }
}