Написание клиентских приложений с помощью вебсокетов

Draft
This page is not complete.

Вебсокеты - технология, которя позволяет открыть интерактивную сессию общения между браузером пользователя и сервером. Соединяясь через вебсокеты, веб-приложения могут осуществлять взаимодействие в реальном времени вместо того, чтобы делать запросы к клиенту о входящих/исходящих изменениях.

Замечание: У нас есть работающий пример чата, части кода из которого используются в статье. Пример будет доступен, когда инфраструктура сайта сможет должным образом поддерживать хостинг примеров с использованием вебсокетов.

Доступность вебсокетов

API вебсокетов доступно в Javascript коде, область видимости которого включает объект DOM Window или любой объект, реализующий WorkerUtils; это означает, что вы можете использовать Web Workers.

Замечание: API вебсокетов (как и протокол лежащий в его основе) всё ещё проходят этап активной разработки; в настоящее время существует много проблем совместимости с разными браузерами (и даже с разными релизами одного и того же браузера).

Создание объекта WebSocket

Чтобы общаться через протокол вебсокетов необходимо создать объект WebSocket; при его создании автоматически происходит попытка открыть соединение с сервером.

Конструктор WebSocket принимает один обязательный и один необязательный параметр:

WebSocket WebSocket(
  in DOMString url,
  in optional DOMString protocols
);

WebSocket WebSocket(
  in DOMString url,
  in optional DOMString[] protocols
);
url
URL, с которым происходит соединение; это должен быть URL вебсокет-сервера.
protocols Optional
Может быть одной строкой протокола или массивом таких строк. Эти строки используют для индикации под-протоколов; таким образом, один сервер может реализовывать несколько под-протоколов вебсокетов (к примеру, вам может потребоваться, чтобы сервер мог обрабатывать разные типы взаимодействий в зависимости от определённого под-протокола). Если вы не укажете строку протокола, то будет передана пустая строка.

В конструкторе могут возникать следующие исключения:

SECURITY_ERR
Порт, к которому проводится подключение, заблокирован.

Ошибки подключения

Если ошибка случается во время попытки подключения, то в объект WebSocket сначала посылается простое событие с именем «error» (таким образом, задействуя обработчик onerror), потом - событие CloseEvent  (таким образом, задействуя обработчик onclose) чтобы обозначить причину закрытия соединения.

As of Firefox 11 however, it is typical to receive a descriptive error message in the console from the Mozilla platform, and a closing code as defined in RFC 6455, Section 7.4 through the CloseEvent.

Examples

This simple example creates a new WebSocket, connecting to the server at ws://www.example.com/socketserver. A custom protocol of "protocolOne" is named in the request for the socket in this example, though this can be omitted.

var exampleSocket = new WebSocket("ws://www.example.com/socketserver", "protocolOne");

On return, exampleSocket.readyState is CONNECTING. The readyState will become OPEN once the connection is ready to transfer data.

If you want to open a connection and are flexible about the protocols you support, you can specify an array of protocols:

var exampleSocket = new WebSocket("ws://www.example.com/socketserver", ["protocolOne", "protocolTwo"]);

Once the connection is established (that is, readyState is OPEN), exampleSocket.protocol will tell you which protocol the server selected.

In the above examples ws has replaced http, similarly wss replaces https. Establishing a WebSocket relies on the HTTP Upgrade mechanism, so the request for the protocol upgrade is implicit when we address the HTTP server as ws://www.example.com or wss://www.example.com.

Sending data to the server

Once you've opened your connection, you can begin transmitting data to the server. To do this, simply call the WebSocket object's send() method for each message you want to send:

exampleSocket.send("Here's some text that the server is urgently awaiting!"); 

You can send data as a string, Blob, or ArrayBuffer.

Note: Prior to version 11, Firefox only supported sending data as a string.

As establishing a connection is asynchronous and prone to failure there is no guarantee that calling the send() method immediately after creating a WebSocket object will be successful. We can at least be sure that attempting to send data only takes place once a connection is established by defining an onopen handler to do the work:

exampleSocket.onopen = function (event) {
  exampleSocket.send("Here's some text that the server is urgently awaiting!"); 
};

Using JSON to transmit objects

One handy thing you can do is use JSON to send reasonably complex data to the server. For example, a chat program can interact with a server using a protocol implemented using packets of JSON-encapsulated data:

// Send text to all users through the server
function sendText() {
  // Construct a msg object containing the data the server needs to process the message from the chat client.
  var msg = {
    type: "message",
    text: document.getElementById("text").value,
    id:   clientID,
    date: Date.now()
  };

  // Send the msg object as a JSON-formatted string.
  exampleSocket.send(JSON.stringify(msg));
  
  // Blank the text input element, ready to receive the next line of text from the user.
  document.getElementById("text").value = "";
} 

Receiving messages from the server

WebSockets is an event-driven API; when messages are received, a "message" event is delivered to the onmessage function. To begin listening for incoming data, you can do something like this:

exampleSocket.onmessage = function (event) {
  console.log(event.data);
}

Receiving and interpreting JSON objects

Let's consider the chat client application first alluded to in Using JSON to transmit objects. There are assorted types of data packets the client might receive, such as:

  • Login handshake
  • Message text
  • User list updates

The code that interprets these incoming messages might look like this:

exampleSocket.onmessage = function(event) {
  var f = document.getElementById("chatbox").contentDocument;
  var text = "";
  var msg = JSON.parse(event.data);
  var time = new Date(msg.date);
  var timeStr = time.toLocaleTimeString();
  
  switch(msg.type) {
    case "id":
      clientID = msg.id;
      setUsername();
      break;
    case "username":
      text = "<b>User <em>" + msg.name + "</em> signed in at " + timeStr + "</b><br>";
      break;
    case "message":
      text = "(" + timeStr + ") <b>" + msg.name + "</b>: " + msg.text + "<br>";
      break;
    case "rejectusername":
      text = "<b>Your username has been set to <em>" + msg.name + "</em> because the name you chose is in use.</b><br>"
      break;
    case "userlist":
      var ul = "";
      for (i=0; i < msg.users.length; i++) {
        ul += msg.users[i] + "<br>";
      }
      document.getElementById("userlistbox").innerHTML = ul;
      break;
  }
  
  if (text.length) {
    f.write(text);
    document.getElementById("chatbox").contentWindow.scrollByPages(1);
  }
};

Here we use JSON.parse() to convert the JSON object back into the original object, then examine and act upon its contents.

Text data format

Text received over a WebSocket connection is in UTF-8 format.

Prior to Gecko 9.0 (Firefox 9.0 / Thunderbird 9.0 / SeaMonkey 2.6), certain non-characters in otherwise valid UTF-8 text would cause the connection to be terminated. Now Gecko permits these values.

Closing the connection

When you've finished using the WebSocket connection, call the WebSocket method close():

exampleSocket.close();

It may be helpful to examine the socket's bufferedAmount attribute before attempting to close the connection to determine if any data has yet to be transmitted on the network.

Security considerations

WebSockets should not be used in a mixed content environment; that is, you shouldn't open a non-secure WebSocket connection from a page loaded using HTTPS or vice-versa. In fact, some browsers explicitly forbid this, including Firefox 8 and later.

Document Tags and Contributors

Contributors to this page: ilya_khaustov
Last updated by: ilya_khaustov,