Deno by example: Proxy file server
In Deno by example series, small but commonly used applications are built step by step in Deno. There is no ordering of articles present in Deno by example series.
In this article, we’ll go through the steps to build a simple proxy file server.
Design
A proxy file server is usually a front facing content server that streams the content from other services. In typical deployments, the front facing server would be publicly accessible while the actual content services would be on a private network. Here is a very simplified architecture:
We’ll build a small application that acts as a proxy file server. On receiving a request, the proxy file server will serve it via a file service.
Steps to build a proxy file server
Let’s go step by step and build a simple proxy file server. We’ll start with a simple HTTP server and slowly add client, streaming, headers, etc.
Step 1
As a proxy file server is an HTTP server, the first step is to write a simple HTTP server that listens on say 8100 port. We need to import serve API from standard library.
The articles to learn about how to write HTTP servers in Deno are here & here.
import { serve } from "https://deno.land/std/http/mod.ts";async function reqHandler(req: Request) {
return new Response();
}serve(reqHandler, { port: 8100 });
The above code simply sends a 200 OK back without a response body. Let’s do a quick test:
$ curl http://localhost:8100 -v
> GET / HTTP/1.1
> Host: localhost:8100
> User-Agent: curl/7.77.0
> Accept: */*
>
< HTTP/1.1 200 OK
< content-length: 0
< date: Fri, 28 Jan 2022 21:33:14 GMT
Step 2
As the proxy file server is also an HTTP client, we need to make a fetch API call to the file service which usually is located in the private network behind load balancers, etc. The file path would be coming in the request URL. The file path is extracted (using URL API) and forwarded to the file service after appending it to the request URL.
import { serve } from "https://deno.land/std/http/mod.ts";const fileService = "http://localhost:9000/";async function reqHandler(req: Request) {
const path = new URL(req.url).pathname;
const proxyRes = await fetch(fileService + path);
return new Response(null, { status: proxyRes.status });
}serve(reqHandler, { port: 8100 });
The above code receives an incoming HTTP request, makes an HTTP request to the file service, proxies the response code as is. Let’s do a couple of quick tests:
$ curl http://localhost:8100 -v
> GET / HTTP/1.1
> Host: localhost:8100
> User-Agent: curl/7.77.0
> Accept: */*
>
< HTTP/1.1 404 Not Found
< content-length: 0
< date: Fri, 28 Jan 2022 22:00:24 GMT$ curl http://localhost:8100/sample.txt -v
> GET /sample.txt HTTP/1.1
> Host: localhost:8100
> User-Agent: curl/7.77.0
> Accept: */*
>
< HTTP/1.1 200 OK
< content-length: 0
< date: Fri, 28 Jan 2022 22:00:59 GMT
As we can see, the file service sends 404 error code for in existent files or directories. For valid files, we receive 200 OK, but there is no content in it. Let’s learn that in the next step.
Step 3
The final step is to forward the response body that was received from the file service. Also, the relevant response headers such as content-length, content-type and content-disposition needs to be forwarded too. The response body can be used as is, i.e. without any processing. Null check is not required on response body, as that’s taken care by Deno.
import { serve } from "https://deno.land/std/http/mod.ts";const fileService = "http://localhost:9000/";function copyHeader (headerName: string, to: Headers, from: Headers) {
const hdrVal = from.get(headerName);
if (hdrVal) {
to.set(headerName, hdrVal);
}
};async function reqHandler(req: Request) {
const path = new URL(req.url).pathname;
const proxyRes = await fetch(fileService + path);
const headers = new Headers();
copyHeader("content-length", headers, proxyRes.headers);
copyHeader("content-type", headers, proxyRes.headers);
copyHeader("content-disposition", headers, proxyRes.headers);
return new Response(proxyRes.body, {
status: proxyRes.status,
headers,
});
}serve(reqHandler, { port: 8100 });
Let’s do some quick tests:
$ curl http://localhost:8100/someRandomFile.pdf -v
> GET /someRandomFile.pdf HTTP/1.1
> Host: localhost:8100
> User-Agent: curl/7.77.0
> Accept: */*
>
< HTTP/1.1 404 Not Found
< content-length: 0
< date: Sat, 29 Jan 2022 00:00:02 GMT$ curl http://localhost:8100/sample.txt -v
> GET /sample.txt HTTP/1.1
> Host: localhost:8100
> User-Agent: curl/7.77.0
> Accept: */*
>
< HTTP/1.1 200 OK
< content-length: 22
< content-type: text/plain
< date: Sat, 29 Jan 2022 00:00:42 GMT
<
Learning Deno Is Fun!$ curl http://localhost:8100/sample.pdf -v --output /dev/null -s
> GET /sample.pdf HTTP/1.1
> Host: localhost:8100
> User-Agent: curl/7.77.0
> Accept: */*
>
< HTTP/1.1 200 OK
< content-length: 69273
< content-type: application/pdf
< date: Sat, 29 Jan 2022 00:01:17 GMT
<
<< OUTPUT REDIRECTED TO /dev/null >>$ curl http://localhost:8100/sample.mp3 -v --output /dev/null -s
> GET /sample.mp3 HTTP/1.1
> Host: localhost:8100
> User-Agent: curl/7.77.0
> Accept: */*
>
< HTTP/1.1 200 OK
< content-length: 2169782
< content-type: audio/mpeg
< date: Sat, 29 Jan 2022 00:02:02 GMT
<
<< OUTPUT REDIRECTED TO /dev/null >>
The simple proxy file server works fine!