Skip to content

Instantly share code, notes, and snippets.

@progress44
Last active July 17, 2018 09:36
Show Gist options
  • Save progress44/bba5d6c10b5022fad7bb339c2bca7c6e to your computer and use it in GitHub Desktop.
Save progress44/bba5d6c10b5022fad7bb339c2bca7c6e to your computer and use it in GitHub Desktop.
Utility wrapping requests and enabling caching with redis for http requests
const https = require('https')
const qs = require('querystring')
const redis = require("redis")
const sha1 = require("sha1")
const url = require('url')
const util = require('util')
const client = redis.createClient({
host: config.redis.host,
port: config.redis.port,
retry_strategy: (opts) => {
if (opts.error && opts.error.code === 'ECONNREFUSED') return undefined
if (opts.total_retry_time > 1000 * 60 * 60) return undefined
if (opts.attempt > 10) return undefined
}
})
client.on("error", function (err) {
console.log(err)
})
const DEFAULT_TIMEOUT_MS = 5000
const ACK_METHODS = ["POST", "GET"]
const _checkCache = function(hash) {
return new Promise((resolve, reject) => {
if (!client) return reject({})
client.get(hash, (err, resp) => {
if (!err && resp != null) {
console.log("Cache HIT for <" + hash + ">")
try {
resolve(JSON.parse(resp))
} catch (e) {
resolve(resp)
}
} else {
console.log("Cache MISS for <" + hash + ">")
reject(err)
}
})
})
}
const _clearCache = function(hash) {
return new Promise((resolve, reject) => {
try {
client.DEL(hash, (err, resp) => {
if (!err && resp) {
console.log("Cache deleted")
resolve()
} else {
console.log("Error deleting cache")
reject(err)
}
})
} catch (e) {
reject(e)
}
})
}
const _responseHandler = (res, body, hash, ttl) => {
return new Promise( (resolve, reject) => {
var payload
var responseText = body.join('').replace("])}while(1);</x>", "")
try {
payload = JSON.parse(responseText)
} catch (err) {
return reject(`Failed to parse response: ${body}`)
}
var statusCode = res.statusCode
var statusType = Math.floor(res.statusCode / 100)
if (statusType == 4 || statusType == 5) {
var err = payload && payload.errors && payload.errors[0] ? payload.errors[0] : payload
reject(err.message ? err.message : err)
} else if (statusType == 2) {
// Adding redis cache
if(client) client.set(hash, JSON.stringify(payload.data || payload))
resolve(payload.data || payload)
} else {
reject('Unexpected response')
}
})
}
const _requestStream = (res, hash, ttl) => {
return new Promise((resolve, reject) => {
var body = []
res.setEncoding('utf-8')
res.on('data', data => {
body.push(data)
} )
res.on('end', () => _responseHandler(res, body, hash, ttl).then(resolve).catch(reject) )
})
}
const _makeRequest = function (requestParams, hash) {
return new Promise((resolve, reject) => {
if (requestParams.method == "GET" && requestParams.data) {
requestParams.path += "?"
_.each(requestParams.data, (v, i) => {
requestParams.path += `${i}=${v}&`
})
requestParams.path += `t=` + Date.now()
}
var req = https.request(_.omit(requestParams, ["ttl", "data", "headers", "contentType"]), (data) => {
return _requestStream(data, hash, requestParams.ttl)
.then(resolve)
.catch(reject)
})
req.on('error', (e) => {
console.log(`<HTTP> Error: ${e.message}`)
return reject(e.message)
})
req.setHeader('Content-Type', requestParams.contentType || 'application/json')
req.setHeader('Accept', 'application/json')
req.setHeader('Accept-Charset', 'utf-8')
if (requestParams.headers && _.isObject(requestParams.headers)) {
for (var k in requestParams.headers) {
if (requestParams.headers[k]) {
req.setHeader(k, requestParams.headers[k])
} else if (null == requestParams.headers[k]) {
req.removeHeader(k)
}
}
}
req.setTimeout(DEFAULT_TIMEOUT_MS, () => {
// Aborting a request triggers the 'error' event.
req.abort()
reject("Http request aborted")
})
if (requestParams.data && requestParams.method != "GET") {
var data = requestParams.data
if (typeof data == 'object' && (!requestParams.contentType || requestParams.contentType == "application/json")) {
data = JSON.stringify(data)
}
req.write(data)
}
req.end()
})
}
const http = (options) => {
return new Promise((resolve, reject) => {
var requestParams = {
host: options.url,
hostname: options.url,
port: options.port || 443,
method: options.method,
path: options.path,
contentType: options.contentType || null,
headers: options.headers || [],
data: options.data || null,
ttl: options.ttl,
protocol: options.protocol || 'https:'
}
var urlHash = sha1(requestParams.method + "|" + requestParams.host + ":" + requestParams.port + "/" + requestParams.path + "/" + JSON.stringify(requestParams.data))
if (options.ttl === 0) _clearCache(urlHash).then(1).catch(e => console.log(`Error deleting cache ${e}`))
_checkCache(urlHash)
.then(resolve)
.catch((e) => {
return _makeRequest(requestParams, urlHash)
.then(resolve)
.catch(reject)
})
})
}
module.exports.download = function(options) {
var requestParams = {
host: options.url,
hostname: options.url,
port: options.port || 443,
method: options.method || 'GET',
path: options.path,
contentType: options.contentType || null,
headers: options.headers || [],
data: options.data || null,
ttl: options.ttl,
protocol: options.protocol || 'https:'
}
var urlHash = sha1(requestParams.method + "|" + requestParams.host + ":" + requestParams.port + "/" + requestParams.path + "/" + JSON.stringify(requestParams.data))
if (options.ttl === 0) _clearCache(urlHash).then(1).catch(e => console.log(`Error deleting cache ${e}`))
_checkCache(urlHash)
.then(resolve)
.catch((e) => {
return _makeRequest(requestParams, urlHash)
.then(resolve)
.catch(reject)
})
}
module.exports.request = http
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment