From 851837a38182e8b90f023d7134ce9866f05b7c10 Mon Sep 17 00:00:00 2001 From: libin_li Date: Sun, 17 Jan 2021 12:43:53 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=20stream=20?= =?UTF-8?q?=E7=9A=84=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/request.js | 38 ++++++++++++++++++++++++++++++-------- package.json | 1 + 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/lib/request.js b/lib/request.js index 6d108f6..8831ac6 100644 --- a/lib/request.js +++ b/lib/request.js @@ -2,6 +2,7 @@ const http = require('http'); const https = require('https'); const { URL } = require('url'); const qs = require('querystring'); +const pump = require('pump'); const Response = require('./response'); const createError = require('./createError'); const enhanceError = require('./enhanceError'); @@ -41,12 +42,20 @@ async function request(options = {}) { options.headers || {} ); const contentType = headers['Content-Type']; + const isStream = + data && + data.readable !== false && + typeof data === 'object' && + typeof data.pipe === 'function' && + typeof data._read === 'function' && + typeof data._readableState === 'object' + if ( Object.prototype.toString.call(data) === '[object Object]' && contentType.startsWith('application/x-www-form-urlencoded') ) { data = qs.stringify(data); - } else if (Object.prototype.toString.call(data) !== '[object String]') { + } else if (Object.prototype.toString.call(data) !== '[object String]' && !isStream) { data = JSON.stringify(data); } @@ -67,6 +76,13 @@ async function request(options = {}) { const resHandler = (res) => { const statusCode = res.statusCode; + + // 如果 responseType 为 stream 直接返回 + if (options.responseType === 'stream'){ + resolve(res); + return; + } + let response = new Response({ headers: res.headers, statusCode, @@ -77,6 +93,7 @@ async function request(options = {}) { response.addChunk(chunk); }); res.on('end', () => { + console.log(response.body.toString()) if (responseType === 'json' && statusCode === 200) { response.data = response.json(); } else if (responseType === 'buffer' && statusCode === 200) { @@ -91,6 +108,13 @@ async function request(options = {}) { }); }; + const errHandler = (err) => { + if (req.aborted) { + return; + } + reject(enhanceError(err, options, null, req)); + } + let req; if (protocol === 'http:') { req = http.request(requestOptions, resHandler); @@ -101,12 +125,7 @@ async function request(options = {}) { } // Handle errors - req.on('error', (err) => { - if (req.aborted) { - return; - } - reject(enhanceError(err, options, null, req)); - }); + req.on('error', errHandler); // Handle request timeout if (timeout) { @@ -116,9 +135,12 @@ async function request(options = {}) { }); } - if (data && ['GET', 'HEAD'].indexOf(method) === -1) { + if (isStream) { + return pump(data, req, (err) => err && errHandler(err)); + } else if (data && ['GET', 'HEAD'].indexOf(method) === -1) { req.write(data); } + req.end(); }); } diff --git a/package.json b/package.json index 9336f1e..ee15fd8 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ }, "homepage": "https://github.com/node-labx/lightning-request", "dependencies": { + "pump": "^3.0.0", "sync-rpc": "^1.3.6" } } From e15db93b22766fa1a8ba638c6fa99cd56bc8537e Mon Sep 17 00:00:00 2001 From: libin_li Date: Tue, 19 Jan 2021 14:31:24 +0800 Subject: [PATCH 2/5] =?UTF-8?q?delete:=20=E5=8E=BB=E6=8E=89=E8=B0=83?= =?UTF-8?q?=E8=AF=95=E7=9A=84=20console.log?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/request.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/request.js b/lib/request.js index 8831ac6..cf3f693 100644 --- a/lib/request.js +++ b/lib/request.js @@ -93,7 +93,6 @@ async function request(options = {}) { response.addChunk(chunk); }); res.on('end', () => { - console.log(response.body.toString()) if (responseType === 'json' && statusCode === 200) { response.data = response.json(); } else if (responseType === 'buffer' && statusCode === 200) { From eb64bb2cb5b29799b09e4f44a625e1bd61a85ca7 Mon Sep 17 00:00:00 2001 From: libin_li Date: Sun, 24 Jan 2021 14:04:09 +0800 Subject: [PATCH 3/5] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=20reponseType=20?= =?UTF-8?q?=E4=B8=BA=20stream=20=E7=9A=84=E8=BF=94=E5=9B=9E=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/request.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/request.js b/lib/request.js index cf3f693..78b0a4e 100644 --- a/lib/request.js +++ b/lib/request.js @@ -77,18 +77,19 @@ async function request(options = {}) { const resHandler = (res) => { const statusCode = res.statusCode; - // 如果 responseType 为 stream 直接返回 - if (options.responseType === 'stream'){ - resolve(res); - return; - } - let response = new Response({ headers: res.headers, statusCode, statusMessage: res.statusMessage, }); + // 如果 responseType 为 stream 直接返回 + if (options.responseType === 'stream') { + response.data = res; + resolve(response); + return; + } + res.on('data', (chunk) => { response.addChunk(chunk); }); @@ -130,6 +131,11 @@ async function request(options = {}) { if (timeout) { req.setTimeout(timeout, () => { req.abort(); + + if (isStream) { + data.destroy(); + } + reject(createError('timeout of ' + timeout + 'ms exceeded', options, 'ECONNABORTED', req)); }); } From 032744d4aa32ff1f1a05be362017f8b7c2a45985 Mon Sep 17 00:00:00 2001 From: libin_li Date: Sun, 24 Jan 2021 14:04:38 +0800 Subject: [PATCH 4/5] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=20stream=20?= =?UTF-8?q?=E7=9A=84=E5=8D=95=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- test/file/tmp.txt | 1 + test/request-with-stream.test.js | 64 ++++++++++++++++++++++++++++++++ test/server/index.js | 47 +++++++++++++++++++++++ 4 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 test/file/tmp.txt create mode 100644 test/request-with-stream.test.js create mode 100644 test/server/index.js diff --git a/package.json b/package.json index ee15fd8..b6175e9 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ }, "ava": { "files": [ - "test/**/*.js" + "test/**/*.test.js" ] }, "repository": { diff --git a/test/file/tmp.txt b/test/file/tmp.txt new file mode 100644 index 0000000..c317603 --- /dev/null +++ b/test/file/tmp.txt @@ -0,0 +1 @@ +this is a text file for stream test \ No newline at end of file diff --git a/test/request-with-stream.test.js b/test/request-with-stream.test.js new file mode 100644 index 0000000..60df61b --- /dev/null +++ b/test/request-with-stream.test.js @@ -0,0 +1,64 @@ +const test = require('ava'); +const fs = require('fs'); +const path = require('path'); +const request = require('../index'); +const httpServer = require('./server/index'); + +let port; +let server; + +test.before('run server', async () => { + try { + const result = await httpServer(); + port = result.port; + server = result.server; + } catch (err) { + console.log('test before hook error: ', err); + } +}); + +test.after('close server', function() { + server && server.close(); +}); + +test('put request with stream', async (t) => { + const tmp = path.join(process.cwd(), 'test/file/tmp.txt'); + const stream = fs.createReadStream(tmp); + + try { + const result = await request({ + url: 'http://localhost:' + port + '/stream', + data: stream, + method: 'PUT', + }); + + t.is(result.data, fs.readFileSync(tmp, { encoding: 'utf-8'})); + } catch (err) { + console.log(err); + t.fail(err); + } +}); + +test.cb('get request with stream responseType', (t) => { + t.plan(1); + + request({ + url: 'http://localhost:' + port + '/stream', + responseType: 'stream', + }).then((result) => { + const chunks = []; + + result.data.on('data', (buf) => chunks.push(buf)); + result.data.on('end', () => { + const datas = Buffer.concat(chunks).toString(); + const tmp = path.join(process.cwd(), 'test/file/tmp.txt'); + + t.is(datas, fs.readFileSync(tmp, { encoding: 'utf-8'})); + t.end(); + }) + }).catch((err) => { + console.log(err); + t.fail(err); + t.end(); + }); +}) diff --git a/test/server/index.js b/test/server/index.js new file mode 100644 index 0000000..85f755c --- /dev/null +++ b/test/server/index.js @@ -0,0 +1,47 @@ +const http = require('http'); +const fs = require('fs'); +const path = require('path'); + +module.exports = () => { + return new Promise((resolve, reject) => { + const server = http.createServer(function(req, res) { + const chunks = []; + + req.on('data', (buf) => { + chunks.push(buf); + }); + + req.on('end', () => { + const content = Buffer.concat(chunks).toString(); + + if (req.url === '/stream') { + if (req.method === 'PUT') { + res.end(content); + } else if (req.method === 'GET') { + const tmp = path.join(process.cwd(), 'test/file/tmp.txt'); + res.end(fs.readFileSync(tmp, { encoding: 'utf-8' })) + } + + return; + } + + res.end('test server') + }); + }); + + server.on('error', reject); + server.on('close', () => { + // console.log('test server closeed'); + }) + + server.listen(function() { + const address = server.address(); + + // console.log('test server listen at ', address); + resolve({ + port: address.port, + server, + }); + }); + }) +}; \ No newline at end of file From 1a7d9d3ebabc9a951fbe28d614a021c0875dbd5d Mon Sep 17 00:00:00 2001 From: libin_li Date: Tue, 26 Jan 2021 20:50:18 +0800 Subject: [PATCH 5/5] =?UTF-8?q?doc:=20=E4=BF=AE=E6=94=B9=20README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 14acb50..d004a4e 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ These are the available config options for making requests. Only the url is requ timeout: 1000, // default is `15000` milliseconds // `responseType` indicates the type of data that the server will respond with - // options are: 'json', 'text', 'buffer' + // options are: 'json', 'text', 'buffer','stream' responseType: 'json', // default // `agent` define a custom agent to be used when performing http or https requests,