Monday, February 15, 2016

Re: Use synchronous RPC on browser close ?

package com.aris.modeling.gwt.shared.rpc;

import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.SyncRequestBuilder;
import com.google.gwt.user.client.rpc.RpcRequestBuilder;

/**
* User: roda
* Date: 10.01.12
* Time: 11:26
*/
public class SyncRpcRequestBuilder extends RpcRequestBuilder {

@Override
protected RequestBuilder doCreate(String serviceEntryPoint) {
return new SyncRequestBuilder(RequestBuilder.POST, serviceEntryPoint);
}
}
package com.aris.modeling.gwt.shared.rpc;

import com.google.gwt.core.client.GWT;
import com.google.gwt.http.client.*;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.InvocationException;
import com.google.gwt.user.client.rpc.RpcRequestBuilder;
import com.google.gwt.user.client.rpc.impl.RemoteServiceProxy;
import com.google.gwt.user.client.rpc.impl.RequestCallbackAdapter;
import com.google.gwt.user.client.rpc.impl.RpcStatsContext;
import com.google.gwt.user.client.rpc.impl.Serializer;

/**
* User: roda
* Date: 22.11.11
* Time: 14:31
*/
public class GetRemoteServiceProxy extends RemoteServiceProxy {

private boolean m_isSync = false;

protected GetRemoteServiceProxy(String moduleBaseURL, String remoteServiceRelativePath, String serializationPolicyName, Serializer serializer) {
super(moduleBaseURL, remoteServiceRelativePath, serializationPolicyName, serializer);
}

@Override
protected <T> Request doInvoke(RequestCallbackAdapter.ResponseReader responseReader, String methodName, RpcStatsContext statsContext, String requestData, AsyncCallback<T> callback) {
if (m_isSync) {
this.setRpcRequestBuilder(new SyncRpcRequestBuilder());
}
String url = getServiceEntryPoint() + "?rpc=" + URL.encode(requestData);

RequestBuilder rb = doPrepareRequestBuilder(responseReader, methodName,
statsContext, requestData, callback);

// replace requestBuilder if httpMethod is 'GET'
if (rb.getHTTPMethod().equals(com.google.gwt.http.client.RequestBuilder.GET.toString())) {

RequestBuilder b =new RequestBuilder(RequestBuilder.GET, url);
b.setHeader(RpcRequestBuilder.STRONG_NAME_HEADER, GWT.getPermutationStrongName());
b.setHeader(RpcRequestBuilder.MODULE_BASE_HEADER, GWT.getModuleBaseURL());
RequestCallback responseHandler = doCreateRequestCallback(responseReader,
methodName, statsContext, callback);
b.setCallback(responseHandler);
rb = b;
}

try {
return rb.send();
} catch (RequestException ex) {
InvocationException iex = new InvocationException(
"Unable to initiate the asynchronous service invocation (" +
methodName + ") -- check the network connection",
ex);
callback.onFailure(iex);
} finally {
if (statsContext.isStatsAvailable()) {
statsContext.stats(statsContext.bytesStat(methodName,
requestData.length(), "requestSent"));
}
}
return null;
}

public boolean isSync() {
return m_isSync;
}

public void setSync(boolean sync) {
this.m_isSync = sync;
}
}
package com.google.gwt.http.client;

import com.google.gwt.core.client.JavaScriptException;

import java.util.HashMap;
import java.util.Map;

