Software zum Installieren eines Smart-Mirror Frameworks , zum Nutzen von hochschulrelevanten Informationen, auf einem Raspberry-Pi.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.js 6.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. 'use strict';
  2. const EventEmitter = require('events');
  3. const urlLib = require('url');
  4. const normalizeUrl = require('normalize-url');
  5. const getStream = require('get-stream');
  6. const CachePolicy = require('http-cache-semantics');
  7. const Response = require('responselike');
  8. const lowercaseKeys = require('lowercase-keys');
  9. const cloneResponse = require('clone-response');
  10. const Keyv = require('keyv');
  11. class CacheableRequest {
  12. constructor(request, cacheAdapter) {
  13. if (typeof request !== 'function') {
  14. throw new TypeError('Parameter `request` must be a function');
  15. }
  16. this.cache = new Keyv({
  17. uri: typeof cacheAdapter === 'string' && cacheAdapter,
  18. store: typeof cacheAdapter !== 'string' && cacheAdapter,
  19. namespace: 'cacheable-request'
  20. });
  21. return this.createCacheableRequest(request);
  22. }
  23. createCacheableRequest(request) {
  24. return (opts, cb) => {
  25. let url;
  26. if (typeof opts === 'string') {
  27. url = normalizeUrlObject(urlLib.parse(opts));
  28. opts = {};
  29. } else if (opts instanceof urlLib.URL) {
  30. url = normalizeUrlObject(urlLib.parse(opts.toString()));
  31. opts = {};
  32. } else {
  33. const [pathname, ...searchParts] = (opts.path || '').split('?');
  34. const search = searchParts.length > 0 ?
  35. `?${searchParts.join('?')}` :
  36. '';
  37. url = normalizeUrlObject({ ...opts, pathname, search });
  38. }
  39. opts = {
  40. headers: {},
  41. method: 'GET',
  42. cache: true,
  43. strictTtl: false,
  44. automaticFailover: false,
  45. ...opts,
  46. ...urlObjectToRequestOptions(url)
  47. };
  48. opts.headers = lowercaseKeys(opts.headers);
  49. const ee = new EventEmitter();
  50. const normalizedUrlString = normalizeUrl(
  51. urlLib.format(url),
  52. {
  53. stripWWW: false,
  54. removeTrailingSlash: false,
  55. stripAuthentication: false
  56. }
  57. );
  58. const key = `${opts.method}:${normalizedUrlString}`;
  59. let revalidate = false;
  60. let madeRequest = false;
  61. const makeRequest = opts => {
  62. madeRequest = true;
  63. let requestErrored = false;
  64. let requestErrorCallback;
  65. const requestErrorPromise = new Promise(resolve => {
  66. requestErrorCallback = () => {
  67. if (!requestErrored) {
  68. requestErrored = true;
  69. resolve();
  70. }
  71. };
  72. });
  73. const handler = response => {
  74. if (revalidate && !opts.forceRefresh) {
  75. response.status = response.statusCode;
  76. const revalidatedPolicy = CachePolicy.fromObject(revalidate.cachePolicy).revalidatedPolicy(opts, response);
  77. if (!revalidatedPolicy.modified) {
  78. const headers = revalidatedPolicy.policy.responseHeaders();
  79. response = new Response(revalidate.statusCode, headers, revalidate.body, revalidate.url);
  80. response.cachePolicy = revalidatedPolicy.policy;
  81. response.fromCache = true;
  82. }
  83. }
  84. if (!response.fromCache) {
  85. response.cachePolicy = new CachePolicy(opts, response, opts);
  86. response.fromCache = false;
  87. }
  88. let clonedResponse;
  89. if (opts.cache && response.cachePolicy.storable()) {
  90. clonedResponse = cloneResponse(response);
  91. (async () => {
  92. try {
  93. const bodyPromise = getStream.buffer(response);
  94. await Promise.race([
  95. requestErrorPromise,
  96. new Promise(resolve => response.once('end', resolve))
  97. ]);
  98. if (requestErrored) {
  99. return;
  100. }
  101. const body = await bodyPromise;
  102. const value = {
  103. cachePolicy: response.cachePolicy.toObject(),
  104. url: response.url,
  105. statusCode: response.fromCache ? revalidate.statusCode : response.statusCode,
  106. body
  107. };
  108. let ttl = opts.strictTtl ? response.cachePolicy.timeToLive() : undefined;
  109. if (opts.maxTtl) {
  110. ttl = ttl ? Math.min(ttl, opts.maxTtl) : opts.maxTtl;
  111. }
  112. await this.cache.set(key, value, ttl);
  113. } catch (error) {
  114. ee.emit('error', new CacheableRequest.CacheError(error));
  115. }
  116. })();
  117. } else if (opts.cache && revalidate) {
  118. (async () => {
  119. try {
  120. await this.cache.delete(key);
  121. } catch (error) {
  122. ee.emit('error', new CacheableRequest.CacheError(error));
  123. }
  124. })();
  125. }
  126. ee.emit('response', clonedResponse || response);
  127. if (typeof cb === 'function') {
  128. cb(clonedResponse || response);
  129. }
  130. };
  131. try {
  132. const req = request(opts, handler);
  133. req.once('error', requestErrorCallback);
  134. req.once('abort', requestErrorCallback);
  135. ee.emit('request', req);
  136. } catch (error) {
  137. ee.emit('error', new CacheableRequest.RequestError(error));
  138. }
  139. };
  140. (async () => {
  141. const get = async opts => {
  142. await Promise.resolve();
  143. const cacheEntry = opts.cache ? await this.cache.get(key) : undefined;
  144. if (typeof cacheEntry === 'undefined') {
  145. return makeRequest(opts);
  146. }
  147. const policy = CachePolicy.fromObject(cacheEntry.cachePolicy);
  148. if (policy.satisfiesWithoutRevalidation(opts) && !opts.forceRefresh) {
  149. const headers = policy.responseHeaders();
  150. const response = new Response(cacheEntry.statusCode, headers, cacheEntry.body, cacheEntry.url);
  151. response.cachePolicy = policy;
  152. response.fromCache = true;
  153. ee.emit('response', response);
  154. if (typeof cb === 'function') {
  155. cb(response);
  156. }
  157. } else {
  158. revalidate = cacheEntry;
  159. opts.headers = policy.revalidationHeaders(opts);
  160. makeRequest(opts);
  161. }
  162. };
  163. const errorHandler = error => ee.emit('error', new CacheableRequest.CacheError(error));
  164. this.cache.once('error', errorHandler);
  165. ee.on('response', () => this.cache.removeListener('error', errorHandler));
  166. try {
  167. await get(opts);
  168. } catch (error) {
  169. if (opts.automaticFailover && !madeRequest) {
  170. makeRequest(opts);
  171. }
  172. ee.emit('error', new CacheableRequest.CacheError(error));
  173. }
  174. })();
  175. return ee;
  176. };
  177. }
  178. }
  179. function urlObjectToRequestOptions(url) {
  180. const options = { ...url };
  181. options.path = `${url.pathname || '/'}${url.search || ''}`;
  182. delete options.pathname;
  183. delete options.search;
  184. return options;
  185. }
  186. function normalizeUrlObject(url) {
  187. // If url was parsed by url.parse or new URL:
  188. // - hostname will be set
  189. // - host will be hostname[:port]
  190. // - port will be set if it was explicit in the parsed string
  191. // Otherwise, url was from request options:
  192. // - hostname or host may be set
  193. // - host shall not have port encoded
  194. return {
  195. protocol: url.protocol,
  196. auth: url.auth,
  197. hostname: url.hostname || url.host || 'localhost',
  198. port: url.port,
  199. pathname: url.pathname,
  200. search: url.search
  201. };
  202. }
  203. CacheableRequest.RequestError = class extends Error {
  204. constructor(error) {
  205. super(error.message);
  206. this.name = 'RequestError';
  207. Object.assign(this, error);
  208. }
  209. };
  210. CacheableRequest.CacheError = class extends Error {
  211. constructor(error) {
  212. super(error.message);
  213. this.name = 'CacheError';
  214. Object.assign(this, error);
  215. }
  216. };
  217. module.exports = CacheableRequest;