Skip to content

Commit

Permalink
Merge pull request #581 from namecheap/bugfix/404errorsrenderedtemplate
Browse files Browse the repository at this point in the history
feat: return client errors on internal ilc render template endpoint
  • Loading branch information
stas-nc authored Mar 20, 2024
2 parents 1b66bce + 850de5f commit 5dc17bb
Show file tree
Hide file tree
Showing 18 changed files with 1,165 additions and 144 deletions.
20 changes: 20 additions & 0 deletions ilc/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions ilc/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
"test:client": "npm run build:systemjs && cross-env NODE_ENV=test karma start",
"test:client:watch": "npm run test:client -- --single-run=false --auto-watch=true",
"start": "node --max-http-header-size 30000 dist/server/index.js",
"dev": "npm run build:polyfills && cross-env NODE_ENV=development nodemon -w server -w common --exec \"npm run build:server && npm run start | pino-pretty\" ",
"dev": "npm run build:polyfills && cross-env NODE_ENV=development nodemon -w server -w common -e ts,js,json --exec \"npm run build:server && npm run start | pino-pretty\" ",
"build": "rimraf public && npm run build:server && npm run build:systemjs && npm run build:client && npm run build:polyfills",
"build:server": "npx tsc --project ./tsconfig.server.json && npx copyfiles -f ./config/* ./dist/config/",
"build:server": "rimraf ./dist && tsc --project ./tsconfig.server.json && copyfiles ./config/* ./server/assets/* ./dist/",
"build:client": "webpack --config build/webpack.js --progress",
"build:systemjs": "node ./systemjs/build.js",
"build:polyfills": "mkdir public || true && nwget https://polyfill.io/v3/polyfill.min.js?features=URL%2CObject.entries%2CDocumentFragment.prototype.append%2CElement.prototype.append%2CElement.prototype.remove%2CObject.assign%2CElement.prototype.closest%2CCustomEvent%2CObject.values%2CArray.from -O ./public/polyfill.min.js"
Expand All @@ -35,6 +35,7 @@
"eventemitter3": "^5.0.1",
"fast-glob": "^3.3.1",
"fastify": "^2.15.2",
"http-status-codes": "^2.3.0",
"ilc-plugins-sdk": "^2.1.0",
"ilc-sdk": "^5.1.4",
"is-url": "^1.2.4",
Expand All @@ -60,7 +61,9 @@
"@babel/preset-env": "^7.22.20",
"@babel/register": "^7.22.15",
"@types/chai": "4.3.7",
"@types/config": "^3.3.3",
"@types/mocha": "10.0.2",
"@types/newrelic": "^9.14.3",
"@types/sinon": "10.0.19",
"babel-loader": "^9.1.3",
"babel-plugin-istanbul": "^6.1.1",
Expand Down
154 changes: 35 additions & 119 deletions ilc/server/app.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
const newrelic = require('newrelic');
import { AsyncResource } from 'node:async_hooks';
import { renderTemplateHandlerFactory } from './routes/renderTemplateHandlerFactory';
import { wildcardRequestHandlerFactory } from './routes/wildcardRequestHandlerFactory';

