Using Remote Services
The BeNOW Web API provides access to services over HTTP. By using the API
remote users can interact with local services at either the raw HTTP (a REST-like
request) or via a Java translation layer. This tutorial will cover publishing and
accessing of services from Java and by HTTP. After this tutorial, you'll be able
to connect to, query and invoke remote services.
Contents
- Publishing Services
- Browsing Services
- Connecting to Remote Services
- Calling Remote Services
- Accessing Services Via HTTP
- Advanced Service Provision Topics
Publishing Services
Once services have been authored, secured and cleared for
publishing (via a @Publish annotation) they are automatically made
available by the Web API. Services are intrinsic to the web framework... they
are used when creating dynamic web pages, for javascript ajax
requests and for local and remote access.
As far as security is concerned, there is only one security model to maintain. The
same users who may access web pages are the same users who may access services used for
those pages and for remote service access in general. The security is integrated into
the services via the security annotations, as shown in the security tutorial.
Proper security definition within the services and assignment to the users is critical. It
really sucks to have a web crawler or malicious user delete precious content, so ensure
methods are locked down and permissions are assigned correctly. In practice, the security
within the Service API is locked down by default, but that is of no protection if security
is not applied correctly.
Once the service is ready to go, and packaged in a jar (via ant jar), and that
jar is on the classpath, it will automatically be discovered and be available for page construction,
javascript, and local and remote access.
Browsing Services
The easy way to discover services, is to hit the service listing (localhost:8080)
with a web browser. It will show a javadoc-like listing services available to the user:
Clicking on a service shows the methods available from that service:
The getLog() method, for example, returns a LogBundle and requires
the org.benow.log.LogService.invoke permission.
The service listing provides an easy way to determine which services are available. The easy
discovery of services and methods can be a help when constructing applications with much functionality
and eases multi-user/distributed development. It also provides a nice way for users to access site
functionality without having to go through pages, which makes make for interesting remote application and
remote site integration possibilities.
Connecting to Remote Services
Now that you know what services are available, we're going to connect and call a service. The java
remote service api will be used for this task. The java remote service api eases the calling of
remote services with java. Behind the scenes, interaction with remote services is done via XML
over HTTP, which works well, but is too wordy and complex for quick development. The java
remote service api does all the translation work for you, allowing you to use java as if you were
interacting with the service directly.
First, we'll want to connect to the service. We can do that using the HttpRemoteServices
accessor class. Open src/java/test/org/benow/web/Tutorial.java and find the doConnect()
method.
RemoteServices rsvcs=new HttpRemoteServices(user, pass, host, port);
List<ServiceSpecification> svcl = rsvcs.listServices();
A connection is defined with HttpRemoteServices(user, pass, host, port) which
will connect to the services on localhost, port 8080 as admin
with the password adm1n. HttpRemoteServices are RemoteServices
which internally uses the XML over HTTP connection method. The remote services can then be used.
In the above example, the remote services accessor is used to list the available services.
The work is done on the remote machine (in this case, the same machine, as it's localhost),
with the results being delivered back to the caller.
Behind the scenes there is quite a bit being done. The request is formulated as
a HTTP request with basic authentication, method and parameters (if any). The response comes
back as XML which is parsed into java objects. HttpRemoteServices automates all this,
so it's as simple as a local method call.
To run this example, do bin/tutorial_web.sh connect or bin\tutorial_web.bat connect
in windows. To specify different connect param, add a user, pass, host and port, in that order, ie
bin/tutorial_web.sh connect fred fredspass fred.com 80.
Calling Remote Services
Calling services is no more than connecting, getting the desired service and calling the method(s). See
doExec() in Tutorial.java:
RemoteServices rsvcs=new HttpRemoteServices(user, pass, host, port);
TutorialService svc=(TutorialService) rsvcs.takeService(TutorialService.class);
try {
System.out.println("All people:");
List<Person> people=svc.getPeople();
for (Person p: people)
System.out.println("\t"+p);
System.out.println("People in Heresville:");
people=svc.getPeopleInCity("Heresville");
for (Person p: people)
System.out.println("\t"+p);
System.out.println("People in 180cm or over:");
people=svc.getPeopleOfHeight(180);
for (Person p: people)
System.out.println("\t"+p);
} finally {
LocalServices.returnAService(svc);
}
The service accessor is created and a service is taken (by service interface class). The returned
service instance is used to call methods and get results. The service is then returned (for reuse).
To run this example, do bin/tutorial_web.sh exec or bin\tutorial_web.bat exec
in windows specify connect parameters as required.
Remote service invocation is really only useful to remote users (as opposed to running locally
as in this example). The service interface and other classes used in the service must be known to the
accessor... ie they must have TutorialService and Person in their classpath, but
the service implementations need not be (ie no need for TutorialServiceImpl).
The underlying service implementation returned by takeService(Class) is a dynamically compiled class
which does the result translation. The implementation takes care of using the
service accessor to make remote HTTP requests, translates results to java objects and returns them. The
remote service api generates the implementation of the requested class. As dynamic compilation is done,
the client must be accessing with the JDK (as opposed to the JRE) as the JDK has javac, the
java compiler. It automatically finds javac for the JDK that is being used, and will burp
if it can't find javac. Again, the remote service api does all the encoding, transmission, reception and decoding
for you, making remote service use from java quite simple.
Accessing Services Via HTTP
Accessing the services using a raw HTTP request can be useful if the services are not
being accessed from java. The HTTP request must be specially formatted and will
return XML. HTTP request construction is demonstrated in doHTTPExec() within
Tutorial.java:
String serviceClassName="test.org.benow.service.TutorialSecureService";
String methodSig="deletePerson(String,String)";
String firstName="John";
String lastName="Smith";
NVPair[] params = new NVPair[] {
new NVPair("exec", methodSig),
new NVPair("firstName", firstName),
new NVPair("lastName", lastName)
};
HTTPConnection conn = new HTTPConnection(host,port);
conn.addBasicAuthorization("", user, pass);
NVPair[] headers = new NVPair[2];
headers[1] = new NVPair("User-Agent", Tutorial.class.getName());
HTTPResponse response = conn.Post("/svc/"+serviceClassName, params, headers);
BufferedReader in = new BufferedReader(new InputStreamReader(response.getInputStream()));
System.out.println("Got response:");
String read = in.readLine();
while (read != null) {
System.out.println(read);
read = in.readLine();
}
This example uses HTTPClient to formulate the request, but any other
method could be used. The request is made to
http://localhost:8080/svc/test.org.benow.service.TutorialSecureService as
admin with password adm1n . HTTPS may be used if the web service has
been started with HTTPS enabled by specifying the --ssl parameter. The full name of the method
deletePerson(String,String) is passed as the exec parameter
Note that java.lang classes, such as java.lang.String may be abbreviated as just the simple
class name, ie String, but fully qualified class names should be used for
non java.lang classes, ie org.fred.FredsClass. The method parameters
are also passed in, firstName and lastName. The connection is established
to the host and port with the user and password specified using basic authentication. The user agent
is set (which is recommended, but not required), and the post connection is made. Post is used
in order to pass more complex objects. In this example, we're passing a string, but if the method
required a more complex object, the XML representation of the object could be passed, and would be
unmarshalled to a java object on the server site. The method is then invoked on the server and the
resulting objects are dumped to xml and delivered in the response. In this case it is a simple boolean
result. If an error were to occur the result code would be a non 200, such as 500.
If the requested service or method does not exist, a 404 is returned.
To run this example, do bin/tutorial_web.sh http or bin\tutorial_web.bat http
in windows specify connect parameters as required.
By using HTTP post directly, service requests can be made from any network aware environment. This opens
your web application to advanced cross-platform usage, while keeping it secure.
Advanced Service Provision Topics
There are a few things of note when implementing services.
Streaming Data
Data may be streamed by returning an InputStream or Reader. When requested, the
data from the stream will be included directly as the request result. A ResultStreamer or
ResultWriter may also be used, which allow for the specification of a mime type and suggested
file name. In such a way dynamic files can be returned.
Returning Files
If a file is returned, the mime type, content length, suggested file name and content will be returned,
so returning a file is as simple as returning a file.
Lists
Lists of objects are automatically handled. When marshalled to the result XML, the XML representation of the
list items will be within containing list XML.
Asynchronous Registration
A client may register with the server and be notified of server events. Registration is quite simple. The service
method should accept a org.benow.java.notify.Listener which the notification recipient should
implement. On the client side a listener bridge is established and posted and remembered on the server side.
As server events occur, they are passed over the bridge to the registered client and trigger the onEvent(Object,Event)
method of the Listener, in such a way, distributed asynchronous handling of server events is done. This
is a rare feature, and might prove useful in high performance distributed applications.
The BeNOW tutorials are a work in progress. If you have any comments or suggestions, please email <
andy@benow.ca>.