Hi guys,
So I don't have a fix, but I do have a work around using IE's
XDomainRequest.
This is what I did, (from reading atmosphere code):
1) Introduced a IEXDRCometTransport that extends the existing
RawDataCometTransport
public class IEXDRCometTransport extends RawDataCometTransport {
private static final String SEPARATOR = "\n";
private int read;
private XDomainRequest transportRequest;
private XDomainRequestListener xDomainRequestListener = new
XDomainRequestListener() {
@Override
public void onError(XDomainRequest request) {
if (isCurrent(request)) {
expectingDisconnection = true;
listener.onError(new
StatusCodeException(Response.SC_INTERNAL_SERVER_ERROR, ""), true);
transportRequest = null;
}
}
@Override
public void onLoad(XDomainRequest request, String
responseText) {
request.clearListener();
if (isCurrent(request)) {
transportRequest = null;
if (!disconnecting) {
onReceiving(Response.SC_OK, responseText, false);
}
}
}
@Override
public void onProgress(XDomainRequest request, String
responseText) {
if (!disconnecting && isCurrent(request)) {
onReceiving(Response.SC_OK, responseText, true);
} else {
request.clearListener();
request.abort();
if (isCurrent(request)) {
transportRequest = null;
}
}
}
@Override
public void onTimeout(XDomainRequest request) {
if (isCurrent(request)) {
if (!expectingDisconnection) {
listener.onError(new RequestException("Unexpected
connection timeout " + request.getTimeout()),
false);
}
}
}
public boolean isCurrent(XDomainRequest request) {
return request == transportRequest;
}
};
@Override
public void connect(int connectionCount) {
super.connect(connectionCount);
read = 0;
transportRequest = XDomainRequest.create();
try {
transportRequest.setListener(xDomainRequestListener);
transportRequest.openGET(getUrl(connectionCount));
transportRequest.send();
} catch (JavaScriptException ex) {
if (transportRequest != null) {
transportRequest.abort();
transportRequest = null;
}
listener.onError(new RequestException(ex.getMessage()),
false);
}
}
@Override
public void disconnect() {
super.disconnect();
if (transportRequest != null) {
transportRequest.clearListener();
transportRequest.abort();
transportRequest = null;
}
listener.onDisconnected();
}
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>();
JsArrayString data =
split(responseText.substring(read, index), SEPARATOR);
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();
}
}
}
native static JsArrayString split(String string, String
separator) /*-{
return string.split(separator);
}-*/;
static String unescape(String string) {
return string.replace("\\n", "\n").replace("\\\\", "\\");
}
}
2. introduce GWT representation of XDomainRequest objects:
public class XDomainRequest extends JavaScriptObject {
public static native XDomainRequest create() /*-{
// XDomainRequest object does not play well with GWT
JavaScriptObject so store in local variable
var me = new Object();
me.request = new XDomainRequest();
return me;
}-*/;
public native static boolean isSupported() /*-{
if ($wnd.XDomainRequest) {
return true;
} else {
return false;
}
}-*/;
public final native void setListener(XDomainRequestListener
listener) /*-{
var self = this;
this.request.onload = function() {
listener.@net.zschech.gwt.comet.client.impl.XDomainRequestListener::onLoad(Lnet/
zschech/gwt/comet/client/impl/XDomainRequest;Ljava/lang/String;)
(self,self.request.responseText);
};
this.request.onprogress = function() {
listener.@net.zschech.gwt.comet.client.impl.XDomainRequestListener::onProgress(Lnet/
zschech/gwt/comet/client/impl/XDomainRequest;Ljava/lang/String;)
(self,self.request.responseText);
};
this.request.ontimeout = function() {
listener.@net.zschech.gwt.comet.client.impl.XDomainRequestListener::onTimeout(Lnet/
zschech/gwt/comet/client/impl/XDomainRequest;)(self);
};
this.request.onerror = function() {
listener.@net.zschech.gwt.comet.client.impl.XDomainRequestListener::onError(Lnet/
zschech/gwt/comet/client/impl/XDomainRequest;)(self);
};
}-*/;
public final native void clearListener() /*-{
var self = this;
$wnd.setTimeout(function() {
// Using a function literal here leaks memory on ie6
// Using the same function object kills HtmlUnit
self.request.onload = new Function();
self.request.onprogress = new Function();
self.request.ontimeout = new Function();
self.request.onerror = new Function();
}, 0);
}-*/;
public final native String getContentType() /*-{
return this.request.contentType;
}-*/;
/**
*
* @return the body of the response returned by the server.
*/
public final native String getResponseText() /*-{
return this.request.responseText;
}-*/;
/**
* set the timeout in milliseconds
*
* @param timeout
*/
public final native void setTimeout(int timeout) /*-{
this.request.timeout = timeout;
}-*/;
public final native int getTimeout() /*-{
return this.request.timeout;
}-*/;
/**
* The abort method terminates a pending send.
*/
public final native void abort() /*-{
this.request.abort();
}-*/;
/**
* Creates a connection with a domain's server.
*
* @param url
*/
public final native void openGET(String url) /*-{
this.request.open("GET", url);
}-*/;
/**
* Creates a connection with a domain's server.
*
* @param url
*/
public final native void openPOST(String url) /*-{
this.request.open("POST", url);
}-*/;
/**
* Transmits a empty string to the server for processing.
*/
public final native void send() /*-{
this.request.send();
}-*/;
/**
* Transmits a data string to the server for processing.
*
* @param data
*/
public final native void send(String data) /*-{
this.request.send(data);
}-*/;
protected XDomainRequest() {
}
}
public interface XDomainRequestListener {
/**
* Raised when there is an error that prevents the completion of
the
* cross-domain request.
*/
public void onError(XDomainRequest request);
/**
* Raised when the object has been completely received from the
server.
*
* @param responseText
*/
public void onLoad(XDomainRequest request, String responseText);
/**
* Raised when the browser starts receiving data from the server.
*
* @param responseText
* partial data received
*/
public void onProgress(XDomainRequest request, String
responseText);
/**
* Raised when there is an error that prevents the completion of
the
* request.
*/
public void onTimeout(XDomainRequest request);
}
3. Next implement the server side for this IEXDRCometTransport by
extending the existing RawDataCometServletResponse
public class IEXDRCometServletResponse extends
RawDataCometServletResponse {
// so much padding...
private static final int PADDING_REQUIRED = 2048;
public IEXDRCometServletResponse(HttpServletRequest request,
HttpServletResponse response,
SerializationPolicy serializationPolicy, ClientOracle
clientOracle, CometServlet servlet,
AsyncServlet async, int heartbeat) {
super(request, response, serializationPolicy, clientOracle,
servlet, async, heartbeat);
}
@Override
protected void setupHeaders(HttpServletResponse response) {
super.setupHeaders(response);
response.setContentType("application/comet");
response.setCharacterEncoding("UTF-8");
String origin = getRequest().getHeader("Origin");
if (origin != null) {
response.setHeader("Access-Control-Allow-Origin", origin);
}
}
@Override
protected OutputStream getOutputStream(OutputStream outputStream)
{
return setupCountOutputStream(outputStream);
}
@Override
protected int getPaddingRequired() {
return PADDING_REQUIRED;
}
}
4. finally we need to make GWT-COMET select this new implementation
for IE9:
I wasn't sure how to get the right user.agent for gwt.xml to do
dynamic binding (i'm not on latest gwt), so I overrode CometClient to:
public CometClientTransportWrapper() {
if (EventSource.isSupported()) {
transport = new EventSourceCometTransport();
} else if (XDomainRequest.isSupported()) {
transport = new IEXDRCometTransport();
} else {
transport = GWT.create(CometTransport.class);
}
transport.initiate(CometClient.this, this);
}
and then overrode CometServlet to:
protected CometServletResponseImpl
createCometServletResponse(HttpServletRequest request,
HttpServletResponse response, SerializationPolicy
serializationPolicy, ClientOracle clientOracle,
int requestHeartbeat) {
String userAgent = request.getHeader("User-Agent");
System.out.println("UA: " + userAgent);
if (userAgent.contains("MSIE 9.0")) {
return new IEXDRCometServletResponse(request, response,
serializationPolicy, clientOracle, this, async, heartbeat);
} else {
return super.createCometServletResponse(request, response,
serializationPolicy, clientOracle, requestHeartbeat);
}
}
It seems to be working for me.
Thanks,
Icky