(a collection of notes)

Sending and Receiving Binary Files via WebSockets

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:

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()

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