Esta tradução está incompleta. Por favor, ajude a traduzir este artigo.

Um servidor de WebSocket  é uma aplicação TCP que escuta uma porta de um servidor que segue um protocolo específico, simples assim. A tarefa de criar um servidor personalizado costuma assustar as pessoas; no entanto, pode ser fácil implementar um simples servidor WebSocket na sua plataforma de escolha. 

Um servidor WebSocket pode ser escrito em qualquer linguagem de programação server-side  que é capaz de utilizar Berkeley sockets,  tais como C(++) , ou Python, ou mesmo o PHP e o server-side JavaScript. Esse não é um tutorial em uma linguagem de programação específica, mas serve como guia para facilitar a escrita do seu próprio servidor.

Você precisará saber como o HTTP funciona e ter uma experiência média com programação.

Dependendo do suporte da linguagem, pode ser necessário o conhecimento dos soquetes TCP. O escopo deste guia é apresentar o conhecimento mínimo que você precisa para escrever um servidor WebSocket.

Leia a útlima especificação sobre WebSockets, a RFC 6455. As seções 1 e 4-7 são especialmente interessantes para implementadores de servidores. A seção 10 discute assuntos sobre segurança que você definitivamente deveria examinar antes de expor seu servidor.

Um servidor de WebSocket é explicado de maneira bem simples aqui. Servidores de WebSocket  geralmente são servidores separados e especializados (por balanceamento de carga ou outars razões práticas), so you will often use a reverse proxy (such as a regular HTTP server) to detect WebSocket handshakes, pre-process them, and send those clients to a real WebSocket server. This means that you don't have to bloat your server code with cookie and authentication handlers (for example).

O Handshake ("aperto de mão") do WebSocket

Primeiro de tudo, o servidor deve ouvir as conexões socket recebidas usando um socket TCP padrão. Dependendo da sua plataforma, isso pode já ter sido tratado previamente. Por exemplo, vamos assumir que seu servidor está ouvindo example.com, porta 8000, e seu servidor soket responde às requisições  GET em/chat.

Aviso: O servidor pode ouvir qualquer porta que escolher, mas se escolher qualquer porta diferente de 80 e 443, podem ocorrer problemas relacionados aos firewalls e/ou proxies. Conexões na porta 443 tendem a ter mais sucesso com mais frequência, isso requer uma conexão segura (TLS/SSL). Também, note que a maioria dos browsers (notavelmente o  Firefox 8+) não permite conexões de servidores WebSocket de páginas seguras.

O handshake é a "Web" no WebSockets. É a ponte do HTTP para o Websocket. No handshake, detalhes da conexão são negociados, e qualquer uma das partes pode voltar antes da conclusão se os termos são desfavoráveis. O servidor deve ser cuidadoso para entender tudo que o cliente perguntar,  caso contrário, serão introduzidas questões de segurança.

Requisição Handshake do Cliente

Mesmo que você esteja construindo um servidor, um cliente ainda precisa iniciar o processo de handshake do WebSocket. Então você deve saber como interpretar a requisição do cliente. O cliente vai enviar uma requisição HTTP padrão que é parecida com isso (a versão do HTTP deve ser 1.1 ou maior, e o método deve ser um GET):

GET /chat HTTP/1.1
Host: example.com:8000
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

O cliente pode solicitar extensões e/ou subprotocolos aqui; veja Miscellaneous para mais detalhes. Também, cabeçalhos comuns como User-Agent, RefererCookie, ou cabeçalhos de autenticação poderiam estar bem aqui. Faça o que você quiser com eles; eles não pertencem diretamente ao WebSocket. Também é seguro ignora-los. Em muitas configurações comuns, um proxy reverso ja tratou deles.

