Permalink
Cannot retrieve contributors at this time
/* | |
* Copyright 2009 Richard Zschech. | |
* | |
* 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 net.zschech.gwt.comet.client.impl; | |
import java.io.Serializable; | |
import java.util.ArrayList; | |
import java.util.List; | |
import com.google.gwt.core.client.JavaScriptException; | |
import com.google.gwt.dom.client.NativeEvent; | |
import com.google.gwt.event.dom.client.KeyCodes; | |
import com.google.gwt.event.dom.client.KeyDownEvent; | |
import com.google.gwt.http.client.RequestException; | |
import com.google.gwt.http.client.Response; | |
import com.google.gwt.regexp.shared.RegExp; | |
import com.google.gwt.regexp.shared.SplitResult; | |
import com.google.gwt.user.client.Event; | |
import com.google.gwt.user.client.Event.NativePreviewEvent; | |
import com.google.gwt.user.client.Event.NativePreviewHandler; | |
import com.google.gwt.user.client.rpc.StatusCodeException; | |
import com.google.gwt.xhr.client.ReadyStateChangeHandler; | |
import com.google.gwt.xhr.client.XMLHttpRequest; | |
/** | |
* This class uses a XmlHttpRequest and onreadystatechange events to process stream events. | |
* | |
* The main issue with this implementation is that GWT does not generate RECEIVING events from its XMLHTTPRequest. The | |
* implementation of XMLHTTPRequest included in this package overrides that behaviour. | |
* | |
* Another issues is that the memory required for the XMLHTTPRequest's responseText constantly grows so the server | |
* occasionally disconnects the client who then reestablishes the connection. | |
* | |
* The protocol for this transport is a '\n' separated transport messages. The different types of transport message are | |
* identified by the first character in the line as follows: | |
* | |
* ! A connection message followed by the heartbeat duration as an integer | |
* | |
* ? A clean server disconnection message | |
* | |
* # A heartbeat message | |
* | |
* * A padding message to cause the browser to start processing the stream | |
* | |
* ] A string message that needs unescaping | |
* | |
* | A string message that does not need unescaping | |
* | |
* [ A GWT serialized object | |
* | |
* R, r or f A GWT deRPC object | |
* | |
* string messages are escaped for '\\' and '\n' characters as '\n' is the message separator. | |
* | |
* GWT serialized object messages are escaped by GWT so do not need to be escaped by the transport | |
* | |
* @author Richard Zschech | |
*/ | |
public class HTTPRequestCometTransport extends RawDataCometTransport { | |
private static final String SEPARATOR = "\n"; | |
private static RegExp separator; | |
static { | |
Event.addNativePreviewHandler(new NativePreviewHandler() { | |
@Override | |
public void onPreviewNativeEvent(NativePreviewEvent e) { | |
if (e.getTypeInt() == Event.getTypeInt(KeyDownEvent.getType().getName())) { | |
NativeEvent nativeEvent = e.getNativeEvent(); | |
if (nativeEvent.getKeyCode() == KeyCodes.KEY_ESCAPE) { | |
nativeEvent.preventDefault(); | |
} | |
} | |
} | |
}); | |
separator = RegExp.compile(SEPARATOR); | |
} | |
private XMLHttpRequest xmlHttpRequest; | |
private int read; | |
@Override | |
public void connect(int connectionCount) { | |
super.connect(connectionCount); | |
read = 0; | |
xmlHttpRequest = XMLHttpRequest.create(); | |
try { | |
xmlHttpRequest.open("GET", getUrl(connectionCount)); | |
xmlHttpRequest.setRequestHeader("Accept", "application/comet"); | |
xmlHttpRequest.setOnReadyStateChange(new ReadyStateChangeHandler() { | |
@Override | |
public void onReadyStateChange(XMLHttpRequest request) { | |
if (!disconnecting) { | |
switch (request.getReadyState()) { | |
case XMLHttpRequest.LOADING: | |
onReceiving(request.getStatus(), request.getResponseText()); | |
break; | |
case XMLHttpRequest.DONE: | |
onLoaded(request.getStatus(), request.getResponseText()); | |
break; | |
} | |
} | |
} | |
}); | |
xmlHttpRequest.send(); | |
} | |
catch (JavaScriptException e) { | |
xmlHttpRequest = null; | |
listener.onError(new RequestException(e.getMessage()), false); | |
} | |
} | |
@Override | |
public void disconnect() { | |
super.disconnect(); | |
if (xmlHttpRequest != null) { | |
xmlHttpRequest.clearOnReadyStateChange(); | |
xmlHttpRequest.abort(); | |
xmlHttpRequest = null; | |
} | |
} | |
private void onLoaded(int statusCode, String responseText) { | |
xmlHttpRequest.clearOnReadyStateChange(); | |
xmlHttpRequest = null; | |
onReceiving(statusCode, responseText, false); | |
} | |
private void onReceiving(int statusCode, String responseText) { | |
onReceiving(statusCode, responseText, true); | |
} | |
private void onReceiving(int statusCode, String responseText, boolean connected) { | |
if (statusCode != Response.SC_OK) { | |
if (!connected) { | |
super.disconnect(); | |
listener.onError(new StatusCodeException(statusCode, responseText), connected); | |
} | |
} | |
else { | |
int index = responseText.lastIndexOf(SEPARATOR); | |
if (index > read) { | |
List<Serializable> messages = new ArrayList<Serializable>(); | |
SplitResult data = separator.split(responseText.substring(read, index), index); | |
int length = data.length(); | |
for (int i = 0; i < length; i++) { | |
if (disconnecting) { | |
return; | |
} | |
String message = data.get(i); | |
if (!message.isEmpty()) { | |
parse(message, messages); | |
} | |
} | |
read = index + 1; | |
if (!messages.isEmpty()) { | |
listener.onMessage(messages); | |
} | |
} | |
if (!connected) { | |
super.disconnected(); | |
} | |
} | |
} | |
} |