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
[email protected]!/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 [email protected]!/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/[email protected]!/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
[email protected]!/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/[email protected]!/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/[email protected]!/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/[email protected]!/bootstrap.js -> resource://gre/modules/commonjs/toolkit/loader.js -> resource://gre/modules/commonjs/sdk/timers.js:40
nsISocketServer
implements a simple TCP/bind server, but does not implement the websocket protocol.
nsISocketTransport
s 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