Skip to content

Commit

Permalink
Merge pull request #283 from namecheap/feature/supportFewDomains_500
Browse files Browse the repository at this point in the history
feat: support a few domains (500)
  • Loading branch information
Volodymyr Makukha authored Apr 20, 2021
2 parents 62e167e + b2ce5be commit 342a2dc
Show file tree
Hide file tree
Showing 23 changed files with 628 additions and 34 deletions.
1 change: 1 addition & 0 deletions e2e/codecept.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ exports.config = {
newsPage: path.join(__dirname, 'spec', 'pages', 'news.ts'),
planetsPage: path.join(__dirname, 'spec', 'pages', 'planets.ts'),
hooksPage: path.join(__dirname, 'spec', 'pages', 'hooks.ts'),
common: path.join(__dirname, 'spec', 'pages', 'common.ts'),
},
tests: './spec/**/*.spec.e2e.ts',
name: 'ilc',
Expand Down
39 changes: 39 additions & 0 deletions e2e/spec/500.spec.e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
Feature('500 error handling');

//region 500 page
Scenario('Renders 500 page for domain "localhost:8233" (default)', (I, common: common) => {
I.amOnPage(common.url.urlInternalServerError);
I.seeInSource(common.textError500);
I.seeInSource(common.textErrorId);
I.dontSeeInSource(common.textError500ForLocalhostAsIPv4);
I.seeInCurrentUrl(common.url.urlInternalServerError);
});
Scenario('should open 500 error page when an error happens for domain "localhost:8233" (default)', async (I, newsPage: newsPage, common: common) => {
I.amOnPage(newsPage.url.main);
I.waitInUrl(newsPage.url.main, 10);
I.waitForElement(newsPage.generateError, 10);
I.click(newsPage.generateError);
I.seeInSource(common.textError500);
I.seeInSource(common.textErrorId);
I.dontSeeInSource(common.textError500ForLocalhostAsIPv4);
I.seeInCurrentUrl(newsPage.url.main);
});

Scenario('Renders 500 page for domain "127.0.0.1:8233"', (I, common: common) => {
I.amOnPage(common.url.localhostAsIPv4 + common.url.urlInternalServerError);
I.seeInSource(common.textError500ForLocalhostAsIPv4);
I.seeInSource(common.textErrorId);
I.dontSeeInSource(common.textError500);
I.seeInCurrentUrl(common.url.localhostAsIPv4 + common.url.urlInternalServerError);
});
Scenario('should open 500 error page when an error happens for domain "127.0.0.1:8233"', async (I, newsPage: newsPage, common: common) => {
I.amOnPage(common.url.localhostAsIPv4 + newsPage.url.main);
I.waitInUrl(newsPage.url.main, 10);
I.waitForElement(newsPage.generateError, 10);
I.click(newsPage.generateError);
I.seeInSource(common.textError500ForLocalhostAsIPv4);
I.seeInSource(common.textErrorId);
I.dontSeeInSource(common.textError500);
I.seeInCurrentUrl(common.url.localhostAsIPv4 + newsPage.url.main);
});
//endregion 500 page
9 changes: 0 additions & 9 deletions e2e/spec/news.spec.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,3 @@ Scenario('should open an article page from a direct link', async (I, newsPage: n
I.seeInCurrentUrl(lastNewsSourceLinkHref);
I.closeOtherTabs();
});

Scenario('should open 500 error page when an error happens', async (I, newsPage: newsPage) => {
I.amOnPage(newsPage.url.main);
I.waitInUrl(newsPage.url.main, 10);
I.waitForElement(newsPage.generateError, 10);
I.click(newsPage.generateError);
I.waitForElement(newsPage.errorId);
I.seeInCurrentUrl(newsPage.url.main);
});
8 changes: 8 additions & 0 deletions e2e/spec/pages/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const url = {
urlInternalServerError: '/_ilc/500',
localhostAsIPv4: 'http://127.0.0.1:8233',
};

