function handlerFactory()

in components/base-rest-api/packages/api-handler-factory/lib/handler.js [32:142]


function handlerFactory({ registerServices, registerRoutes, registerI18n }) {
  return async (event, context) => {
    if (cachedHandler) return cachedHandler(event, context);

    const apiRouter = express.Router({ mergeParams: true });
    const app = express();
    app.disable('x-powered-by');

    // register services
    const servicesContainer = new ServicesContainer(['settings', 'log']);
    await registerServices(servicesContainer);
    await servicesContainer.initServices();

    // check circular dependencies
    const servicesList = servicesContainer.validate();

    // resolve settings and log services
    const logger = await servicesContainer.find('log');
    const settingsService = await servicesContainer.find('settings');

    // create app context
    const appContext = new AppContext({ app, settings: settingsService, log: logger, servicesContainer });

    // register routes
    await registerRoutes(appContext, apiRouter);

    // setup CORS, compression and body parser
    const isDev = settingsService.get('envType') === 'dev';
    let allowList = settingsService.optionalObject('corsAllowList', []);
    if (isDev) allowList = _.concat(allowList, settingsService.optionalObject('corsAllowListLocal', []));
    const corsOptions = {
      origin: (origin, callback) => {
        if (allowList.indexOf(origin) !== -1) {
          callback(null, true);
        } else {
          callback(null, false);
        }
      },
      optionsSuccessStatus: 200,
    };

    //  i18n
    // Supported languages will be a hash of language names to the actual translation files that support that language e.g. {'en-GB':'en'}
    const supportedLanguagesSetting = settingsService.optional('supportedLanguages', '');
    if (!_.isEmpty(supportedLanguagesSetting)) {
      try {
        const supportedLanguages = JSON.parse(supportedLanguagesSetting);

        // The translators is a hash of 'roddeh-i18n' instances that are keyed based on the language files.
        translators = await registerI18n();

        // The defaultLanguage to use when the language associated with the request is not supported by the solution e.g. the http-header requests 'cn-ZH' but we don't support, so use English instead
        const defaultLanguage = settingsService.get('defaultLanguage');
        // The translator instance needs to be set on a per-request basis as requests coming for different languages may be handled by the same lambda instance.
        app.use(i18nHandler(translators, supportedLanguages, defaultLanguage));
      } catch (_e) {
        logger.warn('WARNING! Invalid supportedLanguages setting. I18N has not been configured.');
      }
    }

    app.use(compression());
    app.use(cors(corsOptions));
    app.use(bodyParser.json({ limit: '50mb' })); // see https://stackoverflow.com/questions/19917401/error-request-entity-too-large
    app.use(bodyParser.urlencoded({ limit: '50mb', extended: true })); // for parsing application/x-www-form-urlencoded
    // Set default security headers
    app.use((req, res, next) => {
      const securityHeaders = {};
      securityHeaders['strict-transport-security'] = 'max-age=63072000; includeSubdomains';
      const cspRules = [`default-src 'self'`];
      securityHeaders['content-security-policy'] = cspRules.join('; ');
      securityHeaders['x-content-type-options'] = 'nosniff';
      securityHeaders['x-frame-options'] = 'SAMEORIGIN';
      securityHeaders['x-xss-protection'] = '1; mode=block';
      securityHeaders['referrer-policy'] = 'same-origin';
      res.set(securityHeaders);
      next();
    });

    // mount all routes under /
    app.use('/', apiRouter);

    // add global error handler
    app.use(errorHandler());

    // allow options for all
    app.options('*');

    // prepare the handler
    cachedHandler = serverless(app, {
      callbackWaitsForEmptyEventLoop: true,
      request(req, { requestContext = {} }) {
        // expose the lambda event request context
        req.context = requestContext;
      },
    });

    const isVerbose = settingsService.optional('isVerbose', 'yes');
    if (isVerbose === 'yes') {
      // print useful information
      const settingsList = settingsService.entries;

      logger.info('Settings available are :');
      logger.info(settingsList);

      logger.info('Services available are :');
      logger.info(servicesList);
    }

    return cachedHandler(event, context);
  };
}