com.vaadin.client.communication.XhrConnection Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of vaadin-client Show documentation
Show all versions of vaadin-client Show documentation
Vaadin is a web application framework for Rich Internet Applications (RIA).
Vaadin enables easy development and maintenance of fast and
secure rich web
applications with a stunning look and feel and a wide browser support.
It features a server-side architecture with the majority of the logic
running
on the server. Ajax technology is used at the browser-side to ensure a
rich
and interactive user experience.
/*
* Copyright 2000-2016 Vaadin Ltd.
*
* 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.vaadin.client.communication;
import java.util.logging.Logger;
import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.http.client.Response;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.Window.ClosingEvent;
import com.google.gwt.user.client.Window.ClosingHandler;
import com.vaadin.client.ApplicationConnection;
import com.vaadin.client.ApplicationConnection.CommunicationHandler;
import com.vaadin.client.ApplicationConnection.RequestStartingEvent;
import com.vaadin.client.ApplicationConnection.ResponseHandlingEndedEvent;
import com.vaadin.client.ApplicationConnection.ResponseHandlingStartedEvent;
import com.vaadin.client.BrowserInfo;
import com.vaadin.client.Profiler;
import com.vaadin.client.Util;
import com.vaadin.client.ValueMap;
import com.vaadin.shared.ApplicationConstants;
import com.vaadin.shared.JsonConstants;
import com.vaadin.shared.ui.ui.UIConstants;
import com.vaadin.shared.util.SharedUtil;
import elemental.json.JsonObject;
/**
* Provides a connection to the /UIDL url on the server and knows how to send
* messages to that end point
*
* @since 7.6
* @author Vaadin Ltd
*/
public class XhrConnection {
private ApplicationConnection connection;
/**
* Webkit will ignore outgoing requests while waiting for a response to a
* navigation event (indicated by a beforeunload event). When this happens,
* we should keep trying to send the request every now and then until there
* is a response or until it throws an exception saying that it is already
* being sent.
*/
private boolean webkitMaybeIgnoringRequests = false;
public XhrConnection() {
Window.addWindowClosingHandler(new ClosingHandler() {
@Override
public void onWindowClosing(ClosingEvent event) {
webkitMaybeIgnoringRequests = true;
}
});
}
/**
* Sets the application connection this instance is connected to. Called
* internally by the framework.
*
* @param connection
* the application connection this instance is connected to
*/
public void setConnection(ApplicationConnection connection) {
this.connection = connection;
connection.addHandler(ResponseHandlingEndedEvent.TYPE,
new CommunicationHandler() {
@Override
public void onRequestStarting(RequestStartingEvent e) {
}
@Override
public void onResponseHandlingStarted(
ResponseHandlingStartedEvent e) {
}
@Override
public void onResponseHandlingEnded(
ResponseHandlingEndedEvent e) {
webkitMaybeIgnoringRequests = false;
}
});
}
private static Logger getLogger() {
return Logger.getLogger(XhrConnection.class.getName());
}
protected XhrResponseHandler createResponseHandler() {
return new XhrResponseHandler();
}
public class XhrResponseHandler implements RequestCallback {
private JsonObject payload;
private double requestStartTime;
public XhrResponseHandler() {
}
/**
* Sets the payload which was sent to the server
*
* @param payload
* the payload which was sent to the server
*/
public void setPayload(JsonObject payload) {
this.payload = payload;
}
@Override
public void onError(Request request, Throwable exception) {
getConnectionStateHandler().xhrException(
new XhrConnectionError(request, payload, exception));
}
@Override
public void onResponseReceived(Request request, Response response) {
int statusCode = response.getStatusCode();
if (statusCode != 200) {
// There was a problem
XhrConnectionError problemEvent = new XhrConnectionError(
request, payload, response);
getConnectionStateHandler().xhrInvalidStatusCode(problemEvent);
return;
}
getLogger().info("Server visit took " + Util.round(
Profiler.getRelativeTimeMillis() - requestStartTime, 3)
+ "ms");
// for(;;);["+ realJson +"]"
String responseText = response.getText();
ValueMap json = MessageHandler.parseWrappedJson(responseText);
if (json == null) {
// Invalid string (not wrapped as expected or can't parse)
getConnectionStateHandler().xhrInvalidContent(
new XhrConnectionError(request, payload, response));
return;
}
getConnectionStateHandler().xhrOk();
getLogger().info("Received xhr message: " + responseText);
getMessageHandler().handleMessage(json);
}
/**
* Sets the relative time (see {@link Profiler#getRelativeTimeMillis()})
* when the request was sent.
*
* @param requestStartTime
* the relative time when the request was sent
*/
private void setRequestStartTime(double requestStartTime) {
this.requestStartTime = requestStartTime;
}
};
/**
* Sends an asynchronous UIDL request to the server using the given URI.
*
* @param payload
* The URI to use for the request. May includes GET parameters
* @throws RequestException
* if the request could not be sent
*/
public void send(JsonObject payload) {
RequestBuilder rb = new RequestBuilder(RequestBuilder.POST, getUri());
// TODO enable timeout
// rb.setTimeoutMillis(timeoutMillis);
// TODO this should be configurable
rb.setHeader("Content-Type", JsonConstants.JSON_CONTENT_TYPE);
rb.setRequestData(payload.toJson());
XhrResponseHandler responseHandler = createResponseHandler();
responseHandler.setPayload(payload);
responseHandler.setRequestStartTime(Profiler.getRelativeTimeMillis());
rb.setCallback(responseHandler);
getLogger().info("Sending xhr message to server: " + payload.toJson());
try {
final Request request = rb.send();
if (webkitMaybeIgnoringRequests && BrowserInfo.get().isWebkit()) {
final int retryTimeout = 250;
new Timer() {
@Override
public void run() {
// Use native js to access private field in Request
if (resendRequest(request)
&& webkitMaybeIgnoringRequests) {
// Schedule retry if still needed
schedule(retryTimeout);
}
}
}.schedule(retryTimeout);
}
} catch (RequestException e) {
getConnectionStateHandler()
.xhrException(new XhrConnectionError(null, payload, e));
}
}
/**
* Retrieves the URI to use when sending RPCs to the server
*
* @return The URI to use for server messages.
*/
protected String getUri() {
String uri = connection
.translateVaadinUri(ApplicationConstants.APP_PROTOCOL_PREFIX
+ ApplicationConstants.UIDL_PATH + '/');
uri = SharedUtil.addGetParameters(uri, UIConstants.UI_ID_PARAMETER + "="
+ connection.getConfiguration().getUIId());
return uri;
}
private ConnectionStateHandler getConnectionStateHandler() {
return connection.getConnectionStateHandler();
}
private MessageHandler getMessageHandler() {
return connection.getMessageHandler();
}
private static native boolean resendRequest(Request request)
/*-{
var xhr = [email protected]::xmlHttpRequest
if (xhr == null) {
// This might be called even though the request has completed,
// if the webkitMaybeIgnoringRequests has been set to true on beforeunload
// but unload was cancelled after that. It will then stay on until the following
// request and if that request completes before we get here (<250mS), we will
// hit this case.
return false;
}
if (xhr.readyState != 1) {
// Progressed to some other readyState -> no longer blocked
return false;
}
try {
xhr.send();
return true;
} catch (e) {
// send throws exception if it is running for real
return false;
}
}-*/;
}