Streaming XHR.
This commit is contained in:
parent
be65c3c929
commit
5c8f210c3a
6
json.ml
6
json.ml
|
@ -53,8 +53,8 @@ let rec to_string j =
|
|||
and kv_to_string (k, v) =
|
||||
str_to_string k ^ ":" ^ to_string v
|
||||
|
||||
let resp code reason j =
|
||||
let resp code reason extra_headers j =
|
||||
Httpd.resp_generic code reason
|
||||
[Httpd.content_type_header_name, "application/json"]
|
||||
((Httpd.content_type_header_name, "application/json") :: extra_headers)
|
||||
(Httpd.Fixed (to_string j))
|
||||
let resp_ok j = resp 200 "OK" j
|
||||
let resp_ok extra_headers j = resp 200 "OK" extra_headers j
|
||||
|
|
37
ui_main.ml
37
ui_main.ml
|
@ -54,14 +54,41 @@ let start (s, peername) =
|
|||
|
||||
let boot_time = Unix.time ()
|
||||
let api_server_stats r =
|
||||
Json.resp_ok (Json.Rec
|
||||
["connection_count", Json.Num (float_of_int !Connections.connection_count);
|
||||
"boot_time", Json.Num boot_time;
|
||||
"uptime", Json.Num (Unix.time () -. boot_time)])
|
||||
Json.resp_ok [] (Json.Rec
|
||||
["connection_count", Json.Num (float_of_int !Connections.connection_count);
|
||||
"boot_time", Json.Num boot_time;
|
||||
"uptime", Json.Num (Unix.time () -. boot_time)])
|
||||
|
||||
let api_tap_source r =
|
||||
let id = Uuid.create () in
|
||||
let id_block_and_padding = Stringstream.const_flush (id ^ ";" ^ String.make 2048 'h' ^ ";") in
|
||||
let rec message_stream () =
|
||||
Thread.delay 0.1;
|
||||
let v = Json.to_string (Json.Rec ["now", Json.Num (Unix.time ());
|
||||
"id", Json.Str (Uuid.create ())]) in
|
||||
Some (Printf.sprintf "%d;%s;" (String.length v) v, true, Stringstream.make message_stream)
|
||||
in
|
||||
Httpd.resp_generic 200 "Streaming"
|
||||
[Httpd.text_content_type_header;
|
||||
"Access-Control-Allow-Origin", "*"]
|
||||
(Httpd.Variable
|
||||
(Stringstream.switch_after 131072
|
||||
(Stringstream.seq id_block_and_padding (Stringstream.make message_stream))
|
||||
Stringstream.empty))
|
||||
|
||||
let api_tap_sink r =
|
||||
Httpd.resp_generic 202 "Accepted" [] (Httpd.Fixed "")
|
||||
|
||||
let api_tap r =
|
||||
match r.Httpd.verb with
|
||||
| "GET" -> api_tap_source r
|
||||
| "POST" -> api_tap_sink r
|
||||
| _ -> Httpd.http_error_html 400 "Unsupported tap method" []
|
||||
|
||||
let register_api_hooks () =
|
||||
List.iter register_dispatcher
|
||||
["/_/server_stats", api_server_stats]
|
||||
["/_/server_stats", api_server_stats;
|
||||
"/_/tap", api_tap]
|
||||
|
||||
let init () =
|
||||
register_api_hooks ();
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
<link rel="stylesheet" type="text/css" href="style.css">
|
||||
<link rel="stylesheet" type="text/css" href="ui.css">
|
||||
<script type="text/javascript" src="jquery-1.7.2.min.js"></script>
|
||||
<script type="text/javascript" src="jquery.stream-1.2.js"></script>
|
||||
<script type="text/javascript" src="ui_main.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
@ -41,6 +42,9 @@
|
|||
<td id="server_stats_uptime"></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h2>Debug</h2>
|
||||
<pre id="debug_container"></pre>
|
||||
</div>
|
||||
</div>
|
||||
<script>$(document).ready(ui_main);</script>
|
||||
|
|
|
@ -0,0 +1,763 @@
|
|||
/*
|
||||
* jQuery Stream 1.2
|
||||
* Comet Streaming JavaScript Library
|
||||
* http://code.google.com/p/jquery-stream/
|
||||
*
|
||||
* Copyright 2011, Donghwan Kim
|
||||
* Licensed under the Apache License, Version 2.0
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Compatible with jQuery 1.5+
|
||||
*/
|
||||
(function($, undefined) {
|
||||
|
||||
var // Stream object instances
|
||||
instances = {},
|
||||
|
||||
// Streaming agents
|
||||
agents = {},
|
||||
|
||||
// HTTP Streaming transports
|
||||
transports = {},
|
||||
|
||||
// Does the throbber of doom exist?
|
||||
throbber = $.browser.webkit && !$.isReady;
|
||||
|
||||
// Once the window is fully loaded, the throbber of doom will not be appearing
|
||||
if (throbber) {
|
||||
$(window).load(function() {
|
||||
throbber = false;
|
||||
});
|
||||
}
|
||||
|
||||
// Stream is based on The WebSocket API
|
||||
// W3C Working Draft 19 April 2011 - http://www.w3.org/TR/2011/WD-websockets-20110419/
|
||||
$.stream = function(url, options) {
|
||||
|
||||
// Returns the first Stream in the document
|
||||
if (!arguments.length) {
|
||||
for (var i in instances) {
|
||||
return instances[i];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Stream to which the specified url or alias is mapped
|
||||
var instance = instances[url];
|
||||
|
||||
if (!options) {
|
||||
return instance || null;
|
||||
} else if (instance && instance.readyState < 3) {
|
||||
return instance;
|
||||
}
|
||||
|
||||
var // Stream object
|
||||
stream = {
|
||||
|
||||
// URL to which to connect
|
||||
url: url,
|
||||
|
||||
// Merges options
|
||||
options: $.stream.setup({}, options),
|
||||
|
||||
// The state of stream
|
||||
// 0: CONNECTING, 1: OPEN, 2: CLOSING, 3: CLOSED
|
||||
readyState: 0,
|
||||
|
||||
// Fake send
|
||||
send: function() {},
|
||||
|
||||
// Fake close
|
||||
close: function() {}
|
||||
|
||||
},
|
||||
match = /^(http|ws)s?:/.exec(stream.url),
|
||||
open = function() {
|
||||
// Delegates open process
|
||||
agents[stream.options.type](stream);
|
||||
};
|
||||
|
||||
// Stream type
|
||||
if (match) {
|
||||
stream.options.type = match[1];
|
||||
}
|
||||
|
||||
// Makes arrays of event handlers
|
||||
for (var i in {open: 1, message: 1, error: 1, close: 1}) {
|
||||
stream.options[i] = $.makeArray(stream.options[i]);
|
||||
}
|
||||
|
||||
// The url and alias are a identifier of this instance within the document
|
||||
instances[stream.url] = stream;
|
||||
if (stream.options.alias) {
|
||||
instances[stream.options.alias] = stream;
|
||||
}
|
||||
|
||||
// Deals with the throbber of doom
|
||||
if (stream.options.type === "ws" || !throbber) {
|
||||
open();
|
||||
} else {
|
||||
switch (stream.options.throbber.type || stream.options.throbber) {
|
||||
case "lazy":
|
||||
$(window).load(function() {
|
||||
setTimeout(open, stream.options.throbber.delay || 50);
|
||||
});
|
||||
break;
|
||||
case "reconnect":
|
||||
open();
|
||||
$(window).load(function() {
|
||||
if (stream.readyState === 0) {
|
||||
stream.options.open.push(function() {
|
||||
stream.options.open.pop();
|
||||
setTimeout(reconnect, 10);
|
||||
});
|
||||
} else {
|
||||
reconnect();
|
||||
}
|
||||
|
||||
function reconnect() {
|
||||
stream.options.close.push(function() {
|
||||
stream.options.close.pop();
|
||||
setTimeout(function() {
|
||||
$.stream(stream.url, stream.options);
|
||||
}, stream.options.throbber.delay || 50);
|
||||
});
|
||||
|
||||
var reconn = stream.options.reconnect;
|
||||
stream.close();
|
||||
stream.options.reconnect = reconn;
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return stream;
|
||||
};
|
||||
|
||||
$.extend($.stream, {
|
||||
|
||||
version: "1.2",
|
||||
|
||||
// Logic borrowed from jQuery.ajaxSetup
|
||||
setup: function(target, options) {
|
||||
if (!options) {
|
||||
options = target;
|
||||
target = $.extend(true, $.stream.options, options);
|
||||
} else {
|
||||
$.extend(true, target, $.stream.options, options);
|
||||
}
|
||||
|
||||
for (var field in {context: 1, url: 1}) {
|
||||
if (field in options) {
|
||||
target[field] = options[field];
|
||||
} else if (field in $.stream.options) {
|
||||
target[field] = $.stream.options[field];
|
||||
}
|
||||
}
|
||||
|
||||
return target;
|
||||
},
|
||||
|
||||
options: {
|
||||
// Stream type
|
||||
type: window.WebSocket ? "ws" : "http",
|
||||
// Whether to automatically reconnect when stream closed
|
||||
reconnect: true,
|
||||
// Whether to trigger global stream event handlers
|
||||
global: true,
|
||||
// Only for WebKit
|
||||
throbber: "lazy",
|
||||
// Message data type
|
||||
dataType: "text",
|
||||
// Message data converters
|
||||
converters: {
|
||||
text: window.String,
|
||||
json: $.parseJSON,
|
||||
xml: $.parseXML
|
||||
}
|
||||
// Additional parameters for GET request
|
||||
// openData: null,
|
||||
// WebSocket constructor argument
|
||||
// protocols: null,
|
||||
// XDomainRequest transport
|
||||
// enableXDR: false,
|
||||
// rewriteURL: null
|
||||
// Polling interval
|
||||
// operaInterval: 0
|
||||
// iframeInterval: 0
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
$.extend(agents, {
|
||||
|
||||
// WebSocket wrapper
|
||||
ws: function(stream) {
|
||||
if (!window.WebSocket) {
|
||||
return;
|
||||
}
|
||||
|
||||
var // Absolute WebSocket URL
|
||||
url = prepareURL(getAbsoluteURL(stream.url).replace(/^http/, "ws"), stream.options.openData),
|
||||
// WebSocket instance
|
||||
ws = stream.options.protocols ? new window.WebSocket(url, stream.options.protocols) : new window.WebSocket(url);
|
||||
|
||||
// WebSocket event handlers
|
||||
$.extend(ws, {
|
||||
onopen: function(event) {
|
||||
stream.readyState = 1;
|
||||
trigger(stream, event);
|
||||
},
|
||||
onmessage: function(event) {
|
||||
trigger(stream, $.extend({}, event, {data: stream.options.converters[stream.options.dataType](event.data)}));
|
||||
},
|
||||
onerror: function(event) {
|
||||
stream.options.reconnect = false;
|
||||
trigger(stream, event);
|
||||
},
|
||||
onclose: function(event) {
|
||||
var readyState = stream.readyState;
|
||||
|
||||
stream.readyState = 3;
|
||||
trigger(stream, event);
|
||||
|
||||
// Reconnect?
|
||||
if (stream.options.reconnect && readyState !== 0) {
|
||||
$.stream(stream.url, stream.options);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Overrides send and close
|
||||
$.extend(stream, {
|
||||
send: function(data) {
|
||||
if (stream.readyState === 0) {
|
||||
$.error("INVALID_STATE_ERR: Stream not open");
|
||||
}
|
||||
|
||||
ws.send(typeof data === "string" ? data : param(data));
|
||||
},
|
||||
close: function() {
|
||||
if (stream.readyState < 2) {
|
||||
stream.readyState = 2;
|
||||
stream.options.reconnect = false;
|
||||
ws.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// HTTP Streaming
|
||||
http: function(stream) {
|
||||
var // Transport
|
||||
transportFn,
|
||||
transport,
|
||||
// Low-level request and response handler
|
||||
handleOpen,
|
||||
handleMessage,
|
||||
handleSend,
|
||||
// Latch for AJAX
|
||||
sending,
|
||||
// Data queue
|
||||
dataQueue = [],
|
||||
// Helper object for parsing response
|
||||
message = {
|
||||
// The index from which to start parsing
|
||||
index: 0,
|
||||
// The temporary data
|
||||
data: ""
|
||||
};
|
||||
|
||||
// Chooses a proper transport
|
||||
transportFn = transports[
|
||||
// xdr
|
||||
stream.options.enableXDR && window.XDomainRequest ? "xdr" :
|
||||
// iframe
|
||||
window.ActiveXObject ? "iframe" :
|
||||
// xhr
|
||||
window.XMLHttpRequest ? "xhr" : null];
|
||||
|
||||
if (!transportFn) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Default response handler
|
||||
handleOpen = stream.options.handleOpen || function(text, message, stream) {
|
||||
// The top of the response is made up of the id and padding
|
||||
// optional identifier within the server
|
||||
stream.id = text.substring(0, text.indexOf(";"));
|
||||
// message.index = text.indexOf(";", stream.id.length + ";".length) + ";".length;
|
||||
message.index = text.indexOf(";", stream.id.length + 1) + 1;
|
||||
};
|
||||
handleMessage = stream.options.handleMessage || function(text, message) {
|
||||
// Response could contain a single message, multiple messages or a fragment of a message
|
||||
// default message format is message-size ; message-data ;
|
||||
if (message.size == null) {
|
||||
// Checks a semicolon of size part
|
||||
var sizeEnd = text.indexOf(";", message.index);
|
||||
if (sizeEnd < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
message.size = +text.substring(message.index, sizeEnd);
|
||||
// index: sizeEnd + ";".length,
|
||||
message.index = sizeEnd + 1;
|
||||
}
|
||||
|
||||
var data = text.substr(message.index, message.size - message.data.length);
|
||||
message.data += data;
|
||||
message.index += data.length;
|
||||
|
||||
// Has stream message been completed?
|
||||
if (message.size !== message.data.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Checks a semicolon of data part
|
||||
var dataEnd = text.indexOf(";", message.index);
|
||||
if (dataEnd < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// message.index = dataEnd + ";".length;
|
||||
message.index = dataEnd + 1;
|
||||
|
||||
// Completes parsing
|
||||
delete message.size;
|
||||
};
|
||||
|
||||
// Default request handler
|
||||
handleSend = stream.options.handleSend || function(type, options, stream) {
|
||||
var metadata = {"metadata.id": stream.id, "metadata.type": type};
|
||||
|
||||
options.data =
|
||||
// Close
|
||||
type === "close" ? param(metadata) :
|
||||
// Send
|
||||
// converts data if not already a string
|
||||
((typeof options.data === "string" ? options.data : param(options.data)) + "&" + param(metadata));
|
||||
};
|
||||
|
||||
transport = transportFn(stream, {
|
||||
response: function(text) {
|
||||
if (stream.readyState === 0) {
|
||||
if (handleOpen(text, message, stream) === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
stream.readyState = 1;
|
||||
trigger(stream, "open");
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
if (handleMessage(text, message, stream) === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (stream.readyState < 3) {
|
||||
// Pseudo MessageEvent
|
||||
trigger(stream, "message", {
|
||||
// Converts the data type
|
||||
data: stream.options.converters[stream.options.dataType](message.data),
|
||||
origin: "",
|
||||
lastEventId: "",
|
||||
source: null,
|
||||
ports: null
|
||||
});
|
||||
}
|
||||
|
||||
// Resets the data
|
||||
message.data = "";
|
||||
}
|
||||
},
|
||||
close: function(isError) {
|
||||
var readyState = stream.readyState;
|
||||
stream.readyState = 3;
|
||||
|
||||
if (isError) {
|
||||
// Prevents reconnecting
|
||||
stream.options.reconnect = false;
|
||||
|
||||
// If establishing a connection fails, fires the close event instead of the error event
|
||||
if (readyState === 0) {
|
||||
// Pseudo CloseEvent
|
||||
trigger(stream, "close", {
|
||||
wasClean: false,
|
||||
code: null,
|
||||
reason: ""
|
||||
});
|
||||
} else {
|
||||
trigger(stream, "error");
|
||||
}
|
||||
} else {
|
||||
// Pseudo CloseEvent
|
||||
trigger(stream, "close", {
|
||||
// Presumes that the stream closed cleanly
|
||||
wasClean: true,
|
||||
code: null,
|
||||
reason: ""
|
||||
});
|
||||
|
||||
// Reconnect?
|
||||
if (stream.options.reconnect) {
|
||||
$.stream(stream.url, stream.options);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, message);
|
||||
|
||||
transport.open();
|
||||
|
||||
// Overrides send and close
|
||||
$.extend(stream, {
|
||||
send: function(data) {
|
||||
if (stream.readyState === 0) {
|
||||
$.error("INVALID_STATE_ERR: Stream not open");
|
||||
}
|
||||
|
||||
// Pushes the data into the queue
|
||||
dataQueue.push(data);
|
||||
|
||||
if (!sending) {
|
||||
sending = true;
|
||||
|
||||
// Performs an Ajax iterating through the data queue
|
||||
(function post() {
|
||||
if (stream.readyState === 1 && dataQueue.length) {
|
||||
var options = {url: stream.url, type: "POST", data: dataQueue.shift()};
|
||||
|
||||
if (handleSend("send", options, stream) !== false) {
|
||||
$.ajax(options).complete(post);
|
||||
} else {
|
||||
post();
|
||||
}
|
||||
} else {
|
||||
sending = false;
|
||||
}
|
||||
})();
|
||||
}
|
||||
},
|
||||
close: function() {
|
||||
// Do nothing if the readyState is in the CLOSING or CLOSED
|
||||
if (stream.readyState < 2) {
|
||||
stream.readyState = 2;
|
||||
|
||||
var options = {url: stream.url, type: "POST"};
|
||||
|
||||
if (handleSend("close", options, stream) !== false) {
|
||||
// Notifies the server
|
||||
$.ajax(options);
|
||||
}
|
||||
|
||||
// Prevents reconnecting
|
||||
stream.options.reconnect = false;
|
||||
transport.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
$.extend(transports, {
|
||||
|
||||
// XMLHttpRequest: Modern browsers except Internet Explorer
|
||||
xhr: function(stream, handler, message) {
|
||||
var stop,
|
||||
polling,
|
||||
preStatus,
|
||||
xhr = new window.XMLHttpRequest();
|
||||
|
||||
xhr.onreadystatechange = function() {
|
||||
switch (xhr.readyState) {
|
||||
// Handles open and message event
|
||||
case 3:
|
||||
if (xhr.status !== 200) {
|
||||
return;
|
||||
}
|
||||
|
||||
handler.response(xhr.responseText);
|
||||
|
||||
// For Opera
|
||||
if ($.browser.opera && !polling) {
|
||||
polling = true;
|
||||
|
||||
stop = iterate(function() {
|
||||
if (xhr.readyState === 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (xhr.responseText.length > message.index) {
|
||||
handler.response(xhr.responseText);
|
||||
}
|
||||
}, stream.options.operaInterval);
|
||||
}
|
||||
break;
|
||||
// Handles error or close event
|
||||
case 4:
|
||||
// HTTP status 0 could mean that the request is terminated by abort method
|
||||
// but it's not error in Stream object
|
||||
handler.close(xhr.status !== 200 && preStatus !== 200);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
open: function() {
|
||||
xhr.open("GET", prepareURL(stream.url, stream.options.openData));
|
||||
xhr.send();
|
||||
},
|
||||
close: function() {
|
||||
if (stop) {
|
||||
stop();
|
||||
}
|
||||
|
||||
// Saves status
|
||||
try {
|
||||
preStatus = xhr.status;
|
||||
} catch (e) {}
|
||||
xhr.abort();
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
// Hidden iframe: Internet Explorer
|
||||
iframe: function(stream, handler, message) {
|
||||
var stop,
|
||||
closed,
|
||||
onload = function() {
|
||||
if (!closed) {
|
||||
closed = true;
|
||||
handler.close();
|
||||
}
|
||||
},
|
||||
doc = new window.ActiveXObject("htmlfile");
|
||||
|
||||
doc.open();
|
||||
doc.close();
|
||||
|
||||
return {
|
||||
open: function() {
|
||||
var iframe = doc.createElement("iframe");
|
||||
iframe.src = prepareURL(stream.url, stream.options.openData);
|
||||
|
||||
doc.body.appendChild(iframe);
|
||||
|
||||
// For the server to respond in a consistent format regardless of user agent, we polls response text
|
||||
var cdoc = iframe.contentDocument || iframe.contentWindow.document;
|
||||
|
||||
stop = iterate(function() {
|
||||
if (!cdoc.documentElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Detects connection failure
|
||||
if (cdoc.readyState === "complete") {
|
||||
try {
|
||||
$.noop(cdoc.fileSize);
|
||||
} catch(e) {
|
||||
handler.close(true);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var response = cdoc.body.lastChild,
|
||||
readResponse = function() {
|
||||
// Clones the element not to disturb the original one
|
||||
var clone = response.cloneNode(true);
|
||||
|
||||
// If the last character is a carriage return or a line feed, IE ignores it in the innerText property
|
||||
// therefore, we add another non-newline character to preserve it
|
||||
clone.appendChild(cdoc.createTextNode("."));
|
||||
|
||||
var text = clone.innerText;
|
||||
return text.substring(0, text.length - 1);
|
||||
};
|
||||
|
||||
// To support text/html content type
|
||||
if (!$.nodeName(response, "pre")) {
|
||||
// Injects a plaintext element which renders text without interpreting the HTML and cannot be stopped
|
||||
// it is deprecated in HTML5, but still works
|
||||
var head = cdoc.head || cdoc.getElementsByTagName("head")[0] || cdoc.documentElement,
|
||||
script = cdoc.createElement("script");
|
||||
|
||||
script.text = "document.write('<plaintext>')";
|
||||
|
||||
head.insertBefore(script, head.firstChild);
|
||||
head.removeChild(script);
|
||||
|
||||
// The plaintext element will be the response container
|
||||
response = cdoc.body.lastChild;
|
||||
}
|
||||
|
||||
// Handles open event
|
||||
handler.response(readResponse());
|
||||
|
||||
// Handles message and close event
|
||||
stop = iterate(function() {
|
||||
var text = readResponse();
|
||||
if (text.length > message.index) {
|
||||
handler.response(text);
|
||||
|
||||
// Empties response every time that it is handled
|
||||
response.innerText = "";
|
||||
message.index = 0;
|
||||
}
|
||||
|
||||
if (cdoc.readyState === "complete") {
|
||||
onload();
|
||||
return false;
|
||||
}
|
||||
}, stream.options.iframeInterval);
|
||||
|
||||
return false;
|
||||
});
|
||||
},
|
||||
close: function() {
|
||||
if (stop) {
|
||||
stop();
|
||||
}
|
||||
|
||||
doc.execCommand("Stop");
|
||||
onload();
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
// XDomainRequest: Optionally Internet Explorer 8+
|
||||
xdr: function(stream, handler) {
|
||||
var xdr = new window.XDomainRequest(),
|
||||
rewriteURL = stream.options.rewriteURL || function(url) {
|
||||
// Maintaining session by rewriting URL
|
||||
// http://stackoverflow.com/questions/6453779/maintaining-session-by-rewriting-url
|
||||
var rewriters = {
|
||||
JSESSIONID: function(sid) {
|
||||
return url.replace(/;jsessionid=[^\?]*|(\?)|$/, ";jsessionid=" + sid + "$1");
|
||||
},
|
||||
PHPSESSID: function(sid) {
|
||||
return url.replace(/\?PHPSESSID=[^&]*&?|\?|$/, "?PHPSESSID=" + sid + "&").replace(/&$/, "");
|
||||
}
|
||||
};
|
||||
|
||||
for (var name in rewriters) {
|
||||
// Finds session id from cookie
|
||||
var matcher = new RegExp("(?:^|;\\s*)" + encodeURIComponent(name) + "=([^;]*)").exec(document.cookie);
|
||||
if (matcher) {
|
||||
return rewriters[name](matcher[1]);
|
||||
}
|
||||
}
|
||||
|
||||
return url;
|
||||
};
|
||||
|
||||
// Handles open and message event
|
||||
xdr.onprogress = function() {
|
||||
handler.response(xdr.responseText);
|
||||
};
|
||||
// Handles error event
|
||||
xdr.onerror = function() {
|
||||
handler.close(true);
|
||||
};
|
||||
// Handles close event
|
||||
var onload = xdr.onload = function() {
|
||||
handler.close();
|
||||
};
|
||||
|
||||
return {
|
||||
open: function() {
|
||||
xdr.open("GET", prepareURL(rewriteURL(stream.url), stream.options.openData));
|
||||
xdr.send();
|
||||
},
|
||||
close: function() {
|
||||
xdr.abort();
|
||||
onload();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// Closes all stream when the document is unloaded
|
||||
// this works right only in IE
|
||||
$(window).bind("unload.stream", function() {
|
||||
for (var url in instances) {
|
||||
instances[url].close();
|
||||
delete instances[url];
|
||||
}
|
||||
});
|
||||
|
||||
$.each("streamOpen streamMessage streamError streamClose".split(" "), function(i, o) {
|
||||
$.fn[o] = function(f) {
|
||||
return this.bind(o, f);
|
||||
};
|
||||
});
|
||||
|
||||
// Works even in IE6
|
||||
function getAbsoluteURL(url) {
|
||||
var div = document.createElement("div");
|
||||
div.innerHTML = "<a href='" + url + "'/>";
|
||||
|
||||
return div.firstChild.href;
|
||||
}
|
||||
|
||||
function trigger(stream, event, props) {
|
||||
event = event.type ?
|
||||
event :
|
||||
$.extend($.Event(event), {bubbles: false, cancelable: false}, props);
|
||||
|
||||
var handlers = stream.options[event.type],
|
||||
applyArgs = [event, stream];
|
||||
|
||||
// Triggers local event handlers
|
||||
for (var i = 0, length = handlers.length; i < length; i++) {
|
||||
handlers[i].apply(stream.options.context, applyArgs);
|
||||
}
|
||||
|
||||
if (stream.options.global) {
|
||||
// Triggers global event handlers
|
||||
$.event.trigger("stream" + event.type.substring(0, 1).toUpperCase() + event.type.substring(1), applyArgs);
|
||||
}
|
||||
}
|
||||
|
||||
function prepareURL(url, data) {
|
||||
// Converts data into a query string
|
||||
if (data && typeof data !== "string") {
|
||||
data = param(data);
|
||||
}
|
||||
|
||||
// Attaches a time stamp to prevent caching
|
||||
var ts = $.now(),
|
||||
ret = url.replace(/([?&])_=[^&]*/, "$1_=" + ts);
|
||||
|
||||
return ret + (ret === url ? (/\?/.test(url) ? "&" : "?") + "_=" + ts : "") + (data ? ("&" + data) : "");
|
||||
}
|
||||
|
||||
function param(data) {
|
||||
return $.param(data, $.ajaxSettings.traditional);
|
||||
}
|
||||
|
||||
function iterate(fn, interval) {
|
||||
var timeoutId;
|
||||
|
||||
// Though the interval is 0 for real-time application, there is a delay between setTimeout calls
|
||||
// For detail, see https://developer.mozilla.org/en/window.setTimeout#Minimum_delay_and_timeout_nesting
|
||||
interval = interval || 0;
|
||||
|
||||
(function loop() {
|
||||
timeoutId = setTimeout(function() {
|
||||
if (fn() === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
loop();
|
||||
}, interval);
|
||||
})();
|
||||
|
||||
return function() {
|
||||
clearTimeout(timeoutId);
|
||||
};
|
||||
}
|
||||
|
||||
})(jQuery);
|
|
@ -1,18 +1,50 @@
|
|||
var $tap;
|
||||
|
||||
function server_disconnected() {
|
||||
$("#server_ok")[0].className = "server_not_ok";
|
||||
$("#server_stats_connection_count").text("—");
|
||||
$("#server_stats_boot_time").text("—");
|
||||
$("#server_stats_uptime").text("— seconds");
|
||||
}
|
||||
|
||||
function refresh_server_stats() {
|
||||
$.getJSON("/_/server_stats", function (data) {
|
||||
$("#server_ok")[0].className = "server_ok";
|
||||
$("#server_stats_connection_count").text(data.connection_count);
|
||||
$("#server_stats_boot_time").text(new Date(data.boot_time * 1000));
|
||||
$("#server_stats_uptime").text(data.uptime + " seconds");
|
||||
}).error(function () {
|
||||
$("#server_ok")[0].className = "server_not_ok";
|
||||
$("#server_stats_connection_count").text("—");
|
||||
$("#server_stats_boot_time").text("—");
|
||||
$("#server_stats_uptime").text("— seconds");
|
||||
switch ($tap.readyState) {
|
||||
case 0: // connecting
|
||||
case 1: // open
|
||||
case 2: // closing
|
||||
break;
|
||||
case 3: // closed
|
||||
reset_tap_stream();
|
||||
}
|
||||
}).error(server_disconnected);
|
||||
}
|
||||
|
||||
function reset_tap_stream() {
|
||||
$tap = $.stream("/_/tap", {
|
||||
type: "http",
|
||||
dataType: "json",
|
||||
|
||||
open: function () {
|
||||
refresh_server_stats();
|
||||
},
|
||||
message: function (event, stream) {
|
||||
$("#debug_container").text(JSON.stringify(event.data));
|
||||
stream.send({ok: true});
|
||||
},
|
||||
error: server_disconnected,
|
||||
close: server_disconnected
|
||||
});
|
||||
}
|
||||
|
||||
function ui_main() {
|
||||
refresh_server_stats();
|
||||
setInterval(refresh_server_stats, 5000);
|
||||
|
||||
$.stream.setup({enableXDR: true});
|
||||
reset_tap_stream();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue