WebRTC per il trasferimento file: tutorial browser-to-browser
Trasferisci file direttamente tra browser usando WebRTC. Data channel, segnalazione e configurazione della connessione peer per la condivisione in tempo reale.
WebRTC sposta i byte direttamente tra due browser usando un RTCDataChannel, senza alcun server nel percorso dei dati dopo l'handshake iniziale. I componenti necessari: un canale di segnalazione (WebSocket o qualsiasi piccolo relay) per scambiare offerte SDP e candidati ICE, server STUN per la scoperta NAT, un fallback TURN per NAT simmetrici, e un data channel configurato per la consegna ordinata e affidabile. Una volta che la connessione peer è attiva, si usa channel.send() per inviare blocchi da 16-256 KB e si osservano i byte fluire attraverso un'associazione SCTP cifrata con DTLS 1.2 a velocità prossima alla linea di rete.
Come WebRTC ottiene davvero la connessione peer-to-peer
WebRTC non è magia — è ICE più SDP più DTLS più SCTP impilati insieme. Il mittente crea un RTCPeerConnection, apre un data channel, genera un'offerta SDP e la invia al destinatario tramite il canale di segnalazione. Il destinatario risponde. Entrambi i lati scambiano poi i candidati ICE (IP locale, IP riflessivo tramite STUN, IP relay tramite TURN) finché trovano un percorso funzionante. DTLS 1.2 stringe la mano end-to-end, SCTP ci gira sopra per lo streaming affidabile, e i byte iniziano a fluire.
La crittografia è obbligatoria e integrata. Non puoi rinunciarvi. Questo è un vantaggio di sicurezza significativo — a differenza dei WebSocket, non devi ricordarti di avvolgere nulla con crypto a livello applicativo per proteggerti dagli intercettatori di rete. Per una vera crittografia end-to-end contro il tuo stesso server di segnalazione, aggiungi un secondo livello di AES-256-GCM, perché un server di segnalazione malintenzionato potrebbe sostituire il proprio certificato DTLS.
Segnalazione: la parte che WebRTC non definisce
WebRTC lascia deliberatamente la segnalazione a te. Un WebSocket sul tuo server va bene; così come un codice stanza condiviso pubblicato su un database Firebase realtime, o persino stringhe SDP incollate manualmente. L'importante è che entrambi i peer alla fine si scambino un'offerta, una risposta e un flusso di candidati ICE.
Server di segnalazione minimale in Node:
const rooms = new Map();
wss.on('connection', (ws) => {
ws.on('message', (raw) => {
const msg = JSON.parse(raw);
if (msg.type === 'join') {
const room = rooms.get(msg.room) ?? new Set();
room.add(ws); rooms.set(msg.room, room);
} else {
for (const peer of rooms.get(msg.room) ?? []) {
if (peer !== ws) peer.send(raw);
}
}
});
});
Meno di 20 righe. Il server non vede mai i byte del file — solo metadati SDP e ICE. Puoi ospitarlo su un VPS da 5 € o su Cloudflare Workers e servire centinaia di trasferimenti concorrenti.
Attivare la connessione peer
Crea la connessione con lo STUN pubblico di Google più un fallback TURN:
const pc = new RTCPeerConnection({
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'turn:turn.esempio.com:3478',
username: 'utente', credential: 'password' }
]
});
Circa il 15-25% delle connessioni residenziali si trova dietro NAT simmetrici che STUN non riesce a superare, quindi TURN non è opzionale per un servizio in produzione. Esegui coturn su un VPS con banda sufficiente per gestire il traffico relay, o paga per un provider TURN gestito come Xirsys o Twilio.
Apri il data channel sul lato dell'offerente prima di creare l'offerta:
const channel = pc.createDataChannel('file', {
ordered: true, maxRetransmits: null
});
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
signaling.send({ type: 'offer', sdp: offer.sdp });
Ordinato + retry illimitati offre affidabilità equivalente a TCP. La modalità non ordinata è più veloce ma richiede il riassemblaggio a livello applicativo.
Suddividere il file per il canale
SCTP ha un limite pratico di 256 KB per messaggio, e i browser più vecchi hanno problemi sopra i 16 KB. Un default sicuro è blocchi da 16 KB letti dal file tramite File.slice():
const chunkSize = 16 * 1024;
let offset = 0;
channel.bufferedAmountLowThreshold = 1024 * 1024;
function sendNext() {
while (offset < file.size && channel.bufferedAmount < 4 * 1024 * 1024) {
const chunk = file.slice(offset, offset + chunkSize);
chunk.arrayBuffer().then((buf) => channel.send(buf));
offset += chunkSize;
}
}
channel.onbufferedamountlow = sendNext;
sendNext();
Il watermark bufferedAmount impedisce di accodare gigabyte nel buffer di invio SCTP ed esaurire la memoria. Quando il buffer scende sotto 1 MB, riempilo fino a 4 MB. Questo offre un throughput vicino a 40-80 MB/s sulle reti locali e 5-20 MB/s sulla banda larga residenziale tipica.
Ricevere e scrivere i byte su disco
Sul lato rispondente, ascolta il canale in arrivo:
pc.ondatachannel = ({ channel }) => {
const chunks = [];
let received = 0;
channel.onmessage = ({ data }) => {
chunks.push(data);
received += data.byteLength;
updateProgress(received);
if (received === expectedSize) finish(chunks);
};
};
Per file superiori a 500 MB, non accumulare in RAM. Usa l'API File System Access per trasmettere direttamente su disco:
const handle = await window.showSaveFilePicker({ suggestedName: fileName });
const writable = await handle.createWritable();
channel.onmessage = async ({ data }) => writable.write(data);
Firefox e Safari non supportano ancora showSaveFilePicker, quindi usa un fallback con Blob + URL.createObjectURL per quei browser, limitato a 2 GB.
Inviare i metadati del file prima dei byte
Il destinatario deve conoscere il nome, la dimensione e il tipo MIME del file prima che inizi il flusso di byte. Usa un piccolo handshake JSON sul data channel:
channel.send(JSON.stringify({
type: 'metadata', name: file.name,
size: file.size, mime: file.type, sha256: fileHash
}));
Poi passa alla modalità binaria. Il destinatario commuta in base al fatto che data sia una stringa o un ArrayBuffer. Includi uno SHA-256 del file per la verifica dell'integrità post-trasferimento, e facoltativamente un'impronta digitale della chiave se stai aggiungendo AES-256-GCM a livello applicativo.
Gestire gli errori di connessione
I data channel WebRTC falliscono in tre modi: ICE non si completa mai (NAT, firewall bloccano), l'handshake DTLS fallisce (sfasamento dell'orologio, problemi di certificato), o la connessione cade a metà trasferimento (laptop in sleep, cambio di rete). Ascolta pc.oniceconnectionstatechange e agisci su 'failed' o 'disconnected'. Chrome mantiene 'disconnected' per alcuni secondi prima di passare a 'failed'; Safari è meno paziente.
In caso di errore a metà trasferimento, riavvia ICE senza ricostruire l'intera connessione:
await pc.restartIce();
const offer = await pc.createOffer({ iceRestart: true });
// rinvia tramite la segnalazione
Se il riavvio fallisce, passa all'upload ripristinabile tramite server — un'architettura ibrida P2P + server. Alcuni strumenti di trasferimento WebRTC come Wormhole e justbeamit usano questo schema perché gestisce il 15% delle condizioni di rete dove il P2P puro semplicemente non funziona.
Aggiungere la crittografia end-to-end contro il proprio server
Il DTLS integrato di WebRTC protegge dagli aggressori di rete ma non da un server di segnalazione compromesso. Per una vera crittografia end-to-end, fai generare a entrambi i peer una coppia di chiavi ECDH P-256, scambia le chiavi pubbliche tramite un codice fuori banda breve (QR o passphrase di 6 parole), deriva un segreto condiviso tramite HKDF-SHA256, e cifra ogni messaggio del data channel con AES-256-GCM prima di chiamare send. In questo modo, anche se il server di segnalazione scambia certificati DTLS, non può leggere i tuoi file.
HexaTransfer usa un'architettura ibrida — testo cifrato memorizzato lato server con AES-256-GCM lato client, invece che P2P — scambiando la direttezza per la condivisione tollerante all'offline. Prova su https://hexatransfer.com — gratuito, senza account, massimo 10 GB.
Dove WebRTC vince e dove perde
Il trasferimento file WebRTC eccelle quando entrambi i peer sono online contemporaneamente, quando la privacy dal proprio server è importante, e quando i file sono abbastanza grandi (100 MB+) che i costi della banda relay sarebbero significativi. Perde quando gli utenti vogliono inviare e andarsene, quando i destinatari aprono il link ore dopo, o quando i destinatari si trovano su reti aziendali restrittive che bloccano STUN e TURN. Per uno strumento di trasferimento generico, il P2P puro copre comodamente circa il 60% dei casi d'uso. L'altro 40% ha bisogno di un fallback con supporto server — motivo per cui quasi ogni prodotto di "trasferimento file P2P" ha un relay da qualche parte nell'architettura.
Invia file di grandi dimensioni in modo sicuro con crittografia end-to-end
Trasferisci file fino a 10 GB gratuitamente con crittografia end-to-end. Nessun account necessario. I tuoi file vengono crittografati nel browser prima del caricamento — nessun altro può leggerli.
Invia un file