Skip to content

Commit

Permalink
Merge pull request #7310 from wellcomecollection/fix/server_close
Browse files Browse the repository at this point in the history
rename server/app files and process.exit
  • Loading branch information
jamesgorrie authored Nov 16, 2021
2 parents 926afed + 49da143 commit 3c6d5b7
Show file tree
Hide file tree
Showing 10 changed files with 325 additions and 313 deletions.
86 changes: 86 additions & 0 deletions catalogue/webapp/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/* eslint-disable @typescript-eslint/no-var-requires, import/first */
// This needs to be the first module loaded in the application
require('@weco/common/services/apm/initApm')('catalogue-server');

import Koa from 'koa';
import Router from 'koa-router';
import next from 'next';

import {
middleware,
route,
handleAllRoute,
timers as middlewareTimers,
} from '@weco/common/koa-middleware/withCachedValues';
import apmErrorMiddleware from '@weco/common/services/apm/errorMiddleware';
import { init as initServerData } from '@weco/common/server-data';

const dev = process.env.NODE_ENV !== 'production';
const nextApp = next({ dev });
const handle = nextApp.getRequestHandler();

const appPromise = nextApp.prepare().then(async () => {
await initServerData();

const koaApp = new Koa();
const router = new Router();

koaApp.use(apmErrorMiddleware);
koaApp.use(middleware);

// Used for redirecting from cognito to actual works pages
router.get('/works/auth-code', async (ctx, next) => {
const authRedirect = ctx.cookies.get('WC_auth_redirect');

if (authRedirect) {
const originalPathnameAndSearch = authRedirect.split('?');
const originalPathname = originalPathnameAndSearch[0];
const originalSearchParams = new URLSearchParams(
originalPathnameAndSearch[1]
);
const requestSearchParams = new URLSearchParams(ctx.request.search);
const code = requestSearchParams.get('code');

if (code) {
originalSearchParams.set('code', code);
}

ctx.status = 303;
ctx.cookies.set('WC_auth_redirect', null);
ctx.redirect(`${originalPathname}?${originalSearchParams.toString()}`);
return;
}

return next();
});

// Next routing
route('/works/progress', '/progress', router, nextApp);
route('/works/:id', '/work', router, nextApp);
route('/works', '/works', router, nextApp);
route('/works/:workId/items', '/item', router, nextApp);
route('/works/:workId/images', '/image', router, nextApp);
route('/works/:workId/download', '/download', router, nextApp);

router.get('/works/management/healthcheck', async ctx => {
ctx.status = 200;
ctx.body = 'ok';
});

router.get('*', handleAllRoute(handle));
router.post(
'/account/api/users/:userId/item-requests',
handleAllRoute(handle)
);

koaApp.use(async (ctx, next) => {
ctx.res.statusCode = 200;
await next();
});

koaApp.use(router.routes());
return koaApp;
});

