@podium/client
Advanced tools
Comparing version 5.0.0-next.11 to 5.0.0-next.12
@@ -0,1 +1,8 @@ | ||
# [5.0.0-next.12](https://github.com/podium-lib/client/compare/v5.0.0-next.11...v5.0.0-next.12) (2022-09-20) | ||
### Features | ||
* replace request with undici for http requests ([31163db](https://github.com/podium-lib/client/commit/31163db538d87cb797135d7febcc6cc244eac17a)) | ||
# [5.0.0-next.11](https://github.com/podium-lib/client/compare/v5.0.0-next.10...v5.0.0-next.11) (2022-09-08) | ||
@@ -2,0 +9,0 @@ |
/* eslint-disable no-param-reassign */ | ||
import { pipeline } from 'stream'; | ||
import Metrics from '@metrics/client'; | ||
import request from 'request'; | ||
import abslog from 'abslog'; | ||
import * as putils from '@podium/utils'; | ||
import { Boom, badGateway } from '@hapi/boom'; | ||
import { join, dirname } from 'path'; | ||
@@ -15,6 +12,10 @@ import { fileURLToPath } from 'url'; | ||
import Response from './response.js'; | ||
import HTTP from './http.js'; | ||
const currentDirectory = dirname(fileURLToPath(import.meta.url)); | ||
const pkgJson = fs.readFileSync(join(currentDirectory, '../package.json'), 'utf-8'); | ||
const pkgJson = fs.readFileSync( | ||
join(currentDirectory, '../package.json'), | ||
'utf-8', | ||
); | ||
const pkg = JSON.parse(pkgJson); | ||
@@ -28,9 +29,7 @@ | ||
#histogram; | ||
#httpAgent; | ||
#httpsAgent; | ||
#http; | ||
constructor(options = {}) { | ||
this.#http = options.http || new HTTP(); | ||
const name = options.clientName; | ||
this.#log = abslog(options.logger); | ||
this.#httpAgent = options.httpAgent; | ||
this.#httpsAgent = options.httpsAgent; | ||
this.#metrics = new Metrics(); | ||
@@ -48,3 +47,3 @@ this.#histogram = this.#metrics.histogram({ | ||
this.#metrics.on('error', error => { | ||
this.#metrics.on('error', (error) => { | ||
this.#log.error( | ||
@@ -61,203 +60,173 @@ 'Error emitted by metric stream in @podium/client module', | ||
resolve(outgoing) { | ||
return new Promise((resolve, reject) => { | ||
if (outgoing.kill && outgoing.throwable) { | ||
this.#log.warn( | ||
`recursion detected - failed to resolve fetching of podlet ${outgoing.recursions} times - throwing - resource: ${outgoing.name} - url: ${outgoing.manifestUri}`, | ||
); | ||
reject( | ||
badGateway( | ||
`Recursion detected - failed to resolve fetching of podlet ${outgoing.recursions} times`, | ||
), | ||
); | ||
return; | ||
} | ||
async resolve(outgoing) { | ||
if (outgoing.kill && outgoing.throwable) { | ||
this.#log.warn( | ||
`recursion detected - failed to resolve fetching of podlet ${outgoing.recursions} times - throwing - resource: ${outgoing.name} - url: ${outgoing.manifestUri}`, | ||
); | ||
throw badGateway( | ||
`Recursion detected - failed to resolve fetching of podlet ${outgoing.recursions} times`, | ||
); | ||
} | ||
if (outgoing.kill) { | ||
this.#log.warn( | ||
`recursion detected - failed to resolve fetching of podlet ${outgoing.recursions} times - serving fallback - resource: ${outgoing.name} - url: ${outgoing.manifestUri}`, | ||
); | ||
outgoing.success = true; | ||
outgoing.pushFallback(); | ||
resolve(outgoing); | ||
return; | ||
} | ||
if (outgoing.kill) { | ||
this.#log.warn( | ||
`recursion detected - failed to resolve fetching of podlet ${outgoing.recursions} times - serving fallback - resource: ${outgoing.name} - url: ${outgoing.manifestUri}`, | ||
); | ||
outgoing.success = true; | ||
outgoing.pushFallback(); | ||
return outgoing; | ||
} | ||
if (outgoing.status === 'empty' && outgoing.throwable) { | ||
this.#log.warn( | ||
`no manifest available - cannot read content - throwing - resource: ${outgoing.name} - url: ${outgoing.manifestUri}`, | ||
); | ||
reject( | ||
badGateway(`No manifest available - Cannot read content`), | ||
); | ||
return; | ||
} | ||
if (outgoing.status === 'empty' && outgoing.throwable) { | ||
this.#log.warn( | ||
`no manifest available - cannot read content - throwing - resource: ${outgoing.name} - url: ${outgoing.manifestUri}`, | ||
); | ||
throw badGateway(`No manifest available - Cannot read content`); | ||
} | ||
if (outgoing.status === 'empty') { | ||
this.#log.warn( | ||
`no manifest available - cannot read content - serving fallback - resource: ${outgoing.name} - url: ${outgoing.manifestUri}`, | ||
); | ||
outgoing.success = true; | ||
outgoing.pushFallback(); | ||
resolve(outgoing); | ||
return; | ||
} | ||
if (outgoing.status === 'empty') { | ||
this.#log.warn( | ||
`no manifest available - cannot read content - serving fallback - resource: ${outgoing.name} - url: ${outgoing.manifestUri}`, | ||
); | ||
outgoing.success = true; | ||
outgoing.pushFallback(); | ||
return outgoing; | ||
} | ||
const headers = { | ||
...outgoing.reqOptions.headers, | ||
'User-Agent': UA_STRING, | ||
}; | ||
const headers = { | ||
...outgoing.reqOptions.headers, | ||
'User-Agent': UA_STRING, | ||
}; | ||
putils.serializeContext(headers, outgoing.context, outgoing.name); | ||
putils.serializeContext(headers, outgoing.context, outgoing.name); | ||
const reqOptions = { | ||
rejectUnauthorized: outgoing.rejectUnauthorized, | ||
timeout: outgoing.timeout, | ||
method: 'GET', | ||
agent: outgoing.contentUri.startsWith('https://') | ||
? this.#httpsAgent | ||
: this.#httpAgent, | ||
uri: putils.uriBuilder( | ||
outgoing.reqOptions.pathname, | ||
outgoing.contentUri, | ||
), | ||
qs: outgoing.reqOptions.query, | ||
headers, | ||
}; | ||
const uri = putils.uriBuilder( | ||
outgoing.reqOptions.pathname, | ||
outgoing.contentUri, | ||
) | ||
if (outgoing.redirectable) { | ||
reqOptions.followRedirect = false; | ||
} | ||
const reqOptions = { | ||
rejectUnauthorized: outgoing.rejectUnauthorized, | ||
bodyTimeout: outgoing.timeout, | ||
method: 'GET', | ||
query: outgoing.reqOptions.query, | ||
headers, | ||
}; | ||
const timer = this.#histogram.timer({ | ||
labels: { | ||
podlet: outgoing.name, | ||
}, | ||
}); | ||
if (outgoing.redirectable) { | ||
reqOptions.follow = false; | ||
} | ||
this.#log.debug( | ||
`start reading content from remote resource - manifest version is ${outgoing.manifest.version} - resource: ${outgoing.name} - url: ${outgoing.contentUri}`, | ||
); | ||
const timer = this.#histogram.timer({ | ||
labels: { | ||
podlet: outgoing.name, | ||
}, | ||
}); | ||
const r = request(reqOptions); | ||
this.#log.debug( | ||
`start reading content from remote resource - manifest version is ${outgoing.manifest.version} - resource: ${outgoing.name} - url: ${outgoing.contentUri}`, | ||
); | ||
r.on('response', response => { | ||
// Remote responds but with an http error code | ||
const resError = response.statusCode >= 400; | ||
if (resError && outgoing.throwable) { | ||
timer({ | ||
labels: { | ||
status: 'failure', | ||
}, | ||
}); | ||
try { | ||
const { | ||
statusCode, | ||
headers: hdrs, | ||
body, | ||
} = await this.#http.request(uri, reqOptions); | ||
this.#log.debug( | ||
`remote resource responded with non 200 http status code for content - code: ${response.statusCode} - resource: ${outgoing.name} - url: ${outgoing.contentUri}`, | ||
); | ||
// Remote responds but with an http error code | ||
const resError = statusCode >= 400; | ||
if (resError && outgoing.throwable) { | ||
timer({ | ||
labels: { | ||
status: 'failure', | ||
}, | ||
}); | ||
const errorMessage = `Could not read content. Resource responded with ${response.statusCode} on ${outgoing.contentUri}`; | ||
this.#log.debug( | ||
`remote resource responded with non 200 http status code for content - code: ${statusCode} - resource: ${outgoing.name} - url: ${outgoing.contentUri}`, | ||
); | ||
const errorOptions = { | ||
statusCode: response.statusCode, | ||
decorate: { | ||
statusCode: response.statusCode, | ||
}, | ||
}; | ||
const errorMessage = `Could not read content. Resource responded with ${statusCode} on ${outgoing.contentUri}`; | ||
reject(new Boom(errorMessage, errorOptions)); | ||
return; | ||
} | ||
if (resError) { | ||
timer({ | ||
labels: { | ||
status: 'failure', | ||
}, | ||
}); | ||
const errorOptions = { | ||
statusCode, | ||
decorate: { | ||
statusCode, | ||
}, | ||
}; | ||
this.#log.warn( | ||
`remote resource responded with non 200 http status code for content - code: ${response.statusCode} - resource: ${outgoing.name} - url: ${outgoing.contentUri}`, | ||
); | ||
outgoing.success = true; | ||
outgoing.pushFallback(); | ||
resolve(outgoing); | ||
return; | ||
} | ||
throw new Boom(errorMessage, errorOptions); | ||
} | ||
if (resError) { | ||
timer({ | ||
labels: { | ||
status: 'failure', | ||
}, | ||
}); | ||
const contentVersion = utils.isHeaderDefined( | ||
response.headers, | ||
'podlet-version', | ||
) | ||
? response.headers['podlet-version'] | ||
: undefined; | ||
this.#log.debug( | ||
`got head response from remote resource for content - header version is ${response.headers['podlet-version']} - resource: ${outgoing.name} - url: ${outgoing.contentUri}`, | ||
this.#log.warn( | ||
`remote resource responded with non 200 http status code for content - code: ${statusCode} - resource: ${outgoing.name} - url: ${outgoing.contentUri}`, | ||
); | ||
outgoing.success = true; | ||
outgoing.pushFallback(); | ||
return outgoing; | ||
} | ||
if ( | ||
contentVersion !== outgoing.manifest.version && | ||
contentVersion !== undefined | ||
) { | ||
timer({ | ||
labels: { | ||
status: 'success', | ||
}, | ||
}); | ||
const contentVersion = utils.isHeaderDefined(hdrs, 'podlet-version') | ||
? hdrs['podlet-version'] | ||
: undefined; | ||
this.#log.info( | ||
`podlet version number in header differs from cached version number - aborting request to remote resource for content - resource: ${outgoing.name} - url: ${outgoing.contentUri}`, | ||
); | ||
r.abort(); | ||
outgoing.status = 'stale'; | ||
return; | ||
} | ||
this.#log.debug( | ||
`got head response from remote resource for content - header version is ${hdrs['podlet-version']} - resource: ${outgoing.name} - url: ${outgoing.contentUri}`, | ||
); | ||
outgoing.success = true; | ||
outgoing.headers = response.headers; | ||
if ( | ||
contentVersion !== outgoing.manifest.version && | ||
contentVersion !== undefined | ||
) { | ||
timer({ | ||
labels: { | ||
status: 'success', | ||
}, | ||
}); | ||
if (outgoing.redirectable && response.statusCode >= 300) { | ||
outgoing.redirect = { | ||
statusCode: response.statusCode, | ||
location: response.headers && response.headers.location, | ||
}; | ||
} | ||
outgoing.emit( | ||
'beforeStream', | ||
new Response({ | ||
headers: outgoing.headers, | ||
js: outgoing.manifest.js, | ||
css: outgoing.manifest.css, | ||
redirect: outgoing.redirect, | ||
}), | ||
this.#log.info( | ||
`podlet version number in header differs from cached version number - aborting request to remote resource for content - resource: ${outgoing.name} - url: ${outgoing.contentUri}`, | ||
); | ||
// TODO r.abort(); | ||
outgoing.status = 'stale'; | ||
return outgoing; | ||
} | ||
pipeline([r, outgoing], (err) => { | ||
if (err) { | ||
this.#log.warn('error while piping content stream', err); | ||
} | ||
}); | ||
}); | ||
outgoing.success = true; | ||
outgoing.headers = hdrs; | ||
r.on('error', error => { | ||
// Network error | ||
if (outgoing.throwable) { | ||
timer({ | ||
labels: { | ||
status: 'failure', | ||
}, | ||
}); | ||
if (outgoing.redirectable && statusCode >= 300) { | ||
outgoing.redirect = { | ||
statusCode, | ||
location: hdrs && hdrs.location, | ||
}; | ||
} | ||
this.#log.warn( | ||
`could not create network connection to remote resource when trying to request content - resource: ${outgoing.name} - url: ${outgoing.contentUri}`, | ||
); | ||
reject( | ||
badGateway( | ||
`Error reading content at ${outgoing.contentUri}`, | ||
error, | ||
), | ||
); | ||
return; | ||
outgoing.emit( | ||
'beforeStream', | ||
new Response({ | ||
headers: outgoing.headers, | ||
js: outgoing.manifest.js, | ||
css: outgoing.manifest.css, | ||
redirect: outgoing.redirect, | ||
}), | ||
); | ||
pipeline([body, outgoing], (err) => { | ||
if (err) { | ||
this.#log.warn('error while piping content stream', err); | ||
} | ||
}); | ||
} catch (error) { | ||
if (error.isBoom) throw error; | ||
// Network error | ||
if (outgoing.throwable) { | ||
timer({ | ||
@@ -272,23 +241,36 @@ labels: { | ||
); | ||
throw badGateway( | ||
`Error reading content at ${outgoing.contentUri}`, | ||
error, | ||
); | ||
} | ||
outgoing.success = true; | ||
outgoing.pushFallback(); | ||
resolve(outgoing); | ||
timer({ | ||
labels: { | ||
status: 'failure', | ||
}, | ||
}); | ||
r.on('end', () => { | ||
timer({ | ||
labels: { | ||
status: 'success', | ||
}, | ||
}); | ||
this.#log.warn( | ||
`could not create network connection to remote resource when trying to request content - resource: ${outgoing.name} - url: ${outgoing.contentUri}`, | ||
); | ||
this.#log.debug( | ||
`successfully read content from remote resource - resource: ${outgoing.name} - url: ${outgoing.contentUri}`, | ||
); | ||
outgoing.success = true; | ||
resolve(outgoing); | ||
}); | ||
outgoing.pushFallback(); | ||
return outgoing; | ||
} | ||
timer({ | ||
labels: { | ||
status: 'success', | ||
}, | ||
}); | ||
this.#log.debug( | ||
`successfully read content from remote resource - resource: ${outgoing.name} - url: ${outgoing.contentUri}`, | ||
); | ||
return outgoing; | ||
} | ||
@@ -299,2 +281,2 @@ | ||
} | ||
}; | ||
} |
/* eslint-disable no-param-reassign */ | ||
import request from 'request'; | ||
import abslog from 'abslog'; | ||
import Metrics from '@metrics/client'; | ||
import { join, dirname } from 'path'; | ||
import { fileURLToPath } from 'url'; | ||
import fs from 'fs'; | ||
import HTTP from './http.js'; | ||
const currentDirectory = dirname(fileURLToPath(import.meta.url)); | ||
const pkgJson = fs.readFileSync(join(currentDirectory, '../package.json'), 'utf-8'); | ||
const pkgJson = fs.readFileSync( | ||
join(currentDirectory, '../package.json'), | ||
'utf-8', | ||
); | ||
const pkg = JSON.parse(pkgJson); | ||
@@ -22,10 +24,8 @@ | ||
#histogram; | ||
#httpAgent; | ||
#httpsAgent; | ||
#http; | ||
constructor(options = {}) { | ||
this.#http = options.http || new HTTP(); | ||
const name = options.clientName; | ||
this.#log = abslog(options.logger); | ||
this.#metrics = new Metrics(); | ||
this.#httpAgent = options.httpAgent; | ||
this.#httpsAgent = options.httpsAgent; | ||
this.#histogram = this.#metrics.histogram({ | ||
@@ -42,3 +42,3 @@ name: 'podium_client_resolver_fallback_resolve', | ||
this.#metrics.on('error', error => { | ||
this.#metrics.on('error', (error) => { | ||
this.#log.error( | ||
@@ -55,106 +55,96 @@ 'Error emitted by metric stream in @podium/client module', | ||
resolve(outgoing) { | ||
return new Promise(resolve => { | ||
if (outgoing.status === 'cached') { | ||
resolve(outgoing); | ||
return; | ||
} | ||
async resolve(outgoing) { | ||
if (outgoing.status === 'cached') { | ||
return outgoing; | ||
} | ||
// Manifest has no fallback, fetching of manifest likely failed. | ||
// Its not possible to fetch anything | ||
// Do not set fallback so we can serve any previous fallback we might have | ||
if (outgoing.manifest.fallback === undefined) { | ||
resolve(outgoing); | ||
return; | ||
} | ||
// Manifest has no fallback, fetching of manifest likely failed. | ||
// Its not possible to fetch anything | ||
// Do not set fallback so we can serve any previous fallback we might have | ||
if (outgoing.manifest.fallback === undefined) { | ||
return outgoing; | ||
} | ||
// If manifest fallback is empty, there is no fallback content to fetch | ||
// Set fallback to empty string | ||
if (outgoing.manifest.fallback === '') { | ||
this.#log.debug( | ||
`no fallback defined in manifest - resource: ${outgoing.name}`, | ||
); | ||
outgoing.fallback = ''; | ||
resolve(outgoing); | ||
return; | ||
} | ||
// If manifest fallback is empty, there is no fallback content to fetch | ||
// Set fallback to empty string | ||
if (outgoing.manifest.fallback === '') { | ||
this.#log.debug( | ||
`no fallback defined in manifest - resource: ${outgoing.name}`, | ||
); | ||
outgoing.fallback = ''; | ||
return outgoing; | ||
} | ||
// The manifest fallback holds a URI, fetch its content | ||
const headers = { | ||
'User-Agent': UA_STRING, | ||
}; | ||
// The manifest fallback holds a URI, fetch its content | ||
const headers = { | ||
'User-Agent': UA_STRING, | ||
}; | ||
const reqOptions = { | ||
rejectUnauthorized: outgoing.rejectUnauthorized, | ||
timeout: outgoing.timeout, | ||
method: 'GET', | ||
agent: outgoing.fallbackUri.startsWith('https://') | ||
? this.#httpsAgent | ||
: this.#httpAgent, | ||
uri: outgoing.fallbackUri, | ||
headers, | ||
}; | ||
const reqOptions = { | ||
rejectUnauthorized: outgoing.rejectUnauthorized, | ||
timeout: outgoing.timeout, | ||
method: 'GET', | ||
headers, | ||
}; | ||
const timer = this.#histogram.timer({ | ||
labels: { | ||
podlet: outgoing.name, | ||
}, | ||
}); | ||
const timer = this.#histogram.timer({ | ||
labels: { | ||
podlet: outgoing.name, | ||
}, | ||
}); | ||
request(reqOptions, (error, res, body) => { | ||
this.#log.debug( | ||
`start reading fallback content from remote resource - resource: ${outgoing.name} - url: ${outgoing.fallbackUri}`, | ||
); | ||
try { | ||
this.#log.debug( | ||
`start reading fallback content from remote resource - resource: ${outgoing.name} - url: ${outgoing.fallbackUri}`, | ||
); | ||
const { statusCode, body } = await this.#http.request( | ||
outgoing.fallbackUri, | ||
reqOptions, | ||
); | ||
// Network error | ||
if (error) { | ||
timer({ | ||
labels: { | ||
status: 'failure', | ||
}, | ||
}); | ||
// Remote responds but with an http error code | ||
const resError = statusCode !== 200; | ||
if (resError) { | ||
timer({ | ||
labels: { | ||
status: 'failure', | ||
}, | ||
}); | ||
this.#log.warn( | ||
`could not create network connection to remote resource for fallback content - resource: ${outgoing.name} - url: ${outgoing.fallbackUri}`, | ||
); | ||
this.#log.warn( | ||
`remote resource responded with non 200 http status code for fallback content - code: ${statusCode} - resource: ${outgoing.name} - url: ${outgoing.fallbackUri}`, | ||
); | ||
outgoing.fallback = ''; | ||
resolve(outgoing); | ||
return; | ||
} | ||
outgoing.fallback = ''; | ||
return outgoing; | ||
} | ||
// Remote responds but with an http error code | ||
const resError = res.statusCode !== 200; | ||
if (resError) { | ||
timer({ | ||
labels: { | ||
status: 'failure', | ||
}, | ||
}); | ||
// Response is OK. Store response body as fallback html for caching | ||
timer({ | ||
labels: { | ||
status: 'success', | ||
}, | ||
}); | ||
this.#log.warn( | ||
`remote resource responded with non 200 http status code for fallback content - code: ${res.statusCode} - resource: ${outgoing.name} - url: ${outgoing.fallbackUri}`, | ||
); | ||
// Set fallback to the fetched content | ||
outgoing.fallback = await body.text(); | ||
outgoing.fallback = ''; | ||
resolve(outgoing); | ||
return; | ||
} | ||
this.#log.debug( | ||
`successfully read fallback from remote resource - resource: ${outgoing.name} - url: ${outgoing.fallbackUri}`, | ||
); | ||
return outgoing; | ||
} catch (error) { | ||
timer({ | ||
labels: { | ||
status: 'failure', | ||
}, | ||
}); | ||
// Response is OK. Store response body as fallback html for caching | ||
timer({ | ||
labels: { | ||
status: 'success', | ||
}, | ||
}); | ||
this.#log.warn( | ||
`could not create network connection to remote resource for fallback content - resource: ${outgoing.name} - url: ${outgoing.fallbackUri}`, | ||
); | ||
// Set fallback to the fetched content | ||
outgoing.fallback = body; | ||
this.#log.debug( | ||
`successfully read fallback from remote resource - resource: ${outgoing.name} - url: ${outgoing.fallbackUri}`, | ||
); | ||
resolve(outgoing); | ||
}); | ||
}); | ||
outgoing.fallback = ''; | ||
return outgoing; | ||
} | ||
} | ||
@@ -165,2 +155,2 @@ | ||
} | ||
}; | ||
} |
import Metrics from '@metrics/client'; | ||
import abslog from 'abslog'; | ||
import assert from 'assert'; | ||
import HTTP from './http.js'; | ||
import Manifest from './resolver.manifest.js'; | ||
@@ -22,6 +23,7 @@ import Fallback from './resolver.fallback.js'; | ||
const log = abslog(options.logger); | ||
const http = new HTTP(); | ||
this.#cache = new Cache(registry, options); | ||
this.#manifest = new Manifest(options); | ||
this.#fallback = new Fallback(options); | ||
this.#content = new Content(options); | ||
this.#manifest = new Manifest({ ...options, http }); | ||
this.#fallback = new Fallback({ ...options, http }); | ||
this.#content = new Content({ ...options, http }); | ||
this.#metrics = new Metrics(); | ||
@@ -28,0 +30,0 @@ |
/* eslint-disable no-param-reassign */ | ||
import CachePolicy from 'http-cache-semantics'; | ||
import {manifest as validateManifest } from'@podium/schemas'; | ||
import request from 'request'; | ||
import { manifest as validateManifest } from '@podium/schemas'; | ||
import Metrics from '@metrics/client'; | ||
import abslog from 'abslog'; | ||
import * as utils from '@podium/utils'; | ||
import { join, dirname } from 'path'; | ||
import { fileURLToPath } from 'url'; | ||
import fs from 'fs'; | ||
import HTTP from './http.js'; | ||
const currentDirectory = dirname(fileURLToPath(import.meta.url)); | ||
const pkgJson = fs.readFileSync(join(currentDirectory, '../package.json'), 'utf-8'); | ||
const pkgJson = fs.readFileSync( | ||
join(currentDirectory, '../package.json'), | ||
'utf-8', | ||
); | ||
const pkg = JSON.parse(pkgJson); | ||
@@ -24,11 +26,9 @@ | ||
#metrics; | ||
#httpAgent; | ||
#httpsAgent; | ||
#histogram; | ||
#http; | ||
constructor(options = {}) { | ||
this.#http = options.http || new HTTP(); | ||
const name = options.clientName; | ||
this.#log = abslog(options.logger); | ||
this.#metrics = new Metrics(); | ||
this.#httpAgent = options.httpAgent; | ||
this.#httpsAgent = options.httpsAgent; | ||
this.#histogram = this.#metrics.histogram({ | ||
@@ -45,3 +45,3 @@ name: 'podium_client_resolver_manifest_resolve', | ||
this.#metrics.on('error', error => { | ||
this.#metrics.on('error', (error) => { | ||
this.#log.error( | ||
@@ -58,154 +58,147 @@ 'Error emitted by metric stream in @podium/client module', | ||
resolve(outgoing) { | ||
return new Promise(resolve => { | ||
if (outgoing.status === 'cached') { | ||
resolve(outgoing); | ||
return; | ||
} | ||
async resolve(outgoing) { | ||
if (outgoing.status === 'cached') { | ||
return outgoing; | ||
} | ||
const headers = { | ||
'User-Agent': UA_STRING, | ||
}; | ||
const headers = { | ||
'User-Agent': UA_STRING, | ||
}; | ||
const reqOptions = { | ||
rejectUnauthorized: outgoing.rejectUnauthorized, | ||
timeout: outgoing.timeout, | ||
method: 'GET', | ||
agent: outgoing.manifestUri.startsWith('https://') | ||
? this.#httpsAgent | ||
: this.#httpAgent, | ||
json: true, | ||
uri: outgoing.manifestUri, | ||
headers, | ||
}; | ||
const reqOptions = { | ||
rejectUnauthorized: outgoing.rejectUnauthorized, | ||
timeout: outgoing.timeout, | ||
method: 'GET', | ||
json: true, | ||
headers, | ||
}; | ||
const timer = this.#histogram.timer({ | ||
labels: { | ||
podlet: outgoing.name, | ||
}, | ||
}); | ||
const timer = this.#histogram.timer({ | ||
labels: { | ||
podlet: outgoing.name, | ||
}, | ||
}); | ||
request(reqOptions, (error, res, body) => { | ||
this.#log.debug( | ||
`start reading manifest from remote resource - resource: ${outgoing.name} - url: ${outgoing.manifestUri}`, | ||
); | ||
this.#log.debug( | ||
`start reading manifest from remote resource - resource: ${outgoing.name} - url: ${outgoing.manifestUri}`, | ||
); | ||
// Network error or JSON parsing error | ||
if (error) { | ||
timer({ | ||
labels: { | ||
status: 'failure', | ||
}, | ||
}); | ||
try { | ||
const { | ||
statusCode, | ||
headers: hdrs, | ||
body, | ||
} = await this.#http.request(outgoing.manifestUri, reqOptions); | ||
this.#log.warn( | ||
`could not create network connection to remote manifest - resource: ${outgoing.name} - url: ${outgoing.manifestUri}`, | ||
); | ||
resolve(outgoing); | ||
return; | ||
} | ||
// Remote responds but with an http error code | ||
const resError = statusCode !== 200; | ||
if (resError) { | ||
timer({ | ||
labels: { | ||
status: 'failure', | ||
}, | ||
}); | ||
// Remote responds but with an http error code | ||
const resError = res.statusCode !== 200; | ||
if (resError) { | ||
timer({ | ||
labels: { | ||
status: 'failure', | ||
}, | ||
}); | ||
this.#log.warn( | ||
`remote resource responded with non 200 http status code for manifest - code: ${statusCode} - resource: ${outgoing.name} - url: ${outgoing.manifestUri}`, | ||
); | ||
return outgoing; | ||
} | ||
this.#log.warn( | ||
`remote resource responded with non 200 http status code for manifest - code: ${res.statusCode} - resource: ${outgoing.name} - url: ${outgoing.manifestUri}`, | ||
); | ||
resolve(outgoing); | ||
return; | ||
} | ||
const manifest = validateManifest(await body.json()); | ||
const manifest = validateManifest(body); | ||
// Manifest validation error | ||
if (manifest.error) { | ||
timer({ | ||
labels: { | ||
status: 'failure', | ||
}, | ||
}); | ||
this.#log.warn( | ||
`could not parse manifest - resource: ${outgoing.name} - url: ${outgoing.manifestUri}`, | ||
); | ||
resolve(outgoing); | ||
return; | ||
} | ||
// Manifest is valid, calculate maxAge for caching and continue | ||
// Manifest validation error | ||
if (manifest.error) { | ||
timer({ | ||
labels: { | ||
status: 'success', | ||
status: 'failure', | ||
}, | ||
}); | ||
const resValues = { | ||
status: res.statusCode, | ||
headers: res.headers, | ||
}; | ||
this.#log.warn( | ||
`could not parse manifest - resource: ${outgoing.name} - url: ${outgoing.manifestUri}`, | ||
); | ||
return outgoing; | ||
} | ||
const cachePolicy = new CachePolicy(reqOptions, resValues, { | ||
ignoreCargoCult: true, | ||
}); | ||
const maxAge = cachePolicy.timeToLive(); | ||
if (maxAge !== 0) { | ||
this.#log.debug( | ||
`remote resource has cache header which yelds a max age of ${maxAge}ms, using this as cache ttl - resource: ${outgoing.name} - url: ${outgoing.manifestUri}`, | ||
); | ||
outgoing.maxAge = maxAge; | ||
} | ||
// Manifest is valid, calculate maxAge for caching and continue | ||
timer({ | ||
labels: { | ||
status: 'success', | ||
}, | ||
}); | ||
// Build absolute content and fallback URIs | ||
if (manifest.value.fallback !== '') { | ||
manifest.value.fallback = utils.uriRelativeToAbsolute( | ||
manifest.value.fallback, | ||
outgoing.manifestUri, | ||
); | ||
} | ||
const resValues = { | ||
status: statusCode, | ||
headers: hdrs, | ||
}; | ||
manifest.value.content = utils.uriRelativeToAbsolute( | ||
manifest.value.content, | ||
const cachePolicy = new CachePolicy(reqOptions, resValues, { | ||
ignoreCargoCult: true, | ||
}); | ||
const maxAge = cachePolicy.timeToLive(); | ||
if (maxAge !== 0) { | ||
this.#log.debug( | ||
`remote resource has cache header which yelds a max age of ${maxAge}ms, using this as cache ttl - resource: ${outgoing.name} - url: ${outgoing.manifestUri}`, | ||
); | ||
outgoing.maxAge = maxAge; | ||
} | ||
// Build absolute content and fallback URIs | ||
if (manifest.value.fallback !== '') { | ||
manifest.value.fallback = utils.uriRelativeToAbsolute( | ||
manifest.value.fallback, | ||
outgoing.manifestUri, | ||
); | ||
} | ||
// Construct css and js objects with absolute URIs | ||
manifest.value.css = manifest.value.css.map(obj => { | ||
obj.value = utils.uriRelativeToAbsolute( | ||
obj.value, | ||
outgoing.manifestUri, | ||
); | ||
return new utils.AssetCss(obj); | ||
}); | ||
manifest.value.content = utils.uriRelativeToAbsolute( | ||
manifest.value.content, | ||
outgoing.manifestUri, | ||
); | ||
manifest.value.js = manifest.value.js.map(obj => { | ||
obj.value = utils.uriRelativeToAbsolute( | ||
obj.value, | ||
outgoing.manifestUri, | ||
); | ||
return new utils.AssetJs(obj); | ||
}); | ||
// Construct css and js objects with absolute URIs | ||
manifest.value.css = manifest.value.css.map((obj) => { | ||
obj.value = utils.uriRelativeToAbsolute( | ||
obj.value, | ||
outgoing.manifestUri, | ||
); | ||
return new utils.AssetCss(obj); | ||
}); | ||
// Build absolute proxy URIs | ||
Object.keys(manifest.value.proxy).forEach(key => { | ||
manifest.value.proxy[key] = utils.uriRelativeToAbsolute( | ||
manifest.value.proxy[key], | ||
outgoing.manifestUri, | ||
); | ||
}); | ||
manifest.value.js = manifest.value.js.map((obj) => { | ||
obj.value = utils.uriRelativeToAbsolute( | ||
obj.value, | ||
outgoing.manifestUri, | ||
); | ||
return new utils.AssetJs(obj); | ||
}); | ||
outgoing.manifest = manifest.value; | ||
outgoing.status = 'fresh'; | ||
this.#log.debug( | ||
`successfully read manifest from remote resource - resource: ${outgoing.name} - url: ${outgoing.manifestUri}`, | ||
// Build absolute proxy URIs | ||
Object.keys(manifest.value.proxy).forEach((key) => { | ||
manifest.value.proxy[key] = utils.uriRelativeToAbsolute( | ||
manifest.value.proxy[key], | ||
outgoing.manifestUri, | ||
); | ||
resolve(outgoing); | ||
}); | ||
}); | ||
outgoing.manifest = manifest.value; | ||
outgoing.status = 'fresh'; | ||
this.#log.debug( | ||
`successfully read manifest from remote resource - resource: ${outgoing.name} - url: ${outgoing.manifestUri}`, | ||
); | ||
return outgoing; | ||
} catch (error) { | ||
timer({ | ||
labels: { | ||
status: 'failure', | ||
}, | ||
}); | ||
this.#log.warn( | ||
`could not create network connection to remote manifest - resource: ${outgoing.name} - url: ${outgoing.manifestUri}`, | ||
); | ||
return outgoing; | ||
} | ||
} | ||
@@ -216,2 +209,2 @@ | ||
} | ||
}; | ||
} |
/* eslint-disable no-param-reassign */ | ||
import Metrics from '@metrics/client'; | ||
import stream from 'stream'; | ||
import abslog from 'abslog'; | ||
@@ -66,14 +65,2 @@ import assert from 'assert'; | ||
const chunks = []; | ||
const to = new stream.Writable({ | ||
write: (chunk, encoding, next) => { | ||
chunks.push(chunk); | ||
next(); | ||
}, | ||
}); | ||
stream.pipeline([outgoing, to], () => { | ||
// noop | ||
}); | ||
const { manifest, headers, redirect } = await this.#resolver.resolve( | ||
@@ -83,2 +70,8 @@ outgoing, | ||
const chunks = []; | ||
// eslint-disable-next-line no-restricted-syntax | ||
for await (const chunk of outgoing) { | ||
chunks.push(chunk) | ||
} | ||
const content = !outgoing.redirect | ||
@@ -85,0 +78,0 @@ ? Buffer.concat(chunks).toString() |
{ | ||
"name": "@podium/client", | ||
"version": "5.0.0-next.11", | ||
"version": "5.0.0-next.12", | ||
"type": "module", | ||
@@ -45,4 +45,4 @@ "license": "MIT", | ||
"lodash.clonedeep": "^4.5.0", | ||
"request": "^2.88.0", | ||
"ttl-mem-cache": "4.1.0" | ||
"ttl-mem-cache": "4.1.0", | ||
"undici": "^5.10.0" | ||
}, | ||
@@ -49,0 +49,0 @@ "devDependencies": { |
18
102267
1354
+ Addedundici@^5.10.0
+ Added@fastify/busboy@2.1.1(transitive)
+ Addedundici@5.28.5(transitive)
- Removedrequest@^2.88.0
- Removedajv@6.12.6(transitive)
- Removedasn1@0.2.6(transitive)
- Removedassert-plus@1.0.0(transitive)
- Removedasynckit@0.4.0(transitive)
- Removedaws-sign2@0.7.0(transitive)
- Removedaws4@1.13.2(transitive)
- Removedbcrypt-pbkdf@1.0.2(transitive)
- Removedcaseless@0.12.0(transitive)
- Removedcombined-stream@1.0.8(transitive)
- Removedcore-util-is@1.0.2(transitive)
- Removeddashdash@1.14.1(transitive)
- Removeddelayed-stream@1.0.0(transitive)
- Removedecc-jsbn@0.1.2(transitive)
- Removedextend@3.0.2(transitive)
- Removedextsprintf@1.3.0(transitive)
- Removedfast-json-stable-stringify@2.1.0(transitive)
- Removedforever-agent@0.6.1(transitive)
- Removedform-data@2.3.3(transitive)
- Removedgetpass@0.1.7(transitive)
- Removedhar-schema@2.0.0(transitive)
- Removedhar-validator@5.1.5(transitive)
- Removedhttp-signature@1.2.0(transitive)
- Removedis-typedarray@1.0.0(transitive)
- Removedisstream@0.1.2(transitive)
- Removedjsbn@0.1.1(transitive)
- Removedjson-schema@0.4.0(transitive)
- Removedjson-schema-traverse@0.4.1(transitive)
- Removedjson-stringify-safe@5.0.1(transitive)
- Removedjsprim@1.4.2(transitive)
- Removedmime-db@1.52.0(transitive)
- Removedmime-types@2.1.35(transitive)
- Removedoauth-sign@0.9.0(transitive)
- Removedperformance-now@2.1.0(transitive)
- Removedpsl@1.15.0(transitive)
- Removedqs@6.5.3(transitive)
- Removedrequest@2.88.2(transitive)
- Removedsafer-buffer@2.1.2(transitive)
- Removedsshpk@1.18.0(transitive)
- Removedtough-cookie@2.5.0(transitive)
- Removedtunnel-agent@0.6.0(transitive)
- Removedtweetnacl@0.14.5(transitive)
- Removeduuid@3.4.0(transitive)
- Removedverror@1.10.0(transitive)