Le applicazioni web standard sono in genere limitate a protocolli di comunicazione specifici come HTTP e API come WebSocket e WebRTC. Sebbene siano potenti, sono progettate per essere strettamente vincolate per prevenire abusi. Non possono stabilire connessioni TCP o UDP non elaborate, il che limita la capacità delle app web di comunicare con sistemi legacy o dispositivi hardware che utilizzano protocolli non web propri. Ad esempio, potresti voler creare un client SSH basato sul web, connetterti a una stampante locale o gestire una flotta di dispositivi IoT. In passato, questo richiedeva plug-in del browser o applicazioni helper native.
L'API Direct Sockets risolve questa limitazione consentendo alle app web isolate (IWA) di stabilire connessioni TCP e UDP dirette senza un server di inoltro. Grazie a misure di sicurezza aggiuntive, come la rigorosa Content Security Policy (CSP) e l'isolamento tra origini diverse, questa API può essere esposta in modo sicuro.
Casi d'uso
Quando conviene utilizzare Direct Sockets anziché WebSocket standard?
- IoT e smart device:comunicazione con hardware che utilizza TCP/UDP non elaborato anziché HTTP.
- Sistemi legacy:connessione a server di posta meno recenti (SMTP/IMAP), server di chat IRC o stampanti.
- Terminali e desktop remoto:implementazione di client SSH, Telnet o RDP.
- Sistemi P2P:implementazione di tabelle hash distribuite (DHT) o strumenti di collaborazione resilienti (come IPFS).
- Trasmissione di contenuti multimediali:utilizzo di UDP per lo streaming di contenuti a più endpoint contemporaneamente (multicast), consentendo casi d'uso come la riproduzione coordinata di video su una rete di chioschi retail.
- Funzionalità di server e listener:configurazione di IWA in modo che funga da
endpoint di ricezione per le connessioni TCP in entrata o i datagrammi UDP utilizzando
TCPServerSocketoUDPSocketvincolato.
Prerequisiti per Direct Sockets
Prima di utilizzare Direct Sockets, devi configurare un'IWA funzionale. A questo punto puoi integrare Direct Sockets nelle tue pagine.
Aggiungi policy di autorizzazione
Per utilizzare Direct Sockets, devi configurare l'oggetto permissions_policy nel
manifest IWA. Devi aggiungere la chiave direct-sockets per abilitare esplicitamente
l'API. Inoltre, devi includere il tasto cross-origin-isolated. Questa
chiave non è specifica per Direct Sockets, ma è obbligatoria per tutte le IWA e
determina se il documento può accedere alle API che richiedono l'isolamento
cross-origin.
{
"permissions_policy": {
"direct-sockets": ["self"],
"cross-origin-isolated": ["self"]
}
}
La chiave direct-sockets determina se le chiamate a new TCPSocket(...), new
TCPServerSocket(...) o new UDPSocket(...) sono consentite. Se questo criterio non viene
impostato, questi costruttori verranno rifiutati immediatamente con un NotAllowedError.
Implementare TCPSocket
Le applicazioni possono richiedere una connessione TCP creando un'istanza TCPSocket.
Aprire una connessione
Per aprire una connessione, utilizza l'operatore new e await la promessa aperta.
Il costruttore TCPSocket avvia la connessione utilizzando remoteAddress e remotePort specificati.
const remoteAddress = 'example.com';
const remotePort = 7;
// Configure options like keepAlive or buffering
const options = {
keepAlive: true,
keepAliveDelay: 720000
};
let tcpSocket = new TCPSocket(remoteAddress, remotePort, options);
// Wait for the connection to be established
let { readable, writable } = await tcpSocket.opened;
L'oggetto di configurazione facoltativo consente un controllo granulare della rete; in
questo caso specifico, keepAliveDelay è impostato su 720.000 millisecondi per mantenere
la connessione durante i periodi di inattività. Gli sviluppatori possono anche configurare altre proprietà qui, ad esempio noDelay, che disattiva l'algoritmo di Nagle per impedire al sistema di raggruppare piccoli pacchetti, riducendo potenzialmente la latenza, oppure sendBufferSize e receiveBufferSize per gestire la velocità effettiva.
Nell'ultima parte dello snippet precedente, il codice attende la promessa aperta,
che viene risolta solo al termine dell'handshake, restituendo un
oggetto TCPSocketOpenInfo contenente i flussi leggibili e scrivibili necessari
per la trasmissione dei dati.
Lettura e scrittura
Una volta aperto il socket, interagisci con esso utilizzando le interfacce API Streams standard.
- Scrittura:il flusso scrivibile accetta un
BufferSource(ad esempio unArrayBuffer). - Lettura:lo stream leggibile produce dati
Uint8Array.
// Writing data
const writer = writable.getWriter();
const encoder = new TextEncoder();
await writer.write(encoder.encode("Hello Server"));
// Call when done
writer.releaseLock();
// Reading data
const reader = readable.getReader();
const { value, done } = await reader.read();
if (!done) {
const decoder = new TextDecoder();
console.log("Received:", decoder.decode(value));
}
// Call when done
reader.releaseLock();
Lettura ottimizzata con BYOB
Per le applicazioni ad alte prestazioni in cui la gestione dell'allocazione della memoria è fondamentale, l'API supporta la lettura "Bring Your Own Buffer" (BYOB). Anziché lasciare che il browser allochi un nuovo buffer per ogni blocco di dati ricevuto, puoi passare un buffer preallocato al lettore. In questo modo, il sovraccarico della garbage collection viene ridotto scrivendo i dati direttamente nella memoria esistente.
// 1. Get a BYOB reader explicitly
const reader = readable.getReader({ mode: 'byob' });
// 2. Allocate a reusable buffer (e.g., 4KB)
let buffer = new Uint8Array(4096);
// 3. Read directly into the existing buffer
const { value, done } = await reader.read(buffer);
if (!done) {
// 'value' is a view of the data written directly into your buffer
console.log("Bytes received:", value.byteLength);
}
reader.releaseLock();
Implementa UDPSocket
La classe UDPSocket consente la comunicazione UDP. Funziona in due modalità distinte
a seconda di come configuri le opzioni.
Modalità connessa
In questa modalità, il socket comunica con una singola destinazione specifica. Questa funzionalità è utile per le attività client-server standard.
// Connect to a specific remote host
let udpSocket = new UDPSocket({
remoteAddress: 'example.com',
remotePort: 7 });
let { readable, writable } = await udpSocket.opened;
Modalità vincolata
In questa modalità, il socket è associato a un endpoint IP locale. Può ricevere datagrammi da origini arbitrarie e inviarli a destinazioni arbitrarie. Questo viene spesso utilizzato per protocolli di rilevamento locali o comportamenti simili a quelli di un server.
// Bind to all interfaces (IPv6)
let udpSocket = new UDPSocket({
localAddress: '::'
// omitting localPort lets the OS pick one
});
// localPort will tell you the OS-selected port.
let { readable, writable, localPort } = await udpSocket.opened;
Gestire i messaggi UDP
A differenza del flusso di byte TCP, i flussi UDP gestiscono oggetti UDPMessage, che
contengono i dati e le informazioni sull'indirizzo remoto. Il seguente codice mostra
come gestire le operazioni di input/output quando si utilizza un UDPSocket in "modalità vincolata".
// Writing (Bound Mode requires specifying destination)
const writer = writable.getWriter();
await writer.write({
data: new TextEncoder().encode("Ping"),
remoteAddress: '192.168.1.50',
remotePort: 8080
});
// Reading
const reader = readable.getReader();
const { value } = await reader.read();
// value contains: { data, remoteAddress, remotePort }
console.log(`Received from ${value.remoteAddress}:`, value.data);
A differenza della "modalità connessa", in cui il socket è bloccato su un peer specifico, la modalità
associata consente al socket di comunicare con destinazioni arbitrarie. Di conseguenza,
quando scrivi dati nello stream scrivibile, devi passare un oggetto UDPMessage
che specifica esplicitamente remoteAddress e remotePort per ogni pacchetto,
indicando al socket esattamente dove instradare quel datagramma specifico. Analogamente,
quando si legge dallo stream leggibile, il valore restituito include non solo il
payload di dati, ma anche remoteAddress e remotePort del mittente, consentendo
alla tua applicazione di identificare l'origine di ogni pacchetto in entrata.
Nota:quando utilizzi UDPSocket in "modalità connessa", il socket è effettivamente
bloccato a un peer specifico, semplificando il processo di I/O. In questa modalità, le proprietà
remoteAddress e remotePort non hanno effetto durante la scrittura,
in quanto la destinazione è già fissa. Analogamente, quando leggi i messaggi, queste proprietà restituiranno null, poiché l'origine è garantita essere il peer connesso.
Supporto del multicast
Per casi d'uso come la sincronizzazione della riproduzione video su più chioschi o l'implementazione del rilevamento di dispositivi locali (ad esempio mDNS), Direct Sockets supporta UDP multicast. In questo modo, i messaggi possono essere inviati a un indirizzo "di gruppo" e ricevuti da tutti gli abbonati della rete, anziché da un singolo peer specifico.
Autorizzazioni multicast
Per utilizzare le funzionalità di multicast, devi aggiungere l'autorizzazione
direct-sockets-multicast al manifest dell'IWA. Questa autorizzazione è diversa
dall'autorizzazione standard per i socket diretti ed è necessaria perché il multicast
viene utilizzato solo nelle reti private.
{
"permissions_policy": {
"direct-sockets": ["self"],
"direct-sockets-multicast": ["self"],
"direct-sockets-private": ["self"],
"cross-origin-isolated": ["self"]
}
}
Inviare datagrammi multicast
L'invio a un gruppo multicast è molto simile alla "modalità connessa" UDP standard, con l'aggiunta di opzioni specifiche per controllare il comportamento dei pacchetti.
const MULTICAST_GROUP = '239.0.0.1';
const PORT = 12345;
const socket = new UDPSocket({
remoteAddress: MULTICAST_GROUP,
remotePort: PORT,
// Time To Live: How many router hops the packet can survive (default: 1)
multicastTimeToLive: 5,
// Loopback: Whether to receive your own packets (default: true)
multicastLoopback: true
});
const { writable } = await socket.opened;
// Write to the stream as usual...
Ricevere datagrammi multicast
Per ricevere il traffico multicast, devi aprire un UDPSocket in "modalità vincolata"
(in genere il binding a 0.0.0.0 o ::) e poi unirti a un gruppo specifico utilizzando
MulticastController. Puoi anche utilizzare l'opzione multicastAllowAddressSharing (simile a SO_REUSEADDR su Unix), essenziale per i protocolli di rilevamento
dei dispositivi in cui più applicazioni sullo stesso dispositivo devono
ascoltare la stessa porta.
const socket = new UDPSocket({
localAddress: '0.0.0.0', // Listen on all interfaces
localPort: 12345,
multicastAllowAddressSharing: true // Allow multiple applications to bind to the same address / port pair.
});
// The open info contains the MulticastController
const { readable, multicastController } = await socket.opened;
// Join the group to start receiving packets
await multicastController.joinGroup('239.0.0.1');
const reader = readable.getReader();
// Read the stream...
const { value } = await reader.read();
console.log(`Received multicast from ${value.remoteAddress}`);
// When finished, you can leave the group (this is an optional, but recommended practice)
await multicastController.leaveGroup('239.0.0.1');
Crea un server
L'API supporta anche TCPServerSocket per accettare connessioni TCP in entrata,
consentendo effettivamente alla tua IWA di fungere da server locale. Il seguente codice
mostra come stabilire un server TCP utilizzando l'interfaccia TCPServerSocket.
// Listen on all interfaces (IPv6)
let tcpServerSocket = new TCPServerSocket('::');
// Accept connections via the readable stream
let { readable } = await tcpServerSocket.opened;
let reader = readable.getReader();
// Wait for a client to connect
let { value: clientSocket } = await reader.read();
// 'clientSocket' is a standard TCPSocket you can now read/write to
Se crei un'istanza della classe con l'indirizzo '::', il server si associa a tutte le interfacce di rete IPv6 disponibili per rilevare i tentativi in entrata. A differenza delle API server tradizionali basate su callback, questa API utilizza il pattern dell'API Streams del web: le connessioni in entrata vengono fornite come ReadableStream. Quando chiami
reader.read(), l'applicazione attende e accetta la connessione successiva dalla
coda, risolvendo un valore che è un'istanza TCPSocket completamente funzionale
pronta per la comunicazione bidirezionale con quel client specifico.
Eseguire il debug di Direct Sockets con Chrome DevTools
A partire da Chrome 138, puoi eseguire il debug del traffico Direct Sockets direttamente nel riquadro
Rete di Chrome DevTools, eliminando la necessità di sniffer di pacchetti
esterni. Questi strumenti ti consentono di monitorare le connessioni TCPSocket e il traffico UDPSocket (in modalità associata e connessa) insieme alle richieste HTTP standard.
Per esaminare l'attività di rete della tua app:
- Apri il riquadro Rete in Chrome DevTools.
- Individua e seleziona la connessione socket nella tabella delle richieste.
- Apri la scheda Messaggi per visualizzare un log di tutti i dati trasmessi e ricevuti.

