-
Notifications
You must be signed in to change notification settings - Fork 649
/
server.js
104 lines (85 loc) · 4.07 KB
/
server.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
/**
* @license Copyright 2019 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/
'use strict';
/** @typedef {{port: number, close: () => Promise<void>, storageMethod: StorageMethod}} ServerInstance */
const path = require('path');
const createHttpServer = require('http').createServer;
const express = require('express');
const morgan = require('morgan');
const compression = require('compression');
const bodyParser = require('body-parser');
const ApiClient = require('@lhci/utils/src/api-client.js');
const createProjectsRouter = require('./api/routes/projects.js');
const StorageMethod = require('./api/storage/storage-method.js');
const {errorMiddleware, createBasicAuthMiddleware} = require('./api/express-utils.js');
const {startPsiCollectCron} = require('./cron/psi-collect.js');
const {startDeleteOldBuildsCron} = require('./cron/delete-old-builds');
const version = require('../package.json').version;
const DIST_FOLDER = path.join(__dirname, '../dist');
/**
* @param {LHCI.ServerCommand.Options} options
* @return {Promise<{app: Parameters<typeof createHttpServer>[1], storageMethod: StorageMethod}>}
*/
async function createApp(options) {
const {storage} = options;
const storageMethod = StorageMethod.from(storage);
await storageMethod.initialize(storage);
const context = {storageMethod, options};
const app = express();
if (options.logLevel !== 'silent') app.use(morgan('short'));
// While LHCI should be served behind nginx/apache that handles compression, it won't always be.
app.use(compression());
// 1. Support large payloads because LHRs are big.
// 2. Support JSON primitives because `PUT /builds/<id>/lifecycle "sealed"`
app.use(bodyParser.json({limit: '10mb', strict: false}));
// The entire server can be protected by HTTP Basic Authentication
const authMiddleware = createBasicAuthMiddleware(context);
if (authMiddleware) app.use(authMiddleware);
app.get('/', (_, res) => res.redirect('/app'));
app.use('/version', (_, res) => res.send(version));
app.use('/v1/projects', createProjectsRouter(context));
app.use('/app', express.static(DIST_FOLDER));
app.get('/app/*', (_, res) => res.sendFile(path.join(DIST_FOLDER, 'index.html')));
app.use(errorMiddleware);
startPsiCollectCron(storageMethod, options);
startDeleteOldBuildsCron(storageMethod, options);
return {app, storageMethod};
}
/**
* @param {LHCI.ServerCommand.Options} options
* @return {Promise<ServerInstance>}
*/
async function createServer(options) {
const {app, storageMethod} = await createApp(options);
return new Promise(resolve => {
const server = createHttpServer(app);
// Node default socket timeout is 2 minutes.
// Some LHCI API operations (computing statistics) can take longer than that under heavy load.
// Set the timeout even higher to allow for these requests to complete.
server.on('connection', socket => {
socket.setTimeout(20 * 60 * 1000); // 20 minutes
socket.on('timeout', () => {
if (options.logLevel !== 'silent') {
process.stdout.write(`${new Date().toISOString()} - socket timed out\n`);
}
socket.end();
});
});
server.listen(options.port, () => {
const serverAddress = server.address();
const listenPort =
typeof serverAddress === 'string' || !serverAddress ? options.port : serverAddress.port;
resolve({
storageMethod,
port: listenPort,
close: async () => {
await Promise.all([new Promise(r => server.close(r)), storageMethod.close()]);
},
});
});
});
}
module.exports = {createApp, createServer, ApiClient};