A GitHub repo is at this link for a full-functioning version of this, with additional comments and features:
https://github.com/benwills/sending-and-receiving-binary-files-via-websockets
Because the WebSockets protocol doesn’t have a set of custom headers It does have headers, but they are compact, predefined, and not customizable. like HTTP headers, you can’t simply send a file via WebSocket. Well, you can, but now your server is receiving a file with no information about its filename, mime type, etc.
To send a file where you’d like to also know details about it on the server side, you have to/get to design your own protocol. It’s not hard, but I wasn’t finding anyone putting it together explicitly.
For the solution you’ll see below, it’s fairly straightforward:
- On the Client Side:
- Create an `input type=file` tag
- Get the file meta details (name, size, etc) via JS, to JSON
- Convert the file to a BinaryArray type Here’s the critical part:
- Combine the JSON metadata and the BinaryArray data for the file. Put them together into a format you’re prepared to parse on the server side.
JavaScript: Processing file inputs
HTML file input/upload tag:
<input id="FilesToUpload" name="FilesToUpload" type="file" value="Upload" multiple>
JavaScript: retrieving file+metadata and preparing for our custom SendFile() method (not to be confused with .send() on a WebSocket) Go to this function in the GitHub repo
function SendFiles()
{
let files = document.getElementById('FilesToUpload').files;
for (let i = 0; i < totalFiles; i++)
{
const file = files[i];
const reader = new FileReader();
reader.onabort = function(e) { /* @TODO */ }
reader.onerror = function(e) { /* @TODO */ }
reader.onloadstart = function(e) { /* @TODO */ }
reader.onprogress = function(e) { /* @TODO */ }
reader.onload = function(e) // only triggered if successful
{
let rawData = new ArrayBuffer();
rawData = e.target.result;
Wss.SendFile(file, rawData);
}
reader.readAsArrayBuffer(file); // _must_ use ArrayBuffer
}
}
JavaScript: Preparing for websocket.send()
- In the example below, I use:
- An exclamation mark for the first byte of data; it's a "magic byte" letting my WebSocket server know it's not receiving the common JSON message.
- Followed by the JSON
- Followed by \r\n\r\n ...an homage to HTTP headers. This gives me a clear delineator I can search for to break out the JSON from the file's binary.
- And finally, the binary data of the file.
The final data will look something like this: "!{...json...}\r\n\r\n...file_binary..."
JavaScript: Data combining and send()ing Go to this function in the GitHub repo
SendFile(fileMeta, fileData)
{
const fileMetaJson = JSON.stringify({
lastModified : fileMeta.lastModified,
name : fileMeta.name,
size : fileMeta.size,
type : fileMeta.type,
});
// _must_ do this to encode as a ArrayBuffer / Uint8Array
const enc = new TextEncoder(); // always utf-8, Uint8Array()
const buf1 = enc.encode('!');
const buf2 = enc.encode(fileMetaJson);
const buf3 = enc.encode("\r\n\r\n");
const buf4 = fileData;
let sendData = new Uint8Array(buf1.byteLength + buf2.byteLength + buf3.byteLength + buf4.byteLength);
sendData.set(new Uint8Array(buf1), 0);
sendData.set(new Uint8Array(buf2), buf1.byteLength);
sendData.set(new Uint8Array(buf3), buf1.byteLength + buf2.byteLength);
sendData.set(new Uint8Array(buf4), buf1.byteLength + buf2.byteLength + buf3.byteLength);
this.conn.binaryType = "arraybuffer";
this.conn.send(sendData);
this.conn.binaryType = "blob";
}
PHP/OpenSwoole WebSocket Server onMessage() Example:
I think this example is fairly straightforward, and should translate to any language.
There are only three lines, starting with `list()`, required to extract your file, along with the JSON of the file's metadata.
Then `file_put_contents()` shows how to save it with the appropriate filename.
PHP: WebSocket server onMessage() handler Go to this method in the GitHub repo
$server->on('Message', function(Server $server, Frame $frame)
{
if ('!' === $frame->data[0])
{
list($jsonMsg, $fileData) = explode("\r\n\r\n", $frame->data, 2);
$jsonMsg = substr($jsonMsg, 1); // trim '!' here vs on the larger file
$jsonMsg = json_decode($jsonMsg);
if ($jsonMsg->size === strlen($fileData)) {
file_put_contents(__DIR__.'/uploads/'.$jsonMsg->name, $fileData);
}
else {
echo "\nfile byte length reported from browser unequal to data received\n";
}
}
else
{
// ...handle your other message types
}
});
Final notes:
This is intended to be a very simple example. Any time I'm trying to figure anything out, I try to create the simplest example possible.
That said, you'll have to adapt this to whatever protocol you design for your WebSocket communcation with the server. For example, above, we don't say what kind of file this is, or who it belongs to, etc. That will all be a part of the protocol you design, and isn't included in this example.
A GitHub repo is at this link for a full-functioning version of this, with additional comments and features:
https://github.com/benwills/sending-and-receiving-binary-files-via-websockets