Questa visualizzazione fornisce un visualizzatore esadecimale, che consente di ispezionare il payload binario non elaborato dei messaggi TCP e UDP, garantendo che l'implementazione del protocollo sia perfetta a livello di byte.
Demo
IWA Kitchen Sink include un'app con più schede, ognuna delle quali mostra un'API IWA diversa, come Direct Sockets, Controlled Frame e altro ancora.
In alternativa, la demo del client telnet contiene un'app web isolata che consente all'utente di connettersi a un server TCP/IP tramite un terminale interattivo. In altre parole, un client Telnet.
Conclusione
L'API Direct Sockets colma una lacuna funzionale critica consentendo alle applicazioni web di gestire protocolli di rete non elaborati che in precedenza era impossibile supportare senza wrapper nativi. Va oltre la semplice connettività client: con
TCPServerSocket, le applicazioni possono ascoltare le connessioni in entrata, mentre
UDPSocket offre modalità flessibili sia per la comunicazione peer-to-peer sia per l'individuazione
della rete locale.
Esposizione di queste funzionalità TCP e UDP non elaborate tramite la moderna API Streams, ora puoi creare implementazioni complete di protocolli legacy, come SSH, RDP o standard IoT personalizzati, direttamente in JavaScript. Poiché questa API concede l'accesso alla rete di basso livello, ha implicazioni significative per la sicurezza. Pertanto, è limitato alle app web isolate (IWA), garantendo che questo potere venga concesso solo ad applicazioni attendibili e installate esplicitamente che applicano rigidi criteri di sicurezza. Questo equilibrio ti consente di creare applicazioni potenti e incentrate sul dispositivo, mantenendo al contempo la sicurezza che gli utenti si aspettano dalla piattaforma web.