Skip to content

Commit 340abe0

Browse files
committed
feat(gzip): add opts.gzip convenience opt
1 parent 9287951 commit 340abe0

File tree

4 files changed

+119
-3
lines changed

4 files changed

+119
-3
lines changed

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,15 @@ packages.
214214
See also [`opts.retry`](#opts-retry) to provide all retry options as a single
215215
object.
216216

217+
##### <a name="opts-gzip"></a> `opts.gzip`
218+
219+
* Type: Boolean
220+
* Default: false
221+
222+
If true, `npm-registry-fetch` will set the `Content-Encoding` header to `gzip`
223+
and use `zlib.gzip()` or `zlib.createGzip()` to gzip-encode
224+
[`opts.body`](#opts-body).
225+
217226
##### <a name="opts-headers"></a> `opts.headers`
218227

219228
* Type: Object

config.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ module.exports = figgyPudding({
1818
'fetch-retry-maxtimeout': {},
1919
'fetch-retry-mintimeout': {},
2020
'gid': {},
21+
'gzip': {},
2122
'headers': {},
2223
'https-proxy': {},
2324
'integrity': {},
@@ -56,7 +57,7 @@ module.exports = figgyPudding({
5657
'prefer-online': {},
5758
'projectScope': {},
5859
'project-scope': 'projectScope',
59-
'Promise': {},
60+
'Promise': {default: () => Promise},
6061
'proxy': {},
6162
'query': {},
6263
'refer': {},

index.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const fetch = require('make-fetch-happen')
99
const npa = require('npm-package-arg')
1010
const qs = require('querystring')
1111
const url = require('url')
12+
const zlib = require('zlib')
1213

1314
module.exports = regFetch
1415
function regFetch (uri, opts) {
@@ -38,6 +39,18 @@ function regFetch (uri, opts) {
3839
} else if (body && !headers['content-type']) {
3940
headers['content-type'] = 'application/octet-stream'
4041
}
42+
if (opts.gzip) {
43+
headers['content-encoding'] = 'gzip'
44+
if (bodyIsStream) {
45+
const gz = zlib.createGzip()
46+
body.on('error', err => gz.emit('error', err))
47+
body = body.pipe(gz)
48+
} else {
49+
body = new opts.Promise((resolve, reject) => {
50+
zlib.gzip(body, (err, gz) => err ? reject(err) : resolve(gz))
51+
})
52+
}
53+
}
4154
if (opts.get('query')) {
4255
let q = opts.get('query')
4356
if (typeof q === 'string') {
@@ -51,7 +64,7 @@ function regFetch (uri, opts) {
5164
)
5265
uri = url.format(parsed)
5366
}
54-
return fetch(uri, {
67+
return opts.Promise.resolve(body).then(body => fetch(uri, {
5568
agent: opts.get('agent'),
5669
algorithms: opts.get('algorithms'),
5770
body,
@@ -82,7 +95,7 @@ function regFetch (uri, opts) {
8295
gid: opts.get('gid')
8396
}).then(res => checkResponse(
8497
opts.get('method') || 'GET', res, registry, startTime, opts
85-
))
98+
)))
8699
}
87100

88101
module.exports.json = fetchJSON

test/index.js

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const silentLog = require('../silentlog.js')
88
const ssri = require('ssri')
99
const test = require('tap').test
1010
const tnock = require('./util/tnock.js')
11+
const zlib = require('zlib')
1112

1213
const fetch = require('../index.js')
1314

@@ -117,6 +118,98 @@ test('stream body param', t => {
117118
.then(json => t.deepEqual(json, {hello: 'world'}))
118119
})
119120

121+
test('JSON body param', t => {
122+
tnock(t, OPTS.registry)
123+
.matchHeader('content-type', ctype => {
124+
t.equal(ctype[0], 'application/json', 'content-type automatically set')
125+
return ctype[0] === 'application/json'
126+
})
127+
.matchHeader('content-encoding', enc => {
128+
t.equal(enc[0], 'gzip', 'content-encoding automatically set')
129+
return enc[0] === 'gzip'
130+
})
131+
.post('/hello')
132+
// NOTE: can't really test the body itself here because nock freaks out.
133+
.reply(200)
134+
const opts = Object.assign({
135+
method: 'POST',
136+
body: {hello: 'world'},
137+
gzip: true
138+
}, OPTS)
139+
return fetch('/hello', opts)
140+
.then(res => {
141+
t.equal(res.status, 200, 'request succeeded')
142+
})
143+
})
144+
145+
test('gzip + buffer body param', t => {
146+
tnock(t, OPTS.registry)
147+
.matchHeader('content-type', ctype => {
148+
t.equal(ctype[0], 'application/octet-stream', 'content-type automatically set')
149+
return ctype[0] === 'application/octet-stream'
150+
})
151+
.matchHeader('content-encoding', enc => {
152+
t.equal(enc[0], 'gzip', 'content-encoding automatically set')
153+
return enc[0] === 'gzip'
154+
})
155+
.post('/hello')
156+
.reply(200, (uri, reqBody) => {
157+
reqBody = zlib.gunzipSync(Buffer.from(reqBody, 'hex'))
158+
t.deepEqual(
159+
Buffer.from(reqBody, 'utf8').toString('utf8'),
160+
'hello',
161+
'got the JSON version of the body'
162+
)
163+
return reqBody
164+
})
165+
const opts = Object.assign({
166+
method: 'POST',
167+
body: Buffer.from('hello', 'utf8'),
168+
gzip: true
169+
}, OPTS)
170+
return fetch('/hello', opts)
171+
.then(res => {
172+
t.equal(res.status, 200)
173+
return res.buffer()
174+
})
175+
.then(buf =>
176+
t.deepEqual(buf, Buffer.from('hello', 'utf8'), 'got response')
177+
)
178+
})
179+
180+
test('gzip + stream body param', t => {
181+
tnock(t, OPTS.registry)
182+
.matchHeader('content-type', ctype => {
183+
t.equal(ctype[0], 'application/octet-stream', 'content-type automatically set')
184+
return ctype[0] === 'application/octet-stream'
185+
})
186+
.matchHeader('content-encoding', enc => {
187+
t.equal(enc[0], 'gzip', 'content-encoding automatically set')
188+
return enc[0] === 'gzip'
189+
})
190+
.post('/hello')
191+
.reply(200, (uri, reqBody) => {
192+
reqBody = zlib.gunzipSync(Buffer.from(reqBody, 'hex'))
193+
t.deepEqual(JSON.parse(reqBody.toString('utf8')), {
194+
hello: 'world'
195+
}, 'got the stringified version of the body')
196+
return reqBody
197+
})
198+
const stream = new PassThrough()
199+
setImmediate(() => stream.end(JSON.stringify({hello: 'world'})))
200+
const opts = Object.assign({
201+
method: 'POST',
202+
body: stream,
203+
gzip: true
204+
}, OPTS)
205+
return fetch('/hello', opts)
206+
.then(res => {
207+
t.equal(res.status, 200)
208+
return res.json()
209+
})
210+
.then(json => t.deepEqual(json, {hello: 'world'}))
211+
})
212+
120213
test('query strings', t => {
121214
tnock(t, OPTS.registry)
122215
.get('/hello?hi=there&who=wor%20ld')

0 commit comments

Comments
 (0)