Se qualquer cabeçalho não foi entendido ou conter um valor incorreto, o servidor deve enviar um erro "400 Bad Request" e fechar o socket imediatamente. É comum, também dar a razão pelo qual o handshake falhou no body da resposta do HTTP, mas muitas mensages nunca serão mostradas (os browsers não mostram isso). Se o servidor não reconhecer a versão do WebSockets, deve enviar um cabeçalho Sec-WebSocket-Version que contenha a(s) versão(versões) que o mesmo entenda. (Esse guia explica o v13, o mais novo). Agora, vamos continuar para o cabeçalho mais curioso, o Sec-WebSocket-Key.

Dica: Todos os browsers  vão enviar um Origin header. Você pode usar esse cabeçalho por segurança (verifique pelo de mesma origem, whitelisting/ blacklisting, etc.) e envie uma 403 Forbidden se você não gostou do que viu. Sobretanto, fique ciente que os agentes non-browser podem apenas enviar uma falsa Origin. Muitas aplicações vão rejeitar requisições sem cabeçalho.

Dica: A request-uri (/chat aqui) não tem significado definido na especificação. Muitas pessoas utilizam habilmente para que servidores lidem com muiltíplas aplicações WebSocket. Por exemplo, example.com/chat deve invocar um app de chat com multiplos usuários, enquanto /game no mesmo servidor poderia invocar um jogo multiplayer.

Nota: Regular HTTP status codes podem apenas ser usados antes do handshake. Depois que o handshake sucede, você deve usar um conjunto de códigos diferentes (definidos na seção 7.4 da especificação).

Resposta Handshake do Servidor

Quando pegar uma requisição, o servidor deve enviar uma odd-looking (mas ainda HTTP) resposta que parece com isso (lembre-se cada final do cabeçalho com \r\n e coloque um \r\n extra depois do último):

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

Adicionalmente, o servidor pode decidir sobre os pedidos de extensão/subprotocolo aqui; veja Miscellaneous para mais detalhes. O Sec-WebSocket-Accept é uma parte interessante. O servidor deve deriva-lo do Sec-WebSocket-Key que o cliente enviou. Para obte-lo, concatenate o Sec-WebSocket-Key do cliente e "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" juntos (isso é uma "magic string"), pegue o SHA-1 hash do resultado, e retorne o codigo base64 do hash.

FYI: Esse processo, aparentemente complicado existe para que seja óbvio para o cliente, seja ou não que o servidor suporte WebSockets. Isso é importante por causa de problemas com segurança que aparecem se o servidor aceita a conexão WeSocket mas interpreta que os dados são uma requisição HTTP.

Então se a chave foi "dGhlIHNhbXBsZSBub25jZQ==", a aceita será "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=". Uma vez que o servidor envie estes cabeçalhos, o handshake esta completo e você pode começar a trocar dados!

O servidor pode enviar outros cabeçalhos como Set-Cookie, ou perguntar por autenticação ou redirecionar via outros códigos de status, antes enviando  a resposta do handshake.

Mantendo caminhos dos clientes

This doesn't directly relate to the WebSocket protocol, but it's worth mentioning here: your server will have to keep track of clients' sockets so you don't keep handshaking again with clients who have already completed the handshake. The same client IP address can try to connect multiple times (but the server can deny them if they attempt too many connections in order to save itself from Denial-of-Service attacks).

Exchanging Data Frames

Either the client or the server can choose to send a message at any time — that's the magic of WebSockets. However, extracting information from these so-called "frames" of data is a not-so-magical experience. Although all frames follow the same specific format, data going from the client to the server is masked using XOR encryption (with a 32-bit key). Section 5 of the specification describes this in detail.

Format

Each data frame (from the client to the server or vice-versa) follows this same format:

Frame format:  
​​
      0                   1                   2                   3
      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
     +-+-+-+-+-------+-+-------------+-------------------------------+
     |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
     |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
     |N|V|V|V|       |S|             |   (if payload len==126/127)   |
     | |1|2|3|       |K|             |                               |
     +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
     |     Extended payload length continued, if payload len == 127  |
     + - - - - - - - - - - - - - - - +-------------------------------+
     |                               |Masking-key, if MASK set to 1  |
     +-------------------------------+-------------------------------+
     | Masking-key (continued)       |          Payload Data         |
     +-------------------------------- - - - - - - - - - - - - - - - +
     :                     Payload Data continued ...                :
     + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
     |                     Payload Data continued ...                |
     +---------------------------------------------------------------+

