Atmosphere/atmosphere — Issue #70: Add support for TrackMessageSize
Author: jfarcand
Created: 2011-11-22 16:41:41+00:00
State: open

The following contribution add support for tracking/expected message size

/**
* 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.
*
* Authors :
* Jean François Arcand (jfarcand@apache.org - initial author)
* Vincent Barrier (vbarrier@kagilum.com - refactor to callback only on complete message, based on work of Nik Armstrong, nikolasarmstrong@gmail.com)
*
*
*/
jQuery.atmosphere = function()
{
var activeRequest;
var ajaxRequests = new Array();
jQuery(window).unload(function()
{
for ( var i=0, len=ajaxRequests.length; i') == -1){
response.junkParsed = true;
request.expectedMessageSize = -1;
junkSize = 0;
}

//is it junk code ?
if(ajaxRequest.responseText.length >= junkSize && !request.junkParsed){
request.junkParsed = true;
request.lastIndex += junkSize;
request.expectedMessageSize = -1;
}

//If we care about integrity of the message
if (request.trackMessageSize){
//new message ? yes get the size of the next message
response.responseBody = ajaxRequest.responseText;

if (request.expectedMessageSize == -1){
var reg = /\d+|/;
var match = reg.exec(response.responseBody.substr(request.lastIndex));
if (match[0]){
request.expectedMessageSize = parseInt(match[0]);
}
}

//loop until we have the entire message
while(response.responseBody.length >= request.expectedMessageSize+request.lastIndex){
if (request.lastIndex > 0){
var message = response.responseBody.substr(request.lastIndex+(request.expectedMessageSize+"").length+1, request.expectedMessageSize);
if (message.length == request.expectedMessageSize && request.junkParsed){
response.responseBody = message;
jQuery.atmosphere.processMessage(response,request);
request.lastIndex += request.expectedMessageSize + (request.expectedMessageSize+"").length+1;
}
}

//Another message (partial or full)
response.responseBody = ajaxRequest.responseText;
match = reg.exec(response.responseBody.substr(request.lastIndex));
if (match[0]){
request.expectedMessageSize = parseInt(match[0]);
}
else {
request.expectedMessageSize = -1;
break;
}
}
}else{
if(request.transport == 'streaming'){
response.responseBody = ajaxRequest.responseText.substring(request.lastIndex);
request.lastIndex += response.responseBody.length;
}else{
response.responseBody = ajaxRequest.responseText.substring(junkSize);
}
jQuery.atmosphere.processMessage(response,request);
}
}
};
ajaxRequest.send(request.data);

if (request.suspend) {
request.id = setTimeout(function()
{
ajaxRequest.abort();
jQuery.atmosphere.subscribe(request.url, null, request);

}, request.timeout);
}
} else {
jQuery.atmosphere.log(logLevel, ["Max re-connection reached."]);
}
},

processMessage:function(response,request){
response.state = (request.suspend) ? "messageReceived" : "messagePublished";
try {
response.status = activeRequest.status;
response.headers = activeRequest.getAllResponseHeaders();
}
catch(e) {
response.status = 404;
}
if (response.responseBody.indexOf("parent.callback") != -1) {
var index = 0;
var responseBody = response.responseBody;
while ( responseBody.indexOf("('", index) != -1) {
var start = responseBody.indexOf("('", index) + 2;
var end = responseBody.indexOf("')", index);
response.responseBody = responseBody.substring(start, end);
index = end + 2;
jQuery.atmosphere.invokeCallback(response);
}
} else {
jQuery.atmosphere.invokeCallback(response);
}
},

operaStreaming: function()
{

jQuery.atmosphere.closeSuspendedConnection();

var url = jQuery.atmosphere.request.url;
var callback = jQuery.atmosphere.request.callback;
jQuery.atmosphere.response.push = function (url)
{
jQuery.atmosphere.request.transport = 'polling';
jQuery.atmosphere.request.callback = null;
jQuery.atmosphere.publish(url, null, jQuery.atmosphere.request);
};

function init()
{
var iframe = document.createElement("iframe");
iframe.style.width = "0px";
iframe.style.height = "0px";
iframe.style.border = "0px";
iframe.id = "__atmosphere";
document.body.appendChild(iframe);
var d;
if (iframe.contentWindow) {
d = iframe.contentWindow.document;
} else if (iframe.document) {
d = iframe.document;
} else if (iframe.contentDocument) {
d = iframe.contentDocument;
}

if (/\?/i.test(url)) url += "&";
else url += "?";
url += "callback=jquery.atmosphere.streamingCallback";
iframe.src = url;
}

init();

},

