0

Problem

Related to Get UTF-8 html content with Node's http.get - but that answer isn't working for me.

I'm trying to call the Stack Overflow API questions endpoint:

https://api.stackexchange.com/2.3/questions?site=stackoverflow&filter=total

Which should return the following JSON response:

{"total":21951385}

Example Code

I'm using the https node module to submit a get request like this:

const getRequest = (url: string) => new Promise((resolve, reject) => {
    const options: RequestOptions = {
        headers: {
            'Accept': 'text/*',
            'Accept-Encoding':'identity',
            'Accept-Charset' : 'utf8',
        }
    }

    const req = get(url, options, (res) => {
        res.setEncoding('utf8');
        let responseBody = '';
        res.on('data', (chunk) => responseBody += chunk);
        res.on('end', () => resolve(responseBody));
    });

    req.on('error', (err) => reject(err));
    req.end();
})

And then invoking it like this:

const questionsUrl = 'https://api.stackexchange.com/2.3/questions?&site=stackoverflow&filter=total'
const resp = await getRequest(questionsUrl)
console.log(resp)

However, I get the response:

▼�
�V*�/I�Q�22�454�0�♣��♥‼���↕

What I've Tried

I've tried doing several variations of the following:

  • I'm calling setEncoding to utf8 on the stream

  • I've set the Accept header to text/* - which

    Provides a text MIME type, but without a subtype

  • I've set the Accept-Encoding header to identity - which

    Indicates the identity function (that is, without modification or compression)

This code also works just fine with pretty much any other API server, for example using the following url:

https://jsonplaceholder.typicode.com/todos/1

But the StackOverlow API works anywhere else I've tried it, so there must be a way to instruct node how to execute it.

KyleMit
  • 30,350
  • 66
  • 462
  • 664

2 Answers2

2

My suggestion is to use an http library that supports both promises and gzip built in. My current favorite is got(). http.get() is like the least featured http request library anywhere. You really don't have to write all this yourself. Here's what your entire code would look like with the got() library:

const got = require('got');

function getRequest(url) {
    return got(url).json();
}

This library handles all these things you need for you automatically:

  1. Promises
  2. JSON conversion
  3. Gzip decoding
  4. 2xx status detection (other status codes like 404 are turned into a promise rejection which your code does not do).

And, it has many, many other useful features for other general use. The days of coding manually with http.get() should be long over. No need to rewrite code that has already been written and well-tested for you.

FYI, there's a list of very capable http libraries here: https://github.com/request/request/issues/3143. You can pick the one that has the API you like the best.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
0

Response Header - Content-Encoding - Gzip

As jfriend00 pointed out - looks like the server isn't respecting the Accept-Encoding value being passed and returning a gzipped response none-the-less.

Postman Headers Screenshot

Unzipping Response

According to the answer on How do I ungzip (decompress) a NodeJS request's module gzip response body?, you can unzip like this:

import { get } from 'https'
import { createGunzip } from 'zlib'

const getRequest = (url: string) => new Promise((resolve, reject) => {
    const req = get(url, (res) => {
        const buffer: string[] = [];

        if (!res.headers['content-encoding']?.includes('gzip')) {
            console.log('utf8')
            res.on('data', (chunk) => buffer.push(chunk));
            res.on('end', () => resolve(buffer.join("")))
        } else {
            console.log('gzip')
            const gunzip = createGunzip();
            res.pipe(gunzip);
            gunzip.on('data', (data) => buffer.push(data.toString()))
            gunzip.on("end", () => resolve(buffer.join("")))
            gunzip.on("error", (e) => reject(e))
        }
    });

    req.on('error', (err) => reject(err));
    req.end();
})
KyleMit
  • 30,350
  • 66
  • 462
  • 664
  • 1
    Or you could just use an http library such as [`got()`](https://www.npmjs.com/package/got) that supports promises (built-in) and gzip. `http.get()` is like the least featured http request library anywhere. You really don't have to write all this yourself. – jfriend00 Nov 28 '21 at 01:07
  • 1
    Yeah, Identity is not going to fly .... [*According to the HTTP specification identity is a valid Accept-Encoding and should instruct the server to not use compression. This is where our luck runs out as the Stack Exchange server is non-compliant here. It simply falls back to its preferred default compression: gzip. I think SE should return a 406 Not Acceptable in this case, but meh.*](https://stackapps.com/a/9217) – rene Nov 28 '21 at 06:55