{8.0.0-updated} REST/RPC

The REST/RPC (RPC over REST) API allows the creation of client-side remote proxy interfaces for calling methods on server-side POJOs using entirely REST.

Remote Interfaces

The following example shows a remote interface:

@RemoteInterface // Annotation is optional public interface IAddressBook { void init() throws Exception; List<Person> getPeople(); List<Address> getAddresses(); int createPerson(CreatePerson cp) throws Exception; Person findPerson(int id); Address findAddress(int id); Person findPersonWithAddress(int id); Person removePerson(int id); }

The requirements for a remote interface method are:

Throwables with public no-arg or single-arg-string constructors are automatically recreated on the client side when thrown on the server side.

Client side

Remote Interface proxies are instantiated on the client side using one of the following methods:

Since we build upon the existing RestClient API, we inherit all of it's features. For example, convenience methods for setting POJO filters and properties to customize the behavior of the serializers and parsers, and the ability to provide your own customized Apache HttpClient for handling various scenarios involving authentication and Internet proxies.

Here's an example of the above interface being used:

// Create a RestClient using JSON for serialization, and point to the server-side remote interface servlet. RestClient client = RestClient.create() .json() .rootUrl("http://localhost:10000/remote") .build(); // Create a proxy interface. IAddressBook ab = client.getRrpcInterface(IAddressBook.class); // Invoke a method on the server side and get the returned result. Person p = ab.createPerson( new Person( "John Smith", "Aug 1, 1999", new Address("My street", "My city", "My state", 12345, true) ) );

Under the covers, this method call gets converted to a REST POST.

HTTP POST http://localhost:10000/remote/org.apache.juneau.examples.addressbook.IAddressBook/createPerson(org.apache.juneau.examples.addressbook.Person) Accept: application/json Content-Type: application/json [ { "name":"John Smith", "birthDate":"Aug 1, 1999", "addresses":[ { "street":"My street", "city":"My city", "state":"My state", "zip":12345, "isCurrent":true } ] } ]

Note that the body of the request is an array. This array contains the serialized arguments of the method. The object returned by the method is then serialized as the body of the response.

Server side

There are two ways to expose remote interfaces on the server side:

  1. Extending from {@link oajr.remote.RrpcServlet}.
  2. Using a @RestMethod(name=RRPC) annotation on a Java method.

In either case, the proxy communications layer is pure REST. Therefore, in cases where the interface classes are not available on the client side, the same method calls can be made through pure REST calls. This can also aid significantly in debugging, since calls to the remote interface service can be made directly from a browser with no coding involved.

RrpcServlet

The {@link oajr.remote.RrpcServlet} class is a simple specialized servlet with an abstract getServiceMap() method to define the server-side POJOs:

@Rest( path="/remote" ) public class SampleRrpcServlet extends RrpcServlet { // Our server-side POJO. private AddressBook addressBook = new AddressBook(); @Override /* RrpcServlet */ protected Map<Class<?>,Object> getServiceMap() throws Exception { Map<Class<?>,Object> m = new LinkedHashMap<Class<?>,Object>(); // In this simplified example, we expose the same POJO service under two different interfaces. // One is IAddressBook which only exposes methods defined on that interface, and // the other is AddressBook itself which exposes all methods defined on the class itself (dangerous!). m.put(IAddressBook.class, addressBook); m.put(AddressBook.class, addressBook); return m; } }

@RestMethod(name=RRPC)

The @RestMethod(name=RRPC) approach is easier if you only have a single interface you want to expose. You simply define a Java method whose return type is an interface, and return the implementation of that interface:

// Our exposed interface. @RestMethod(name=RRPC, path="/addressbookproxy/*") public IAddressBook getProxy() { return addressBook; }

RrpcServlet in a browser

If you point your browser to the servlet above, you get a list of available interfaces:

http://localhost:10000/remote

Clicking the hyperlinks on each shows you the list of methods that can be invoked on that service. Note that the IAddressBook link shows that you can only invoke methods defined on that interface, whereas the AddressBook link shows ALL public methods defined on that class.

IAddressBook

http://localhost:10000/remote/org.apache.juneau.examples.addressbook.IAddressBook

Since AddressBook extends from LinkedList, you may notice familiar collections framework methods listed.

AddressBook

http://localhost:10000/remote/org.apache.juneau.examples.addressbook.AddressBook

Let's see how we can interact with this interface through nothing more than REST calls to get a better idea on how this works. We'll use the same method call as in the introduction. First, we need to create the serialized form of the arguments:

Object[] args = new Object[] { new CreatePerson("Test Person", AddressBook.toCalendar("Aug 1, 1999"), new CreateAddress("Test street", "Test city", "Test state", 12345, true)) }; String asJson = SimpleJsonSerializer.DEFAULT_READABLE.toString(args); System.err.println(asJson);

That produces the following JSON output:

[ { name: 'Test Person', birthDate: 'Aug 1, 1999', addresses: [ { street: 'Test street', city: 'Test city', state: 'Test state', zip: 12345, isCurrent: true } ] } ]

Note that in this example we're using JSON. However, various other content types can also be used such as XML, URL-Encoding, UON, or HTML. In practice however, JSON will preferred since it is often the most efficient.

Next, we can use a tool such as Poster to make the REST call. Methods are invoked by POSTing the serialized object array to the URI of the interface method. In this case, we want to POST our JSON to the following URL:

http://localhost:10000/remote/org.apache.juneau.examples.addressbook.IAddressBook/createPerson(org.apache.juneau.examples.addressbook.CreatePerson)

Make sure that we specify the Content-Type of the body as text/json. We also want the results to be returned as JSON, so we set the Accept header to text/json as well.

When we execute the POST, we should see the following successful response whose body contains the returned Person bean serialized to JSON:

From there, we could use the following code snippet to reconstruct the response object from JSON:

String response = "output from above"; Person p = JsonParser.DEFAULT.parse(response, Person.class);

If we alter our servlet to allow overloaded GET requests, we can invoke methods using nothing more than a browser...

@Rest( path="/remote", // Allow us to use method=POST from a browser. allowedMethodParams="*" ) public class SampleRrpcServlet extends RrpcServlet {

For example, to invoke the getPeople() method on our bean:

http://localhost:10000/remote/org.apache.juneau.examples.addressbook.IAddressBook/getPeople?method=POST

Here we call the findPerson(int) method to retrieve a person and get the returned POJO (in this case as HTML since that's what's in the Accept header when calling from a browser):

http://localhost:10000/remote/org.apache.juneau.examples.addressbook.IAddressBook/findPerson(int)?method=POST&body=@(3)

When specifying the POST body as a &body parameter, the method arguments should be in UON notation. See {@link oaj.uon.UonSerializer} for more information about this encoding. Usually you can also pass in JSON if you specify &Content-Type=text/json in the URL parameters but passing in unencoded JSON in a URL may not work in all browsers. Therefore, UON is preferred.

The hyperlinks on the method names above lead you to a simple form-entry page where you can test passing parameters in UON notation as URL-encoded form posts.

Sample form entry page
Sample form entry page results