ieStreaming : function()
{

if (!(typeof(transferDoc) == 'undefined')) {
if (transferDoc != null) {
transferDoc = null;
CollectGarbage();
}
}

var url = jQuery.atmosphere.request.url;
jQuery.atmosphere.response.push = function (url)
{
jQuery.atmosphere.request.transport = 'polling';
jQuery.atmosphere.request.callback = null;
jQuery.atmosphere.publish(url, null, jQuery.atmosphere.request);
};

//Must not use var here to avoid IE from disconnecting
transferDoc = new ActiveXObject("htmlfile");
transferDoc.open();
transferDoc.close();
var ifrDiv = transferDoc.createElement("div");
transferDoc.body.appendChild(ifrDiv);
ifrDiv.innerHTML = "";
transferDoc.parentWindow.callback = jQuery.atmosphere.streamingCallback;
}
,

streamingCallback : function(args)
{
var response = jQuery.atmosphere.response;
response.transport = "streaming";
response.status = 200;
response.responseBody = args;
response.state = "messageReceived";
jQuery.atmosphere.invokeCallback(response);
}
,

executeWebSocket : function()
{
var request = jQuery.atmosphere.request;
var success = false;
jQuery.atmosphere.log(logLevel, ["Invoking executeWebSocket"]);
jQuery.atmosphere.response.transport = "websocket";
var url = jQuery.atmosphere.request.url;
var callback = jQuery.atmosphere.request.callback;

if (url.indexOf("http") == -1 && url.indexOf("ws") == -1) {
url = jQuery.atmosphere.parseUri(document.location, url);
}
var location = url.replace('http:', 'ws:').replace('https:', 'wss:');

var websocket = null;
if (jQuery.atmosphere.request.webSocketImpl != null) {
websocket = jQuery.atmosphere.request.webSocketImpl;
} else {
websocket = new WebSocket(location);
}

jQuery.atmosphere.websocket = websocket;

jQuery.atmosphere.response.push = function (url)
{
var data;
var ws = jQuery.atmosphere.websocket;
try {
data = jQuery.atmosphere.request.data;
ws.send(jQuery.atmosphere.request.data);
} catch (e) {
jQuery.atmosphere.log(logLevel, ["Websocket failed. Downgrading to Comet and resending " + data]);
// Websocket is not supported, reconnect using the fallback transport.
request.transport = request.fallbackTransport;
jQuery.atmosphere.response.transport = request.fallbackTransport;
jQuery.atmosphere.request = request;
jQuery.atmosphere.executeRequest();

ws.onclose = function(message) {
};
ws.close();
}
};

websocket.onopen = function(message)
{
success = true;
jQuery.atmosphere.response.state = 'openning';
jQuery.atmosphere.invokeCallback(jQuery.atmosphere.response);
};

websocket.onmessage = function(message)
{
var data = message.data;
if (data.indexOf("parent.callback") != -1) {
var start = data.indexOf("('") + 2;
var end = data.indexOf("')");
jQuery.atmosphere.response.responseBody = data.substring(start, end);
}
else {
jQuery.atmosphere.response.responseBody = data;
}
jQuery.atmosphere.invokeCallback(jQuery.atmosphere.response);
};

websocket.onerror = function(message)
{
jQuery.atmosphere.response.state = 'error';
jQuery.atmosphere.invokeCallback(jQuery.atmosphere.response);
};

websocket.onclose = function(message)
{
if (!success) {
var data = jQuery.atmosphere.request.data;
jQuery.atmosphere.log(logLevel, ["Websocket failed. Downgrading to Comet and resending " + data]);
// Websocket is not supported, reconnect using the fallback transport.
request.transport = request.fallbackTransport;
jQuery.atmosphere.response.transport = request.fallbackTransport;

jQuery.atmosphere.request = request;
jQuery.atmosphere.executeRequest();
} else {
jQuery.atmosphere.response.state = 'closed';
jQuery.atmosphere.invokeCallback(jQuery.atmosphere.response);
}
};
}
,

addCallback: function(func)
{
if (jQuery.inArray(func, jQuery.atmosphere.callbacks) == -1) {
jQuery.atmosphere.callbacks.push(func);
}
}
,

removeCallback: function(func)
{
if (jQuery.inArray(func, jQuery.atmosphere.callbacks) != -1) {
jQuery.atmosphere.callbacks.splice(index);
}
}
,

invokeCallback: function(response)
{
var call = function (index, func)
{
func(response);
};

jQuery.atmosphere.log(logLevel, ["Invoking " + jQuery.atmosphere.callbacks.length + " callbacks"]);
if (jQuery.atmosphere.callbacks.length > 0) {
jQuery.each(jQuery.atmosphere.callbacks, call);
}
}
,