/**
* User: roda
* Date: 09.01.12
* Time: 11:30
*/
public class SyncRequestBuilder extends RequestBuilder {

private Map<String, String> headers;

public SyncRequestBuilder(Method httpMethod, String url) {
super(httpMethod, url);
}

public SyncRequestBuilder(String httpMethod, String url) {
super(httpMethod, url);
}

public void setHeader(String header, String value) {
super.setHeader(header, value);
StringValidator.throwIfEmptyOrNull("header", header);
StringValidator.throwIfEmptyOrNull("value", value);

if (headers == null) {
headers = new HashMap<String, String>();
}

headers.put(header, value);
}

@Override
public Request send() throws RequestException {
StringValidator.throwIfNull("callback", getCallback());
return doSend(getRequestData(), getCallback());
}

/**
* Sends an HTTP request based on the current builder configuration. If no
* request headers have been set, the header "Content-Type" will be used with
* a value of "text/plain; charset=utf-8".
*
* @return a {@link Request} object that can be used to track the request
* @throws RequestException if the call fails to initiate
* @throws NullPointerException if request data has not been set
* @throws NullPointerException if a request callback has not been set
*/
private Request doSend(String requestData, final RequestCallback callback)
throws RequestException {
XMLHttpRequestWrapper xmlHttpRequest = new XMLHttpRequestWrapper();

try {
if ((getUser() != null) && (getPassword() != null)) {
xmlHttpRequest.openSync(getHTTPMethod(), getUrl(), getUser(), getPassword());
} else if (getUser() != null) {
xmlHttpRequest.openSync(getHTTPMethod(), getUrl(), getUser());
} else {
xmlHttpRequest.openSync(getHTTPMethod(), getUrl());
}
} catch (JavaScriptException e) {
RequestPermissionException requestPermissionException = new RequestPermissionException(getUrl());
requestPermissionException.initCause(new RequestException(e.getMessage()));
throw requestPermissionException;
}

setHeaders(xmlHttpRequest);

final Request request = new Request(xmlHttpRequest.getDelegate(), getTimeoutMillis(), callback);

try {
xmlHttpRequest.send(requestData);
} catch (JavaScriptException e) {
throw new RequestException(e.getMessage());
}

xmlHttpRequest.clearOnReadyStateChange();
request.fireOnResponseReceived(callback);

return request;
}

/*
* Internal method that actually sets our cached headers on the underlying
* JavaScript XmlHttpRequest object. If there are no headers set, then we set
* the "Content-Type" to "text/plain; charset=utf-8". This is really lining us
* up for integration with RPC.
*/
private void setHeaders(XMLHttpRequestWrapper xmlHttpRequest) throws RequestException {
if ((headers != null) && (headers.size() > 0)) {
for (Map.Entry<String, String> header : headers.entrySet()) {
try {
xmlHttpRequest.setRequestHeader(header.getKey(), header.getValue());
} catch (JavaScriptException e) {
throw new RequestException(e.getMessage());
}
}
} else {
xmlHttpRequest.setRequestHeader("Content-Type", "text/plain; charset=utf-8");
}
}
}
<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 2.0//EN"
"http://google-web-toolkit.googlecode.com/svn/releases/2.0/distro-source/core/src/gwt-module.dtd">
<module rename-to="SyncRpcRequest">
<source path='http'/>
<source path='xhr'/>
</module>
/*
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.gwt.xhr.client;

import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.typedarrays.shared.ArrayBuffer;

/**
* The native XMLHttpRequest object. Most applications should use the higher-
* level {@link com.google.gwt.http.client.RequestBuilder} class unless they
* need specific functionality provided by the XMLHttpRequest object.
*
* See <a href="http://www.w3.org/TR/XMLHttpRequest/"
* >http://www.w3.org/TR/XMLHttpRequest/</a>/
*/
public class XMLHttpRequest extends JavaScriptObject {

/**
* The type of response expected from the XHR.
*/
public enum ResponseType {
/**
* The default response type -- use {@link XMLHttpRequest#getResponseText()}
* for the return value.
*/
Default(""),

/**
* The default response type -- use
* {@link XMLHttpRequest#getResponseArrayBuffer()} for the return value.
* This value may only be used if
* {@link com.google.gwt.typedarrays.shared.TypedArrays#isSupported()}
* returns true.
*/
ArrayBuffer("arraybuffer");

// not implemented yet
/*
Blob("blob"),

Document("document"),

Text("text");
*/

private final String responseTypeString;

private ResponseType(String responseTypeString) {
this.responseTypeString = responseTypeString;
}

public String getResponseTypeString() {
return responseTypeString;
}
}

/*
* NOTE: Testing discovered that for some bizarre reason, on Mozilla, the
* JavaScript <code>XmlHttpRequest.onreadystatechange</code> handler
* function maybe still be called after it is deleted. The theory is that the
* callback is cached somewhere. Setting it to null or an empty function does
* seem to work properly, though.
*
* On IE, there are two problems: Setting onreadystatechange to null (as
* opposed to an empty function) sometimes throws an exception. With
* particular (rare) versions of jscript.dll, setting onreadystatechange from
* within onreadystatechange causes a crash. Setting it from within a timeout
* fixes this bug (see issue 1610).
*
* End result: *always* set onreadystatechange to an empty function (never to
* null). Never set onreadystatechange from within onreadystatechange (always
* in a setTimeout()).
*/

/**
* When constructed, the XMLHttpRequest object must be in the UNSENT state.
*/
public static final int UNSENT = 0;

/**
* The OPENED state is the state of the object when the open() method has been
* successfully invoked. During this state request headers can be set using
* setRequestHeader() and the request can be made using send().
*/
public static final int OPENED = 1;

/**
* The HEADERS_RECEIVED state is the state of the object when all response
* headers have been received.
*/
public static final int HEADERS_RECEIVED = 2;

/**
* The LOADING state is the state of the object when the response entity body
* is being received.
*/
public static final int LOADING = 3;

/**
* The DONE state is the state of the object when either the data transfer has
* been completed or something went wrong during the transfer (infinite
* redirects for instance).
*/
public static final int DONE = 4;

/**
* Creates an XMLHttpRequest object.
*
* @return the created object
*/
public static native XMLHttpRequest create() /*-{
// Don't check window.XMLHttpRequest, because it can
// cause cross-site problems on IE8 if window's URL
// is javascript:'' .
var xhr;
if ($wnd.XMLHttpRequest) {
xhr = new $wnd.XMLHttpRequest();
} else {
try {
xhr = new $wnd.ActiveXObject('MSXML2.XMLHTTP.3.0');
} catch (e) {
xhr = new $wnd.ActiveXObject("Microsoft.XMLHTTP");
}
}
return xhr;
}-*/;

protected XMLHttpRequest() {
}

/**
* Aborts the current request.
* <p>
* See <a href="http://www.w3.org/TR/XMLHttpRequest/#the-abort-method"
* >http://www.w3.org/TR/XMLHttpRequest/#the-abort-method</a>.
*/
public final native void abort() /*-{
this.abort();
}-*/;

/**
* Clears the {@link ReadyStateChangeHandler}.
* <p>
* See <a href="http://www.w3.org/TR/XMLHttpRequest/#handler-xhr-onreadystatechange"
* >http://www.w3.org/TR/XMLHttpRequest/#handler-xhr-onreadystatechange</a>.
*
* @see #clearOnReadyStateChange()
*/
public final native void clearOnReadyStateChange() /*-{
var self = this;
@com.google.gwt.core.client.impl.Impl::setTimeout(Lcom/google/gwt/core/client/JavaScriptObject;I)(function() {
// Using a function literal here leaks memory on ie6
// Using the same function object kills HtmlUnit
self.onreadystatechange = new Function();
}, 0);
}-*/;

/**
* Gets all the HTTP response headers, as a single string.
* <p>
* See <a href="http://www.w3.org/TR/XMLHttpRequest/#the-getallresponseheaders-method"
* >http://www.w3.org/TR/XMLHttpRequest/#the-getallresponseheaders-method</a>.
*
* @return the response headers.
*/
public final native String getAllResponseHeaders() /*-{
return this.getAllResponseHeaders();
}-*/;

/**
* Get's the current ready-state.
* <p>
* See <a href="http://www.w3.org/TR/XMLHttpRequest/#dom-xmlhttprequest-readystate"
* >http://www.w3.org/TR/XMLHttpRequest/#dom-xmlhttprequest-state</a>.
*
* @return the ready-state constant
*/
public final native int getReadyState() /*-{
return this.readyState;
}-*/;

/**
* Get the response as an {@link ArrayBuffer}.
*
* @return an {@link ArrayBuffer} containing the response, or null if the
* request is in progress or failed
*/
public final native ArrayBuffer getResponseArrayBuffer() /*-{
return this.response;
}-*/;

/**
* Gets an HTTP response header.
* <p>
* See <a href="http://www.w3.org/TR/XMLHttpRequest/#the-getresponseheader-method"
* >http://www.w3.org/TR/XMLHttpRequest/#the-getresponseheader-method</a>.
*
* @param header the response header to be retrieved
* @return the header value
*/
public final native String getResponseHeader(String header) /*-{
return this.getResponseHeader(header);
}-*/;

/**
* Gets the response text.
* <p>
* See <a href="http://www.w3.org/TR/XMLHttpRequest/#the-responsetext-attribute"
* >http://www.w3.org/TR/XMLHttpRequest/#the-responsetext-attribute</a>.
*
* @return the response text
*/
public final native String getResponseText() /*-{
return this.responseText;
}-*/;

/**
* Gets the response type.
* <p>
* See <a href="http://www.w3.org/TR/XMLHttpRequest/#the-responsetype-attribute"
* >http://www.w3.org/TR/XMLHttpRequest/#the-responsetype-attribute</a>
*
* @return the response type
*/
public final native String getResponseType() /*-{
return this.responseType || "";
}-*/;

/**
* Gets the status code.
* <p>
* See <a href="http://www.w3.org/TR/XMLHttpRequest/#the-status-attribute"
* >http://www.w3.org/TR/XMLHttpRequest/#the-status-attribute</a>.
*
* @return the status code
*/
public final native int getStatus() /*-{
return this.status;
}-*/;

/**
* Gets the status text.
* <p>
* See <a href="http://www.w3.org/TR/XMLHttpRequest/#the-statustext-attribute"
* >http://www.w3.org/TR/XMLHttpRequest/#the-statustext-attribute</a>.
*
* @return the status text
*/
public final native String getStatusText() /*-{
return this.statusText;
}-*/;

/**
* Opens an asynchronous connection.
* <p>
* See <a href="http://www.w3.org/TR/XMLHttpRequest/#the-open-method"
* >http://www.w3.org/TR/XMLHttpRequest/#the-open-method</a>.
*
* @param httpMethod the HTTP method to use
* @param url the URL to be opened
*/
public final native void open(String httpMethod, String url) /*-{
this.open(httpMethod, url, true);
}-*/;

/**
* Opens an asynchronous connection.
* <p>
* See <a href="http://www.w3.org/TR/XMLHttpRequest/#the-open-method"
* >http://www.w3.org/TR/XMLHttpRequest/#the-open-method</a>.
*
* @param httpMethod the HTTP method to use
* @param url the URL to be opened
* @param user user to use in the URL
*/
public final native void open(String httpMethod, String url, String user) /*-{
this.open(httpMethod, url, true, user);
}-*/;

/**
* Opens an asynchronous connection.
* <p>
* See <a href="http://www.w3.org/TR/XMLHttpRequest/#the-open-method"
* >http://www.w3.org/TR/XMLHttpRequest/#the-open-method</a>.
*
* @param httpMethod the HTTP method to use
* @param url the URL to be opened
* @param user user to use in the URL
* @param password password to use in the URL
*/
public final native void open(String httpMethod, String url, String user,
String password) /*-{
this.open(httpMethod, url, true, user, password);
}-*/;

/**
* Initiates a request with no request data. This simply calls
* {@link #send(String)} with <code>null</code> as an argument, because the
* no-argument <code>send()</code> method is unavailable on Firefox.
*/
public final void send() {
send(null);
}

/**
* Initiates a request with data. If there is no data, specify null.
* <p>
* See <a href="http://www.w3.org/TR/XMLHttpRequest/#the-send-method"
* >http://www.w3.org/TR/XMLHttpRequest/#the-send-method</a>.
*
* @param requestData the data to be sent with the request
*/
public final native void send(String requestData) /*-{
this.send(requestData);
}-*/;

/**
* Sets the {@link ReadyStateChangeHandler} to be notified when the object's
* ready-state changes.
* <p>
* See <a href="http://www.w3.org/TR/XMLHttpRequest/#handler-xhr-onreadystatechange"
* >http://www.w3.org/TR/XMLHttpRequest/#handler-xhr-onreadystatechange</a>.
*
* <p>
* Note: Applications <em>must</em> call {@link #clearOnReadyStateChange()}
* when they no longer need this object, to ensure that it is cleaned up
* properly. Failure to do so will result in memory leaks on some browsers.
* </p>
*
* @param handler the handler to be called when the ready state changes
* @see #clearOnReadyStateChange()
*/
public final native void setOnReadyStateChange(ReadyStateChangeHandler handler) /*-{
// The 'this' context is always supposed to point to the xhr object in the
// onreadystatechange handler, but we reference it via closure to be extra sure.
var _this = this;
this.onreadystatechange = $entry(function() {
handler.@com.google.gwt.xhr.client.ReadyStateChangeHandler::onReadyStateChange(Lcom/google/gwt/xhr/client/XMLHttpRequest;)(_this);
});
}-*/;

/**
* Sets a request header.
* <p>
* See <a href="http://www.w3.org/TR/XMLHttpRequest/#the-setrequestheader-method"
* >http://www.w3.org/TR/XMLHttpRequest/#the-setrequestheader-method</a>.
*
* @param header the header to be set
* @param value the header's value
*/
public final native void setRequestHeader(String header, String value) /*-{
this.setRequestHeader(header, value);
}-*/;

/**
* Sets withCredentials attribute.
* <p>
* See <a href="http://www.w3.org/TR/XMLHttpRequest/#the-withcredentials-attribute"
* >http://www.w3.org/TR/XMLHttpRequest/#the-withcredentials-attribute</a>.
*
* @param withCredentials whether to include credentials in XHR
*/
public final native void setWithCredentials(boolean withCredentials) /*-{
this.withCredentials = withCredentials;
}-*/;

/**
* Sets the response type.
* <p>
* See <a href="http://www.w3.org/TR/XMLHttpRequest/#the-responsetype-attribute"
* >http://www.w3.org/TR/XMLHttpRequest/#the-responsetype-attribute</a>
*
* @param responseType the type of response desired. See {@link ResponseType}
* for limitations on using the different values
*/
public final void setResponseType(ResponseType responseType) {
this.setResponseType(responseType.getResponseTypeString());
}

/**
* Sets the response type.
* <p>
* See <a href="http://www.w3.org/TR/XMLHttpRequest/#the-responsetype-attribute"
* >http://www.w3.org/TR/XMLHttpRequest/#the-responsetype-attribute</a>
*
* @param responseType the type of response desired. See {@link ResponseType}
* for limitations on using the different values
*/
public final native void setResponseType(String responseType) /*-{
this.responseType = responseType;
}-*/;
}package com.google.gwt.http.client;