const config = require('config');
const fastify = require('fastify');
const tailorFactory = require('./tailor/factory');
const serveStatic = require('./serveStatic');
const errorHandlingService = require('./errorHandler/factory');
const i18n = require('./i18n');
const GuardManager = require('./GuardManager');
const UrlProcessor = require('../common/UrlProcessor');
const ServerRouter = require('./tailor/server-router');
const mergeConfigs = require('./tailor/merge-configs');
const parseOverrideConfig = require('./tailor/parse-override-config');
const { SlotCollection } = require('../common/Slot/SlotCollection');
const CspBuilderService = require('./services/CspBuilderService');
const Application = require('./application/application');
const reportingPluginManager = require('./plugins/reportingPlugin');
const AccessLogger = require('./logger/accessLogger');
Expand All @@ -23,17 +18,22 @@ const accessLogger = new AccessLogger(config);
* @param {Registry} registryService
*/
module.exports = (registryService, pluginManager, context) => {
const guardManager = new GuardManager(pluginManager);
const reportingPlugin = reportingPluginManager.getInstance();

const appConfig = Application.getConfig(reportingPlugin);
const logger = reportingPluginManager.getLogger();

const app = fastify(appConfig);

const asyncResourceSymbol = Symbol('asyncResource');

app.addHook('onRequest', (req, reply, done) => {
context.run({ request: req }, async () => {
try {
const asyncResource = new AsyncResource('fastify-request-context');
req[asyncResourceSymbol] = asyncResource;
const doneWithContext = () => asyncResource.runInAsyncScope(done, req.raw);

const { url, method } = req.raw;
accessLogger.logRequest();

Expand All @@ -46,7 +46,7 @@ module.exports = (registryService, pluginManager, context) => {
req.raw.ilcState = {};

if (isStaticFile(url) || isHealthCheck(url) || ['OPTIONS', 'HEAD'].includes(method)) {
return done();
return doneWithContext();
}

const domainName = req.hostname;
Expand All @@ -58,135 +58,51 @@ module.exports = (registryService, pluginManager, context) => {
);

await i18nOnRequest(req, reply);
done();

doneWithContext();
} catch (error) {
errorHandlingService.handleError(error, req, reply);
}
});
});

app.addHook('onResponse', (req, reply, done) => {
context.run({ request: req }, async () => {
try {
accessLogger.logResponse({
statusCode: reply.statusCode,
responseTime: reply.getResponseTime(),
});
done();
} catch (error) {
errorHandlingService.handleError(error);
}
});
/**
* Solves issue when async context is lost in webpack-dev-middleware during initial bundle build
* Took from here
* https://github.com/fastify/fastify-request-context/blob/master/index.js#L46
* TODO: after migration to fastify v4 makes sense to use above plugin instead of custom impl
*/
app.addHook('preValidation', (req, res, done) => {
const asyncResource = req[asyncResourceSymbol];
asyncResource.runInAsyncScope(done, req.raw);
});

const autoInjectNrMonitoringConfig = config.get('newrelic.automaticallyInjectBrowserMonitoring');
const autoInjectNrMonitoring =
typeof autoInjectNrMonitoringConfig === 'boolean'
? autoInjectNrMonitoringConfig
: autoInjectNrMonitoringConfig !== 'false';
const tailor = tailorFactory(
registryService,
config.get('cdnUrl'),
config.get('newrelic.customClientJsWrapper'),
autoInjectNrMonitoring,
logger,
);
app.addHook('onResponse', (req, reply, done) => {
try {
accessLogger.logResponse({
statusCode: reply.statusCode,
responseTime: reply.getResponseTime(),
});
done();
} catch (error) {
errorHandlingService.handleError(error);
}
});

if (config.get('cdnUrl') === null) {
app.use(config.get('static.internalUrl'), serveStatic(config.get('productionMode')));
}

app.register(require('./ping'));

app.get('/_ilc/api/v1/registry/template/:templateName', async (req, res) => {
const currentDomain = req.hostname;
const locale = req.raw.ilcState.locale;
const data = await registryService.getTemplate(req.params.templateName, { locale, forDomain: currentDomain });
res.status(200).send(data.data.content);
});
app.get('/_ilc/api/v1/registry/template/:templateName', renderTemplateHandlerFactory(registryService));

// Route to test 500 page appearance
app.get('/_ilc/500', async () => {
throw new Error('500 page test error');
});

app.all('*', async (req, reply) => {
const currentDomain = req.hostname;
let registryConfig = await registryService.getConfig({ filter: { domain: currentDomain } });
const url = req.raw.url;
const urlProcessor = new UrlProcessor(registryConfig.settings.trailingSlash);
const processedUrl = urlProcessor.process(url);
if (processedUrl !== url) {
reply.redirect(processedUrl);
return;
}

req.headers['x-request-host'] = req.hostname;
req.headers['x-request-uri'] = url;

const overrideConfigs = parseOverrideConfig(
req.headers.cookie,
registryConfig.settings.overrideConfigTrustedOrigins,
logger,
);
// Excluding LDE related transactions from NewRelic
if (overrideConfigs !== null) {
req.raw.ldeRelated = true;
newrelic.getTransaction().ignore();
}

registryConfig = mergeConfigs(registryConfig, overrideConfigs);

const unlocalizedUrl = i18n.unlocalizeUrl(registryConfig.settings.i18n, url);
req.raw.registryConfig = registryConfig;
req.raw.router = new ServerRouter(req.log, req.raw, unlocalizedUrl);

const redirectTo = await guardManager.redirectTo(req);

if (redirectTo) {
reply.redirect(
urlProcessor.process(
i18n.localizeUrl(registryConfig.settings.i18n, redirectTo, {
locale: req.raw.ilcState.locale,
}),
),
);
return;
}

const route = req.raw.router.getRoute();

const csp = new CspBuilderService(
registryConfig.settings.cspConfig,
!!registryConfig.settings.cspEnableStrict,
!!req.raw.ldeRelated,
registryConfig.settings.cspTrustedLocalHosts,
);

try {
reply.res = csp.setHeader(reply.res);
} catch (error) {
errorHandlingService.noticeError(error, {
message: 'CSP object processing error',
});
}

const isRouteWithoutSlots = !Object.keys(route.slots).length;
if (isRouteWithoutSlots) {
const locale = req.raw.ilcState.locale;
let { data } = await registryService.getTemplate(route.template, { locale });

reply.header('Content-Type', 'text/html');
reply.status(200).send(data.content);
return;
}

const slotCollection = new SlotCollection(route.slots, registryConfig);
slotCollection.isValid();

reply.sent = true; // claim full responsibility of the low-level request and response, see https://www.fastify.io/docs/v2.12.x/Reply/#sent
tailor.requestHandler(req.raw, reply.res);
});
app.all('*', wildcardRequestHandlerFactory(logger, registryService, pluginManager));

app.setErrorHandler(errorHandlingService.handleError);

Expand Down
Loading

0 comments on commit 5dc17bb

Please sign in to comment.