Jon Michael Galindo

~ writing, programming, art ~

<< Previous next >>
1 January 2016

JS Programming Tutorial: Binary Save


I've decided to kick off the new year with something a little different. This is, after all, a programming blog, among other things.

This Javascript tutorial will teach you a fast, easy way to save binary data from a website's javascript into a file on the server, and then read it back out.

We will have two parts: a javascript file "binarySave.js", and a PHP file "binarySave.php".


If you're impatient, here's the code:

0 /* binarySave.js */ 1 2 var BinarySave = { 3 Save : function(name,typedArray) { 4 var fd = new FormData(), 5 xhr = new XMLHttpRequest(), 6 b = new Blob([typedArray], {type:"application/octet-binary"}); 7 fd.append("mode","save"); 8 fd.append("name",name); 9 fd.append("data",b); 10 11 xhr.open("POST", "binarySave.php", true); 12 xhr.send(fd); 13 }, 14 Load : function(name,callback) { 15 var xhr = new XMLHttpRequest(); 16 xhr.callback = callback; 17 xhr.open("GET", name+".bin", true); 18 xhr.responseType = "arraybuffer"; 19 xhr.onreadystatechange = function() { 20 if(xhr.readyState==4 && xhr.status==200) {{ 21 xhr.callback(xhr.response); 22 } 23 } 24 xhr.send(); 25 } 26 };

And the PHP:

0 <?php 1 $mode = $_POST['mode']; 2 $name = $_POST['name']; 3 4 if($mode=='save') { 5 $data = file_get_contents($_FILES["data"]["tmp_name"]); 6 $fp = fopen($name.'.bin', 'w'); 7 fwrite($fp, $data); 8 fclose($fp); 9 } 10 if($mode=='load') { 11 $fp = fopen($name.'.bin','rb'); 12 $fsize = filesize($name.'.bin'); 13 $content = fread($fp,$fsize); 14 fclose($handle); 15 header('content-type: application/octet-binary'); 16 header('content-length: ' . $fsize); 17 echo $content; 18 } 19 ?>

Simple, right? Let's walk through it and look at a usage example.


The Basic Idea


Saving

- We'll call the Save() function, passing a file name and a typed array.

- The Save() function will send to the PHP file: a "mode" parameter, telling it to save data, a "name" parameter, for the filename, and a "data" parameter, with the array.

- The PHP script will save the file.

Loading

- We'll call the Load() function, passing a file name and a Callback() function.

- The Load() function will send to the PHP file: a "mode" parameter, telling it to load data, and a "name" parameter, for the filename.

- The PHP file will read in the file and send it back to the client.

- The javascript will call the Callback() function, passing it a buffer.


So, to save we would start by pulling the javascript into our html file:

0 <script type='text/javascript' src='binarySave.js'></script>

Next, we add our own code to save some data from a typed array. For this example, I'll just save four random bytes.

1 <script type='text/javascript'> 2 var DataToSave = new Uint32Array( [ 0xFF, 0xCD, 0x1A, 0x2F ] ); 3 BinarySave.Save( "binary_file", DataToSave ); 4 </script>

Notice when I wrote the filename I didn't include an extension. The PHP will automatically save every file with the ".bin" extension, for a little safety. It will also only serve files ending in the ".bin" extension.

Now we have called the save function. Let's watch what happens.

(The function creates a form, fills it, and POSTs it to the PHP file.)


Create the form:

4 var fd = new FormData(),

Create the XMLHttpRequest that will POST the form:

5 xhr = new XMLHttpRequest(),

Create the Blob that will safely carry our binary data over the internet:

6 b = new Blob([typedArray], {type:"application/octet-binary"});

Now, we can fill the form with our three pieces of info:

7 fd.append("mode","save"); 8 fd.append("name",name); 9 fd.append("data",b);

If we wanted to get all fancy and careful, we could add checking to make sure that we were passed an array and a filename and we could expect a response from the server, maybe telling us whether or not the file saved successfully, and we could even do a little back-and-forth for overwriting an existing file with user confirmation.

And, if you feel so inclined, by all means do so. However, this tutorial only serves to teach a basic structure for saving binary data. The bells and whistles are up to you. :-)

Instead, we'll just go ahead and send our XMLHttpRequest to the PHP.

