Downloading and Decompressing a Zip File With Deno
Deno is a new runtime for Javascript and Typescript. Like Node, it uses V8 under the hood however unlike Node, it is written in Rust and it does not require a package manager. Deno intends to correct the design flaws of Node and offer a more secure platform. Because the runtime is a single binary and the first class Typescript support does not require a build step, Deno seems particularly well-suited for serverless applications and functions as a service.
However, one feature I am particularly interested in is Deno’s ability to execute remote code. It is possible to tell Deno to execute a remote program simply by passing a URL to the deno run
command. To demonstrate this functionality, I wrote a small script to download a zip file and unzip it on the local filesystem.
You can find the source code of the following example on github.
Downloading a binary file
Deno follows the browser Javascript API so it is really straightforward to get started; the use of promises combined with async/await
makes the code wonderfully readable.
To download the zip file we can simply use the browser fetch API. The Deno runtime provides the necessary API to work with the filesystem.
/**
* Download the source file and write it into the destination
*/
async function download(source: string, destination: string): Promise<void> {
// We use browser fetch API
const response = await fetch(source);
const blob = await response.blob();
// We convert the blob into a typed array
// so we can use it to write the data into the file
const buf = await blob.arrayBuffer();
const data = new Uint8Array(buf);
// We then create a new file and write into it
const file = await Deno.create(destination);
await Deno.writeAll(file, data);
// We can finally close the file
Deno.close(file.rid);
}
Decompressing a zip file
We have the possibility to execute arbitrary commands in a subprocess so we can unzip the file by calling directly the unzip
command.
/**
* Unzip the file
*/
async function unzip(filepath: string): Promise<void> {
// We execute the command
// The function returns details about the spawned process
const process = Deno.run({
cmd: ["unzip", filepath],
stdout: "piped",
stderr: "piped",
});
// We can access the status of the process
const { success, code } = await process.status();
if (!success) {
// We retrieve the error
const raw = await process.stderrOutput();
const str = new TextDecoder().decode(raw);
throw new Error(`$Command failed: code ${code}, message: ${str}`);
} else {
// Similarly to access the command output
const raw = await process.output();
const str = new TextDecoder().decode(raw);
console.log(str);
}
}
Putting things together
top-level await
is not yet supported so we still need to wrap our code in an IIFE.
(async function () {
const filename = "archive.zip";
const url =
"https://raw.githubusercontent.com/mickaelvieira/deno-download-unzip-file/master/archive.zip";
try {
await download(url, filename);
await unzip(filename);
// Move into the newly unarchived directory
Deno.chdir("./archive");
// Do something with the archive's content
} catch (e) {
console.error(e);
Deno.exit(1);
}
}());
Executing the script
Deno executes the code in a secure sandbox. By design, a script is not allowed to execute a command, access the filesystem or even the network so we need to explicitly allow those operations.
deno run \
--allow-net \
--allow-read \
--allow-write \
--allow-run https://raw.githubusercontent.com/mickaelvieira/deno-download-unzip-file/master/installer.ts