import com.google.gwt.xhr.client.XMLHttpRequest;

/**
* Created by IntelliJ IDEA.
* User: y9676
* Date: 1/12/12
* Time: 2:24 PM
*
* How to use:
*
RPCServiceAsync serviceAsync = RPCService.Instance.get();

GetRemoteServiceProxy getRemoteServiceProxy = (GetRemoteServiceProxy) serviceAsync;
getRemoteServiceProxy.setSync(true);
getRemoteServiceProxy.getServiceEntryPoint();

Window.alert("First!");

serviceAsync.getFoo(new AsyncCallback...
@Override
public void onSuccess(...) {
Window.alert("Second!");
}
});

Window.alert("Third!");
*
*/
public class XMLHttpRequestWrapper {

private XMLHttpRequest m_delegate;

public XMLHttpRequest getDelegate() {
return m_delegate;
}

protected void setDelegate(XMLHttpRequest m_delegate) {
this.m_delegate = m_delegate;
}

public final void clearOnReadyStateChange() {
if(m_delegate == null) {
throw new IllegalStateException("No open connection");
}

m_delegate.clearOnReadyStateChange();
}

public final void send(String requestData) {
if(m_delegate == null) {
throw new IllegalStateException("No open connection");
}

m_delegate.send(requestData);
}

public final void setRequestHeader(String header, String value) {
if(m_delegate == null) {
throw new IllegalStateException("No open connection");
}

m_delegate.setRequestHeader(header, value);
}

/**
* Opens a synchronous connection.
*
* @param httpMethod the HTTP method to use
* @param url the URL to be opened
* @see http://www.w3.org/TR/XMLHttpRequest/#open
*/
public final native void openSync(String httpMethod, String url) /*-{
var rq = @com.google.gwt.xhr.client.XMLHttpRequest::create()();

if(rq == undefined) {
throw "Native XMLHTTPRequest is undefined!";
}

this.@com.google.gwt.http.client.XMLHttpRequestWrapper::m_delegate = rq;

rq.open(httpMethod, url, false);
}-*/;

/**
* Opens a synchronous connection.
*
* @param httpMethod the HTTP method to use
* @param url the URL to be opened
* @param user user to use in the URL
* @see http://www.w3.org/TR/XMLHttpRequest/#open
*/
public final native void openSync(String httpMethod, String url, String user) /*-{
var rq = @com.google.gwt.xhr.client.XMLHttpRequest::create()();

if(rq == undefined) {
throw "Native XMLHTTPRequest is undefined!";
}

this.@com.google.gwt.http.client.XMLHttpRequestWrapper::m_delegate = rq;

rq.open(httpMethod, url, false, user);
}-*/;

/**
* Opens an asynchronous connection.
*
* @param httpMethod the HTTP method to use
* @param url the URL to be opened
* @param user user to use in the URL
* @param password password to use in the URL
* @see http://www.w3.org/TR/XMLHttpRequest/#open
*/
public final native void openSync(String httpMethod, String url, String user, String password) /*-{
var rq = @com.google.gwt.xhr.client.XMLHttpRequest::create()();

if(rq == undefined) {
throw "Native XMLHTTPRequest is undefined!";
}

this.@com.google.gwt.http.client.XMLHttpRequestWrapper::m_delegate = rq;

rq.open(httpMethod, url, false, user, password);
}-*/;
}
Hi Dominic,

