cross domain RPC (gwt 1.4)

368 views
Skip to first unread message

Janick Reynders

unread,
Jan 15, 2008, 3:44:05 PM1/15/08
to Google-Web-Tool...@googlegroups.com
I made changes against the 1.4 release that enable the cross-domain invocation of a RemoteService  (following GWT coding guidelines, unit tested). A service can be invoked cross-domain by annotating the service class with "@ gwt.RPCTransport ScriptTag". This makes it possible to put the host page on a different domain than the GWT application. Non-annotated RemoteServices fall back on the XHR transport (you can also explicitly annotate them with "@ gwt.RPCTransport XHR").

See also: http://groups.google.com/group/Google-Web-Toolkit-Contributors/browse_thread/thread/c0aa9feecb2c88e7/c03aaa5a882033cc

I sent a signed CLA to cla-sub...@google.com


--Janick
gwt-1.4-cross-domain.diff.txt

Andrés Testi

unread,
Jan 15, 2008, 3:54:12 PM1/15/08
to Google Web Toolkit Contributors
Hi Janick:

How are you serializing the method arguments? Are you coding with
base64? Complex objects are allowed?

-- Andrés

On 15 ene, 17:44, "Janick Reynders" <janick.reynd...@gmail.com> wrote:
> I made changes against the 1.4 release that enable the cross-domain
> invocation of a RemoteService (following GWT coding guidelines, unit
> tested). A service can be invoked cross-domain by annotating the service
> class with "@gwt.RPCTransport ScriptTag". This makes it possible to put the
> host page on a different domain than the GWT application. Non-annotated
> RemoteServices fall back on the XHR transport (you can also explicitly
> annotate them with "@gwt.RPCTransport XHR").
>
> See also:http://groups.google.com/group/Google-Web-Toolkit-Contributors/browse...
>
> I sent a signed CLA to cla-submissi...@google.com
>
> --Janick
>
> gwt-1.4-cross-domain.diff.txt
> 114 KDescargar

janick reynders

unread,
Jan 16, 2008, 4:32:10 AM1/16/08
to Google Web Toolkit Contributors
Hi Andrés,

> How are you serializing the method arguments? Are you coding with
> base64? Complex objects are allowed?

It's basically GWT-RPC over script tags instead of XmlHttpRequest. The
GWT-RPC request is urlencoded and transmitted via an url parameter.
Since complex objects are supported by GWT-RPC, these are also
supported when the communication is done using script tags, the only
thing that changes is that the GWT-RPC payload is transported in a
different way.

Code your service as described in
http://code.google.com/webtoolkit/documentation/com.google.gwt.doc.DeveloperGuide.RemoteProcedureCalls.html

but use:
/**
* @gwt.RPCTransport ScriptTag
*/
public interface MyService extends RemoteService {
public String myMethod(String s);
}

instead of:
public interface MyService extends RemoteService {
public String myMethod(String s);
}

--Janick

Andrés Testi

unread,
Jan 16, 2008, 9:13:34 AM1/16/08
to Google Web Toolkit Contributors
I like this approach. I've another question: what about the URL 2KB
limitation? An exception is throwed if the payload is over 2KB?
A kind of protocol is needed to send big payloads to cross domain, to
divide the payload in 2KB segments, and manage cross domain sessions.
A quick solution would be compress the payload to gain some bytes.
I've developed a GWT based LZW compressor with ASCII input and base64
output. Please, take a look in this demo: http://www.juglar.org/tests/blackpill/CompressionTest.html

-- Andrés


On 16 ene, 06:32, janick reynders <janick.reynd...@gmail.com> wrote:
> Hi Andrés,
>
> > How are you serializing the method arguments? Are you coding with
> > base64? Complex objects are allowed?
>
> It's basically GWT-RPC over script tags instead of XmlHttpRequest. The
> GWT-RPC request is urlencoded and transmitted via an url parameter.
> Since complex objects are supported by GWT-RPC, these are also
> supported when the communication is done using script tags, the only
> thing that changes is that the GWT-RPC payload is transported in a
> different way.
>
> Code your service as described inhttp://code.google.com/webtoolkit/documentation/com.google.gwt.doc.De...

Andrés Testi

