When I tried it out, I was kind of surprised to see that uploading to
the non-existent form action closed the GWT shell. When I did a GWT
compile, firefox didn't do anything.
So I'm trying to generally understand; by default, if you go to one of
these external servlets, does the returning call "wipe out" the GWT
application in the root of the browser, or can it happen more "behind
the scenes" in a frame or something like that? Looking at the
documentation, I thought the latter, but I'm trying to figure out why
my halfway-there code would cause GWT shell to unceremoniously close.
Thanks...
Hence, your window WILL move on to the form target, period.
The practical way to get around this, is to perform the form upload
stuff inside an iframe, that way it's just the iframe that is forced
to go to another site.
So the only way to use the upload widget (assuming you don't want to
have the your app end and exit to whatever the upload servet sends
back) would be to... I dunno, have an iframe in your main app, and
then a separate EntryPoint for a distinct "uploader GWT application"
that might use the tag. (Or, possibly more practically, just have a
separate static HTML page.
is that the status quo? Is there any hope of future GWT revisions
addressing this? Just to speculate out loud, i would guess that you
might have problems getting RemoteServiceServlet to handle binary
data, or at least overcome the javascript sandbox ... but I wonder if
they couldn't make a special FileUploaderForm widget, maybe nested in
an iframe, that could hold widgets, at least until the upload occured,
then it would go away until reset or something, the HTML content then
being whatever the server sent back.
I'm not sure if that last paragraph makes sense or violates too many
paradigms of how the GWT works.
On Mar 13, 8:12 pm, "Dan Morrill" <morri...@google.com> wrote:
> Hi!
>
> I believe Jason Essington at one point was putting some thought into how to
> build a mechanism where you could get an RPC-style callback with response
> data, as the response returned from a form upload.
>
> Perhaps he'll spot this thread and comment on that technique...
>
> - Dan Morrill
>
My Proof of Concept goes something like this.
A form is created on the client.
VerticalPanel panel = new VerticalPanel();
form = new FormPanel();
form.setAction("services/PostMessage");
form.setEncoding(FormPanel.ENCODING_MULTIPART);
form.setMethod(FormPanel.METHOD_POST);
form.addFormHandler(new PostFormHandler(this));
form.setWidget(panel);
The PostFormHandler needs to be able to deserialize an RPC response,
so it needs to be able to hijack a type serializer that was created
for some other RPC. to do this I create an interface that I use along
with deferred binding to grab the serializer at compile time:
serializer = (Serializer) GWT.create
(Collaberation_TypeSerializer.class);
Collaberation_TypeSerializer.class is just a marker interface that I
use for the deferred binding.
from the .gwt.xml file ...
<replace-with
class="com.grcs.orbit.collab.client.interfaces.CollaberationService_Type
Serializer">
<when-type-is
class="com.grcs.orbit.collab.client.serialization.Collaberation_TypeSeri
alizer" />
</replace-with>
This binding assumes that there is a CollaberationService that uses
all of the types / exceptions that you expect to get back from the
form post. The easy way to figure out what TypeSerializer to hijack
is to add the -gen switch to your compile script to force GWT to save
the generated java source it creates.
Once the formHandler successfully deserializes the response, it'll
need an AsyncCallback to invoke the onSuccess or onFailed methods:
public void onSubmitComplete(FormSubmitCompleteEvent event)
{
Object result = null;
Throwable caught = null;
try
{
if (serializer != null)
{
String responseText = event.getResults();
ClientSerializationStreamReader stream = new
ClientSerializationStreamReader(serializer);
if (responseText.startsWith("{OK}"))
{
stream.prepareToRead(responseText.substring(4));
// our expected response type is a string this time.
result = stream.readString();
// result = stream.readObject();
}
else if (responseText.startsWith("{EX}"))
{
stream.prepareToRead(responseText.substring(4));
caught = (Throwable) stream.readObject();
}
else
{
// unlike RPC, if this isn't a serialized response,
we just assume that our response is a String
result = responseText;
}
}
else
result = event.getResults();
}
catch (Throwable e)
{
Window.alert(e.getMessage());
caught = e;
}
if (caught == null)
{
callback.onSuccess(result);
}
else
{
callback.onFailure(caught);
}
}
That pretty much does it for the client side. Now for the server
side, you need a regular servlet that is capable of handling the form
post, and serializing the response. I created an abstract servlet
that can be extended to handle the request. It uses quite a lot of
code stolen from RemoteServiceServlet to perform the serialization of
the response object, and looks something like this:
public abstract class PostMessageServlet extends HttpServlet //
AuthenticationAgent
{
protected Logger log = Logger.getLogger(getClass());
private final ThreadLocal<HttpServletRequest> perThreadRequest =
new ThreadLocal<HttpServletRequest>();
private final ThreadLocal<AuthenticationAgent> authAgent = new
ThreadLocal<AuthenticationAgent>();
private final ServerSerializableTypeOracle serializableTypeOracle;
public static final String MULTIPART = "multipart/form-data";
public static final String RESPONSE_CONTENT_TYPE = "text/html";
public PostMessageServlet()
{
serializableTypeOracle = new ServerSerializableTypeOracleImpl
(getPackagePaths());
}
@Override
protected void service(HttpServletRequest req,
HttpServletResponse res) throws ServletException, IOException
{
perThreadRequest.set(req);
authAgent.set(new AuthenticationAgent(req));
String reqMethod = req.getMethod();
if ("POST".equals(reqMethod))
{
try
{
Object resObj = doNonRPCPost(req, res);
writeResponse(req, res, createResponse(resObj.getClass
(), resObj, false));
}
catch (Throwable e)
{
writeResponse(req, res, createResponse(e.getClass(), e,
true));
}
}
else
super.service(req, res);
}
protected HttpServletRequest getThreadLocalRequest()
{
return perThreadRequest.get();
}
protected AuthenticationAgent getThreadLocalAuthAgent()
{
return authAgent.get();
}
protected abstract Object doNonRPCPost(HttpServletRequest req,
HttpServletResponse res) throws Exception;
private void writeResponse(HttpServletRequest req,
HttpServletResponse res, String responsePayload)
throws IOException
{
StringBuffer responseText = new StringBuffer();
responseText.append(responsePayload);
byte[] responseBytes = responseText.toString().getBytes("UTF-8");
res.setContentLength(responseBytes.length);
res.setContentType(RESPONSE_CONTENT_TYPE);
OutputStream out = res.getOutputStream();
res.setStatus(HttpServletResponse.SC_OK);
out.write(responseBytes);
out.close();
}
private String[] getPackagePaths()
{
return new String[] { "com.google.gwt.user.client.rpc.core" };
}
private String createResponse(Class responseType, Object
responseObj, boolean isException)
{
ServerSerializationStreamWriter stream = new
ServerSerializationStreamWriter(serializableTypeOracle);
stream.prepareToWrite();
if (responseType != void.class)
{
try
{
stream.serializeValue(responseObj, responseType);
}
catch (SerializationException e)
{
responseObj = e;
isException = true;
}
}
String bufferStr = (isException ? "{EX}" : "{OK}") +
stream.toString();
return bufferStr;
}
}
basically, on the server side, you extend this servlet, and implement
doNonRPCPost() (sorry for the confusing name, it is legacy from when
I was doing things much uglier than this) to handle the request.
rather than writing any kind of response, you just return the object
that you want returned to the client, and PostMessageServlet handles
the rest.
Exceptions or response objects are serialized and returned to the
client.
DISCLAIMER: this is pretty rough code, and uses some nasty patterns
to get to things that it probably shouldn't be allowed to access, so
it may be fragile. It does work in my case, but no guarantees.
-jason