External communication between client and server (ServerComm)


Introduction

This document explains how the clients and server communicate with each other.

The communication is centralized in a single component named ServerComm : this component's main role is to queue up messages, in order to send them grouped to the server and save bandwidth.

Using the ServerComm component follows the scheme :

  • first of all you create a message (ServerMessage) by choosing an url to call, the parameters to send, a callback function (as communications are mainly asynchronous), a priority and a response type.
  • then you send it (and show an hourglass in the interface)
  • when the response is back you are notified (and can then hide the hourglass)

The big picture

(1) Each component sends messages.

(2) The messages are queued on the client side and are sent to the server regularly, all grouped in a single request.

(3) When messages arrive on the server they go through the dispatcher, that dispatches them to their destination.

(4) Each server side component answers

(5) The server then aggregates the answers and sends them back to the browser (meaning that if a request takes a long time to be processed, all the responses in the group will take a long time to be sent back to the client).

(6) On the client side, the ServerComm then properly calls each callback method with each xml response.


Sample code

Sending a message :

var serverMessage = new org.ametys.servercomm.ServerMessage(
            this.getPluginName(),                            // The plugin that will receive the message or null for the workspace
            'search/search.xml?' + args,                     // The url required in the plugin sitemap
            null,                                            // A JSon object given to the url
            org.ametys.servercomm.ServerComm.PRIORITY_MAJOR, // A priority for the message
            this.searchCallback,                             // A callback method
            this,                                            // The instance of the object having the callback method
            null,                                            // The arguments give to the callback (in adition to the response of course)
            "xml");                                          // The response type (xml, text or xml2text)

org.ametys.servercomm.ServerComm.getInstance().send(serverMessage);

As you can see here you are free to send arguments directly in the url using the GET way or through a JSON object. The second way is much more powerful but harder to debug.

Callback method :

Imagine the server answer is the following :

<search results="3">
  <result>uid:1234-caba-1af2f5af</result>
  <result>uid:1fe4-c3be-2be3e4be</result>
  <result>uid:c15a-f2f7-3cd4d3cd</result>
</search>

Your callback method would look like :

MyObject.prototype.callback = function(response, args)
{
    if (org.ametys.servercomm.ServerComm.handleBadResponse("An error occured", response, "MyObject"))
    {
        // Handle error
        return;
    }

    var results = response.selectNodes("search/result");
    for (var i=0; i < results.length; i++)
    {
        var result = results[i][org.ametys.servercomm.ServerComm.xmlTextContent];
        // Handle good result
    }
}

Message on the client side

Structure

A message is composed of :

  • A plugin name : the plugin wich will be called on the server side. Can be null to go to the root urls.
  • An url : this url will be called relatively to the plugin. The url can also have GET parameters. "search/search.xml?keyword=test" but you have to encode them manually.
  • JSON parameters : these are simple objects like Maps or Arrays of Booleans, Integers, Doubles or Strings.
    { keyword : "test", sucriteria :{ type: true, value: "yes", maxresult: 212 }}
    
  • Priority : the priority is an important parameter. See the next paragraph.
  • Callback method : the method handling the response.
  • Callback scope (ussually "this" if the callback method is defined in the current object scope)
     
  • Callback arguments : an array of arguments that will also be given to the callback method.
  • Response type : can be "xml", "text" or "xml2text". See the paragraph on Response handling.

The class used is ServerMessage. All the parameters above are in the ServerMessage constructor.

See the API of org.ametys.servercomm.ServerMessage for further information.

Priority

The priorioty is a constant which can have the following values :

  • major : for a fast departure. The message will leave in a few milliseconds (the time to wait for other messages that could be sent in the same JS cycle). The most part of the messages should use this priority.
  • normal : for a delayed departure. The message will leave in the 10 following seconds. For really non urgent messages like updating secondary data that are not visible ; or if you have a js cache, you may want to update it.
  • minor : for a really delayed departure. The message will leave in the 40 following seconds. For background messages like ping or saving preferences.
  • synchronous : see below.
  • long request : see below.

When the messages are queued, the bigger priority will win.

For example, if only three "normal" messages are queued, they will wait for 10 seconds.
But if you send a major message in between, all the messages will leave at once.

The constants are available as :

org.ametys.servercomm.ServerComm.PRIORITY_MAJOR
org.ametys.servercomm.ServerComm.PRIORITY_NORMAL
org.ametys.servercomm.ServerComm.PRIORITY_MINOR
org.ametys.servercomm.ServerComm.PRIORITY_SYNCHRONOUS
org.ametys.servercomm.ServerComm.PRIORITY_LONG_REQUEST

Sending a message

To send a message use the ServerComm.

Ext.ametys.servercomm.ServerComm.getInstance().send(serverMessage);

See the org.ametys.servercomm.ServerComm

Message handling on server side

On the server side, the request is sent to the plugin's sitemap (or workspace's sitemap if null) specified in the message.

In your action or generator you can access the parameters like so :

  • If they were sent using the GET way :
    request.getParameter("test")
    
  • If they were sent using a JSON parameter :
    Map<String, Object> jsParameters = (Map<String, Object>) objectModel.get(ObjectModelHelper.PARENT_CONTEXT);
    jsParameters.get("test")
    

The JSON way is much more powerfull sinceit allows to receive typed objects, Map and List. The null js value will, for example, be converted as null in the java code.

To make it easy for you, the root json parameters are converted automatically to request parameters (if there are no GET parameters).
So you can send the following json object:

{ test: 'foo', subtest: { type: 1, arg: 'foo2' }}

and catch the "simple" values directly using

request.getParameter("test")

but you cannot get complex types (like subtest in this example) this way.

This makes it also available in the sitemap using the request parameter inputdata (always for simple types only) :

 {request-param:test}

 You can serialize your pipeline in whatever format you want : xml, json ...

Response handling on the client side

Callback method

When the response is back, your callback method is called to handle it.

The first thing to do is to handle errors.

The generic ServerComm.handleBadResponse , allows you to test the HTTP return code and display an error message

   if (org.ametys.servercomm.ServerComm.handleBadResponse("An error occured", response, "MyObject"))
   {
        // Handle error
        return;
   }

The first argument is the error message to display to the user. (The server side error message will be available using the 'details' button)

The second argument is the response you are handling (given as an argument of your callback)

The last argument is the log category for client side logs. Can be null.

Handling the response message itself will depend on the response type.

Response type

XML

The XML response type is the default one. It allows you to go throuht the xml received using xpath.

The methods availables are :

  • selectNodes(node, xpath) that returns an array of matching nodes.
  • selectSingleNode(node, xpath) that returns the firts matching node.
  • node.getAttribute(attributeName) to get the value of an attribute of the node
  • node[org.ametys.servercomm.ServerComm.xmlTextContent] to get the text value of the node

For the following XML

<search results="17">
    <result id="identifier">
        <name>Test</name>
    </result>
    ...
</search>

You can parse it using :

var searchNode = response.selectSingleNode("search"); // Do not use absolute xpath
var maxResults = searchNode.getAttribute("results");
alert("There are " + maxResults  + " results");

var results = searchNode.selectNode("result[@id]")
for (var i = 0; i < results.length; i++)
{
    var result = results[i];
    alert("Result n°" + i + "/" + maxResults + ": " + results.selectSingleNode("name")[org.ametys.servercomm.ServerComm.xmlTextContent]);
}

Text

This reponse type send the response of the server as a text node.

If you are using json serializer, use this.

eval("var serverSideAnswer = "response[org.ametys.servercomm.ServerComm.xmlTextContent] + ";");

If the response is an xml, it will be converted as text.

For the following XML

<search results="17">
    <result id="identifier">
        <name>Test</name>
    </result>
    ...
</search>

The xpath methods will now not work since this is text.
But you can now display it to the user directly or insert it in your dom.

The only thing to do is then

alert(response[org.ametys.servercomm.ServerComm.xmlTextContent]);

xml2text

This is the same as text but in addition :

  • the xml prolog is removed
  • the first xml tag is removed
    But the rest is converted to text.

This response type has a main use case that is importing HTML (that you have to encapsulate in a tag) :

on your server side, you draw your HTML and serialize it in XML (becareful to closes tags).
you will receive then this in 'text'

<?xml version="1.0" encoding="UTF-8"?>
<mainTag>
   <div>myhtml</div>
</mainTag>

you will receive then this in 'xml2text'

<div>myhtml</div>

In thoses examples, xml is highlighted for understanding purpose but keep in mind, this is in reallity a simple text (non parsed)

You just now can do a :

mycomponent.innerHTML = response[org.ametys.servercomm.ServerComm.xmlTextContent];

Synchronous request

The main mecanism of the ServerComm is asynchronous and you should use it 99% of the time.

But sometimes you need synchronous requests (that stops the js while waiting for the answer).

Use the SYNCHRONOUS priority and get the response directly from the send method.

Here is the code of the PageMessageTargetFactory

org.ametys.messagebus.bus.impl.PageMessageTargetFactory.prototype.createTarget = function (type, parameters)
{
	var target = new org.ametys.messagebus.message.MessageTarget(type, parameters, []);

	var serverMessage = new org.ametys.servercomm.ServerMessage("web", "/repository/page-info", {id: parameters['id']}, org.ametys.servercomm.ServerComm.PRIORITY_SYNCHRONOUS, null, this, null);
	var response = org.ametys.servercomm.ServerComm.getInstance().send(serverMessage);

   if (org.ametys.servercomm.ServerComm.handleBadResponse("An error occured", response, "org.ametys.messagebus.bus.impl.PageMessageTargetFactory"))
   {
       throw "org.ametys.messagebus.bus.impl.PageMessageTargetFactory request failed";
   }

   var contents = response.selectNodes("page/contents/content");
   for (var i=0; i < contents.length; i++)
   {
	   var contentName = contents[i].selectSingleNode("name")[org.ametys.servercomm.ServerComm.xmlTextContent];
	   var subTarget = org.ametys.messagebus.bus.MessageBuilder.getInstance().createTarget("content", {'id': contentName});
	   target.getSubTargets().push(subTarget);
   }

   return target;
}

Long requests

Keep in mind that all request will come back only when they are all handled.
So if you have a request that you know it will be long, you have to set the LongRequest priority flag in order to :

  • Send the request 'alone' (it will not be grouped) to avoid that others short requests wait for you
  • Set a longer timeout (10x longer compared to normal)

Warning

If your have to send several messages at once, you can "pause" the communication with the server. Do not forget to "unpause" it.

Back to top