A comprehensive guide to HTTP servers in Deno

Mayank C
Tech Tonic

--

Introduction

Writing an HTTP server is the most common use case for runtimes like Node, Deno, etc. HTTP servers can be written in a couple of ways: native only or through frameworks.

The native way needs more user code, but could turn out to be the most efficient way. There is no way to beat the performance of native code because most of the frameworks are built over native code.

The other way is to use the most popular framework: oak. Oak is a callback style framework that comes with a router, auto body parsing, file server, etc. With oak framework, the amount of user code would be quite less.

In this comprehensive article, we’ll learn all about writing native HTTP servers in Deno (no frameworks).

This article would cover:

  • 2 ways of writing HTTP servers (async iterator, callbacks)
  • Serving HTTPS
  • Building a native router
  • Request handling including getting basic data, query params, cookies, headers, different types of bodies (URL encoded, JSON, form data, text, binary, etc.)
  • Response building including setting cookies, headers, different types of bodies (URL encoded, JSON, form data, text, binary, etc.)
  • Saving & serving files
  • Logging of incoming requests
  • A brief look at the performance

Let’s get started!

HTTP servers in two ways

Although Deno is all about using async/promises everywhere possible, there are still two ways to write an HTTP server:

  • Using async iterator: The first way to write an HTTP server is to use async iterators for looping through incoming TCP connections & HTTP requests. This is the lowest level user could go.
  • Using callbacks: The callback style is suitable for those who have are coming from native Node.js/Go/etc. style. For every incoming HTTP request, a callback handler gets called.

Way 1 — Async iterator

The first way is to use async iterators for looping through TCP connections & HTTP requests. The two simple steps are:

  • Process incoming TCP connections
  • Serve HTTP

Incoming TCP connections

This is the first step where TCP connections are handled. Using Deno.listen(), a TCP server is created. All the incoming TCP connections are iterated over & handled. This step is all about handling TCP (no HTTP yet).

const listener = Deno.listen({ port: 3000 });
for await(const conn of listener)
//handle new connection

Each iteration of the loop yields a TCP connection (represented via Deno.Conn object) that contains details about the underlying TCP connection. The usual attributes are:

  • local address
  • remote address
  • connection ID (aka rid)

For better performance, handle new TCP connections in a separate function. Do not wait for current TCP connection handling to finish before starting to handle new connection.

Serve HTTP

The first step was all about TCP. In the second step, the incoming TCP connection is upgraded to serve HTTP. This is achieved by using Deno.serveHttp(). This function takes the Conn object and serves HTTP over it (can also be called upgrading to HTTP). The serveHttp function returns an async iterator that can be used to retrieve all the HTTP requests that comes over the same TCP connection. Usually there would be a single HTTP request per TCP connection.

const listener = Deno.listen({ port: 3000 });
for await(const conn of listener) {
for await(const {request:req, respondWith:res} of Deno.serveHttp(conn)) {
//Handle HTTP request
}
}

In cases of redirection to the same host, it’s possible that the browser calls the redirected URL over the same TCP connection. In this case, there would be more than one HTTP requests per TCP connection.

Each iteration of serveHttp yields the following data:

  • request: The web standard Request object (this object contains all the details of the incoming HTTP request)
  • respondWith: This is a function that can be called to send a response back. This function accepts a web standard Response object.

We’ll go over the request and response handling in detail. For now, here is the complete code of a native Hello world HTTP server:

//Codeconst listener = Deno.listen({ port: 3000 });
for await(const conn of listener) {
for await(const {request:req, respondWith:res} of Deno.serveHttp(conn)) {
res(new Response('Hello world'));
}
}
//Test> curl http://localhost:3000
Hello world

Way 2 — Using callbacks

The second way is to use callback style handlers (like Node.js, go, etc.). This is achieved by importing serve() from Deno’s standard library’s HTTP module. As the name indicated, serve simplies user’s code by combining both the steps (TCP & serving HTTP).

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

This function accepts two inputs:

  • Callback handler: A callback handler function that accepts a Request object and returns a Response object.
  • Listening address: The listening address in object format: {port: 5000}
serve((req:Request) => {/*callback handler*/}, { port: 8000 });

As we can see, the callback style server is shorter and easier to use.

We’ll go over the request and response handling in detail. For now, here is the complete code of a native Hello world HTTP server in callback style:

//Codeimport { serve } from "https://deno.land/std/http/mod.ts";async function reqHandler(req: Request) {
return new Response("Hello world from callback");
}
serve(reqHandler, { port: 8000 });//Test> curl http://localhost:3000
Hello world from callback

