Deno World
Published in

Deno World

HTTP/2 server in Deno

Introduction

From Wiki: HTTP/2 is a major revision of the HTTP protocol used by the World Wide Web. HTTP/2 is the first new version of HTTP since HTTP/1.1, which was standardized in RFC 2068 in 1997. The standardization effort was supported by Chrome, Opera, Firefox, Internet Explorer 11, Safari, Amazon Silk, and Edge browsers. Most major browsers had added HTTP/2 support by the end of 2015. About 97% of web browsers have the capability of getting content over HTTP/2. As of October 2021, 47% of the top 10 million websites supported HTTP/2.

HTTP/2 enables a more efficient use of network resources and a reduced perception of latency by introducing header field compression and allowing multiple concurrent exchanges on the same connection. It also introduces unsolicited push of representations from servers to clients. HTTP/2 also enables more efficient processing of messages through use of binary message framing.

HTTP/2 is specified in RFC 7540.

In this article, we’ll learn how to use HTTP/2 for making HTTP requests and serving HTTP. We’ll look at secure HTTP/2 only (https://), as Deno doesn’t support serving HTTP/2 over unencrypted channel.

Negotiation

The HTTP/2 specification doesn’t limit HTTP/2 to run over secure connections only. Some implementations have stated that they will only support HTTP/2 when it is used over an encrypted connection, and currently no browser supports HTTP/2 unencrypted.

Though HTTP/2 is gaining popularity, not all browsers support HTTP/2. The servers need to support both HTTP/2 and HTTP/1.1 (fallback).

HTTP/2 uses ALPN to extend TLS by including the protocol negotiation in the exchange of hello messages. The client provides a list of protocols it supports, and the server responds with its selected protocol. No additional round-trip required.

Therefore, there are two steps in HTTP/2 connection negotiation between client and server:

  1. Agree on a protocol
  2. Establish a secure connection

Here is a very simple diagram explaining the negotiation & securing the connection:

Older clients would either not use ALPN or send HTTP/1.1 only, while newer clients would likely send both HTTP/1.1 and HTTP/2. The server would choose a protocol out of these two. Then the connection will be secured via cipherSpec.

Making HTTP request

Deno provides browser compatible fetch API to make HTTP requests. There is no need to do anything special for HTTP/2. The URL must be an HTTPS URL for HTTP/2 to get advertised. For HTTP URLs (unsecured), only HTTP/1.1 would be used.

HTTP/2 gets advertised only for HTTPS

Deno doesn’t support H2C

Making HTTP/1.1 requests

Any request without HTTPS uses HTTP/1.1. Here is a fetch API call that uses HTTP/1.1:

await fetch("http://deno.land");

A trace of the HTTP data generated by the above fetch shows that HTTP/1.1 got used:

GET / HTTP/1.1
accept: */*
user-agent: Deno/1.17.3
accept-encoding: gzip, br
host: deno.land

Making HTTP/2 requests

A request with HTTPS would go through ALPN. Both HTTP/2 and HTTP/1.1 would be advertised to the server. The server would decide one of the protocol for the secured connection. The fetch API call is the same, except for HTTPS in the URL.

Deno’s fetch API always advertises HTTP/2 and HTTP/1.1

await fetch("https://deno.land"); //always sends HTTP/2 and HTTP/1.1

In short, HTTP versioning doesn’t matter when working as a client

Serving HTTP/2

A server needs to know what it supports so that it can decide a protocol from the client’s offer. In Deno, we can write a native HTTPS server in two ways:

  • Use serveTls API from Deno’s standard library
  • Use low level core functions like listenTls and serveHttp

Let’s take a look at both.

Using ServeTls API

The following is a simple ‘Hello world’ program serving over HTTPS:

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

Let’s do a test with curl:

$ curl https://localhost:8100 -kv
* Connected to localhost (127.0.0.1) port 8100 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* ALPN, server did not agree to a protocol
> GET / HTTP/1.1
> Host: localhost:8100
> User-Agent: curl/7.77.0
< HTTP/1.1 200 OK
< content-type: text/plain;charset=UTF-8
< content-length: 11
<
Hello world

As per curl logs, the HTTP server didn’t agree to ALPN.

At the time of writing (Deno Standard library version 0.121.0), serveTls API doesn’t use & support ALPN configuration.

Deno’s serveTls API doesn’t support HTTP/2

To use HTTP/2, we need to use low level APIs: listenTls and serveHttp.

Using low-level APIs

To support HTTP/2, we need to use low-level APIs: listenTls and serveHttp. First, let’s have a look at the default support. The following is an HTTP servver using low-level APIs without explicitly setting ALPN.

const certFile = "./l.crt";
const keyFile = "./l.key";
for await (
const conn of Deno.listenTls({
port: 8100,
certFile,
keyFile,
})
) {
for await (const { request: req, respondWith: res } of Deno.serveHttp(conn)) {
res(new Response("Hello world!"));
}
}

Here is a test using curl:

$ curl https://localhost:8100 -kv
* Connected to localhost (127.0.0.1) port 8100 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* ALPN, server did not agree to a protocol
> GET / HTTP/1.1
> Host: localhost:8100
> User-Agent: curl/7.77.0
< HTTP/1.1 200 OK
< content-type: text/plain;charset=UTF-8
< content-length: 11
<
Hello world

The result is the same.

By default, low-level APIs doesn’t support ALPN

The listenTls API takes an additional attributed called alpnProtocols that can take two values: h2, http/1.1.

ALPN is supported under unstable umbrella

The following is the updated code of the HTTP server using low-level APIs:

const certFile = "./l.crt";
const keyFile = "./l.key";
for await (
const conn of Deno.listenTls({
port: 8100,
certFile,
keyFile,
alpnProtocols: ["h2", "http/1.1"],
})
) {
for await (const { request: req, respondWith: res } of Deno.serveHttp(conn)) {
res(new Response("Hello world!"));
}
}

Here is a test using curl:

$ curl https://localhost:8100 -kv
* Connected to localhost (127.0.0.1) port 8100 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* ALPN, server accepted to use h2
> GET / HTTP/2
> Host: localhost:8100
> user-agent: curl/7.77.0
> accept: */*
>
* Connection state changed (MAX_CONCURRENT_STREAMS == 4294967295)!
< HTTP/2 200
< content-type: text/plain;charset=UTF-8
< content-length: 12
<
Hello world

The server now performs ALPN and supports HTTP/2.

To enable HTTP/2, listenTls must be used

This story is a part of the exclusive medium publication on Deno: Deno World.

--

--

--

The one and only exclusive medium magazine with 200+ articles on Deno

Recommended from Medium

Microsoft Is Testing a “Super Duper Secure Mode” for Edge

Microsoft Is Testing a

VRIMCAS | WFH? Stay Safe and Secure!

Mitigating risk when using consumer tech in covert operations

Best Android authenticator app

Authentication and Authorization with OAuth

6 Ways to Strengthen Your Mobile App Security in 2019 and Beyond

Dear XTers,

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Mayank Choubey

Mayank Choubey

Deno, Node.js, etc.

More from Medium

Deno nuggets: TCP client

Configure Deno completions on VSCode

JSON modules in Deno

Deno by example: Content server — Part 6 Deployment on Deno Deploy