Contrib - Cancellable GWT RPC Requests

167 views
Skip to first unread message

melody

unread,
Sep 1, 2007, 9:36:51 PM9/1/07
to Google Web Toolkit
Proposition and Need:
a) GWT RPC mechanism provides no room for cancelling RPC requests once
submitted.
b) It is sometimes necessary to chain RPC requests and have any
requests submitted after a certain one be cancelled if the earlier one
is cancelled or fails.

Someone may already have found a solution for this but I thought I
would add my version of a scheme for submitting RPC requests that can
be cancelled before or after the request has already been submitted to
the server.

Queuing class code
[code]
package com.acme.gwt.client.rpc;

import java.util.ArrayList;
import java.util.List;

/**
*
* @author Melody
*
* Singleton class that contains all RPC requests in a FIFO queue. No
more than one RPC request shall be submitted to the server at any one
time. All requests submitted after another request will only be
excuted
* when that rquest has either succeded or failed, depending on
whether or not that particular request mandates that all requests be
cancelled if it fails
*/
public class ACMERPCRequestQueue {
private final List requests = new ArrayList();
private static ACMERPCRequestQueue INSTANCE;

public static ACMERPCRequestQueue getInstance() {
return (INSTANCE = (INSTANCE == null? new
ACMERPCRequestQueue() : INSTANCE));
}

/** Creates a new instance of ACMERPCRequestQueue */
private ACMERPCRequestQueue() {
}


public void add(ACMEAbstractAsyncCallback request) {
requests.add(request);
}

public void remove(ACMEAbstractAsyncCallback request) {
requests.remove(request);
}

public void clearAll() {
this.requests.clear();
}

public int count() {
return requests.size();
}

public ACMEAbstractAsyncCallback next() {
return (requests.size() > 0 ?
(ACMEAbstractAsyncCallback)requests.get(0) : null);
}
}

[/code]


The AsyncCallBack class code
[code]
package com.acme.gwt.client.rpc;

import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.InvocationException;
import com.acme.gwt.client.ACMEEventDispatcher;
import java.util.ArrayList;
import java.util.List;

/**
*
* @author Melody
*/
public abstract class ACMEAbstractAsyncCallback implements
AsyncCallback, Command
{
private long ii_whencallmade = 0L;
protected boolean ib_server_authentication_exception = false;
private boolean ib_cancelled = false;
private boolean ib_cancel_pendingrequests_onfailure = false;
private boolean ib_bypass_queue = false;

/**
* Creates a new instance of ACMEAbstractAsyncCallback
*/
public ACMEAbstractAsyncCallback()
{
}


/**
* @param ab_cancel_pendingrequests_onfailure If true then all
requests submitted after this one will be cancelled if this request is
cancelled.
* @param ab_bypass_queue If you have request that cannot be
queued but rather must be submitted right away, use this param to
bypass the request queue
*/
public ACMEAbstractAsyncCallback(boolean
ab_cancel_pendingrequests_onfailure, boolean ab_bypass_queue)
{
ib_cancel_pendingrequests_onfailure =
ab_cancel_pendingrequests_onfailure;
ib_bypass_queue = ab_bypass_queue;
}
public final void onSuccess(Object result)
{
ib_server_authentication_exception = false;

ACMEEventDispatcher.getInstance().fireServerResponseReceived(ii_whencallmade,
System.currentTimeMillis());
if (ib_cancelled)
{
return;
}
onSuccessImpl(result);
ACMERPCRequestQueue.getInstance().remove(this);
execNextRequest();
}

public final void onFailure(Throwable result)
{

ACMEEventDispatcher.getInstance().fireServerResponseReceived(ii_whencallmade,
System.currentTimeMillis());
if (ib_cancelled)
{
return;
}
if (catchAuthorizationFailures(result))
{
//process abnormal or auth failures
ib_server_authentication_exception = true;
ACMEEventDispatcher.getInstance().fireServerRequestedAuth();
}
else
{
ib_server_authentication_exception = false;
echoErrorMessage(result);
}
onFailureImpl(result); //process normal failures
if (ib_cancel_pendingrequests_onfailure)
{
cancel();
}
else
{
ACMERPCRequestQueue.getInstance().remove(this);
execNextRequest();
}
}

protected void execNextRequest()
{
ACMEAbstractAsyncCallback lc =
(ACMEAbstractAsyncCallback)ACMERPCRequestQueue.getInstance().next();
if (lc != null)
{
lc.submit();
}
}


public boolean catchAuthorizationFailures(Throwable caught)
{
if (caught == null || !(caught instanceof InvocationException))
{//probably filtered
return false;
}
String ls_message = null;
ls_message = caught.getMessage();
if (ls_message == null)
{
return false;
}
if (ls_message.indexOf("HTTP Status 401") >= 0)
{
ls_message = "Attempted unauthorized access or your session was
timed out";
Window.alert(ls_message);
return true;
}
return false;
}


/**
* Implement or overide this function to echo an error or other
alert messages you may want echoed after the RPC returns or throws an
exception. If however an Authorization exception or 401 error is
received
* from the server, this method will not be called or executed by
the framework.
*/
protected abstract void echoErrorMessage(Throwable caught);

/**
* Implement this to process RPC results returned by the server
*/
public abstract void onSuccessImpl(Object result);


/**
* Implement this to process RPC exceptions thrown by the server
*/
public abstract void onFailureImpl(Throwable result);

/**
* Actually does the remote service call based on your
implementation of executeImpl() method. Once the callback onSucessImpl
and onFailureImpl methods have been created, all you need to do is
call
* ACMEAbstractAsyncCallback.execute() where l_callback is the
instance of this class that encapsulates both the callback and the
command.
*/
public final void execute()
{
if (ib_bypass_queue)
{
submit();
}
else
{
ACMERPCRequestQueue.getInstance().add(this);
if (ACMERPCRequestQueue.getInstance().count() == 1)
{//otherwise leave in queue until executable
submit();
}
}
}

/**
* Implement this to submit your call to the RPC service
*/
public abstract void executeImpl();

protected void submit()
{
ii_whencallmade = System.currentTimeMillis();
ACMEEventDispatcher.getInstance().fireServerCallInitiated();
executeImpl();
}

public void cancel() {
ib_cancelled = true;//just in case we cancelled after RPC
request submitted and b4 RPC response is receieved
if (ib_cancel_pendingrequests_onfailure) {
ACMERPCRequestQueue.getInstance().clearAll();
} else {
ACMERPCRequestQueue.getInstance().remove(this);
}
} }


[/code]

Example use of the classes to dispatch an RPC request
[code]
public void doRPCExample() {
ACMEAbstractAsyncCallback l_command = new ACMEAbstractAsyncCallback()
{
public void onSuccessImpl(Object result) {
//do something with result
}
public void onFailureImpl(Throwable caught) {
//do something with error
}
protected void echoErrorMessage(Throwable caught)
{
Window.alert("Error);
}
public void executeImpl() {
try {

ServerFacade.get().getRemoteService().exampleRPCMethod(1, true, true,
ACMELoginDialog.getInstance().getUid(), this);
} catch (Exception e) {
}
}
};
l_command.execute();
}
}
[/code]

Details
The idea, which you can easily decipher from the code, is that
requests are queued in a FIFO queue (with exceptions).

1) I can call cancel() on the RPC request anytime I want as long as I
have access to the ACMEAbstractAsyncCallback object. So if I decide
that the request has not met the threshold for RPC response I can just
cancel it and any subsequent calls I may have made.

2)If the request is still in the queue it will simply be removed
before the Submit() method is executed.\

3) If however the request had been submitted to the server but the
response was not yet recieved, that response will be ignored when it
does come back

4) In all cases, if the request mandates that all requests that come
after it be cancelled along with it, then the queue will be cleared to
zero on cancellation

5) Also note that the caller/programmer creates a Command/
AsycnCallback by implementing the excecimpl(), onsucessimpl(), and
onfailureimpl() methods and then calling the execute() method of
ACMEAbstractAsyncCallback. What RPC Service method is being called is
up the the programmer

6) The EventDispatcher is somewhat specific to my requirement to know
when the last RPC request was made for browser timeout purposes.

7) You also have the ability to submit requests directly without
putting them on the tail of the queue if there is such need.

Granted, you cannot effect any processing that the request may be
doing on the server if by the time you cancel it had already been
submitted to the RPC servlet. You can however easily make an extension
by implementing a server side version of the queue and actually do
cancel requests on the server if you so wish.

Ideas, criticisms and improvements welcome.

Thanks,

Melody

joster

unread,
Sep 1, 2007, 11:32:35 PM9/1/07
to Google Web Toolkit
Hi-

Thanks for posting your code.

Just trying to understand better:
- The browser already does some kind of queuing of RPC requests,
right? If an application issues (say) 50 RPC requests, the browser
will automatically do some sort of queuing of its own (limit how many
requests can be sent) - is my understanding correct?
- Is the case you have described for chaining RPC requests applicable
to scenarios where the order of results matter for multiple RPC
requests? If that is the case, should the RPC requests itself be
bundled and let the server-side code handle the request in a pre-
defined order?

Your architecture looks good, I am just trying to understand various
usage models and needs.

Joster

melody

unread,
Sep 2, 2007, 1:12:38 PM9/2/07
to Google Web Toolkit
>>
- The browser already does some kind of queuing of RPC requests,
right? If an application issues (say) 50 RPC requests, the browser
will automatically do some sort of queuing of its own (limit how many
requests can be sent) - is my understanding correct?
<<
Yes the browser does its own queuing and allows only two requests to
be sent to the server at any one time. What the browser and GWT do not
give you is the ability to cancel a request (b4 or after it has
already made a trip to the server) depending on what else happens or
on specific threshholds being met. If GWT gave you a handle to the RPC
Servvice class so that you can do whatever you want with it (including
cancel) then my scheme would not be required.

>>
Is the case you have described for chaining RPC requests applicable
to scenarios where the order of results matter for multiple RPC
requests? If that is the case, should the RPC requests itself be
bundled and let the server-side code handle the request in a pre-
defined order?
<<

In some cases the order may not matter so much as the fact that if one
10 different requests submitted to the server fails the rest must also
fail. And talking of bundling, I learned the hard way when I tried
bundling too many seemingly bundlable requests when the server would
timeout because it was taking too long to return (not quite related to
this subject but just mentioning).
There may be better ways to do chaining but I have kind of struggled
with the fact that GWT/AJAX in general only allows async requests when
there are so many scenarios in which I would want to be able to send a
synchronous request followed by another. One could also create a chain
of commands and put them in a list (which is essentially what my
RPCQueue does) and execute them in turn -- not so easy.

> ...
>
> read more »

Reply all
Reply to author
Forward
0 new messages