I'm trying to implement a client-server communications between two or more plugins where each plugin is concurrently both server and client. I use nsIServerSocket for server part and websockets for client part. This is the code:
function startServer(port) {
var listener = {
onSocketAccepted: function(serverSocket, transport) {
console.log("Accepted connection on " + transport.host + ":" + transport.port);
var input = transport.openInputStream(Ci.nsITransport.OPEN_BLOCKING, 0, 0);//.QueryInterface(Ci.nsIAsyncInputStream);
var output = transport.openOutputStream(Ci.nsITransport.OPEN_BLOCKING, 0, 0);
var sin = Cc["#mozilla.org/scriptableinputstream;1"].createInstance(Ci.nsIScriptableInputStream);
try{
sin.init(input);
var readBytes = sin.available();
var request = '';
request = sin.read(readBytes);
console.log('Received: ' + request);
//getUrl(request);
output.write("yes", "yes".length);
output.flush();
}
finally{
sin.close();
input.close();
output.close();
}
}
}
try{
var serverSocket = Cc["#mozilla.org/network/server-socket;1"].createInstance(Ci.nsIServerSocket);
serverSocket.init(port, true, 5);
console.log("Opened socket on " + serverSocket.port);
serverSocket.asyncListen(listener);
}catch(e){
console.log(e);
}
}
For server part, and the following for client part:
var address="ws://otherAddress:1234";// + port;
var window = Cc["#mozilla.org/appshell/appShellService;1"]
.getService(Ci.nsIAppShellService)
.hiddenDOMWindow;
ws = new window.WebSocket(address);
try{
ws.onmessage = function () {
};
ws.onopen = function(){
console.log("connection opened");
// Web Socket is connected. You can send data by send() method
ws.send("lol ");
};
ws.onclose = function() {
// websocket is closed. };
console.log("websocket is closed");
}
}catch(evt){
console.log(evt.data);
}
The client code start when user click on a button....This code is partly working, because from console I see that when user click button, server receive the connection-open, but I can't receive the message......Anyone can help me? Thanks
UPDATE 1
the message that I see in console is like this:
"Received: GET / HTTP/1.1
Host: localhost:1234
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:27.0) Gecko/20100101 Firefox/27.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: it-IT,it;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Sec-WebSocket-Version: 13
Origin: resource://gre-resources
Sec-WebSocket-Key: zh/EpJRRsOAgLfPIbI1EDg==
Connection: keep-alive, Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
UPDATE 2
After nmaier and IvyLynx answers (thanks a lot!!), I modified my code inserting a full "ServerSocket" implementation (mainly because in future I will also be passing binary data). This is the code for a localhost case:
var {Cc, Ci, Cu, Cr, components} = require("chrome");
// the thread manager can be important when using asynchronous mode
var thread_manager = Cc["#mozilla.org/thread-manager;1"].getService();
var socket_service = Cc["#mozilla.org/network/socket-transportservice;1"].getService(Ci.nsISocketTransportService);
// make some constructors so we don't have to worry about this later
var socket = Cc["#mozilla.org/network/serversocket;1"].createInstance(Ci.nsIServerSocket);
// set the second argument to false if you want it to listen
// to connections beyond the computer the extension runs on
socket.init(-1, true, -1);
var output_stream_bin = Cc["#mozilla.org/binaryoutputstream;1"].createInstance(Ci.nsIBinaryOutputStream);
var input_stream_bin = Cc["#mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIBinaryInputStream);
// this is so we can easily instantiate nsIInputStreamPump, which allows us to read input streams properly
var input_stream_pump_c = Cc["#mozilla.org/network/input-stream-pump;1"];
var input_stream_base, input_stream_async_c, input_stream_async, recieved_bytes, recieved_total, input_stream_pump;
var output_stream_base, output_stream_async_c, output_stream_async, generalStream;
var client, client_input_stream, client_output_stream, client_input_stream_pump;
var data_to_send = "hi hi"; // this holds what we want to send
var socket_transport = socket_service.createTransport(null, 0, "localhost", socket.port, null);
var socket_listener = {
onSocketAccepted: function(socket, transport){
client = transport;
client_input_stream = client.openInputStream(0, 0, 0);
client_output_stream = client.openOutputStream(0, 0, 0);
client_output_stream.QueryInterface(Ci.nsIAsyncOutputStream);
generalStream = client_output_stream;
client_input_stream_pump[this_transport] = input_stream_pump_c.createInstance(Ci.nsIInputStreamPump);
client_input_stream_pump[this_transport].init(client_input_stream, -1, -1, 0, 0, false);
client_input_stream_pump[this_transport].asyncRead(socket_reader, socket);
},
onStopListening: function(socket, status){
}
};
socket.asyncListen(socket_listener);
// this guy will get called when we're ready to send data
var output_stream_callback = {
onOutputStreamReady: function(stream){
output_stream_bin.setOutputStream(stream);
output_stream_bin.writeBytes(data_to_send, data_to_send.length);
data_to_send = "";
}
};
var socket_reader = {
onDataAvailable: function(request, context, stream, offset, count){
input_stream_bin.setInputStream(stream);
if(input_stream_bin.available() > 0){
recieved_bytes = input_stream_bin.readByteArray(count);
recieved_total = "";
// this loop converts bytes to characters
// if you don't need to pass binary data around
// you can just use nsIScriptableInputStream instead of
// nsIBinaryInputStream and skip this
for (var i = 0; i < recieved_bytes.length; i++){
recieved_total += String.fromCharCode(recieved_bytes[i]);
}
console.log("Received " + recieved_total)
}else{
stream.close();
}
},
onStartRequest: function(request, context){
},
onStopRequest: function(request, context, status){
}
};
require("sdk/widget").Widget({
id: "mozilla-link",
label: "Mozilla website",
contentURL: data.url("icon.png"),
onClick: listTabs
});
function listTabs() {
//console.log(client_output_stream);
generalStream.asyncWait(output_stream_callback,0,0,thread_manager.mainThread);
};
The problem is the generalStream variable. I call asyncWait method when user click on extension icon, but I also insert the call in other methods. Each generalStream.asyncWait provocate the follow problem (where are ... in reality there are the path of the profile in wich the extension is executed):
console.error: client:
Message: TypeError: generalStream is undefined
Stack:
listTabs#resource://gre/modules/XPIProvider.jsm -> jar:file:///.../extensions/jid1-exo2NP
aadiTkqg#jetpack.xpi!/bootstrap.js -> resource://gre/modules/commonjs/toolkit/lo
ader.js -> resource://jid1-exo2npaaditkqg-at-jetpack/client/lib/main.js:742
_emitOnObject#resource://gre/modules/XPIProvider.jsm -> jar:file:///.../extensions/jid1-exo2N PaadiTkqg#jetpack.xpi!/bootstrap.js -> resource://gre/modules/commonjs/toolkit/l
oader.js -> resource://gre/modules/commonjs/sdk/deprecated/events.js:153
_emit#resource://gre/modules/XPIProvider.jsm -> jar:file:///.../extensions/jid1-exo2NPaadiTkqg#jetpack.xpi!/bootstrap.js -> resource://gre/modules/commonjs/toolkit/loader.js -> resource://gre/modules/commonjs/sdk/deprecated/events.js:123 _onEvent#resource://gre/modules/XPIProvider.jsm -> jar:file:///.../extensions/jid1-exo2NPaadi
Tkqg#jetpack.xpi!/bootstrap.js -> resource://gre/modules/commonjs/toolkit/loader.js -> resource://gre/modules/commonjs/sdk/widget.js:278
WidgetView__onEvent#resource://gre/modules/XPIProvider.jsm -> jar:file:///.../extensions/jid1-exo2NPaadiTkqg#jetpack.xpi!/bootstrap.js -> resource://gre/modules/commonjs/toolkit/loader.js -> resource://gre/modules/commonjs/sdk/widget.js:426
WC_addEventHandlers/listener/<#resource://gre/modules/XPIProvider.jsm -> jar:file:///.../extensions/jid1-exo2NPaadiTkqg#jetpack.xpi!/bootstrap.js -> resource://gre/modules/commonjs/toolkit/loader.js -> resource://gre/modules/commonjs/sdk/widget.js:884
notify#resource://gre/modules/XPIProvider.jsm -> jar:file:///.../extensions/jid1-exo2NPaadiTkqg#jetpack.xpi!/bootstrap.js -> resource://gre/modules/commonjs/toolkit/loader.js -> resource://gre/modules/commonjs/sdk/timers.js:40
nmaier is most probably correct on why your code doesn't work but I'll still post this answer as supplementary information
First of all, you don't need to use both WebSockets and XPCOM Sockets to create extensions that are both server and client. Either one of these technologies will suffice and work fine for this purpose. Personally, having done this myself, I'd recommend using XPCOM Sockets unless you really want to create portable code between browsers (seeing as you don't mention anything like that, I recommend dropping the WebSockets implementation and sticking to XPCOM Sockets - but WebSockets are easier to handle, so you might prefer them - it doesn't matter, just pick one). I'm mentioning this since you have both the WebSocket and the XPCOM Socket listening for connections and I think it's because you want this:
ServerSocket recieves client connection -> Only recieves from client connection
WebSocket connects to server connection -> Only sends to server connection
Instead, you can just have an nsIServerSocket that both reads and sends data to another server socket. Also, as nmaier says, blocking streams are a bad idea, unless you really need them and in this case, you don't. It'll probably help things work better too, if you use asynchronous mode.
Below is a sample implementation of what you want using nsIServerSocket, which I'm including because understanding how this works took me a lot of pain and time. If you don't want the power of native-application level tcp sockets (in other words, you don't need to read binary streams or do complex handling or write your own communication protocol), WebSockets are probably adequate for you and preferrable, as they'll handle a simple messaging system fine. Simply put, if the below is not your cup of tea, just stick to WebSockets until they're not capable of serving your needs.
Note: the following code makes no attempt at namespacing and promptly thrashes the global namespace - it isn't intended to be production code, so normally, you'd want all of these variables and objects wrapped up into your extension's namespacing object.
Here is the sample implementation with nsIServerSocket and binary streams:
Preparation code
// these aliases will shorten the code
var {
utils: Cu,
interfaces: Ci,
classes: Cc,
results: Cr,
stack: Cs,
manager: Cm,
Exception: Ce,
Constructor: CC,
} = Components;
// get related services
// the thread manager can be important when using asynchronous mode
var thread_manager = Cc["#mozilla.org/thread-manager;1"].getService();
var socket_service = Cc["#mozilla.org/network/socket-transport-service;1"].getService(Ci.nsISocketTransportService);
// make some constructors so we don't have to worry about this later
var socket_c = CC("#mozilla.org/network/server-socket;1", "nsIServerSocket", "init");
var output_stream_bin_c = CC("#mozilla.org/binaryoutputstream;1", "nsIBinaryOutputStream", "setOutputStream");
var input_stream_bin_c = CC("#mozilla.org/binaryinputstream;1", "nsIBinaryInputStream", "setInputStream");
// this is so we can easily instantiate nsIInputStreamPump, which allows us to read
// input streams properly
var input_stream_pump_c = Cc["#mozilla.org/network/input-stream-pump;1"];
// normally all these would be placed in a global object. they're declared here
// so we can instantiate them later, but this is just a sample, not production code!
var input_stream_base, input_stream_async_c, input_stream_async, input_stream_bin, recieved_bytes, recieved_total, input_stream_pump;
var output_stream_base, output_stream_async_c, output_stream_async, output_stream_bin;
var client, client_input_stream, client_output_stream, client_input_stream_pump;
var data_to_send = ""; // this holds what we want to send
Make a socket
// this socket will only listen on localhost
// set the second argument to false if you want it to listen
// to connections beyond the computer the extension runs on
var socket = new socket_c(-1, true, -1);
var socket_transport = socket_service.createTransport(null, 0, "localhost", socket.port, null);
Define callbacks and listeners
// this guy will get called when we're ready to send data
var output_stream_callback = {
onOutputStreamReady: function(stream){
output_stream_bin = new output_stream_bin_c(stream);
output_stream_bin.writeBytes(data_to_send, data_to_send.length);
data_to_send = "";
}
};
var socket_reader = {
onDataAvailable: function(request, context, stream, offset, count){
input_stream_bin = new input_stream_bin_c(stream);
if(input_stream_bin.available() > 0){
recieved_bytes = input_stream_bin.readByteArray(count);
recieved_total = ""; // this holds the stuff we get
// this loop converts bytes to characters
// if you don't need to pass binary data around
// you can just use nsIScriptableInputStream instead of
// nsIBinaryInputStream and skip this
for (var i = 0; i < recieved_bytes.length; i++){
recieved_total += String.fromCharCode(recieved_bytes[i]);
}
}else{
stream.close();
// Nothing there, closing stream.
}
},
onStartRequest: function(request, context){
},
onStopRequest: function(request, context, status){
}
};
var socket_listener = {
onSocketAccepted: function(socket, transport){
client = transport;
client_input_stream = client.openInputStream(0, 0, 0);
client_output_stream = client.openOutputStream(0, 0, 0);
client_output_stream.QueryInterface(Ci.nsIAsyncOutputStream);
client_input_stream_pump[this_transport] = input_stream_pump_c.createInstance(Ci.nsIInputStreamPump);
client_input_stream_pump[this_transport].init(client_input_stream, -1, -1, 0, 0, false);
client_input_stream_pump[this_transport].asyncRead(socket_reader, socket);
},
onStopListening: function(socket, status){
}
};
Start listening on the socket
socket.asyncListen(socket_listener);
When you want to send data
(edit- this part should be placed in a function, to be called only after a connection is made and only when you want to send data)
var stream = client_output_stream; // what stream you want to send the data to
// this is the only place where the thread_manager is required
stream.asyncWait(output_stream_callback,0,0,thread_manager.mainThread);
This implementation is fully asynchronous, so it should never have a performance impact or cause problems unless there's too much data (I guess, I don't really think there would be a problem with performance before your connection caps out) or something goes wrong (such as calling .asyncWait from the output stream callback).
Your input is in recieved_total and what you want to send is placed in data_to_send before you call .asyncWait on the connected client's output stream. Remember, this is a sample implementation, so if you want to use this, you'll need to change it so that it uses namespacing and you'll need to add handling functions for whatever data you want to get or send. Also, if you expect more than 1 client connection, you'll need to handle that separately as well (by an object array or something).
If you need information on the components used, the MDN is obviously the place to go to for that. However, nsIInputStreamPump for some reason doesn't have a page. For that you'll need to go to its idl implementation in the source (nsIInputStreamPump.idl). The mozilla lxr is also a great place to check out test implementations of sockets in javascript, as there are some .js files in there that are used to test the implementations.
edit -
for the addon-sdk, this might work better:
try replacing var {Cc, Ci, Cu, Cr, components} = require("chrome"); with
var Components = require("chrome"); and then add
var {
utils: Cu,
interfaces: Ci,
classes: Cc,
results: Cr,
stack: Cs,
manager: Cm,
Exception: Ce,
Constructor: CC,
} = Components;
as was in the original code. Also, add a console.log(Components); after the require line so you can see if you're actually getting the components object.
nsISocketServer implements a simple TCP/bind server, but does not implement the websocket protocol.
You either need to implement the websocket protocol yourself in the server socket (incl. HTTP/1.1 Upgrade)
or use raw TCP sockets (nsISocketTransports via nsISocketTransportService).
Given that raw TCP sockets are usually a mess to deal with and that you'll need to implement some simple exchange protocol on top of that anyway, I'd guess the first option of implementing the websocket protocol in the server socket would be easier (at the very least, you get the client implementation for free).
PS: Blocking mode is a bad idea as it blocks the UI thread for potentially long periods of time.
PS: Apparantly, somebody implemented the websocket protocol already in coffee script and somebody else (from the Add-on SDK team) implemented it in (what appears to be some form of :p) Javascript as well (although the latter is pretty much not self-contained and hard to read/gasp).
Edit I got curious and wrote a stand-alone JS code module WebSocket server, that seems to mostly work. :p
Related
The intention is to have several websockets for redundancy of the same data, so that if one fails, the others will still do the job. In particular, a setup where any one websocket is enough to keep the system working, even if all others fail to connect or return errors.
This is the curent setup, but I know it's wrong:
let queue = []
ws = new WebSocket(`wss://firstsource.com/ws/`);
ws2 = new WebSocket(`wss://secondsource.com/ws/`);
ws3 = new WebSocket(`wss://thirdsource.com/ws/`);
ws4 = new WebSocket(`wss://fourthsource.com/ws/`);
ws.onopen = sendIntro;
ws2.onopen = sendIntro;
ws3.onopen = sendIntro;
ws4.onopen = sendIntro;
ws.onmessage = insertQueue;
ws2.onmessage = insertQueue;
ws3.onmessage = insertQueue;
ws4.onmessage = insertQueue;
function sendIntro() {
ws.send('{"config":"2.0"}')
ws2.send('{"config":"2.0"}')
ws3.send('{"config":"2.0"}')
ws4.send('{"config":"2.0"}')
ws5.send('{"config":"2.0"}')
}
function insertQueue(msg) {
//let's just assume all we want is add the data to a queue
queue.push(msg);
}
What's the right way?
Assume the format of the data is identical for all sources.
I considered creating an onError function and use that to connect to the second websocket, and then another onError2 to connect to the third websocket, and so on, but it takes a long time to trigger onError when there is no response, so the complete process of testing a few sources if done in series like that would take far too long.
Found a working solution.
I figured all I needed was to ensure that at least one of them connect, not to ensure that if one of them disconnects the others will be ready to take over.
For this reduced problem, this worked:
//defined higher up
let sucessfulFirstSocketConnection = false
function sendIntro1() {
if(!sucessfulFirstSocketConnection){
sucessfulFirstSocketConnection = true
ws.send(sendString)
ws.onmessage = insertQueue;
ws2.close();
ws3.close();
ws4.close();
ws5.close();
}
}
function sendIntro2() {
if(!sucessfulFirstSocketConnection){
sucessfulFirstSocketConnection = true
ws2.send(sendString)
ws2.onmessage = insertQueue;
ws.close()
ws3.close();
ws4.close();
ws5.close();
}
}
and so on.
I basically let them compete among themselves, and the first one that connects closes the other connections and stops all others from getting in the way. This is achieved very quickly even if only the last WebSocket is the one that is able to connect and all others don't establish a connection.
I have a website, and I need a way to get html data from a different website via an http request, and I've looked around for ways to implement it and most say via an ajax call instead.
An ajax call is blocked by linked in so I want to try a plain cross domain http request and hope it's not blocked one way or another.
If you have a server running and are able to run code on it, you can make the HTTP call server side. Keep in mind though that most sites only allow so many calls per IP address so you can't serve a lot of users this way.
This is a simple httpListener that downloads an websites content when the QueryString contains ?site=http://linkedin.com:
// setup an listener
using(var listener = new HttpListener())
{
// on port 8080
listener.Prefixes.Add("http://+:8080/");
listener.Start();
while(true)
{
// wait for a connect
var ctx = listener.GetContext();
var req = ctx.Request;
var resp = ctx.Response;
// default page
var cnt = "<html><body>click me </body></html>";
foreach(var key in req.QueryString.Keys)
{
if (key!=null)
{
// if the url contains ?site=some url to an site
switch(key.ToString())
{
case "site":
// lets download
var wc = new WebClient();
// store html in cnt
cnt = wc.DownloadString(req.QueryString[key.ToString()]);
// when needed you can do caching or processing here
// of the results, depending on your needs
break;
default:
break;
}
}
}
// output whatever is in cnt to the calling browser
using(var sw = new StreamWriter(resp.OutputStream))
{
sw.Write(cnt);
}
}
}
To make above code work you might have to set permissions for the url, if you'r on your development box do:
netsh http add urlacl url=http://+:8080/ user=Everyone listen=yes
On production use sane values for the user.
Once that is set run the above code and point your browser to
http://localhost:8080/
(notice the / at the end)
You'll get a simple page with a link on it:
click me
Clicking that link will send a new request to the httplistener but this time with the query string site=http://linkedin.com. The server side code will fetch the http content that is at the url given, in this case from LinkedIn.com. The result is send back one-on-one to the browser but you can do post-processing/caching etc, depending on your requirements.
Legal notice/disclaimer
Most sites don't like being scraped this way and their Terms of Service might actually forbid it. Make sure you don't do illegal things that either harms site reliability or leads to legal actions against you.
I'm working with HTML5 socket functions to establish a socket connection to my server. HTML5 has functions below to handle disconnecting
Socket.onclose = function()
{
...
}
Socket.onerror = function()
{
...
}
My problem is, how try for reconnect after onclose function executes? I tried to put a while loop inside of it like
ws.onclose = function()
{
While(conn==0)
{
ws = new WebSocket("ws://example.com");
}
}
and
ws.onopen = function()
{
conn=1;
...
}
But did't work.
Any idea?
Here's the script that comes with the Plezi websocket framework... It's fairly basic, but it works on the browsers I used it on (Safari, Chrome and FireFox).
The trick is to leverage the onclose method WITHOUT a loop.
The onclose method will be called even if the websocket never opened and the connection couldn't be established (without calling onopen).
Initiating a reconnect within an onclose is enough.
Writing a loop or a conditional review will not only fail, but will halt all the scripts on the page. Allow me to explain:
Javascript is single threaded. Again: it's an even/task based, single threaded, environment.
This means that your code acts like an atomic unit - nothing happens and nothing changes until your code finished running it's course.
Because connections could take a while to establish, the new WebSocket was designed (and rightfully so) as an asynchronous function.
This is how come you can define the onopen event callback AFTER the creation of the event.
The new websocket connection will be attempted only once the current task/event is finished...
...so a loop will get you stuck forever waiting for a task that can't be performed until your code stops running...
Back to the issue at hand, here's the code. If you have any ideas for improvements, please let me know:
// Your websocket URI should be an absolute path. The following sets the base URI.
// remember to update to the specific controller's path to your websocket URI.
var ws_controller_path = window.location.pathname; // change to '/controller/path'
var ws_uri = (window.location.protocol.match(/https/) ? 'wss' : 'ws') + '://' + window.document.location.host + ws_controller_path
// websocket variable.
var websocket = NaN
// count failed attempts
var websocket_fail_count = 0
// to limit failed reconnection attempts, set this to a number.
var websocket_fail_limit = NaN
// to offer more or less space between reconnection attempts, set this interval in miliseconds.
var websocket_reconnect_interval = 250
function init_websocket()
{
if(websocket && websocket.readyState == 1) return true; // console.log('no need to renew socket connection');
websocket = new WebSocket(ws_uri);
websocket.onopen = function(e) {
// reset the count.
websocket_fail_count = 0
// what do you want to do now?
};
websocket.onclose = function(e) {
// If the websocket repeatedly you probably want to reopen the websocket if it closes
if(!isNaN(websocket_fail_limit) && websocket_fail_count >= websocket_fail_limit) {
// What to do if we can't reconnect so many times?
return
};
// you probably want to reopen the websocket if it closes.
if(isNaN(websocket_fail_limit) || (websocket_fail_count <= websocket_fail_limit) ) {
// update the count
websocket_fail_count += 1;
// try to reconect
setTimeout( init_websocket, websocket_reconnect_interval);
};
};
websocket.onerror = function(e) {
// update the count.
websocket_fail_count += 1
// what do you want to do now?
};
websocket.onmessage = function(e) {
// what do you want to do now?
console.log(e.data);
// to use JSON, use:
// var msg = JSON.parse(e.data); // remember to use JSON also in your Plezi controller.
};
}
// setup the websocket connection once the page is done loading
window.addEventListener("load", init_websocket, false);
Right now, I have a node.js server that's able to stream data on GET request, using the stream API. The GET request is Transfer-encoded set to 'chunked'. The data can be on the order of 10 to 30 MBs. (They are sometimes 3D models)
On the browser side, I wish to be able to process the data as I'm downloading it--I wish to be able to display the data on Canvas as I'm downloading it. So you can see the 3D model appear, face by face, as the data is coming in. I don't need duplex communication, and I don't need a persistent connection. But I do need to process the data as soon as it's downloaded, rather than waiting for the entire file to finish downloading. Then after the browser downloads the data, I can close the connection.
How do I do this?
JQuery ajax only calls back when all the data has been received.
I also looked at portal.js (which was jquery-streaming) and socket.io, but they seem to assume persistent reconnection.
So far, I was able to hack a solution using raw XMLHttpRequest, and making a callback when readyStead >= 2 && status == 200, and keeping track of place last read. However, that keeps all the data downloaded in the raw XMLHttpRequest, which I don't want.
There seems to be a better way to do this, but I'm not sure what it is. Any one have suggestions?
oboe.js is a library for streaming responses in the browser.
However, that keeps all the data downloaded in the raw XMLHttpRequest, which I don't want.
I suspect this may be the case with oboe.js as well and potentially a limitation of XMLHttpRequest itself. Not sure as I haven't directly worked on this type of use case. Curious to see what you find out with your efforts and other answers to this question.
So I found the answer, and it's Server-sent events. It basically enables one-way http-streams that the browser can handle a chunk at a time. It can be a little tricky because some existing stream libs are broken (they don't assume you have \n in your stream, and hence you get partial data), or have little documentation. But it's not hard to roll your own (once you figure it out).
You can define your sse_transform like this:
// file sse_stream.coffee
var Transform = require('stream').Transform;
var util = require('util');
util.inherits(SSEStream, Transform);
function SSEStream(option) {
Transform.call(this, option);
this.id = 0;
this.retry = (option && option.retry) || 0;
}
SSEStream.prototype._transform = function(chunk, encoding, cb) {
var data = chunk.toString();
if (data) {
this.push("id:" + this.id + "\n" +
data.split("\n").map(function (e) {
return "data:" + e
}).join("\n") + "\n\n");
//"retry: " + this.retry);
}
this.id++;
cb();
};
SSEStream.prototype._flush = function(next) {
this.push("event: end\n" + "data: end" + "\n\n");
next();
}
module.exports = SSEStream;
Then on the server side (I was using express), you can do something like this:
sse_stream = require('sse_stream')
app.get '/blob', (req, res, next) ->
sse = new sse_stream()
# It may differ here for you, but this is just a stream source.
blobStream = repo.git.streamcmd("cat-file", { p: true }, [blob.id])
if (req.headers["accept"] is "text/event-stream")
res.type('text/event-stream')
blobStream.on("end", () -> res.removeAllListeners()).stdout
.pipe(
sse.on("end", () -> res.end())
).pipe(res)
else
blobStream.stdout.pipe(res)
Then on the browser side, you can do:
source = new EventSource("/blob")
source.addEventListener('open', (event) ->
console.log "On open..."
, false)
source.addEventListener('message', (event) ->
processData(event.data)
, false)
source.addEventListener('end', (event) ->
console.log "On end"
source.close()
, false)
source.addEventListener('error', (event) ->
console.log "On Error"
if event.currentTarget.readyState == EventSource.CLOSED
console.log "Connection was closed"
source.close()
, false)
Notice that you need to listen for the event 'end', that is sent from the server in the transform stream's _flush() method. Otherwise, EventSource in the browser is just going to request the same file over and over again.
Note that you can use libraries on the server side to generate SSE. On the browser side, you can use portal.js to handle SSE. I just spelt things out, so you can see how things would work.
I'm writing a Firefox extension that creates a socket server which will output the active tab's URL when a client makes a connection to it. I have the following code in my javascript file:
var serverSocket;
function startServer()
{
var listener =
{
onSocketAccepted : function(socket, transport)
{
try {
var outputString = gBrowser.currentURI.spec + "\n";
var stream = transport.openOutputStream(0,0,0);
stream.write(outputString,outputString.length);
stream.close();
} catch(ex2){ dump("::"+ex2); }
},
onStopListening : function(socket, status){}
};
try {
serverSocket = Components.classes["#mozilla.org/network/server-socket;1"]
.createInstance(Components.interfaces.nsIServerSocket);
serverSocket.init(7055,true,-1);
serverSocket.asyncListen(listener);
} catch(ex){ dump(ex); }
document.getElementById("status").value = "Started";
}
function stopServer ()
{
if (serverSocket)
serverSocket.close();
}
window.addEventListener("load", function() { startServer(); }, false);
window.addEventListener("unload", function() { stopServer(); }, false);
As it is, it works for multiple tabs in a single window. If I open multiple windows, it ignores the additional windows. I think it is creating a server socket for each window, but since they are using the same port, the additional sockets fail to initialize. I need it to create a server socket when the browser launches and continue running when I close the windows (Mac OS X). As it is, when I close a window but Firefox remains running, the socket closes and I have to restart firefox to get it up an running. How do I go about that?
Firefox extension overlays bind to window objects. One way around this is to create an XPCOM component or find one that someone else already created to allow you to build functionality without binding it to the window objects.
Of course, section #2 below on Observer Notifications may be helpful as well.
Possible workaround: #1
Instead of calling "startServer()" each time a window is opened, you could have a flag called windowCount that you could increment each time you open a new window. If windowCount is greater than 0, don't call startServer().
As windows close, you could decrement the count. Once it hits 0, stop the server.
Here is information from the Mozilla forums on this problem:
http://forums.mozillazine.org/viewtopic.php?f=19&t=2030279
Possible workaround #2:
With that said, I've also found documentation for Observer Notifications, which may be helpful as there is a section on Application Startup and Shutdown:
https://developer.mozilla.org/en/Observer_Notifications
UPDATE:
Here are some resources on creating XPCOM components in JavaScript and in C++:
https://developer.mozilla.org/en/how_to_build_an_xpcom_component_in_javascript
http://www.codeproject.com/KB/miscctrl/XPCOM_Creation.aspx
https://developer.mozilla.org/en/creating_xpcom_components
You probably want to:
Move your code into a JavaScript component
Register your component as a profile-after-change observer
Whenever someone makes a connection to your socket, find the active window and return its URL.
Use something like
var wm = Components.classes["#mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator);
var win = wm.getMostRecentWindow("navigator:browser");
var spec = win ? win.getBrowser().currentURI.spec : "";
var outputString = spec + "\n";
etc.