unread,
Jan 16, 2008, 9:24:44 AM1/16/08
to Google Web Toolkit Contributors
BTW, the GWT serialization stream, allows to optimize the compresion
dividing the output stream in two compression streams: an stream for
numbers (with an initial dictionary for only 16 chars), and a stream
for string (with an initial a dictionary for 256 chars).

-- Andrés

Robert Hanson

unread,
Jan 16, 2008, 9:25:48 AM1/16/08
to Google-Web-Tool...@googlegroups.com
I was about to bring up the same point. The limitation isn't 2K
though, each browser has it's own maximum.

From: http://classicasp.aspfaq.com/forms/what-is-the-limit-on-querystring/get/url-parameters.html

"RFC 2068 states: Servers should be cautious about depending on URI
lengths above 255 bytes, because some older client or proxy
implementations may not properly support these lengths."

...and...

"The spec for URL length does not dictate a minimum or maximum URL
length, but implementation varies by browser. On Windows: Opera
supports ~4050 characters, IE 4.0+ supports exactly 2083 characters,
Netscape 3 -> 4.78 support up to 8192 characters before causing errors
on shut-down, and Netscape 6 supports ~2000 before causing errors on
start-up.

So even if you set the limit to 2000 characters you still run the risk
of failures due to lesser maximums on browsers that are not explicitly
supported and proxy servers.

I like the idea a lot, I am just concerned about potential failures
that I can't even test for. I don't even think there is a
programmatic solution, and that we would need to rely on good
documentation and possible compile-time warnings.

I would be interested on hearing about some testing results on this.
Specifically, what happens when you exceed the threshold? Also, the
info I provided is a bit old, it would be great to hear if newer
browsers have different limitations.

Rob
http://roberthanson.name

Andrés Testi

unread,
Jan 16, 2008, 10:10:13 AM1/16/08
to Google Web Toolkit Contributors


On 16 ene, 11:25, "Robert Hanson" <iamroberthan...@gmail.com> wrote:
> I was about to bring up the same point. The limitation isn't 2K
> though, each browser has it's own maximum.

You're right. I was assuming the lesser maximum was the IE size,
because IE is the lesser capable browser :-P

> So even if you set the limit to 2000 characters you still run the risk
> of failures due to lesser maximums on browsers that are not explicitly
> supported and proxy servers.

Yes, and for this issue I'm talking about a "kind of protocol". Why
"protocol"? Well, because the maximum size is a combination between
browser maximum/proxy maximum/HTTP server maximum. The hypothetical
protocol I'm proposing, begins with a URL size test, to stablish the
maximum size allowed in the channel. Secondly, the segment size is
determined, and the payload is divided in a sequence of numbered
segments. But warning! A session is needed here, to associate the
severals segments with a unique client. And more warning! JSONP
requires non-cookie based session, then the session needs to be
managed explicitally by GWT. And what about if a segment is lost?
There is need of a timeslice?
If it walks like a protocol and quacks like a protocol, I would call
it a protocol. ;-)

-- Andrés

janick reynders

unread,
Jan 16, 2008, 10:15:59 AM1/16/08
to Google Web Toolkit Contributors
Hi Andrés,

On 16 jan, 15:24, "Andrés Testi" <andres.a.te...@gmail.com> wrote:
> BTW, the GWT serialization stream, allows to optimize the compresion
> dividing the output stream in two compression streams: an stream for
> numbers (with an initial dictionary for only 16 chars), and a stream
> for string (with an initial a dictionary for 256 chars).
>

I did not change the way the RPC request and response are built, I
assumed it already called the appropriate methods on the serialization
stream. So this is OK I guess?

--Janick

janick reynders

unread,
Jan 16, 2008, 11:13:56 AM1/16/08
to Google Web Toolkit Contributors
About the URL length:
This is indeed a problem. And in the submitted patch there is no error
handling for it.

> I like the idea a lot, I am just concerned about potential failures
> that I can't even test for. I don't even think there is a
> programmatic solution, and that we would need to rely on good
> documentation and possible compile-time warnings.

Cross-domain communication is a problem I really needed a solution
for. The only known way (to me, I'm rather new to this) to solve this
is by dynamically adding script tags. Keeping this in mind you can do
the communication in 2 different ways:

1) by hand crafting the request by manually, adding the different
parameters and values (and writing the servlet code to parse these
parameters and returning a good response)

