Skip to content

Instantly share code, notes, and snippets.

@davalapar
Last active October 28, 2019 10:42
Show Gist options
  • Save davalapar/a80605f62c4ec194ebff7a7924045a2b to your computer and use it in GitHub Desktop.
Save davalapar/a80605f62c4ec194ebff7a7924045a2b to your computer and use it in GitHub Desktop.
request.js
const fs = require('fs');
const p = require('path');
const http = require('http');
const https = require('https');
const qs = require('querystring');
const request = (config) => {
if (typeof config !== 'object' || config === null) {
throw Error('invalid non-object config');
}
let url;
if (config.url === undefined) {
throw Error('invalid undefined config.url');
} else if (typeof config.url !== 'string') {
throw Error('invalid non-string config.url');
} else {
url = config.url;
}
let agent;
if (url.includes('https://') === true) {
agent = https;
} else if (url.includes('http://') === true) {
agent = http;
} else {
throw Error('invalid non-http non-https protocols');
}
let path;
if (config.path !== undefined) {
if (typeof config.path !== 'string') {
throw Error('invalid non-string config.path');
} else {
path = config.path;
}
}
let method;
if (config.method !== undefined) {
if (typeof config.method !== 'string') {
throw Error('invalid non-string config.method');
} else if (config.method !== 'GET' && config.method !== 'POST') {
throw Error('invalid non-GET & non-POST config.method');
} else {
method = config.method;
}
} else {
method = 'GET';
}
let data;
if (config.data !== undefined) {
if (config.method !== 'POST') {
throw Error('invalid non-POST config.method with non-undefined config.data');
} else if (typeof data !== 'object' || data === null) {
throw Error('invalid non-object config.data');
} else {
data = config.data;
}
}
let timeout;
if (config.timeout !== undefined) {
if (typeof config.timeout !== 'number') {
throw Error('invalid non-number config.timeout');
} else if (Number.isNaN(config.timeout) === true) {
throw Error('invalid NaN config.timeout');
} else if (Number.isFinite(config.timeout) === false) {
throw Error('invalid non-finite config.timeout');
} else {
timeout = config.timeout;
}
}
return new Promise((resolve, reject) => {
let timeoutObject;
const req = agent.request(
new URL(url),
(res) => {
const cType = res.headers['content-type'];
const cLength = Number(res.headers['content-length']);
if (Number.isNaN(cLength) === true) {
reject(new Error('RES_CLENGTH_NAN'));
}
if (res.statusCode === 200) {
if (path === undefined) {
if (timeout !== undefined) {
clearTimeout(timeoutObject);
timeoutObject = setTimeout(() => {
console.log('res :: timeout');
req.abort();
res.removeAllListeners();
res.destroy();
reject(new Error(`RES_TIMEOUT_${timeout}`));
}, timeout);
}
let buffer = Buffer.alloc(0);
res.on('data', (chunk) => {
console.log('res :: data', chunk.byteLength, buffer.byteLength, cLength, (buffer.byteLength / cLength) * 100);
buffer = Buffer.concat([buffer, chunk]);
});
res.on('end', () => {
console.log('res :: end');
if (timeout !== undefined) {
clearTimeout(timeoutObject);
}
if (cType.includes('application/json') === true) {
try {
resolve(JSON.parse(buffer.toString('utf8')));
} catch (e) {
reject(e);
}
} else if (cType.includes('text/plain') === true) {
try {
resolve(buffer.toString('utf8'));
} catch (e) {
reject(e);
}
} else {
resolve(buffer);
}
});
} else {
const dir = p.dirname(path);
if (fs.existsSync(dir) === false) {
fs.mkdirSync(dir, { recursive: true });
}
if (fs.existsSync(path) === true) {
fs.unlinkSync(path);
}
const fws = fs.createWriteStream(path, { flags: 'wx' });
if (timeout !== undefined) {
clearTimeout(timeoutObject);
timeoutObject = setTimeout(() => {
req.abort();
res.removeAllListeners();
res.destroy();
fws.close();
if (fs.existsSync(path) === true) {
fs.unlinkSync(path);
}
reject(new Error(`RES_TIMEOUT_${timeout}`));
}, timeout);
}
res.pipe(fws);
fws.on('error', (e) => {
console.log('fws :: error');
if (timeout !== undefined) {
clearTimeout(timeoutObject);
}
req.abort();
res.removeAllListeners();
res.destroy();
fws.close();
if (fs.existsSync(path) === true) {
fs.unlinkSync(path);
}
reject(e);
});
fws.on('finish', () => {
console.log('fws :: finish');
if (timeout !== undefined) {
clearTimeout(timeoutObject);
}
resolve(cLength);
});
}
} else {
req.abort();
res.removeAllListeners();
res.destroy();
reject(new Error(`RES_UNEXPECTED_${res.statusCode}`));
}
},
);
if (timeout !== undefined) {
timeoutObject = setTimeout(() => {
console.log('req :: timeout');
req.abort();
reject(new Error(`REQ_TIMEOUT_${timeout}`));
}, timeout);
}
req.on('error', (e) => {
console.log('req :: error');
if (timeout !== undefined) {
clearTimeout(timeoutObject);
}
reject(e);
});
if (method === 'POST') {
req.write(qs.stringify(data));
}
req.end();
});
};
(async () => {
/**
* - if path is specified, request returns undefined.
* - if response headers contains application/json, we parse it
* - if response headers contains text/plain, we return it
* - if timeout is specified, and triggered
*/
request({
// large file 50mb
url: 'http://212.183.159.230/50MB.zip',
// image/jpeg
// url: 'http://i3.ytimg.com/vi/J---aiyznGQ/mqdefault.jpg',
// application/json
// url: 'https://jsonplaceholder.typicode.com/todos/1',
// text/plain
// url: 'https://stackoverflow.com/robots.txt',
// path: './a/b/test.jpg',
timeout: 30000,
})
.then((result) => {
console.log({ result });
})
.catch((error) => {
console.error({ error });
});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment