Repository: sindresorhus/got (ts)

Issue:
Allow opting out of decompression
Automatically decompressing the response is really helpful for the majority of use cases. However, there may be some situations where you want to deal with the compressed data, currently there's no way to do this apart from letting Got decompress the data and then manually re-compressing it yourself afterwards. Obviously that's very inefficient.

I don't expect there to be a huge amount of use cases that would benefit from this, however it's a very simple (+2 lines) backwards compatible change so I think it's worth adding.

One use case that would massively benefit from this is implementing a reverse proxy server with Got.

This option would allow you to reverse proxy gzip responses with Got by just passing the stream directly through. Combined with the upcoming cache support (#284), I think a caching reverse proxy would be a very powerful use of Got.

Pull request description:
Add `decompress` option
Adds `opts.decompress` option. By default it's `true` and functionality remains the same. Setting `opts.decompress` to false allows you to opt out of Got's automatic decompression.

Resolves #319 

Patch:
diff --git a/.travis.yml b/.travis.yml
index ccbdd1a..f9456d3 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,7 @@
 sudo: false
 language: node_js
 node_js:
-  - '7'
+  - '8'
   - '6'
   - '4'
 after_success: npm run coveralls
diff --git a/index.js b/index.js
index 3d31fbc..aed91cd 100644
--- a/index.js
+++ b/index.js
@@ -85,9 +85,14 @@ function requestAsEventEmitter(opts) {
 			}
 
 			setImmediate(() => {
-				const response = typeof decompressResponse === 'function' &&
+				const response = opts.decompress === true &&
+					typeof decompressResponse === 'function' &&
 					req.method !== 'HEAD' ? decompressResponse(res) : res;
 
+				if (!opts.decompress && ['gzip', 'deflate'].indexOf(res.headers['content-encoding']) !== -1) {
+					opts.encoding = null;
+				}
+
 				response.redirectUrls = redirects;
 
 				ee.emit('response', response);
@@ -280,6 +285,7 @@ function normalizeArguments(url, opts) {
 		{
 			path: '',
 			retries: 2,
+			decompress: true,
 			useElectronNet: true
 		},
 		url,
diff --git a/readme.md b/readme.md
index e3219f1..bdff9c6 100644
--- a/readme.md
+++ b/readme.md
@@ -152,6 +152,15 @@ Defines if redirect responses should be followed automatically.
 Note that if a `303` is sent by the server in response to any request type (`POST`, `DELETE`, etc.), got will automatically
 request the resource pointed to in the location header via `GET`. This is in accordance with [the spec](https://tools.ietf.org/html/rfc7231#section-6.4.4).
 
+###### decompress
+
+Type: `boolean`<br>
+Default: `true`
+
+Decompress the response automatically.
+
+If this is disabled, a compressed response is returned as a `Buffer`. This may be useful if you want to handle decompression yourself or stream the raw compressed data.
+
 ###### useElectronNet
 
 Type: `boolean`<br>


Test patch:
diff --git a/test/gzip.js b/test/gzip.js
index 1ec90b5..5033509 100644
--- a/test/gzip.js
+++ b/test/gzip.js
@@ -1,15 +1,19 @@
 import zlib from 'zlib';
 import test from 'ava';
 import getStream from 'get-stream';
+import pify from 'pify';
 import got from '..';
 import {createServer} from './helpers/server';
 
 const testContent = 'Compressible response content.\n';
+const testContentUncompressed = 'Uncompressed response content.\n';
 
 let s;
+let gzipData;
 
 test.before('setup', async () => {
 	s = await createServer();
+	gzipData = await pify(zlib.gzip)(testContent);
 
 	s.on('/', (req, res) => {
 		res.statusCode = 200;
@@ -21,7 +25,7 @@ test.before('setup', async () => {
 			return;
 		}
 
-		zlib.gzip(testContent, (_, data) => res.end(data));
+		res.end(gzipData);
 	});
 
 	s.on('/corrupted', (req, res) => {
@@ -35,7 +39,13 @@ test.before('setup', async () => {
 		res.statusCode = 200;
 		res.setHeader('Content-Type', 'text/plain');
 		res.setHeader('Content-Encoding', 'gzip');
-		zlib.gzip(testContent, (_, data) => res.end(data.slice(0, -1)));
+		res.end(gzipData.slice(0, -1));
+	});
+
+	s.on('/uncompressed', (req, res) => {
+		res.statusCode = 200;
+		res.setHeader('Content-Type', 'text/plain');
+		res.end(testContentUncompressed);
 	});
 
 	await s.listen(s.port);
@@ -56,6 +66,16 @@ test('handles gzip error', async t => {
 	t.is(err.name, 'ReadError');
 });
 
+test('decompress option opts out of decompressing', async t => {
+	const response = await got(s.url, {decompress: false});
+	t.true(Buffer.compare(response.body, gzipData) === 0);
+});
+
+test('decompress option doesn\'t alter encoding of uncompressed responses', async t => {
+	const response = await got(`${s.url}/uncompressed`, {decompress: false});
+	t.is(response.body, testContentUncompressed);
+});
+
 test('preserve headers property', async t => {
 	t.truthy((await got(s.url)).headers);
 });