The MASK bit simply tells whether the message is encoded. Messages from the client must be masked, so your server should expect this to be 1. (In fact, section 5.1 of the spec says that your server must disconnect from a client if that client sends an unmasked message.) When sending a frame back to the client, do not mask it and do not set the mask bit. We'll explain masking later. Note: You have to mask messages even when using a secure socket.RSV1-3 can be ignored, they are for extensions.

The opcode field defines how to interpret the payload data: 0x0 for continuation, 0x1 for text (which is always encoded in UTF-8), 0x2 for binary, and other so-called "control codes" that will be discussed later. In this version of WebSockets, 0x3 to 0x7 and 0xB to 0xF have no meaning.

The FIN bit tells whether this is the last message in a series. If it's 0, then the server will keep listening for more parts of the message; otherwise, the server should consider the message delivered. More on this later.

Decoding Payload Length

To read the payload data, you must know when to stop reading. That's why the payload length is important to know. Unfortunately, this is somewhat complicated. To read it, follow these steps:

  1. Read bits 9-15 (inclusive) and interpret that as an unsigned integer. If it's 125 or less, then that's the length; you're done. If it's 126, go to step 2. If it's 127, go to step 3.
  2. Read the next 16 bits and interpret those as an unsigned integer. You're done.
  3. Read the next 64 bits and interpret those as an unsigned integer (The most significant bit MUST be 0). You're done.

Reading and Unmasking the Data

If the MASK bit was set (and it should be, for client-to-server messages), read the next 4 octets (32 bits); this is the masking key. Once the payload length and masking key is decoded, you can go ahead and read that number of bytes from the socket. Let's call the data ENCODED, and the key MASK. To get DECODED, loop through the octets (bytes a.k.a. characters for text data) of ENCODED and XOR the octet with the (i modulo 4)th octet of MASK. In pseudo-code (that happens to be valid JavaScript):

var DECODED = "";
for (var i = 0; i < ENCODED.length; i++) {
    DECODED[i] = ENCODED[i] ^ MASK[i % 4];
}

Now you can figure out what DECODED means depending on your application.

Message Fragmentation

The FIN and opcode fields work together to send a message split up into separate frames.  This is called message fragmentation. Fragmentation is only available on opcodes 0x0 to 0x2.

Recall that the opcode tells what a frame is meant to do. If it's 0x1, the payload is text. If it's 0x2, the payload is binary data. However, if it's 0x0, the frame is a continuation frame. This means the server should concatenate the frame's payload to the last frame it received from that client. Here is a rough sketch, in which a server reacts to a client sending text messages. The first message is sent in a single frame, while the second message is sent across three frames. FIN and opcode details are shown only for the client:

Client: FIN=1, opcode=0x1, msg="hello"
Server: (process complete message immediately) Hi.
Client: FIN=0, opcode=0x1, msg="and a"
Server: (listening, new message containing text started)
Client: FIN=0, opcode=0x0, msg="happy new"
Server: (listening, payload concatenated to previous message)
Client: FIN=1, opcode=0x0, msg="year!"
Server: (process complete message) Happy new year to you too!

Notice the first frame contains an entire message (has FIN=1 and opcode!=0x0), so the server can process or respond as it sees fit. The second frame sent by the client has a text payload (opcode=0x1), but the entire message has not arrived yet (FIN=0). All remaining parts of that message are sent with continuation frames (opcode=0x0), and the final frame of the message is marked by FIN=1. Section 5.4 of the spec describes message fragmentation.

Pings and Pongs: The Heartbeat of WebSockets

At any point after the handshake, either the client or the server can choose to send a ping to the other party. When the ping is received, the recipient must send back a pong as soon as possible. You can use this to make sure that the client is still connected, for example.