take a look to the attached files, we have to solve the same problem so I hacked something around the gwt-rpc to do a sync rpc call.
But remember this should really only be used in the close method of the browser because in general sync call are bad for the user experince!

Some classes need to be put in the com.google.gwt.http.client and com.google.gwt.xhr.client package to overwirte some methods.
So that's way I need the SyncRpcRequest.gwt.xml which needs to be put in the com.google.gwt package.

The default gwt-rpc RemoteServiceProxy always creates a post request, which can not be cached. I also changed this behavior.

BR
Rocco
     

If you have some additional question or in the case I forgot a file give me a short feedback.
I know this solution is ugly but it works. 

Am Montag, 15. Februar 2016 10:33:48 UTC+1 schrieb Dominic Warzok:
Hey Guys, 

we have a very heavy problem. 

If a user clicks the edit button in the webpage, the user sets a lock to the databaserecord. 

I know it is not the right way to handle it on the web but I have no choice. 

I also read all the posts about not doing synchronus RPC calls in GWT.
But we need to clean up the lock if the user closes his window or tab. 

We tried onClose() and onBeforeClose() but if we send a call to the server the thread is closed and the server doesn't get the request. 
If we use an alert-message the server will cleanup the lock because the browser waits a few seconds.

Do you have any idea how to handle this problem? 


Thanks in advance
Dominic 

--
You received this message because you are subscribed to the Google Groups "GWT Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-web-toolkit+unsubscribe@googlegroups.com.
To post to this group, send email to google-web-toolkit@googlegroups.com.
Visit this group at https://groups.google.com/group/google-web-toolkit.
For more options, visit https://groups.google.com/d/optout.

No comments:

Post a Comment