export default appPromise;
export const timers = middlewareTimers as NodeJS.Timer[];
4 changes: 2 additions & 2 deletions catalogue/webapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"license": "MIT",
"scripts": {
"build": "next build",
"dev": "NODE_ENV=development nodemon --watch server.ts --exec ts-node --project tsconfig.server.json start",
"start": "NODE_ENV=production ts-node-transpile-only --project tsconfig.server.json start",
"dev": "NODE_ENV=development nodemon --watch server.ts --exec ts-node --project tsconfig.server.json server",
"start": "NODE_ENV=production ts-node-transpile-only --project tsconfig.server.json server",
"test": "NODE_ENV=test jest --no-cache",
"test:watch": "yarn test -- --watchAll",
"test:updateSnapshots": "yarn test -- --updateSnapshot",
Expand Down
115 changes: 35 additions & 80 deletions catalogue/webapp/server.ts
Original file line number Diff line number Diff line change
@@ -1,86 +1,41 @@
/* eslint-disable @typescript-eslint/no-var-requires, import/first */
// This needs to be the first module loaded in the application
require('@weco/common/services/apm/initApm')('catalogue-server');

import Koa from 'koa';
import Router from 'koa-router';
import next from 'next';

import {
middleware,
route,
handleAllRoute,
timers as middlewareTimers,
} from '@weco/common/koa-middleware/withCachedValues';
import apmErrorMiddleware from '@weco/common/services/apm/errorMiddleware';
import { init as initServerData } from '@weco/common/server-data';

const dev = process.env.NODE_ENV !== 'production';
const nextApp = next({ dev });
const handle = nextApp.getRequestHandler();

const appPromise = nextApp.prepare().then(async () => {
await initServerData();

const koaApp = new Koa();
const router = new Router();

koaApp.use(apmErrorMiddleware);
koaApp.use(middleware);

// Used for redirecting from cognito to actual works pages
router.get('/works/auth-code', async (ctx, next) => {
const authRedirect = ctx.cookies.get('WC_auth_redirect');

if (authRedirect) {
const originalPathnameAndSearch = authRedirect.split('?');
const originalPathname = originalPathnameAndSearch[0];
const originalSearchParams = new URLSearchParams(
originalPathnameAndSearch[1]
import appPromise, { timers } from './app';
import { clear as clearServerDataTimers } from '@weco/common/server-data';

const port = process.env.PORT ?? 3000;

const serverPromise = appPromise
.then(app => {
const server = app.listen(port, () => {
console.log(
`> ${
process.env.NODE_ENV || 'development'
} ready on http://localhost:${port}/works`
);
const requestSearchParams = new URLSearchParams(ctx.request.search);
const code = requestSearchParams.get('code');

if (code) {
originalSearchParams.set('code', code);
});

// We exit gracefully when we can.
// The reason for not clearing intervals here, is we can't
// feign the SIGTERM in the tests, but we can close the server.
// This allows us to account for the real world shutdown, and test it
const close = () => {
server.close();
};

server.on('close', () => {
clearServerDataTimers();
for (const timer of timers) {
clearInterval(timer);
}
process.exit(0);
});

ctx.status = 303;
ctx.cookies.set('WC_auth_redirect', null);
ctx.redirect(`${originalPathname}?${originalSearchParams.toString()}`);
return;
}

return next();
});

// Next routing
route('/works/progress', '/progress', router, nextApp);
route('/works/:id', '/work', router, nextApp);
route('/works', '/works', router, nextApp);
route('/works/:workId/items', '/item', router, nextApp);
route('/works/:workId/images', '/image', router, nextApp);
route('/works/:workId/download', '/download', router, nextApp);
process.on('SIGTERM', close);
process.on('SIGINT', close);

router.get('/works/management/healthcheck', async ctx => {
ctx.status = 200;
ctx.body = 'ok';
return server;
})
.catch(err => {
throw err;
});

router.get('*', handleAllRoute(handle));
router.post(
'/account/api/users/:userId/item-requests',
handleAllRoute(handle)
);

koaApp.use(async (ctx, next) => {
ctx.res.statusCode = 200;
await next();
});

koaApp.use(router.routes());
return koaApp;
});

export default appPromise;
export const timers = middlewareTimers as NodeJS.Timer[];
export default serverPromise;
40 changes: 0 additions & 40 deletions catalogue/webapp/start.ts

This file was deleted.

6 changes: 5 additions & 1 deletion catalogue/webapp/test/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
*/
import supertest, { SuperTest } from 'supertest';
import { Server } from 'http';
import serverPromise from '../start';
import serverPromise from '../server';

jest.mock('@weco/common/server-data');
const mockExit = jest.spyOn(process, 'exit').mockImplementation((() => {
// never
}) as () => never);

let server: Server;
let request: SuperTest<supertest.Test>;
Expand All @@ -17,6 +20,7 @@ beforeAll(async () => {

afterAll(() => {
server?.close();
expect(mockExit).toHaveBeenCalledWith(0);
});

test('healthcheck', async () => {
Expand Down
Loading

0 comments on commit 3c6d5b7

Please sign in to comment.