That’s just a single line of code!

Serving HTTPS

From Wikipedia: Hypertext Transfer Protocol Secure (HTTPS) is an extension of the Hypertext Transfer Protocol (HTTP). It is used for secure communication over a computer network, and is widely used on the Internet. In HTTPS, the communication protocol is encrypted using Transport Layer Security (TLS) or, formerly, Secure Sockets Layer (SSL). The protocol is therefore also referred to as HTTP over TLS, or HTTP over SSL.

In other words, HTTPS is all about securing the underlying TCP connection. Therefore, there is another function called serveTls() that serves TLS over TCP. In addition to port, listenTls needs a key file and a certificate file. Once TCP connection is secured, the procedure of serving HTTP remains the same.

Here is a snippet showing how to natively serve TLS:

const listener = await Deno.listenTls({
hostname: "localhost",
port: 3000,
certFile: "path_to_cert_file/server.crt",
keyFile: "path_to_key_file/server.key",
});
//other steps are the same

Here is a snippet showing how to serve TLS in callback style:

import { serveTls } from "https://deno.land/std/http/mod.ts";const certFile="./l.crt", keyFile='./l.key';async function reqHandler(req: Request) {
return new Response("Secure Hello world");
}
serveTls(reqHandler, { port: 8000, certFile, keyFile });

Here is the output of the curl command:

> curl https://localhost:3000
...
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server did not agree to a protocol
* Server certificate:
* subject: C=US; ST=YourState; L=YourCity; O=Example-Certificates; CN=localhost.local
* start date: Oct 21 16:28:58 2019 GMT
* expire date: Sep 27 16:28:58 2118 GMT
* issuer: C=US; CN=Example-Root-CA
* SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
...
Secure Hello world

The following sections are applicable to any style of HTTP server as the interface is always the web standard Request & Response.

Native router

Deno doesn’t come with a built-in router. The easiest option to get routing is to use frameworks like Oak. But, it’s also possible to write a native router i.e. a router that only uses natively available APIs.

The router is built over one of the newest features of Deno: URLPattern. The URLPattern APIs provide a way to:

  • create URL patterns that may include variables (like /a/:b/c/:d)
  • test URL patterns
  • extract variables out of it (aka path params)

A great introduction of URLPattern is here.

We’ll write a small router that has two basic APIs:

  • add: To add a path (needs method, path pattern, handler)
  • route: To route incoming request to a handler

The request handler is expected to return a Response object (Promise<Response>).

Here is the complete code of a server with a router for 4 sample paths:

import { serve } from "https://deno.land/std/http/mod.ts";type RequestHandler = (
request: Request,
params:Record<string, string>
) => Promise<Response>;
class Router {
#routes:Record<string, Array<any>>={
'GET': [],
'POST': [],
'PUT': []
}
add(method: string, pathname:string, handler: RequestHandler) {
this.#routes[method].push({pattern: new URLPattern({pathname}), handler});
}
async route(req: Request) {
for(const r of this.#routes[req.method]) {
if(r.pattern.test(req.url)) {
const params=r.pattern.exec(req.url).pathname.groups;
return await r['handler'](req, params);
}
}
return new Response(null, {status:404});
}
}
const router=new Router();const getUser=async (req: Request, params:Record<string, string>):Promise<Response> => new Response('get user handler');
const addUser=async (req: Request, params:Record<string, string>):Promise<Response> => new Response('add user handler');
const updateUser=async (req: Request, params:Record<string, string>):Promise<Response> => new Response('update user handler');
const addImage=async (req: Request, params:Record<string, string>):Promise<Response> => new Response('add image handler');
router.add('GET', '/api/users/:userid', getUser);
router.add('POST', '/api/users/:userid', updateUser);
router.add('PUT', '/api/users/', addUser);
router.add('GET', '/api/users/:userid/images/:imageid', addImage);
async function reqHandler(req: Request) {
return await router.route(req);
}
serve(reqHandler, { port: 8000 });

The router code is very simple. For a given request method, it tests all the provisioned paths (test). Whenever there is match, path params are extracted (exec). The handler is called with Request object and path params. The route method always returns a Response object (either itself or from handlers) that’s sent back to the caller.

Let’s run some tests:

