Node.js: HTTP/2 requests

Introduction

In this tutorial we will learn how to do HTTP/2 requests, using Node.js. and the fetch-h2 package.

This package offers an implementation of the Fetch API for Node.js and it supports the HTTP/2 protocol [1]. You can check the GitHub page of the package here.

The package can be installed using NPM with the following command:

npm install fetch-h2

The tests shown below were performed on Windows 8.1, using version 15.2 of Node.js and version 2.5.1 of fetch-h2.

A simple HTTP/2 GET Request

In this section we will check a very simple use case where we will do a HTTP/2 GET request to a testing server. This means that we won’t need to worry about the implementation of our own HTTP/2 server and we can focus on the client. Nonetheless, if you want to test against your own HTTP/2 server, you can check here a tutorial on how to get started.

We will start our code by importing the fetch function from the fetch-h2 module.

const { fetch } = require('fetch-h2');

After this, we can do our HTTP/2 request right away. We will perform the GET request to an endpoint that will return back to us our public IP address. If you want to test the endpoint and check the expected output, you can access it from your browser, as shown in figure 1.

Output of the HTTP/2 endpoint that returns the IP address of the client, on a web browser.
Figure 1 – Output of the endpoint that returns the IP address of the client.

So, we will call the fetch function, passing as first input a string with the endpoint we want to reach. As second input of the function, we can pass an object with some additional parameters of the rest (if not passed, the defaults will apply). In our case, we will set the HTTP method to “GET“.

fetch("https://nghttp2.org/httpbin/ip", { method: "GET" })

As output, this method returns a Promise, wrapping a Response object which will become available once the Promise is fulfilled (assuming no error occurs). As such, we will call the then method on the returned Promise to register a function that will be invoked when it is fulfilled. This method will receive as input the Response object.

.then(response => {
    // Implementation of the promise fulfilled callback function  
}) 

Now that we have access to our Response object, we will take care of printing the HTTP status response from the server. We can obtain it by accessing the status field of the Response object.

console.log(response.status);

We will also print the HTTP version used, which can be obtained from the httpVersion field.

 console.log(response.httpVersion);

To finalize, we will access the response text. This can be done with a call to the text method. Once again, this call will return a Promise, which we should also return as output of our handling function, so we can chain another then call afterwards to access the text.

return response.text(); 

The complete callback function can be seen below.

.then(response => {
    console.log(response.status);
    console.log(response.httpVersion);

    return response.text(); 
})

Like mentioned before, the call to the text method returned a Promise, wrapping the actual text from the response. As such, we will register a second callback, which will simply print that text.

.then(text => console.log(text));

The complete code can be seen below.

const { fetch } = require('fetch-h2');

fetch("https://nghttp2.org/httpbin/ip", { method: "GET" })
.then(response => {
    console.log(response.status);
    console.log(response.httpVersion);

    return response.text(); 
}) 
.then(text => console.log(text));

Upon running the code in a tool of your choice (I’m using Visual Studio Code with the Code Runner extension) you should get an output similar to figure 2. As can be seen, a 200 status code was returned by the server and the HTTP version used was 2. Additionally, we have obtained a body containing a JSON object with the IP address of the client (notice that the payload is in the same format as we obtained in figure 1, when reaching the same endpoint using a web browser).

Output of the HTTP/2 GET request.
Figure 2 – Output of the HTTP/2 GET request.

Passing a request body

Now that we have learned how to do a simple HTTP/2 GET, in this section we will check how to pass a body in our request. For that, we are going to use this testing endpoint that receives a PUT request and returns to us the content of the body capitalized. Note that this time we won’t be able to check it in our browser by accessing the URL because it won’t accept a GET request.

Like before, we start by importing the fetch function.

const { fetch } = require('fetch-h2');

After that, we call the function and pass as first input the endpoint we want to reach, as a string. As second input we pass the object with the additional parameters needed. First, we will set the method to “PUT“. Then, we will also add to the object a property called body, to which we assign our request body. In this case, we will use a “test” string, meaning that we expect to obtain a capitalized “TEST” in our response.

fetch("https://http2.golang.org/ECHO", { method: "PUT", body: "test" })

The rest of the code is similar to what we have done before. For simplicity, the won’t print the status code or the HTTP version this time.

const { fetch } = require('fetch-h2');

fetch("https://http2.golang.org/ECHO", { method: "PUT", body: "test" })
.then(response => response.text()) 
.then(text => console.log(text));

Upon running the code, you should get an output similar to figure 3. As can be seen, we obtained our capitalized response, as expected.

Output of the HTTP/2 PUT request.
Figure 3 – Output of the HTTP/2 PUT request.

Body as stream

So far, we have always obtained the body as a whole. Nonetheless, the library allows us to access the body as a Node.js ReadableStream. To test it, we will use this endpoint, which streams the current time every second. Since it answers to a HTTP GET request, you can test it in your browser, which should give a result similar to figure 4.

Output of the clock stream HTTP/2 endpoint.
Figure 4 – Output of the clock stream HTTP/2 endpoint.

Like before, we import the fetch function and call it afterwards.

const { fetch } = require('fetch-h2');

fetch("https://http2.golang.org/clockstream", { method: "GET" })

In the Promise handling function, we now need to call the readable method on our Response object. This call will return another Promise, wrapping a ReadableStream.

.then(response => response.readable()) 

Like before, we need to register a handling function for when this Promise is resolved. That handling function will receive the ReadableStream.

.then(stream => {
      // handling function
});

In its implementation we will first set the encoding of the data read from the stream to UTF8, with a call to the setEncoding method.

stream.setEncoding('utf8');

Then, we will register an handling function for the “data” event. In the implementation of this function, we will simply print the data we have received from the server.

 stream.on('data', data => console.log(data));

The complete code can be seen in the snippet below.

const { fetch } = require('fetch-h2');

fetch("https://http2.golang.org/clockstream", { method: "GET" })
.then(response => response.readable()) 
.then(stream => {
    
    stream.setEncoding('utf8');

    stream.on('data', data => console.log(data));
});

After running the code, you should get the time printed every second, like shown in figure 5.

HTTP/2 request stream content getting printed.
Figure 5 – HTTP/2 request stream content getting printed.

Related Posts

References

[1] https://www.npmjs.com/package/fetch-h2

Leave a Reply

%d bloggers like this: