-
richard.petersen authoredrichard.petersen authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
createApp.js 4.22 KiB
// Ignore paths for logging, metrics and docs
// Fast, minimalist web framework for node.
import express from 'express'
// Helmet helps you secure your Express app by setting various HTTP headers
import helmet from 'helmet'
// Fastest HTTP logger for Node.js in town
import { createHttpLogger } from '@open-xchange/logging'
// Readiness and liveness checks middleware
import health from '@cloudnative/health-connect'
// Prometheus middleware for standard api metrics
import promBundle from 'express-prom-bundle'
import promClient from 'prom-client'
// Swagger UI for api-docs
import swaggerUi from 'swagger-ui-express'
import yaml from 'js-yaml'
import fs from 'fs'
import { getCSSDependenciesFor, getDependencies, getOxManifests, getVersion, loadViteManifests, viteManifestToDeps } from './manifests.js'
import { fileCache } from './files.js'
const { logger } = createHttpLogger
const ignorePaths = ['/ready', '/healthy']
const swaggerDocument = yaml.load(fs.readFileSync('./src/swagger.yaml', 'utf8'))
const bypass = (request) => ignorePaths.includes(request.path)
const metricsMiddleware = promBundle({ bypass, includePath: true })
const startUpTimeGauge = new promClient.Gauge({
name: 'manifest_service_startup_time',
help: 'Time to warm up cache'
})
export function createApp () {
// The app returned by express() is in fact a JavaScript Function, designed to be passed to Node’s HTTP servers as a callback to handle requests.
const app = express()
const healthCheck = new health.HealthChecker()
const startupCheck = new health.StartupCheck('warmup cache', async function () {
const stopTimer = startUpTimeGauge.startTimer()
try {
const viteManifests = await loadViteManifests()
const deps = viteManifestToDeps(viteManifests)
await fileCache.warmUp(viteManifests, deps)
} catch (e) {
logger.error(`Failed to get dependencies: ${e.message}`)
throw e
} finally {
stopTimer()
}
})
healthCheck.registerStartupCheck(startupCheck)
// Application-level middleware
app.use(createHttpLogger())
app.use((req, res, next) => {
const { sha256Sum } = fileCache.get(req.path)
res.locals.sha256Sum = sha256Sum
next()
})
app.use(helmet({
contentSecurityPolicy: false,
crossOriginEmbedderPolicy: false,
originAgentCluster: false
}))
app.use('/healthy', health.LivenessEndpoint(healthCheck))
app.use('/ready', health.ReadinessEndpoint(healthCheck))
app.use(metricsMiddleware)
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument))
app.use('/swagger.json', (req, res) => res.json(swaggerDocument))
app.timeout = 30000
app.use(async (req, res, next) => {
const version = await getVersion()
if (version) res.setHeader('version', version)
next()
})
app.get('/manifests', async (req, res, next) => {
try {
res.json(await getOxManifests())
} catch (err) {
next(err)
}
})
app.get('/dependencies', async (req, res, next) => {
try {
res.json(Object.assign({ '/': [] }, await getDependencies()))
} catch (err) {
next(err)
}
})
app.get('/', async (req, res, next) => {
const { 'content-type': contentType, content } = fileCache.get('/index.html')
if (content) return res.setHeader('content-type', contentType).status(200).send(content)
next()
})
app.use(async (req, res, next) => {
const { 'content-type': contentType, content } = fileCache.get(req.path)
if (content) {
const dependencies = await getCSSDependenciesFor(req.path.substr(1))
return res
.setHeader('content-type', contentType)
.setHeader('dependencies', dependencies.join(','))
.status(200).send(content)
}
next()
})
app.use(async (req, res, next) => {
try {
const { 'content-type': contentType, content } = await fileCache.fetchAndStore(req.path)
if (content) {
const dependencies = await getCSSDependenciesFor(req.path.substr(1))
return res
.setHeader('content-type', contentType)
.setHeader('dependencies', dependencies.join(','))
.status(200).send(content)
}
} catch (e) {}
next()
})
app.use(function (err, req, res, next) {
logger.error(err)
res.status(500).end()
})
return app
}