File server in 2 ways

Mayank C
Tech Tonic

--

Introduction

Like Node.js, Deno will likely sit in the front to handle API requests, serve UI, etc. While serving UI, one of the common use case is to act as a content server for HTML, CSS, images, JavaScript, etc. For production grade applications, instead of relying on public content servers, all the content would likely be cached in the enterprise’s own content servers. This way, it’s guaranteed that the content would always be available.

The content could be present on a locally accessible path, or it needs to be fetched from another service. For the scope of this article, we’ll assume that the files are locally accessible.

In this article, we’ll see two ways of implementing a content server in Deno. The first way reads & serves the files directly, while the second way uses fetch API to read local files. The second way can be easily extended to fetch remote files.

2 ways of serving files

As mentioned above, there are two ways of writing a content server:

  • Read directly
  • Use fetch

Let’s see both the ways in detail.

First way: Read directly

The first way is to read the locally accessible files directly. This can be done in the following steps:

  • Get the file path from request URL
  • Using the stat API, get the file size
  • Open the file & get a Deno.Reader object
  • Convert Deno.Reader to ReadableStream using readableStreamFromReader()
  • Send a response with ReadableStream

To make this happen, an import of readableStreamFromReader() from Deno’s standard library’s streams module, and an import of serve() from http module is required. All other functionality is present in the core runtime.

import { readableStreamFromReader as toStream } from "https://deno.land/std/streams/mod.ts";
import { serve } from "https://deno.land/std/http/mod.ts";

The request URL needs to be parsed to extract pathname that contains the relative file path. The content type & content length requires to be set explicitly (we’re setting only content-length).

Here is the complete code of the first way of writing a content server:

Here are some tests using curl:

$ curl http://localhost:8080/someFile1.txt -v
< HTTP/1.1 404 Not Found
< content-length: 0
$ curl http://localhost:8080/someDir/someDir2/sample.txt -v
< HTTP/1.1 200 OK
< content-length: 22
< date: Fri, 12 Nov 2021 21:59:58 GMT
<
Learning Deno Is Fun!
$ curl http://localhost:8080/sample.pdf -o /dev/null -sv
* Trying 127.0.0.1:8080...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /sample.pdf HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.75.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< content-length: 69273

Second way: Fetch

The second way is to read locally accessible files by using fetch API. The advantage of this method is that it can be easily extended to fetch remote files when acting as a proxy. This can be done in the following steps:

  • Get the file path from request URL
  • Use fetch API to access the file and get a ReadableStream from Response object
  • Send a response with ReadableStream

To make this happen, an import of serve() from http module is required. All other functionality is present in the core runtime.

import { serve } from "https://deno.land/std/http/mod.ts";

The request URL needs to be parsed to extract pathname that contains the relative file path.

Note: The content-type and content-length headers are not provided by local fetch. Especially for content-length, if user has to stat the file to get it’s size, then there is no purpose of using local fetch. Perhaps Deno can improve this in future releases.

Here is the complete code of the second way of writing a content server:

Here are some tests using curl:

$ curl http://localhost:8080/sample.xyz
< HTTP/1.1 404 Not Found
< content-length: 0
$ curl http://localhost:8080/sample.txt
< HTTP/1.1 200 OK
< transfer-encoding: chunked
<
Learning Deno Is Fun!

--

--