Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

javascript websockets - control initial connection/when does onOpen get bound

Two related questions that may be more rooted in my lack of knowledge of how/if browsers pre-parse javascript:

var ws = new WebSocket("ws://ws.my.url.com");
ws.onOpen = function() { ... };

There appears to be no way to directly control the initialisation of a WebSocket, beyond wrapping it in a callback, so I assume the connection is created as soon as the javascript code is loaded and get to the constructor?

When does the onOpen property get attached to ws? Is there any possibility of a race condition (if for some reason you had some code in between the definition of the socket and the definition of onOpen?) so that onOpen is undecidably bound before/after the connection is established (I know you could optionally check ws.readyState). Supplementary to this, is the WebSocket handshake blocking?

I realise it's all a draft at the moment, possibly implementation dependent and I may have missed something blindingly obvious, but I couldn't see anything particular pertinent on my internet searches/skim through the draft w3c spec, so any help in my understanding of websockets/javascript's inner workings is very much appreciated!

like image 869
dbeacham Avatar asked Dec 05 '11 22:12

dbeacham


People also ask

At what stage a WebSocket client is informed for the incoming message?

The OnMessage event is raised when a client sends data to the server. Inside this event handler, the incoming message can be transmitted to the clients, or probably select only some of them.

Why WebSocket is closed before the connection is established?

In this code what it means is that ws. close() was called (by user code) before the connection was even given a chance to be established. So, the cause of this error is if code attempts to close the WebSocket connection before it's had a chance to actually connect.

How does the browser initiate a WebSocket connection?

The client establishes a WebSocket connection through a process known as the WebSocket handshake. This process starts with the client sending a regular HTTP request to the server. An Upgrade header is included in this request which informs the server that the client wishes to establish a WebSocket connection.

When using WebSocket dots and how do you know when the data sent has been transmitted to the network?

The only way to know the client received the webSocket message for sure is to have the client send your own custom message back to the server to indicate you received it and for you to wait for that message on the server. That's the ONLY end-to-end test that is guaranteed.


4 Answers

JavaScript is single threaded which means the network connection can't be established until the current scope of execution completes and the network execution gets a chance to run. The scope of execution could be the current function (the connect function in the example below). So, you could miss the onopen event if you bind to it very late on using a setTimeout e.g. in this example you can miss the event:

View: http://jsbin.com/ulihup/edit#javascript,html,live

Code:

var ws = null;

function connect() {
  ws = new WebSocket('ws://ws.pusherapp.com:80/app/a42751cdeb5eb77a6889?client=js&version=1.10');
  setTimeout(bindEvents, 1000);
  setReadyState();
}

function bindEvents() {
  ws.onopen = function() {
    log('onopen called');
    setReadyState();
  };
}

function setReadyState() {
  log('ws.readyState: ' + ws.readyState);
}

function log(msg) {
  if(document.body) {
    var text = document.createTextNode(msg);
    document.body.appendChild(text);
  }
}

connect();

If you run the example you may well see that the 'onopen called' log line is never output. This is because we missed the event.

However, if you keep the new WebSocket(...) and the binding to the onopen event in the same scope of execution then there's no chance you'll miss the event.

For more information on scope of execution and how these are queued, scheduled and processed take a look at John Resig's post on Timers in JavaScript.

like image 149
leggetter Avatar answered Oct 16 '22 19:10

leggetter


@leggetter is right, following code did executes sequentially:

(function(){
    ws = new WebSocket("ws://echo.websocket.org");
    ws.addEventListener('open', function(e){
        console.log('open', e);
        ws.send('test');
    });
    ws.addEventListener('message', function(e){console.log('msg', e)});

})();

But, in W3C spec there is a curious line:

Return a new WebSocket object, and continue these steps in the background (without blocking scripts).

It was confusing for me, when I was learning browser api for it. I assume that user agents ignoring it, or I misinterpreting it.

like image 38
senz Avatar answered Oct 16 '22 19:10

senz


TL;DR - The standard states that the connection can be opened "while the [JS] event loop is running" (e.g. by the browser's C++ code), but that firing the open event must be queued to the JS event loop, meaning any onOpen callback registered in the same execution block as new WebSocket(...) is guaranteed to be executed, even if the connection gets opened while the current execution block is still executing.


According to The WebSocket Interface specification in the HTML Standard (emphasis mine):

The WebSocket(url, protocols) constructor, when invoked, must run these steps:

  1. Let urlRecord be the result of applying the URL parser to url.
  2. If urlRecord is failure, then throw a "SyntaxError" DOMException.
  3. If urlRecord's scheme is not "ws" or "wss", then throw a "SyntaxError" DOMException.
  4. If urlRecord's fragment is non-null, then throw a "SyntaxError" DOMException.
  5. If protocols is a string, set protocols to a sequence consisting of just that string.
  6. If any of the values in protocols occur more than once or otherwise fail to match the requirements for elements that comprise the value of Sec-WebSocket-Protocol fields as defined by The WebSocket protocol, then throw a "SyntaxError" DOMException.
  7. Run this step in parallel:

    1. Establish a WebSocket connection given urlRecord, protocols, and the entry settings object. [FETCH]

    NOTE If the establish a WebSocket connection algorithm fails, it triggers the fail the WebSocket connection algorithm, which then invokes the close the WebSocket connection algorithm, which then establishes that the WebSocket connection is closed, which fires the close event as described below.

  8. Return a new WebSocket object whose url is urlRecord.

Note the establishment of the connection is run 'in parallel', and the specification further states that "...in parallel means those steps are to be run, one after another, at the same time as other logic in the standard (e.g., at the same time as the event loop). This standard does not define the precise mechanism by which this is achieved, be it time-sharing cooperative multitasking, fibers, threads, processes, using different hyperthreads, cores, CPUs, machines, etc."

Meaning that the connection can theoretically be opened before onOpen registration, even if onOpen(...) is the next statement after the constructor call.

However... the standard goes on to state under Feedback from the protocol:

When the WebSocket connection is established, the user agent must queue a task to run these steps:

  1. Change the readyState attribute's value to OPEN (1).
  2. Change the extensions attribute's value to the extensions in use, if it is not the null value. [WSP]
  3. Change the protocol attribute's value to the subprotocol in use, if it is not the null value. [WSP]
  4. Fire an event named open at the WebSocket object.

NOTE Since the algorithm above is queued as a task, there is no race condition between the WebSocket connection being established and the script setting up an event listener for the open event.

So in a browser or or library that adheres to the HTML Standard, a callback registered to WebSocket.onOpen(...) is guaranteed to execute, if it is registered before the end of the execution block in which the constructor is called, and before any subsequent statement in the same block that releases the event loop (e.g. await).

like image 5
ipetrik Avatar answered Oct 16 '22 17:10

ipetrik


Pay attention to the fact that I/O may occur within the scope of execution. For example, in the following code

var ws = new WebSocket("ws://localhost:8080/WebSockets/example");
alert("Hi");
ws.onopen = function(){
    writeToScreen("Web Socket is connected!!" + "<br>");
};
function writeToScreen(message) {
    var div = document.getElementById('test');
    div.insertAdjacentHTML( 'beforeend', message );
}

, the message "Web Socket is connected" will appear or not, depending how much time it took you to close the "Hi" alert

like image 2
Alon Halimi Avatar answered Oct 16 '22 18:10

Alon Halimi