85

How do I unzip a gzipped body in a request's module response?

I have tried several examples around the web but none of them appear to work.

request(url, function(err, response, body) {
    if(err) {
        handleError(err)
    } else {
        if(response.headers['content-encoding'] == 'gzip') {    
            // How can I unzip the gzipped string body variable?
            // For instance, this url:
            // http://highsnobiety.com/2012/08/25/norse-projects-fall-2012-lookbook/
            // Throws error:
            // { [Error: incorrect header check] errno: -3, code: 'Z_DATA_ERROR' }
            // Yet, browser displays page fine and debugger shows its gzipped
            // And unzipped by browser fine...
            if(response.headers['content-encoding'] && response.headers['content-encoding'].toLowerCase().indexOf('gzip') > -1) {   
                var body = response.body;                    
                zlib.gunzip(response.body, function(error, data) {
                    if(!error) {
                        response.body = data.toString();
                    } else {
                        console.log('Error unzipping:');
                        console.log(error);
                        response.body = body;
                    }
                });
            }
        }
    }
}
Alexis Tyler
  • 1,394
  • 6
  • 30
  • 48
izk
  • 1,189
  • 2
  • 8
  • 23
  • 3
    Shouldn't the browser transparently do that? – Shi Aug 27 '12 at 20:16
  • I added the node.js tag, but I get that does not make it clear... let me edit the post... – izk Aug 27 '12 at 20:20
  • can you save data to file `req.gz` and unzip it from command line? If yes, what is the output from `gunzip req.gz` and `file req.gz` – Andrey Sidorov Aug 27 '12 at 22:32
  • Hi Andrew! Thanks for the suggestion. If I save the file to a "req.gz" file, extracting it on the desktop produces a file named "req.gz.cpgz". Extacting this file in turn produces a 3rd file namded "req 2.gz". The request body was encoded to utf8 (response.setEncoding('utf8')) prior to reading the body. However, it does not seem to make a difference. I get the same error and similar desktop file results. – izk Aug 29 '12 at 17:27
  • request 3.0 will add automatic support for this once node v0.10 comes out – Jonathan Ong Feb 21 '13 at 09:07

11 Answers11

74

I couldn't get request to work either, so ended up using http instead.

var http = require("http"),
    zlib = require("zlib");

function getGzipped(url, callback) {
    // buffer to store the streamed decompression
    var buffer = [];

    http.get(url, function(res) {
        // pipe the response into the gunzip to decompress
        var gunzip = zlib.createGunzip();            
        res.pipe(gunzip);

        gunzip.on('data', function(data) {
            // decompression chunk ready, add it to the buffer
            buffer.push(data.toString())

        }).on("end", function() {
            // response and decompression complete, join the buffer and return
            callback(null, buffer.join("")); 

        }).on("error", function(e) {
            callback(e);
        })
    }).on('error', function(e) {
        callback(e)
    });
}

getGzipped(url, function(err, data) {
   console.log(data);
});
WearyMonkey
  • 2,519
  • 1
  • 24
  • 22
  • finally! I've been pover setting headers to accept gzip and trying out proxy and all kind of stuff, but this did the trick to work with the stackoverflow API! One minor thing: `var gunzip = gzip.createGunzip();` should be `var gunzip = zlib.createGunzip();` – mlunoe Apr 14 '13 at 23:23
  • I tried using all the methods for request but failed. This one works! – paradite Jul 15 '14 at 18:22
  • This works but there is a much more better and easy way to do this by setting few options on request module. Please read my answer below. – Sai Teja Nov 11 '17 at 06:44
36

try adding encoding: null to the options you pass to request, this will avoid converting the downloaded body to a string and keep it in a binary buffer.

Iftah
  • 9,512
  • 2
  • 33
  • 45
31

Like @Iftah said, set encoding: null.

Full example (less error handling):

request = require('request');
zlib = require('zlib');

request(url, {encoding: null}, function(err, response, body){
    if(response.headers['content-encoding'] == 'gzip'){
        zlib.gunzip(body, function(err, dezipped) {
            callback(dezipped.toString());
        });
    } else {
        callback(body);
    }
});
Alexey B.
  • 11,965
  • 2
  • 49
  • 73
Andrew Homeyer
  • 7,937
  • 5
  • 33
  • 26
30

Actually request module handles the gzip response. In order to tell the request module to decode the body argument in the callback function, We have to set the 'gzip' to true in the options. Let me explain you with an example.

Example:

var opts = {
  uri: 'some uri which return gzip data',
  gzip: true
}

request(opts, function (err, res, body) {
 // now body and res.body both will contain decoded content.
})

Note: The data you get on 'reponse' event is not decoded.

This works for me. Hope it works for you guys too.

The similar problem usually we ran into while working with request module is with JSON parsing. Let me explain it. If u want request module to automatically parse the body and provide you JSON content in the body argument. Then you have to set 'json' to true in the options.

var opts = {
  uri:'some uri that provides json data', 
  json: true
} 
request(opts, function (err, res, body) {
// body and res.body will contain json content
})

Reference: https://www.npmjs.com/package/request#requestoptions-callback

Sai Teja
  • 385
  • 3
  • 6
  • Thank you for this!! it works and I didn't know request-promise had a gzip flag. – Steven R Dec 30 '16 at 21:08
  • 1
    One caveat to setting the `"gzip": true` option... the server response must include a `"Content-Encoding": "gzip"` for the request module to actually decompress the response. I have been dealing with a server which doesn't set the "Content-Encoding" header properly, and I didn't find out this was required until I read the request module's source code. Hope this comment will help others save time trying to figure out why this isn't working if you're facing a similar situation. – dstricks Jul 22 '19 at 15:16
7

As seen in https://gist.github.com/miguelmota/9946206:

Both request and request-promise handle it out of the box as of Dec 2017:

var request = require('request')
  request(
    { method: 'GET'
    , uri: 'http://www.google.com'
    , gzip: true
    }
  , function (error, response, body) {
      // body is the decompressed response body
      console.log('server encoded the data as: ' + (response.headers['content-encoding'] || 'identity'))
      console.log('the decoded data is: ' + body)
    }
  )
5

I have formulated a more complete answer after trying the different ways to gunzip, and solving errors to do with encoding.

Hope this helps you too:

var request = require('request');
var zlib = require('zlib');

var options = {
  url: 'http://some.endpoint.com/api/',
  headers: {
    'X-some-headers'  : 'Some headers',
    'Accept-Encoding' : 'gzip, deflate',
  },
  encoding: null
};

request.get(options, function (error, response, body) {

  if (!error && response.statusCode == 200) {
    // If response is gzip, unzip first
    var encoding = response.headers['content-encoding']
    if (encoding && encoding.indexOf('gzip') >= 0) {
      zlib.gunzip(body, function(err, dezipped) {
        var json_string = dezipped.toString('utf-8');
        var json = JSON.parse(json_string);
        // Process the json..
      });
    } else {
      // Response is not gzipped
    }
  }

});
samwize
  • 25,675
  • 15
  • 141
  • 186
4

Here is my two cents worth. I had the same problem and found a cool library called concat-stream:

let request = require('request');
const zlib = require('zlib');
const concat = require('concat-stream');

request(url)
  .pipe(zlib.createGunzip())
  .pipe(concat(stringBuffer => {
    console.log(stringBuffer.toString());
  }));
Mark Robson
  • 1,298
  • 3
  • 15
  • 40
  • 1
    This was the only approach that worked for me when the remote file was actually a `.gz` file, pre-compressed. – Cwista Apr 15 '19 at 21:16
3

Here's a working example (using the request module for node) that gunzips the response

function gunzipJSON(response){

    var gunzip = zlib.createGunzip();
    var json = "";

    gunzip.on('data', function(data){
        json += data.toString();
    });

    gunzip.on('end', function(){
        parseJSON(json);
    });

    response.pipe(gunzip);
}

Full code: https://gist.github.com/0xPr0xy/5002984

user764155
  • 231
  • 3
  • 5
3

I'm using node-fetch. I was getting response.body, what I really wanted was await response.text().

Denis Howe
  • 2,092
  • 1
  • 23
  • 25
  • 1
    In my case, I was using ```response.json()``` so was getting some error but after using ```response.text()``` it worked. Many thanks :) – Ganesh Thorat Apr 11 '21 at 13:41
2

With got, a request alternative, you can simply do:

got(url).then(response => {
    console.log(response.body);
});

Decompression is handled automagically when needed.

Sindre Sorhus
  • 62,972
  • 39
  • 168
  • 232
1

I used the gunzipSync convenience method in nodejs to decompress the body. This avoids working with callbacks.

import * as zlib from "zlib";

const uncompressedBody:string = zlib.gunzipSync(body).toString("utf-8");

(in typescript)

Tim Van Laer
  • 2,434
  • 26
  • 30