11 xhr.open("POST", "binarySave.php", true); 12 xhr.send(fd);

And that's it for our Javascript!

Of course, the data's journey is ongoing, so let's follow it to the server and into the PHP.

The first thing the PHP does is grab the mode and name info. (Whether reading or writing, we specify a filename.)

1 $mode = $_POST['mode']; 2 $name = $_POST['name'];

We didn't have to use a "mode" variable, we could have made two separate PHP files, one for save and one for load; or, we could have checked to see whether "data" was set, and used that as our mode indicator. But, this works too, and in this case our mode is set to "save".

4 if($mode=='save') {

This PHP is really straightforward. We'll read the binary data out of our blob (don't ask me why it's called "tmp_name", it just is; even if you specify a filename it's still just "tmp_name"):

5 $data = file_get_contents($_FILES["data"]["tmp_name"]);

We'll open the appropriate file for writing:

6 $fp = fopen($name.'.bin', 'w');

Then we'll write the data and close the file, and we're done!:

7 fwrite($fp, $data); 8 fclose($fp); 9 }

Our four bytes have been saved into a simple binary file called "binary_file.bin".


All that's left is to read them back out again. :-)

Let's rewrite our earlier usage example to load the data from "binary_file" instead of save it:

0 <script type='text/javascript' src='binarySave.js'></script> 1 <script type='text/javascript'> 2 function output( buffer ) { 3 var LoadedData = new Uint32Array( buffer ); 4 for(var i in LoadedData) 5 console.log( LoadedData[i].toString(16) ); 6 } 7 8 BinarySave.Load( "binary_file", output ); 9 10 // Output: 11 // FF 12 // CD 13 // 1A 14 // 2F 15 </script>

And, naturally, it worked! The program prints out the exact 4 bytes we sent to the server, no messy conversion to base64 URI necessary, and you can send huge data chunks.

I suppose we should follow the code to see how the load works, although it's virtually identical to the save mechanism.

We call our Load() function with the parameter "binary_file":

8 BinarySave.Load( "binary_file", output );

Our Load() function creates the XMLHttpRequest to be sent to the server:

15 var xhr = new XMLHttpRequest();

It attaches a reference to the callback function for when the loading is done:

16 xhr.callback = callback;

It sets up the connection:

17 xhr.open("GET", name+".bin", true);

By defining the responseType as "arraybuffer", the browser won't do any funny business with our data, like try to parse it as text or something:

18 xhr.responseType = "arraybuffer";

When the server's response come's through, just pass it straight to the callback function:

19 xhr.onreadystatechange = function() { 20 if(xhr.readyState==4 && xhr.status==200) {{ 21 xhr.callback(xhr.response); 22 } 23 }

And our library's job is done! Send the request and the rest is up to the PHP and the user's code.

24 xhr.send();

Of course, we need to see what the PHP's doing. Like last time, we've grabbed the mode and name variable:

1 $mode = $_POST['mode']; 2 $name = $_POST['name'];

Our mode is now "load", so lets follow that section:

10 if($mode=='load') {

We open the file specified by the XMLHttpRequest:

11 $fp = fopen($name.'.bin','rb');

Next, using a couple standard PHP commands we load its entire contents into a variable called $content:

12 $fsize = filesize($name.'.bin'); 13 $content = fread($fp,$fsize);

Finally, to wrap it up, we close our file (we already have the contents), set some headers so the browser understands what its dealing with, and print out the binary data! :-)

14 fclose($handle); 15 header('content-type: application/octet-binary'); 16 header('content-length: ' . $fsize); 17 echo $content; 18 }

You remember the rest from our usage example. The callback function is now called, being passed the binary data received from the server, and we wrap a typed array around it:

2 function output( buffer ) { 3 var LoadedData = new Uint32Array( buffer );

Then, we print it out to the Javascript console, and we're done for real! With everything! :-D

4 for(var i in LoadedData) 5 console.log( LoadedData[i].toString(16) ); 6 }


You can now save binary data to the server in just over 40 lines of code and two extremely simple functions.

Until I knew how to do this, I would always be storing data into images in HTML5 canvas, then parsing it to a URL and passing that to the server. It was very inefficient, although it worked.


Well, I hope you enjoyed the tutorial. I think I'll try something a bit more exotic next time. Until then! :-)



© Jon Michael Galindo 2015