123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155 |
- 'use strict';
-
- const EventEmitter = require('events');
- const urlLib = require('url');
- const normalizeUrl = require('normalize-url');
- const getStream = require('get-stream');
- const CachePolicy = require('http-cache-semantics');
- const Response = require('responselike');
- const lowercaseKeys = require('lowercase-keys');
- const cloneResponse = require('clone-response');
- const Keyv = require('keyv');
-
- class CacheableRequest {
- constructor(request, cacheAdapter) {
- if (typeof request !== 'function') {
- throw new TypeError('Parameter `request` must be a function');
- }
-
- this.cache = new Keyv({
- uri: typeof cacheAdapter === 'string' && cacheAdapter,
- store: typeof cacheAdapter !== 'string' && cacheAdapter,
- namespace: 'cacheable-request'
- });
-
- return this.createCacheableRequest(request);
- }
-
- createCacheableRequest(request) {
- return (opts, cb) => {
- if (typeof opts === 'string') {
- opts = urlLib.parse(opts);
- }
- opts = Object.assign({
- headers: {},
- method: 'GET',
- cache: true,
- strictTtl: false,
- automaticFailover: false
- }, opts);
- opts.headers = lowercaseKeys(opts.headers);
-
- const ee = new EventEmitter();
- const url = normalizeUrl(urlLib.format(opts));
- const key = `${opts.method}:${url}`;
- let revalidate = false;
- let madeRequest = false;
-
- const makeRequest = opts => {
- madeRequest = true;
- const handler = response => {
- if (revalidate) {
- const revalidatedPolicy = CachePolicy.fromObject(revalidate.cachePolicy).revalidatedPolicy(opts, response);
- if (!revalidatedPolicy.modified) {
- const headers = revalidatedPolicy.policy.responseHeaders();
- response = new Response(response.statusCode, headers, revalidate.body, revalidate.url);
- response.cachePolicy = revalidatedPolicy.policy;
- response.fromCache = true;
- }
- }
-
- if (!response.fromCache) {
- response.cachePolicy = new CachePolicy(opts, response);
- response.fromCache = false;
- }
-
- let clonedResponse;
- if (opts.cache && response.cachePolicy.storable()) {
- clonedResponse = cloneResponse(response);
- getStream.buffer(response)
- .then(body => {
- const value = {
- cachePolicy: response.cachePolicy.toObject(),
- url: response.url,
- statusCode: response.fromCache ? revalidate.statusCode : response.statusCode,
- body
- };
- const ttl = opts.strictTtl ? response.cachePolicy.timeToLive() : undefined;
- return this.cache.set(key, value, ttl);
- })
- .catch(err => ee.emit('error', new CacheableRequest.CacheError(err)));
- } else if (opts.cache && revalidate) {
- this.cache.delete(key)
- .catch(err => ee.emit('error', new CacheableRequest.CacheError(err)));
- }
-
- ee.emit('response', clonedResponse || response);
- if (typeof cb === 'function') {
- cb(clonedResponse || response);
- }
- };
-
- try {
- const req = request(opts, handler);
- ee.emit('request', req);
- } catch (err) {
- ee.emit('error', new CacheableRequest.RequestError(err));
- }
- };
-
- const get = opts => Promise.resolve()
- .then(() => opts.cache ? this.cache.get(key) : undefined)
- .then(cacheEntry => {
- if (typeof cacheEntry === 'undefined') {
- return makeRequest(opts);
- }
-
- const policy = CachePolicy.fromObject(cacheEntry.cachePolicy);
- if (policy.satisfiesWithoutRevalidation(opts)) {
- const headers = policy.responseHeaders();
- const response = new Response(cacheEntry.statusCode, headers, cacheEntry.body, cacheEntry.url);
- response.cachePolicy = policy;
- response.fromCache = true;
-
- ee.emit('response', response);
- if (typeof cb === 'function') {
- cb(response);
- }
- } else {
- revalidate = cacheEntry;
- opts.headers = policy.revalidationHeaders(opts);
- makeRequest(opts);
- }
- });
-
- this.cache.on('error', err => ee.emit('error', new CacheableRequest.CacheError(err)));
-
- get(opts).catch(err => {
- if (opts.automaticFailover && !madeRequest) {
- makeRequest(opts);
- }
- ee.emit('error', new CacheableRequest.CacheError(err));
- });
-
- return ee;
- };
- }
- }
-
- CacheableRequest.RequestError = class extends Error {
- constructor(err) {
- super(err.message);
- this.name = 'RequestError';
- Object.assign(this, err);
- }
- };
-
- CacheableRequest.CacheError = class extends Error {
- constructor(err) {
- super(err.message);
- this.name = 'CacheError';
- Object.assign(this, err);
- }
- };
-
- module.exports = CacheableRequest;
|