> curl http://localhost:3000/api/
HTTP/1.1 404 Not Found
> curl http://localhost:3000/api/users/ -X PUT
add user handler
params: {}> curl http://localhost:3000/api/users/1234
get user handler
params: { userid: "1234" }> curl http://localhost:3000/api/users/1234 -X POST
update user handler
params: { userid: "1234" }> curl http://localhost:3000/api/users/1234/images/9999
add image handler
params: { userid: "1234", imageid: "9999" }

Request handling

All incoming HTTP requests are presented as web standard Request object. The request object contains everything that came in the HTTP request, including the body. Let’s go over the basic data like URL, method, etc., followed by query params, cookies, headers, and handling of request body.

Get basic data

Here is the basic data that comes inside the Request object:

  • Full URL: The full URL (including hostname, port, path, etc.) can be accessed through req.url (string) attribute. To access information out of the raw URL, it needs to be parsed with web standard URL API.
> curl http://localhost:3000/api/v1/abcd/efghreq.url; 
http://localhost:3000/api/v1/abcd/efgh
const u=new URL(req.url);
u.hostname; // localhost
u.port; // 3000
u.pathname; // /api/v1/abcd/efgh
u.protocol; // http:
  • Method: The HTTP method can be accessed via req.method
> curl http://localhost:3000/api/v1/abcd/efghreq.method; // GET> curl http://localhost:3000/api/v1/abcd/efgh -X DELETEreq.method; // DELETE

Get query params

The URLSearchParams API can be used to get the query params that came in the URL. To get a URLSearchParams object, the request url (string) needs to be parsed by URL. Once the object is available, functions like get(), has() & entries() can be used to access the query params.

> curl "http://localhost:3000/?a=1&b=2"const qp=new URL(req.url).searchParams;
qp.get('a'); // 1
qp.has('a'); // true
qp.get('b'); // 2
qp.has('c'); // false
qp.get('c'); // null
for(const p of qp.entries())
p;
// [ "a", "1" ]
// [ "b", "2" ]

Get headers

The HTTP headers are automatically parsed and placed in the web standard Headers object. The presence of headers can be checked through has() and fetched through get() call. Additionally, there are iterators to go over all the received headers (entries()).

> curl http://localhost:3000 -H 'hdr1: val1' -H 'hdr2: val2'req.headers.has('hdr1'); // true
req.headers.has('hdr3'); // false
req.headers.get('hdr1'); // val1
req.headers.get('hdr3'); // null
for (const p of req.headers.entries())
p;
// [ "accept", "*/*" ]
// [ "hdr1", "val1" ]
// [ "hdr2", "val2" ]
// [ "host", "localhost:3000" ]
// [ "user-agent", "curl/7.64.1" ]

Get cookies

The HTTP cookies also come in standard headers, but the format of cookies makes it a bit tough to deal with them. Deno’s standard libary’s http module comes with utilities to get cookies in a simple object form that’s extracted from the Headers object.

> curl http://localhost:3000 -H 'Cookie: k1=v1; k2=v2; k3=v3'import {getCookies} from "https://deno.land/std/http/mod.ts";const c=getCookies(req.headers);
// c: { k1: "v1", k2: "v2", k3: "v3" }

Get body

The HTTP requests like POST, PUT, etc. could contain a body. By default, the body stays in the raw format (i.e. the way it was received). The body needs to be decoded based on the content type.

Before working on the body, the first check is if body is present or not. This can be done through req.body that returns null if there is no body associated with the HTTP request.

> curl http://localhost:3000req.body; // null> curl http://localhost:3000 -d 'abcd'req.body; // ReadableStream (i.e. not null)

Text

The text() function can be used to decode the request body as string. This function returns a string containing the entire body.

> curl http://localhost:3000 -H 'content-type: text/plain' -d 'Hello!!!'const s=await req.text();
//s: Hello!!!

Binary

The arrayBuffer() function can be used to read the request body into a buffer. There is no real decoding here. The array buffer can be viewed as a Uint8Array for further processing.

> curl http://localhost:3000 -H 'content-type: application/octet-stream' -d 'Hello!!!'const b=new Uint8Array(await req.arrayBuffer());
//b: Uint8Array(8) [72,101,108,108,111,33,33,33]

URL encoded

The formData() function can be used to decode a URL encoded body (as indicated by content type application/x-www-form-urlencoded). This functions returns a web standard FormData object. Once decoded, the KVs can be accessed through has(), get(), and entries() functions.

> curl http://localhost:3000 -H 'content-type: application/x-www-form-urlencoded' -d 'a=1&b=2&c=d'if(req.headers.get('content-type')==='application/x-www-form-urlencoded') {
const d=await req.formData();
d.has('a'); // true
d.get('a'); // 1
d.get('c'); // d
d.get('d'); // null
d.has('e'); // false
}
//--OR, iterate through allfor(const e of d.entries());
e;
// [ "a", "1" ]
// [ "b", "2" ]
// [ "c", "d" ]

JSON

The json() function can be used to decode a JSON encoded body (as indicated by content type application/json). This function returns a JS object.

> curl http://localhost:3000 -H 'content-type: application/json' -d '{"a": "1", "b": {"c": 2}}'const s=await req.json();
//j: { a: "1", b: { c: 2 } }

Form data

The formData() function (the same that was used to decode url encoded data) can be used to decode multipart form data (as indicated by content type multipart/form-data). The return type is still FormData object. As it’s a mulipart/form-data, the request body could contain simple KVs or files or both.

> curl http://localhost:3000 -F 'a=b' -F 'c=@./testdata/sample.txt'const f=await req.formData();
f.get('a');
//a: b
f.get('c');
//c: File { size: 22, type: "text/plain" }

Response building

The response of an HTTP request can be sent by building a web standard Response object. The response object consists of status code, status text, headers, cookies, and response body. Let’s go through response building in detail.

The general format for building a Response object is:

new Response(/*body*/, /*options like status code, headers*/);

Set status code

The status code and status text can be set by passing it in the options while building a Response object. Based on the status code, status text is set internally.

//Codenew Response(null, {status: 200});
//sends empty 200 OK response
//Test> curl http://localhost:3000
< HTTP/1.1 200 OK
< content-length: 0

Status text is the status message (OK in above case). Note that HTTP/2 doesn’t support status messages. Setting status text isn’t a common operation.

Set headers

The HTTP response headers can be set by building a web standard Headers object. There are several ways to build the headers object. It can be built separately and then given to the Response object, or the headers can be set directly in the options of the Response object. The primary functions are set() and append(). The difference between set and append is that append allows multiple values for a given header (CSV).

//Codeconst headers=new Headers();
h.set('h1', 'v1');
h.append('h2', 'v2');
h.append('h2, 'v3');
new Response(null, {headers});
//Test> curl http://localhost:3000
< HTTP/1.1 200 OK
< h1: v1
< h2: v2, v3
//Codenew Response(null, {
headers: {
'h1': 'v1',
'h2': 'v2'
}
});
//Test> curl http://localhost:3000
< HTTP/1.1 200 OK
< h1: v1
< h2: v2

Set cookies

Cookies also go inside HTTP headers, but they are more complex to build. A cookie can have several attributes like name, value, path, domain, expiry, secure, etc. Setting cookie attributes manually is tough and error prone. Deno’s standard library’s http module has a useful function setCookie() that can be used to create a cookie easily. There is another function deleteCookie() that can be used to remove a cookie from the browser (this is achieved by setting cookie expiry to past).

The inputs to set cookie function are: Headers object and cookie options.

//Codeimport {setCookie, deleteCookie} from "https://deno.land/std/http/mod.ts";const headers=new Headers();
setCookie(headers, {
name: "cookie-1",
value: "value-1"
});
new Response(null, {headers});
//Test> curl http://localhost:3000
< HTTP/1.1 200 OK
< set-cookie: cookie-1=value-1
//CodesetCookie(headers, {
name: "cookie-2",
value: "value-2",
secure: true,
maxAge: 3600,
domain: "deno.land",
path: '/',
expires: new Date(Date.UTC(2021, 9, 7, 15, 32)),
httpOnly: true
});
new Response(null, {headers});
//Test> curl http://localhost:3000
< HTTP/1.1 200 OK
< set-cookie: cookie-1=value-1
< set-cookie: cookie-2=value-2; Secure; HttpOnly; Max-Age=3600; Domain=deno.land; Path=/; Expires=Thu, 07 Oct 2021 15:32:00 GMT
//Codeconst headers=new Headers();
deleteCookie(headers, "cookie-2");
res(new Response(null, {headers}));
//Test> curl http://localhost:3000
< HTTP/1.1 200 OK
< set-cookie: cookie-2=; Expires=Thu, 01 Jan 1970 00:00:00 GMT

Set body

The response body is set pretty much using the same mechanism of getting body from the request. In some cases, the body is given directly to the Resonse object (like text, binary, json), while in other cases, there are functions to set bodies (url encoded, form data). Let’s go over building the common response bodies.

The general format of setting body in the Response object is:

new Response(/*body*/, /*options*/);

Empty response

An empty 200 OK response i.e. without body can be sent by creating an empty Response object (this is same as explicitly setting body null):

//Codenew Response();//Test> curl http://localhost:3000
< HTTP/1.1 200 OK
< content-length: 0

Text

A textual body (aka string) can be passed directly to the Response object. The content length & content type headers will be set internally.

//Codenew Response('Hello!');//Test> curl http://localhost:3000
< HTTP/1.1 200 OK
< content-type: text/plain;charset=UTF-8
< content-length: 6
Hello!

Binary

A binary body i.e. containing raw bytes can be passed as a Uint8Array to the Response object. The content length will be set internally, but content type needs to be set by user.

//Codeconst headers=new Headers({'content-type':'application/octet-stream'});
new Response(new Uint8Array([65,66,67]), {headers});
//Test> curl http://localhost:3000
< HTTP/1.1 200 OK
< content-type: application/octet-stream
< content-length: 3
ABC

URL encoded

A URL encoded body, indicated by content type x-www-form-urlencoded, can be created by building a URLSearchParams object that can be given directly to the Response object. The content type and content length would be set internally.

//Codeconst u=new URLSearchParams({'p1': 'v1'});
u.set('p2', 'u2');
new Response(u);
//Test> curl http://localhost:3000
< HTTP/1.1 200 OK
< content-type: application/x-www-form-urlencoded;charset=UTF-8
< content-length: 11
p1=v1&p2=u2

JSON

Unfortunately, JSON data type is not supported by web standard Response object. Therefore, a JSON encoded body needs to be passed as a string (JSON.stringify). The content length would be set internally, but content type needs to be set by user.

//Codeconst headers=new Headers({'content-type': 'application/json'});
const o={a: 1, b: {c: [1, 2, 3]}};
new Response(JSON.stringify(o), {headers});
//Test> curl http://localhost:3000
< HTTP/1.1 200 OK
< content-type: application/json
< content-length: 25
{"a":1,"b":{"c":[1,2,3]}}

Form data

A multipart body, indicated by content type multipart/form-data, can be created by building a FormData object that can be given directly to the Response object. The content type and content length would be set internally.

//Codeconst fileData=await Deno.readFile('./testdata/sample.txt');
const f=new FormData();
f.set('a', 'b');
f.set('file', new File([fileData], 'samples.txt', {type: 'text/plain'}));
new Response(f);
//Test> curl http://localhost:3000
< HTTP/1.1 200 OK
< content-type: multipart/form-data; boundary=----9367974821808561967793108061
< transfer-encoding: chunked
< date: Thu, 14 Oct 2021 17:27:53 GMT
<
------9367974821808561967793108061
Content-Disposition: form-data; name="a"
b
------9367974821808561967793108061
Content-Disposition: form-data; name="file"; filename="samples.txt"
Content-Type: text/plain
Learning Deno Is Fun!
------9367974821808561967793108061--

Saving and serving files

In some cases, the HTTP server needs to save incoming file (present in request body) on the disk. In other cases, the HTTP server needs to serve a file (to be present in response body) from the disk. Let’s go over both cases.

Saving into files

There are three steps involed in saving a request body into a file:

  • Open a file in write mode (Deno.create)
  • Convert request body to Deno.Reader
  • Copy from Reader to Writer

To convert request body to Reader, we need to import a function readerFromStreamReader() from Deno’s standard libary’s streams module. Also, to copy data, we need to import copy() function from the same module.

//Codeimport { readerFromStreamReader, copy } from "https://deno.land/std/streams/mod.ts";
import { serve } from "https://deno.land/std/http/mod.ts";
async function reqHandler(req: Request) {
if(req.body) {
const dest=await Deno.create('/var/tmp/someFile.txt');
const src=readerFromStreamReader(req.body.getReader());
const n=await copy(src, dest);
return new Response(`Uploaded ${n} bytes`));
}
return new Response(null, { status: 400 });
}
serve(reqHandler, { port: 8000 });//Test> cat testdata/sample.txt
Learning Deno Is Fun!
> curl http://localhost:3000/ --data-binary "@./testdata/sample.txt"
Uploaded 22 bytes
> cat /var/tmp/someFile.txt
Learning Deno Is Fun!

Serving files

Serving files is the reverse process of saving files. This time the steps are:

  • Open file for reading (Deno.Reader)
  • Convert Reader to ReadableStream
  • Use ReadableStream with Response object

