Protocollo Websocket HTML5

Nel precedente articolo, HTML5 e Websocket, abbiamo introdotto i websocket con un po’ di storia. In questo articolo andremo nel dettaglio di websocket, illustrando il protocollo WebSocket.

Il protocollo websocket è definito nell’RFC 6455 e mantenuto da Ian fette. Il testo originale può essere consultato su http://datatracker.ietf.org/doc/rfc6455.

Tale protocollo come già anticipato nell’articolo precedente permette una connessione persistente tra client e server consentendo uno scambio bidirezionale asincrono di messaggi tra client e server. La connessione viene iniziata dal client via http. Questo per garantire una compatibilità di questo protocollo con le infrastrutture di rete attuali, in particolare vengono così supportati gli HTTP proxy e vengono utilizzate di default le porte 80 e 443.

Nel caso in cui il server “comprende” tale richiesta, viene effettuato uno switch tra il protocollo http e il protocollo websocket (tale cambiamento viene indicato con il termine websocket handshake).

Tale protocollo è stato studiato comunque per essere indipendente da HTTP, per cui in un futuro prossimo questo stesso protocollo WebSocket potrebbe essere implementato su una porta dedicata con un handshake più semplice ed efficiente.

WebSocket URI

Sono definiti due schemi di URI la prima per le connessioni standard e l’altra per quelle protette:

          ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ]
          wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ]

          host = <host, defined in [RFC3986], Section 3.2.2>
          port = <port, defined in [RFC3986], Section 3.2.3>
          path = <path-abempty, defined in [RFC3986], Section 3.3>
          query = <query, defined in [RFC3986], Section 3.4>

Come già accennato, se non specificate, per default le porte sono le seguenti:

  • port = 80 per il caso ws:
  • port = 443 per il caso wss:

Per definizione la connessione è sicura se il protocollo iniziale corrisponde a “wss” in maniera case-insensitive.

Handshake e Comunicazione

Andando leggermente più nel dettaglio, anche se per informazioni più dettagliate e di basso livello si rimanda al documento ufficiale dell’RFC 6455, il protocollo WebSocket è formato da due parti: la parte di handshake e la parte di trasferimento dati.

Lato client, in seguito all’invocazione di una url del tipo ws://server.example.com, l’handshake si presenta nel modo seguente:

        GET /chat HTTP/1.1
        Host: server.example.com
        Upgrade: websocket
        Connection: Upgrade
        Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
        Origin: http://example.com
        Sec-WebSocket-Protocol: chat, superchat
        Sec-WebSocket-Version: 13

Da qui si vede come il client tramite HTTP chiede al server un upgrade al protocollo WebSocket, nell’esempio in versione 13. In particolare la riga:

Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==

ha lo scopo di assicurare al client che il server e tutta l’infrastruttura di rete dal client al server supportino il protocollo websocket; il server dovrà manipolare tale stringa in modo “noto” e ritornare al client una nuova stringa rielaborata. Se la rielaborazione è quella giusta il client è sicuro che il server supporta correttamente il protocollo. Nell’ipotesi che il server supporta tale protocollo, la risposta sarà del tipo:

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

Da notare che il successo di una connessione WebSocket si ottiene se il server risponde con codice HTTP 101, codice già previsto dal protocollo HTTP ma poco usato fino ad ora. Qualsiasi altro codice ritornato in tale fase indica che lo switch al protocollo WebSocket non è stato effettuato e quindi la connessione rimane su HTTP.

Dopo che la fase di handshake ha avuto esito positivo sia lato client che lato server, la connessione è stabilita e può partire la fase di trasferimento dati. Si è creato un canale di comunicazione full-duplex nel quale il client e il server in maniera indipendente possono inviare informazioni.

Messaggi e frame

Le informazioni sono inviate in unità chiamate messaggi. Ciascun messaggio è composto da uno o più frame. Osservando la composizione di un frame (vedi schema più avanti) si nota che il primo bit di esso (FIN) indica se si tratta del frame finale di un messaggio oppure no.

Ogni frame è anche caratterizzato da un tipo specifico indicato nello schema dal campo opcode; ogni messaggio è costituito da frame tutti dello stesso tipo. I tipi di frame si possono racchiudere in tre categorie principali (indicato anche il primo byte corrispondente al tipo di frame):

  • 0x81 – Frame testuali (in codifica UTF8)
  • 0x82 – Frame Binari
  • 0x88 – Frame di controllo

Infine ogni frame è caratterizzato anche da un campo indicante la lunghezza dei dati (payload length) e dalla sezione relativa ai dati veri e propri (payload data).

Il protocollo specifica anche che il client deve inviare i dati offuscati: è infatti obbligatorio che il client invii frame con il bit di mask settato e i dati offuscati secondo la chiave specificata.

Riassumiamo la struttura di un frame con lo schema seguente (ogni riga rappresenta 16 bit):

protocollo websocket - struttura di un frame
Struttura di un frame

La connessione verrà chiusa semplicemente inviando sia da parte client che server un messaggio con un apposito frame di controllo indicante il termine della connessione.