Feature(s) impacted
Agent deployment on serverless hosting
Observed behavior
I want to deploy the Forest Admin agent on Google Cloud Run. It’s a serverless service, so when the instance is not used for a moment, it’s shut down. And it’s re-instantiate on demand.
That’s the theory. When the agent starts, its returns a 404 on the route “/forest/authentication” for at least 3 seconds. Until it is initiated.
Because of that, I get a error on the ForestAdmin app on the first page load. The error disappear if I refresh the page (because the agent is loaded).
Expected behavior
Is there a way to just way for the app to be ready to resolve the promise on “/forest/authentication”?
I don’t care if the loading takes 4 seconds the first time. I just don’t want to have an error.
Context
- Project name: mfc-staging
- Team name: Operation
- Environment name: Staging Online
- Agent type & version: forest-express-sequelize v9
I’ve managed to do what I wanted following the post in Error when logging in on Forest with admin backend deployed on AWS in lambda function - #16 by Cyril_Limam
I’ve updated to app.js file to return an async createApp function. And the server.js to use “const app = await createApp();”
It works now
1 Like
Thanks a lot for sharing the solution!
If needed, here is my new app.js file
const express = require('express');
const requireAll = require('require-all');
const path = require('path');
const cookieParser = require('cookie-parser');
const bodyParser = require('body-parser');
const cors = require('cors');
const jwt = require('express-jwt');
const morgan = require('morgan');
const {
errorHandler,
ensureAuthenticated,
PUBLIC_ROUTES,
} = require('forest-express-sequelize');
async function createApp(){
const app = express();
let allowedOrigins = [/\.forestadmin\.com$/, /localhost:\d{4}$/];
if (process.env.CORS_ORIGINS) {
allowedOrigins = allowedOrigins.concat(process.env.CORS_ORIGINS.split(','));
}
const corsConfig = {
origin: allowedOrigins,
maxAge: 86400, // NOTICE: 1 day
credentials: true,
};
app.use(morgan('tiny'));
// Support for request-private-network as the `cors` package
// doesn't support it by default
// See: https://github.com/expressjs/cors/issues/236
app.use((req, res, next) => {
if (req.headers['access-control-request-private-network']) {
res.setHeader('access-control-allow-private-network', 'true');
}
next(null);
});
app.use('/forest/authentication', cors({
...corsConfig,
// The null origin is sent by browsers for redirected AJAX calls
// we need to support this in authentication routes because OIDC
// redirects to the callback route
origin: corsConfig.origin.concat('null')
}));
app.use(cors(corsConfig));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use(jwt({
secret: process.env.FOREST_AUTH_SECRET,
credentialsRequired: false,
algorithms: ['HS256'],
}));
app.use('/forest', (request, response, next) => {
if (PUBLIC_ROUTES.includes(request.url)) {
return next();
}
return ensureAuthenticated(request, response, next);
});
requireAll({
dirname: path.join(__dirname, 'routes'),
recursive: true,
resolve: (Module) => app.use('/forest', Module),
});
const middlewares = requireAll({
dirname: path.join(__dirname, 'middlewares'),
recursive: true,
resolve: (Module) => Module(app),
});
app.use(errorHandler());
await Promise.all(Object.keys(middlewares).map((key) => middlewares[key]));
return app;
}
module.exports = createApp;
And server.js file:
require('dotenv').config();
const debug = require('debug')('{name}:server');
const http = require('http');
const chalk = require('chalk');
const createApp = require("./app");
function normalizePort(val) {
const port = parseInt(val, 10);
if (Number.isNaN(port)) { return val; }
if (port >= 0) { return port; }
return false;
}
async function createServer() {
const port = normalizePort(process.env.PORT || process.env.APPLICATION_PORT || '3310');
const app = await createApp();
app.set('port', port);
const server = http.createServer(app);
server.listen(port);
function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}
const bind = typeof port === 'string'
? `Pipe ${port}`
: `Port ${port}`;
switch (error.code) {
case 'EACCES':
console.error(`${bind} requires elevated privileges`);
process.exit(1);
break;
case 'EADDRINUSE':
console.error(`${bind} is already in use`);
process.exit(1);
break;
default:
throw error;
}
}
function onListening() {
const addr = server.address();
const bind = typeof addr === 'string'
? `pipe ${addr}`
: `port ${addr.port}`;
debug(`Listening on ${bind}`);
console.log(chalk.cyan(`Your application is listening on ${bind}.`));
}
server.on('error', onError);
server.on('listening', onListening);
}
createServer();
1 Like