To convert Reader to ReadableStream, we need to import readableStreamFromReader() from Deno’s standard library’s streams module.

An important point to note that, both content length & content type needs to be set by the user.

//Codeimport { readableStreamFromReader } from "https://deno.land/std/streams/mod.ts";
import { exists } from "https://deno.land/std/fs/mod.ts";
import { serve } from "https://deno.land/std/http/mod.ts";
async function reqHandler(req: Request) {
const name='./'+(new URL(req.url)).pathname;
if(await exists(name)) {
const size=(await Deno.stat(name)).size.toString();
const src=await Deno.open(name);
return new Response(readableStreamFromReader(src), {
headers: {
'content-length': size
}
});
}
return new Response(null, {status:404}));
}
serve(reqHandler, { port: 8000 });//Test> curl http://localhost:3000/testdata/randomFile
HTTP/1.1 404 Not Found
content-length: 0
> curl http://localhost:3000/testdata/sample.txt
HTTP/1.1 200 OK
content-length: 22

Learning Deno Is Fun!

Logging

For debugging purposes, applications may need to log incoming HTTP requests. The basic data from Request object can be read any number times, however the request body can only be read once. Therefore, to log an incoming request:

  • The Request object needs to be cloned using clone() function
  • Read the body as text() in most of the cases except for binary data that should be read by arrayBuffer()

The following code logs incoming HTTP request using a user defined logRequest() function. Depending on the content type, the body is read as a text or binary.

const rc=req.clone();
await logRequest(rc);
async function logRequest(req: Request) {
console.log(new Date()+' New HTTP request');
console.log(`${req.method} ${req.url}`);
console.log(req.headers);
req.body && console.log(isTextualBody(req.headers)?await req.text():new Uint8Array(await req.arrayBuffer()));
}
function isTextualBody(headers: Headers): Boolean {
switch(headers.get('content-type')?.split(';')[0]) {
case 'application/json':
case 'text/plain':
case 'x-www-form-urlencoded':
case 'multipart/form-data':
return true;
}
return false;
}

Here are the server side logs of some curl requests:

> curl http://localhost:3000Thu Oct 14 2021 10:45:24 GMT-0700 (Pacific Daylight Time) New HTTP request
GET http://localhost:3000/
Headers { accept: "*/*", host: "localhost:3000", "user-agent": "curl/7.64.1" }
> curl http://localhost:3000 -H 'content-type: text/plain' -d 'Hello!!!'Thu Oct 14 2021 10:44:16 GMT-0700 (Pacific Daylight Time) New HTTP request
POST http://localhost:3000/
Headers {
accept: "*/*",
"content-length": "8",
"content-type": "text/plain",
host: "localhost:3000",
"user-agent": "curl/7.64.1"
}
Hello!!!
> curl http://localhost:3000 -H 'content-type: application/json' -d '{"a": "1", "b": {"c": 2}}' -X PATCHThu Oct 14 2021 10:44:46 GMT-0700 (Pacific Daylight Time) New HTTP request
PATCH http://localhost:3000/
Headers {
accept: "*/*",
"content-length": "25",
"content-type": "application/json",
host: "localhost:3000",
"user-agent": "curl/7.64.1"
}
{"a": "1", "b": {"c": 2}}

Performance

This is the last part of the article where we’ll take a brief look at the performance of a simple HTTP server in Deno.

We’ll use a simple HTTPS server that expects a JSON body in the request, parses it, takes out an attribute called name, and sends it back as JSON. Here is the code of the server:

const options = {
hostname: "localhost",
port: 3000,
certFile: 'server.crt',
keyFile: 'server.key',
};
const listener = Deno.listenTls(options);
for await(const conn of listener)
handleNewConnection(conn);
async function handleNewConnection(conn: Deno.Conn) {
for await(const {request:req, respondWith:res} of Deno.serveHttp(conn)) {
const headers=new Headers({'content-type': 'application/json'});
res(new Response(JSON.stringify({name: (await req.json()).name}), {headers}));
}
}

We’ll run the above server for 25, 50, and 75 concurrent connections. Let’s take a look at the performance:

Except for total requests & requests per second, all units are in milliseconds

The median response time is 3.7ms, 8.0ms, and 12.8ms for 25, 50, and 75 concurrent connections respectively.

In this long article, we’ve gone through the basic nuts & bolts needed to build an HTTP server in Deno. A comprehensive guide to learn all about making HTTP requests (aka HTTP client) is here.

--

--