2) letting GWT build the request automatically (for example the way
this patch does it)

Both options suffer from the URL length limit. However with (1) you
are building the URL manually and because of that you are very well
aware of the possible URL length, but doing this for each service is a
lot of work. Option (2) lets you avoid the work, and if you want to
you can still fall back on option (1). I also assumed that the RPC
encoding had more potential for compressing the payload than the case
where URLs would be hand crafted.

The programmatic solution proposed by Andrés (the segmentation) looks
interesting, I'll have to think about this a bit more. It also looks
like a daunting task.

Good documentation would indeed be essential. Generating good and
useful compile time warnings looks also difficult because you probably
would have to generate a warning for each remote service that uses the
ScriptTag transport if its methods take non-primitive arguments.

--Janick

Ray Cromwell

unread,
Jan 16, 2008, 11:31:54 AM1/16/08
to Google-Web-Tool...@googlegroups.com
One, somewhat ugly hack, is to perform the request using two requests.
The first, use a cross-domain <form> submit to send the RPC arguments
with a rendezvous id, the second, use a script tag with the ID to pick
up the results. It incurs 2x HTTP requests, and extra server state
tho.

I use script-tag RPC on Chronoscope too, because in widget deployment
mode, I let people people embed the widget locally, but target an RPC
font-metrics server we host. Luckuly, I know the RPC arguments are
just primitives.

Robert Hanson

unread,
Jan 16, 2008, 12:49:30 PM1/16/08
to Google-Web-Tool...@googlegroups.com
I like this idea... unless a better way is suggested.

The ugly part doesn't bother me, just as long as it is under the
covers and I don't need to see it.

Rob

Andrés Testi

unread,
Jan 16, 2008, 12:55:00 PM1/16/08
to Google Web Toolkit Contributors
On 16 ene, 13:31, "Ray Cromwell" <cromwell...@gmail.com> wrote:
> One, somewhat ugly hack, is to perform the request using two requests.
> The first, use a cross-domain <form> submit to send the RPC arguments
> with a rendezvous id, the second, use a script tag with the ID to pick
> up the results. It incurs 2x HTTP requests, and extra server state
> tho.

Don't worry about hacks, AJAX programming is 60% based in hacks ;-)

Matthew Mastracci

unread,
Jan 16, 2008, 2:58:45 PM1/16/08
to Google Web Toolkit Contributors
I've seen a slightly modified version of that where you do two posts
within an <iframe>:

One post submits the RPC call to the remote server with a "return post
URL". The remote server then posts the data back to the return post
URL which is on the same domain. Because of this, it can access the
parent window's script.

The advantage to this method is that there are no size limits (beyond
POST limits) and no server state. The disadvantage is that you
require a dedicated server script in the same scripting domain as the
caller to convert the return POST into a JS callback.

On Jan 16, 9:31 am, "Ray Cromwell" <cromwell...@gmail.com> wrote:
> One, somewhat ugly hack, is to perform the request using two requests.
> The first, use a cross-domain <form> submit to send the RPC arguments
> with a rendezvous id, the second, use a script tag with the ID to pick
> up the results. It incurs 2x HTTP requests, and extra server state
> tho.
>
> I use script-tag RPC on Chronoscope too, because in widget deployment
> mode, I let people people embed the widget locally, but target an RPC
> font-metrics server we host. Luckuly, I know the RPC arguments are
> just primitives.
>
> On Jan 16, 2008 6:25 AM, Robert Hanson <iamroberthan...@gmail.com> wrote:
>
>
>
> > I was about to bring up the same point. The limitation isn't 2K
> > though, each browser has it's own maximum.
>
> > From:http://classicasp.aspfaq.com/forms/what-is-the-limit-on-querystring/g...
>
> > "RFC 2068 states: Servers should be cautious about depending on URI
> > lengths above 255 bytes, because some older client or proxy
> > implementations may not properly support these lengths."
>
> > ...and...
>
> > "The spec for URL length does not dictate a minimum or maximum URL
> > length, but implementation varies by browser. On Windows: Opera
> > supports ~4050 characters, IE 4.0+ supports exactly 2083 characters,
> > Netscape 3 -> 4.78 support up to 8192 characters before causing errors
> > on shut-down, and Netscape 6 supports ~2000 before causing errors on
> > start-up.
>
> > So even if you set the limit to 2000 characters you still run the risk
> > of failures due to lesser maximums on browsers that are not explicitly
> > supported and proxy servers.
>
> > I like the idea a lot, I am just concerned about potential failures
> > that I can't even test for. I don't even think there is a
> > programmatic solution, and that we would need to rely on good
> > documentation and possible compile-time warnings.
>
> > I would be interested on hearing about some testing results on this.
> > Specifically, what happens when you exceed the threshold? Also, the
> > info I provided is a bit old, it would be great to hear if newer
> > browsers have different limitations.
>
> > Rob
> >http://roberthanson.name
>

janick reynders

unread,
Jan 21, 2008, 3:31:05 AM1/21/08
to Google Web Toolkit Contributors
Hi Ray,


On 16 jan, 17:31, "Ray Cromwell" <cromwell...@gmail.com> wrote:
> One, somewhat ugly hack, is to perform the request using two requests.
> The first, use across-domain<form> submit to send the RPC arguments
> with a rendezvous id, the second, use a script tag with the ID to pick
> up the results. It incurs 2x HTTP requests, and extra server state
> tho.
>

I didn't know cross-domain form submit was possible. Do we really need
the 2 requests? For example: what if the form submit uses an iframe as
target, and the response is something like:
<script>super.window.mycallback({"rpcResult": .... });</script>

--Janick

Ray Cromwell

unread,
Jan 21, 2008, 3:35:58 AM1/21/08
to Google-Web-Tool...@googlegroups.com
I dunno if it will work because the domain of the IFRAME will be
different than the domain of the main GWT app. I use a similar
technique to do Google AuthSub in GWT. I send a popup window with
IFRAME to Google's login screen, it redirects back to a servlet which
renders a script tag which invokes a callback in the main GWT app
which closes the popup and updates the UI with success/failure.
However, the servlet which renders the <SCRIPT> is in the same domain
as the GWT app.

-Ray

Rajeev Dayal

unread,
Jan 22, 2008, 10:22:00 AM1/22/08
to Google-Web-Tool...@googlegroups.com
Ray,

You are right - using:


 <script>super.window.mycallback({"rpcResult": .... });</script>

will not work, if the domain of the IFRAME differs from that of the parent window. I had tried doing this before, and the Same-Origin Policy stopped me in my tracks.



Rajeev

pash7ka

unread,
Feb 1, 2008, 3:35:48 PM2/1/08
to Google Web Toolkit Contributors
I've tried the patch and found a problem while decoding request.
Here is a part of the stack trace of exception i had:

2008-02-01 18:46:47,526 ERROR [org.apache.catalina.core.ContainerBase.
[jboss.web].[localhost].[/CustomerInfoPanel]] Exception while
dispatching incoming RPC call
java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.RangeCheck(ArrayList.java:547)
at java.util.ArrayList.get(ArrayList.java:322)
at
com.google.gwt.user.server.rpc.impl.ServerSerializationStreamReader.extract(ServerSerializationStreamReader.java:
282)
at
com.google.gwt.user.server.rpc.impl.ServerSerializationStreamReader.readInt(ServerSerializationStreamReader.java:
142)
at
com.google.gwt.user.client.rpc.impl.AbstractSerializationStreamReader.prepareToRead(AbstractSerializationStreamReader.java:
38)
at
com.google.gwt.user.server.rpc.impl.ServerSerializationStreamReader.prepareToRead(ServerSerializationStreamReader.java:
97)
at com.google.gwt.user.server.rpc.RPC.decodeRequest(RPC.java:234)
at
com.google.gwt.user.server.rpc.RemoteServiceServlet.processCall(RemoteServiceServlet.java:
279)
at
com.google.gwt.user.server.rpc.RemoteServiceServlet.handleRPCRequest(RemoteServiceServlet.java:
495)
at
com.google.gwt.user.server.rpc.RemoteServiceServlet.doGet(RemoteServiceServlet.java:
209)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:690)


As my investigation showed, the request had not been decoded properly
from UTF-8 and because of this could not be parsed by
ServerSerializationStreamReader.
Here is my change of
com.google.gwt.user.server.rpc.RemoteServiceServlet which corrects
this:
===================================================================
--- RemoteServiceServlet.java.bak Thu Jan 31 18:21:32 2008
+++ RemoteServiceServlet.java Fri Feb 01 23:15:56 2008
@@ -68,8 +68,17 @@
}

String readPayload(HttpServletRequest request) throws
IOException, ServletException {
- return new String(request.getParameter("rpc").getBytes(),
"utf-8");
+ return decodeUTF8(request.getParameter("rpc"));
};
+ private String decodeUTF8(String utf8) throws IOException {
+ char[] buf = new char[utf8.length()];
+ utf8.getChars(0, utf8.length(), buf, 0);
+ byte[] bytes = new byte[buf.length];
+ for(int i=0; i < buf.length; i++){
+ bytes[i] = (byte)buf[i];
+ }
+ return new String(bytes, "UTF-8");
+ }
};

abstract String getContentType();
===================================================================

pash7ka

unread,
Feb 25, 2008, 10:30:25 AM2/25/08
to Google Web Toolkit Contributors
Unfortunately, I had to stop using this patch because of Opera's cache
problems: PRC responses are cached by Opera.
There is a work-around for this - we can add timestamp parameter to
request, may be I'll do it later.

John Tamplin

unread,
Feb 25, 2008, 10:44:45 AM2/25/08
to Google-Web-Tool...@googlegroups.com
Are you sure the web server was setting the cache headers properly on the script-tag RPC response?  If Opera caches the response anyway, things are going to be horribly broken on Opera, not just for this.

--
John A. Tamplin
Software Engineer, Google

PashKa

unread,
Feb 25, 2008, 11:10:36 AM2/25/08
to Google-Web-Tool...@googlegroups.com
No, I had not examined that headers. And I hadn't done any special configuration for this responses, just as I hadn't do it for XHR transport. But I've found Script-Tag transport working in IE & Firefox, and totally broken in Opera.
I just have no time now to check for correct headers, sorry.

John Tamplin

unread,
Feb 25, 2008, 11:16:52 AM2/25/08
to Google-Web-Tool...@googlegroups.com
On Mon, Feb 25, 2008 at 11:10 AM, PashKa <pas...@gmail.com> wrote:
No, I had not examined that headers. And I hadn't done any special configuration for this responses, just as I hadn't do it for XHR transport. But I've found Script-Tag transport working in IE & Firefox, and totally broken in Opera.
I just have no time now to check for correct headers, sorry.

Most browsers refuse to cache POST responses, but they do cache script GET reponses.  So, if you want this to work properly, you need to have the server set the proper cache headers (in fact you need to in general to get the best performance).  Otherwise, if you make the same RPC call with the same arguments it will get cached (and I found it to be the case on all browsers, not just Opera, although they may have different triggers for URLs that heuristically don't get cached).

PashKa

unread,
Feb 26, 2008, 8:16:55 AM2/26/08
to Google-Web-Tool...@googlegroups.com
Thanks! I've added function below and calls to it and it seems like the problem had been solved.
-------
    private static DateFormat df = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z");
    protected void addNoCacheHeaders(){
        HttpServletResponse responce = getThreadLocalResponse();
        responce.addHeader("Cache-Control", "no-store");
        responce.addHeader("Expires", df.format(new Date()));
    }
-------

John Tamplin

unread,
Feb 26, 2008, 8:34:49 AM2/26/08
to Google-Web-Tool...@googlegroups.com
We need to do a better job documenting the recommended best practice, but there are a couple of other things you should include:
  • Cache-Control: private, no-cache (no-store just means any cache [such as an intervening proxy] can't store the response to disk, but it can still cache it)
  • Pragma: no-cache (old browsers/proxies)
  • You need proper Date and Last-Modified times as well or some browsers ignore the Expires (I don't know if those are set outside this code snippet, as they aren't really cache headers)
  • Any pre-expired Expires header is sufficient, so you don't need to set it to the current time (and in fact could be dangerous if the client's clock is wrong) - personally I use Fri, 2 Jan 1970 00:00:00 GMT.
I have also seen including a bogus Set-Cookie header for broken caches that ignore both Cache-Control and Pragma, but surely those have long since been replaced.
Reply all
Reply to author
Forward
0 new messages