publish: function(url, callback, request)
{
jQuery.atmosphere.request = jQuery.extend({
connected: false,
timeout: 60000,
method: 'POST',
contentType : '',
headers: {},
cache: true,
async: true,
ifModified: false,
callback: null,
dataType: '',
url : url,
data : '',
suspend : false,
maxRequest : 60,
logLevel : 'info',
requestCount : 0,
transport: 'polling'
}, request);

if (callback != null) {
jQuery.atmosphere.addCallback(callback);
}
jQuery.atmosphere.request.transport = 'polling';
if (jQuery.atmosphere.request.transport != 'websocket') {
jQuery.atmosphere.executeRequest();
} else if (jQuery.atmosphere.request.transport == 'websocket') {
if (!window.WebSocket) {
alert("WebSocket not supported by this browser");
}
else {
jQuery.atmosphere.executeWebSocket();
}
}
}
,

unload: function (arg) {
if (window.addEventListener) {
document.addEventListener('unload', arg, false);
window.addEventListener('unload', arg, false);
} else { // IE
document.attachEvent('onunload', arg);
window.attachEvent('onunload', arg);
}
}
,

kill_load_bar : function() {
if (jQuery.atmosphere.killHiddenIFrame == null) {
jQuery.atmosphere.killHiddenIFrame = document.createElement('iframe');
var ifr = jQuery.atmosphere.killHiddenIFrame;
ifr.style.display = 'block';
ifr.style.width = '0';
ifr.style.height = '0';
ifr.style.border = '0';
ifr.style.margin = '0';
ifr.style.padding = '0';
ifr.style.overflow = 'hidden';
ifr.style.visibility = 'hidden';
}
document.body.appendChild(ifr);
ifr.src = 'about:blank';
document.body.removeChild(ifr);
}
,

log: function (level, args)
{
if (window.console)
{
var logger = window.console[level];
if (typeof logger == 'function')
{
logger.apply(window.console, args);
}
}
}
,

warn: function()
{
log('warn', arguments);
}
,

info :function()
{
if (logLevel != 'warn')
{
log('info', arguments);
}
}
,

debug: function()
{
if (logLevel == 'debug')
{
log('debug', arguments);
}
}
,

close : function()
{
jQuery.atmosphere.closeSuspendedConnection();
},

parseUri : function( baseUrl , uri )
{
var protocol = window.location.protocol;
var host = window.location.host;
var path = window.location.pathname;
var parameters = {};
var anchor = '';
var pos;

if ( (pos = uri.search( /\:/ )) >= 0 )
{
protocol = uri.substring( 0, pos + 1 );
uri = uri.substring( pos + 1 );
}

if ( (pos = uri.search( /\#/ )) >= 0 )
{
anchor = uri.substring( pos + 1 );
uri = uri.substring( 0, pos );
}

if ( (pos = uri.search( /\?/ )) >= 0 )
{
var paramsStr = uri.substring( pos + 1 ) + '&;';
uri = uri.substring( 0, pos );
while ( (pos = paramsStr.search( /\&/ )) >= 0 )
{
var paramStr = paramsStr.substring( 0, pos );
paramsStr = paramsStr.substring( pos + 1 );

if ( paramStr.length )
{
var equPos = paramStr.search( /\=/ );
if ( equPos < 0 )
{
parameters[paramStr] = '';
}
else
{
parameters[paramStr.substring( 0, equPos )] =
decodeURIComponent( paramStr.substring( equPos + 1 ) );
}
}
}
}

if ( uri.search( /\/\// ) == 0 )
{
uri = uri.substring( 2 );
if ( (pos = uri.search( /\// )) >= 0 )
{
host = uri.substring( 0, pos );
path = uri.substring( pos );
}
else
{
host = uri;
path = '/';
}
} else if ( uri.search( /\// ) == 0 )
{
path = uri;
}

else // relative to directory
{
var p = path.lastIndexOf( '/' );
if ( p < 0 )
{
path = '/';
} else if ( p < path.length - 1 )
{
path = path.substring( 0, p + 1 );
}

while ( uri.search( /\.\.\// ) == 0 )
{
var p = path.lastIndexOf( '/', path.lastIndexOf( '/' ) - 1 );
if ( p>= 0 )
{
path = path.substring( 0, p + 1 );
}
uri = uri.substring( 3 );
}
path = path + uri;
}

var uri = protocol + '//' + host + path;
var div = '?';
for ( var key in parameters )
{
uri += div + key + '=' + encodeURIComponent( parameters[key] );
div = '&';
}
return uri;
}

}
}();