A ping or pong is just a regular frame, but it's a control frame. Pings have an opcode of 0x9, and pongs have an opcode of 0xA. When you get a ping, send back a pong with the exact same Payload Data as the ping (for pings and pongs, the max payload length is 125). You might also get a pong without ever sending a ping; ignore this if it happens.

If you have gotten more than one ping before you get the chance to send a pong, you only send one pong.

Fechando a conexão

To close a connection either the client or server can send a control frame with data containing a specified control sequence to begin the closing handshake (detailed in Section 5.5.1). Upon receiving such a frame, the other peer sends a Close frame in response. The first peer then closes the connection. Any further data received after closing of connection is then discarded. 

Miscellaneous

WebSocket codes, extensions, subprotocols, etc. are registered at the IANA WebSocket Protocol Registry.

WebSocket extensions and subprotocols are negotiated via headers during the handshake. Sometimes extensions and subprotocols seem too similar to be different things, but there is a clear distinction. Extensions control the WebSocket frame and modify the payload, while subprotocols structure the WebSocket payload and never modify anything. Extensions are optional and generalized (like compression); subprotocols are mandatory and localized (like ones for chat and for MMORPG games).

Extensions

This section needs expansion. Please edit if you are equipped to do so.

Think of an extension as compressing a file before e-mailing it to someone. Whatever you do, you're sending the same data in different forms. The recipient will eventually be able to get the same data as your local copy, but it is sent differently. That's what an extension does. WebSockets defines a protocol and a simple way to send data, but an extension such as compression could allow sending the same data but in a shorter format.

Extensions are explained in sections 5.8, 9, 11.3.2, and 11.4 of the spec.

TODO

Subprotocols

Think of a subprotocol as a custom XML schema or doctype declaration. You're still using XML and its syntax, but you're additionally restricted by a structure you agreed on. WebSocket subprotocols are just like that. They do not introduce anything fancy, they just establish structure. Like a doctype or schema, both parties must agree on the subprotocol; unlike a doctype or schema, the subprotocol is implemented on the server and cannot be externally refered to by the client.

Subprotocols are explained in sections 1.9, 4.2, 11.3.4, and 11.5 of the spec.

A client has to ask for a specific subprotocol. To do so, it will send something like this as part of the original handshake:

GET /chat HTTP/1.1
...
Sec-WebSocket-Protocol: soap, wamp

ou, equivalentemente:

...
Sec-WebSocket-Protocol: soap
Sec-WebSocket-Protocol: wamp

Now the server must pick one of the protocols that the client suggested and it supports. If there are more than one, send the first one the client sent. Imagine our server can use both soap and wamp. Then, in the response handshake, it'll send:

Sec-WebSocket-Protocol: soap

The server can't send more than one Sec-Websocket-Protocol header.
If the server doesn't want to use any subprotocol, it shouldn't send any Sec-WebSocket-Protocol header. Sending a blank header is incorrect.
The client may close the connection if it doesn't get the subprotocol it wants.

If you want your server to obey certain subprotocols, then naturally you'll need extra code on the server. Let's imagine we're using a subprotocol json. In this subprotocol, all data is passed as JSON. If the client solicits this protocol and the server wants to use it, the server will need to have a JSON parser. Practically speaking, this will be part of a library, but the server will need to pass the data around.

Tip: Para evitar o conflito de nomes, recomenda-se que o subprotocolo seja nomeado como parte da string do domínio. Se você está desenvolvendo um aplicativo de bate-papo personalizado que usa um formato proprietário exclusivo da Exemplo Inc., então você pode usar isso: Sec-WebSocket-Protocol: chat.example.com. Note que isso não é necessário, é apenas uma convenção opcional, e você pode usar qualquer string que desejar.

Relacionado

Etiquetas do documento e colaboradores

Colaboradores desta página: jhserodio, Martinho9818, UzumakiArtanis, wallacemaxters
Última atualização por: jhserodio,