Node.js Find Fastest HTTP/2 Servers from DoH List

·

2 min read

HTTP/2 is particularly beneficial for DoH(DNS over HTTPS) because it allows multiple DNS queries to be sent over the same connection without waiting for previous responses, thanks to its support for multiplexing. This can significantly improve the performance of DNS resolutions, especially when multiple queries are being made.

When hundreds of servers are publicly available, we can use the Node.js HTTP/2 module to find the fastest ones that respond within duration (such as 1s).

First, let's define a function check_url that returns a promise and resolves if the HTTP/2 request is successful.

const http2 = require('http2');

const timeoutDuration = 1000; // 1 seconds

function check_url(URL) {

    let fnResolve, fnReject;
    let myPromise = new Promise((resolve, reject) => {

        fnResolve = resolve;
        fnReject = reject;
    });

    const client = http2.connect(URL);//https://dns.alidns.com , https://dns.quad9.net

    const req = client.request({ ':path': '/dns-query?dns=AAABAAABAAAAAAAAA3d3dwdleGFtcGxlA2NvbQAAAQAB' });

    req.on('response', (headers, flags) => {
        for (const name in headers) {
            console.log(`${name}: ${headers[name]}`);
        }

        fnResolve('    ' + URL);
    });

    return myPromise;
}

Let's also add logic to reject the promise if an error or timeout occurs.

    req.on('error', (err) => {
        console.error('ClientHttp2Stream error: ' + err);
        req.close(http2.constants.NGHTTP2_CANCEL);

        fnReject('!!! ' + URL)
    });

    // Set a timeout on the request
    req.setTimeout(timeoutDuration, () => {
        console.error(`ClientHttp2Stream ${URL} timed out`);

        req.destroy();

        fnReject('--- ' + URL)
    });

Finally, add a deal function to close the HTTP/2 connection when the request stream is complete and close (remember that HTTP/2 allows multiple concurrent exchanges on the same connection, so the connection will not close automatically).

req.on('close', (e) => {
    console.log( 'ClientHttp2Stream is closed' )
    client.close();
})

Now we can use a recursive function to check urls from a list one by one and return the fastest server that responds under 1s.

let dohs = ["https://dns.google/dns-query",
    "https://cloudflare-dns.com/dns-query",
    "https://dns.alidns.com/dns-query",
    "https://dns.quad9.net/dns-query"];

/*
* check_next check doh[index], then doh[++index]...
* show: 
*      0 show urls response within timeoutDuration
*      1 show urls response great than timeoutDuration
*      2 don't show urls, for debug
*/
function check_next(index = 0, { show = 0 } = {}) {
    if (index >= dohs.length || index < 0) return;

    check_url(dohs[index])
        .then(URL => { if (0 === show) console.log(URL) })
        .catch(error => { if (1 === show) console.log(error) })
        .finally(() => {
            //only check_next when last check is done
            check_next(++index, { show })
        });
}

check_next(0, { show: 0 })

Run node code and get the fastest list

node find-fastest-doh-servers.js
# output
#    https://dns.alidns.com/dns-query
#    https://dns.quad9.net/dns-query

The full code can be found on GitHub Gist

https://gist.github.com/likev/7c26cec0103b3187ddcb793f94ae593c