export const textError500 = '<h3>ERROR 500</h3>';
export const textError500ForLocalhostAsIPv4 = '<h3>500 Internal Server Error on 127.0.0.1</h3>';
export const textErrorId = 'Error ID:';
1 change: 0 additions & 1 deletion e2e/spec/pages/news.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ export const newsView = 'body > div#body > div.single-spa-container.news-app > d
export const newsSources = `${newsView} > div.sources > div.container > ol > li.source`;
export const bannerHeadline = `${newsView} > div.banner > h1`;
export const generateError = `${newsView} > div.banner > a`;
export const errorId = `body > div#error > p:nth-child(3)`;
export const lastNewsSource = `${newsSources}:last-child`;
export const lastNewsSourceLink = `${lastNewsSource} > p.action > a`;
export const newsSourceArticles = `${newsView} > div.container > div.articles > ol > li.article`;
Expand Down
5 changes: 3 additions & 2 deletions e2e/typings/steps.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ type peoplePage = typeof import('../spec/pages/people');
type newsPage = typeof import('../spec/pages/news');
type planetsPage = typeof import('../spec/pages/planets');
type hooksPage = typeof import('../spec/pages/hooks');
type common = typeof import('../spec/pages/common');
type MockRequestHelper = import('@codeceptjs/mock-request');

declare namespace CodeceptJS {
interface SupportObject { I: CodeceptJS.I, peoplePage: peoplePage, newsPage: newsPage, planetsPage: planetsPage, hooksPage: hooksPage }
interface CallbackOrder { [0]: CodeceptJS.I; [1]: peoplePage; [2]: newsPage; [3]: planetsPage; [4]: hooksPage }
interface SupportObject { I: CodeceptJS.I, peoplePage: peoplePage, newsPage: newsPage, planetsPage: planetsPage, hooksPage: hooksPage, common: common }
interface CallbackOrder { [0]: CodeceptJS.I; [1]: peoplePage; [2]: newsPage; [3]: planetsPage; [4]: hooksPage; [5]: common }
interface Methods extends CodeceptJS.Puppeteer, MockRequestHelper {}
interface I extends WithTranslation<Methods> {}
namespace Translation {
Expand Down
3 changes: 2 additions & 1 deletion ilc/common/wrapWithCache.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ errors.WrapWithCacheError = extendError('WrapWithCacheError');
const wrapWithCache = (localStorage, logger, createHash = hashFn) => (fn, cacheParams = {}) => {
const {
cacheForSeconds = 60,
name = '', // "hash" of returned value is based only on arguments, so with the help "name" we can add prefix to hash
} = cacheParams;

const cacheResolutionPromise = {};

return (...args) => {
const now = Math.floor(Date.now() / 1000);
const hash = args.length > 0 ? createHash(JSON.stringify(args)) : '__null__';
const hash = `${name ? name + '__' : ''}${args.length > 0 ? createHash(JSON.stringify(args)) : '__null__'}`;

if (localStorage.getItem(hash) === null || JSON.parse(localStorage.getItem(hash)).cachedAt < now - cacheForSeconds) {
if (cacheResolutionPromise[hash] !== undefined) {
Expand Down
3 changes: 2 additions & 1 deletion ilc/server/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ module.exports = (registryService, pluginManager) => {
app.register(require('./ping'));

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

Expand Down
3 changes: 2 additions & 1 deletion ilc/server/errorHandler/ErrorHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ module.exports = class ErrorHandler {
errorId
});

let data = await this.#registryService.getTemplate('500');
const currentDomain = req.hostname;
let data = await this.#registryService.getTemplate('500', currentDomain);
data = data.data.content.replace('%ERRORID%', `Error ID: ${errorId}`);

nres.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
Expand Down
2 changes: 2 additions & 0 deletions ilc/server/errorHandler/ErrorHandler.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ describe('ErrorHandler', () => {
});

it('should show 500 error page with an error id', async () => {
nock(config.get('registry').address).get('/api/v1/router_domains').reply(200, []);
nock(config.get('registry').address).get(`/api/v1/template/500/rendered`).reply(200, {
content:
'<html>' +
Expand Down Expand Up @@ -59,6 +60,7 @@ describe('ErrorHandler', () => {
});

it('should send an error message when showing 500 error page throws an error', async () => {
nock(config.get('registry').address).get('/api/v1/router_domains').reply(200, []);
const replyingError = new Error('Something awful happened.');

nock(config.get('registry').address).get(`/api/v1/template/500/rendered`).replyWithError(replyingError.message);
Expand Down
43 changes: 41 additions & 2 deletions ilc/server/registry/Registry.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ module.exports = class Registry {
#cacheHeated = {
config: false,
template: false,
routerDomains: false,
};

/**
Expand All @@ -30,6 +31,7 @@ module.exports = class Registry {

const getConfigMemo = wrapFetchWithCache(this.#getConfig, {
cacheForSeconds: 5,
name: 'registry_getConfig',
});

this.getConfig = async (options) => {
Expand All @@ -38,13 +40,28 @@ module.exports = class Registry {
return res;
};

this.getTemplate = wrapFetchWithCache(this.#getTemplate, {
this.getRouterDomains = wrapFetchWithCache(this.#getRouterDomains, {
cacheForSeconds: 30,
name: 'registry_routerDomains',
});

const getTemplateMemo = wrapFetchWithCache(this.#getTemplate, {
cacheForSeconds: 30,
});

this.getTemplate = async (templateName, forDomain) => {
if (templateName === '500' && forDomain) {
const routerDomains = await this.getRouterDomains();
const redefined500 = routerDomains.data.find(item => item.domainName === forDomain)?.template500;
templateName = redefined500 || templateName;
}

return await getTemplateMemo(templateName);
};
}

async preheat() {
if (this.#cacheHeated.template && this.#cacheHeated.config) {
if (this.#cacheHeated.template && this.#cacheHeated.config && this.#cacheHeated.routerDomains) {
return;
}

Expand All @@ -53,6 +70,7 @@ module.exports = class Registry {
await Promise.all([
this.getConfig(),
this.getTemplate('500'),
this.getRouterDomains(),
]);

this.#logger.info('Registry preheated successfully!');
Expand Down Expand Up @@ -102,6 +120,27 @@ module.exports = class Registry {
return res.data;
};

#getRouterDomains = async () => {
this.#logger.debug('Calling get routerDomains registry endpoint...');

const url = urljoin(this.#address, 'api/v1/router_domains');
let res;
try {
res = await axios.get(url, { responseType: 'json' });
} catch (e) {
throw new errors.RegistryError({
message: `Error while requesting routerDomains from registry`,
cause: e,
data: {
requestedUrl: url
}
});
}

this.#cacheHeated.routerDomains = true;
return res.data;
};

#filterConfig = (config, filter) => {
if (!filter || !Object.keys(filter).length) {
return config;
Expand Down
9 changes: 8 additions & 1 deletion registry/client/src/routerDomains/Edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {
TextInput,
required,
TextField,
ReferenceInput,
SelectInput,
} from 'react-admin'; // eslint-disable-line import/no-unresolved

const Title = ({ record }) => {
Expand All @@ -19,7 +21,12 @@ const InputForm = ({ mode = 'edit', ...props }) => {
? <TextField source="id" />
: null}

<TextInput source="domainName" fullWidth validate={required()} />
<TextInput source="domainName" fullWidth validate={required()} />
<ReferenceInput reference="template"
source="template500"
label="Template of 500 error">
<SelectInput resettable optionText="name" helperText="Default template name is '500'" />
</ReferenceInput>
</SimpleForm>
);
};
Expand Down
1 change: 1 addition & 0 deletions registry/client/src/routerDomains/List.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const PostList = props => {
<Datagrid rowClick="edit" optimized>
<TextField source="id" sortable={false} />
<TextField source="domainName" sortable={false} />
<TextField source="template500" sortable={false} emptyText="-" />
<ListActionsToolbar>
<EditButton />
</ListActionsToolbar>
Expand Down
2 changes: 1 addition & 1 deletion registry/server/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export default (withAuth: boolean = true) => {
app.use('/api/v1/auth_entities', authMw, routes.authEntities);
app.use('/api/v1/versioning', authMw, routes.versioning);
app.use('/api/v1/settings', routes.settings(authMw));
app.use('/api/v1/router_domains', authMw, routes.routerDomains);
app.use('/api/v1/router_domains', routes.routerDomains(authMw));

app.use(errorHandler);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export async function up(knex: Knex): Promise<any> {
return knex.schema.createTable('router_domains', table => {
table.increments('id');
table.string('domainName', 255).notNullable();
table.string('template500', 50).nullable().references('templates.name')
});
}

Expand Down
12 changes: 9 additions & 3 deletions registry/server/routerDomains/interfaces/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
import Joi from 'joi';
import { templateNameSchema } from '../../templates/interfaces';

export default interface RouterDomains {
id: number,
domainName: string,
template500?: string,
};

export const routerDomainIdSchema = Joi.string().trim().required();

const routerDomainNameSchema = Joi.string().trim().min(1);
const commonRouterDomainsSchema = {
domainName: Joi.string().trim().min(1),
template500: templateNameSchema.allow(null),
};

export const partialRouterDomainsSchema = Joi.object({
domainName: routerDomainNameSchema,
...commonRouterDomainsSchema,
});

export const routerDomainsSchema = Joi.object({
domainName: routerDomainNameSchema.required(),
...commonRouterDomainsSchema,
domainName: commonRouterDomainsSchema.domainName.required(),
});
18 changes: 10 additions & 8 deletions registry/server/routerDomains/routes/index.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import express from 'express';
import express, { RequestHandler } from 'express';

import getRouterDomains from './getRouterDomains';
import getAllRouterDomains from './getAllRouterDomains';
import updateRouterDomains from './updateRouterDomains';
import createRouterDomains from './createRouterDomains';
import deleteRouterDomains from './deleteRouterDomains';

const routerDomainsRouter = express.Router();
export default (authMw: RequestHandler) => {
const routerDomainsRouter = express.Router();

routerDomainsRouter.get('/', ...getAllRouterDomains);
routerDomainsRouter.post('/', ...createRouterDomains);
routerDomainsRouter.get('/:id', ...getRouterDomains);
routerDomainsRouter.put('/:id', ...updateRouterDomains);
routerDomainsRouter.delete('/:id', ...deleteRouterDomains);
routerDomainsRouter.get('/', ...getAllRouterDomains);
routerDomainsRouter.post('/', authMw, ...createRouterDomains);
routerDomainsRouter.get('/:id', authMw, ...getRouterDomains);
routerDomainsRouter.put('/:id', authMw, ...updateRouterDomains);
routerDomainsRouter.delete('/:id', authMw, ...deleteRouterDomains);

export default routerDomainsRouter;
return routerDomainsRouter;
};
3 changes: 2 additions & 1 deletion registry/server/seeds/00_cleanup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ export async function seed(knex: Knex): Promise<any> {
await knex('route_slots').transacting(trx).truncate();
await knex('routes').transacting(trx).truncate();
await knex('apps').transacting(trx).truncate();
await knex('templates').transacting(trx).truncate();
await knex('shared_props').transacting(trx).truncate();
await knex('router_domains').transacting(trx).truncate();
await knex('templates').transacting(trx).truncate();
} finally {
isMySQL(knex) && await knex.schema.raw('SET FOREIGN_KEY_CHECKS = 1;').transacting(trx);
}
Expand Down
6 changes: 6 additions & 0 deletions registry/server/seeds/02_templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,11 @@ export async function seed(knex: Knex): Promise<any> {
encoding: 'utf8',
})
},
{
name: '500ForLocalhostAsIPv4',
content: fs.readFileSync(path.join(__dirname, './data/templates/500ForLocalhostAsIPv4.html'), {
encoding: 'utf8',
})
},
]);
}
11 changes: 11 additions & 0 deletions registry/server/seeds/06_routerDomains.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import * as Knex from 'knex';

export async function seed(knex: Knex): Promise<any> {
return knex("router_domains").insert([
{
id: 1,
domainName: '127.0.0.1:8233',
template500: '500ForLocalhostAsIPv4',
},
]);
}
Loading

0 comments on commit 342a2dc

Please sign in to comment.