refactor to new file

This commit is contained in:
Partouf
2019-10-27 15:26:14 +01:00
parent 6c4c578167
commit d29b5cbf1f
3 changed files with 223 additions and 197 deletions

183
app.js
View File

@@ -43,10 +43,8 @@ const nopt = require('nopt'),
{logger, logToPapertrail, suppressConsoleLog} = require('./lib/logger'),
webpackDevMiddleware = require("webpack-dev-middleware"),
utils = require('./lib/utils'),
clientState = require('./lib/clientstate'),
initialiseWine = require('./lib/exec').initialiseWine,
clientStateGoldenifier = require('./lib/clientstate-normalizer').ClientStateGoldenifier,
clientStateNormalizer = require('./lib/clientstate-normalizer').ClientStateNormalizer;
RouteAPI = require('./lib/handlers/route-api');
// Parse arguments from command line 'node ./app.js args...'
@@ -203,12 +201,6 @@ function contentPolicyHeader(res) {
}
}
function getGoldenLayoutFromClientState(state) {
const goldenifier = new clientStateGoldenifier();
goldenifier.fromClientState(state);
return goldenifier.golden;
}
function measureEventLoopLag(delayMs) {
return new Promise((resolve) => {
const start = process.hrtime.bigint();
@@ -265,8 +257,6 @@ aws.initConfig(awsProps)
const compileHandler = new CompileHandler(compilationEnvironment, awsProps);
const StorageHandler = require('./lib/storage/storage');
const storageHandler = StorageHandler.storageFactory(storageSolution, compilerProps, awsProps, httpRootDir);
const ApiHandler = require('./lib/handlers/api').Handler;
const apiHandler = new ApiHandler(compileHandler, ceProps, storageHandler);
const SourceHandler = require('./lib/handlers/source').Handler;
const sourceHandler = new SourceHandler(fileSources, staticHeaders);
const CompilerFinder = require('./lib/compiler-finder');
@@ -354,6 +344,19 @@ aws.initConfig(awsProps)
logger.info("Not configuring sentry");
}
const webServer = express(),
sFavicon = require('serve-favicon'),
bodyParser = require('body-parser'),
morgan = require('morgan'),
compression = require('compression'),
router = express.Router(),
healthCheck = require('./lib/handlers/health-check');
const healthCheckFilePath = ceProps("healthCheckFilePath", false);
const routeApi = new RouteAPI(router, compileHandler, ceProps,
storageHandler, renderConfig, renderGoldenLayout);
function onCompilerChange(compilers) {
if (JSON.stringify(prevCompilers) === JSON.stringify(compilers)) {
return;
@@ -364,11 +367,11 @@ aws.initConfig(awsProps)
}
prevCompilers = compilers;
clientOptionsHandler.setCompilers(compilers);
apiHandler.setCompilers(compilers);
apiHandler.setLanguages(languages);
apiHandler.setOptions(clientOptionsHandler);
routeApi.apiHandler.setCompilers(compilers);
routeApi.apiHandler.setLanguages(languages);
routeApi.apiHandler.setOptions(clientOptionsHandler);
}
onCompilerChange(compilers);
const rescanCompilerSecs = ceProps('rescanCompilerSecs', 0);
@@ -378,16 +381,6 @@ aws.initConfig(awsProps)
rescanCompilerSecs * 1000);
}
const webServer = express(),
sFavicon = require('serve-favicon'),
bodyParser = require('body-parser'),
morgan = require('morgan'),
compression = require('compression'),
router = express.Router(),
healthCheck = require('./lib/handlers/health-check');
const healthCheckFilePath = ceProps("healthCheckFilePath", false);
webServer
.set('trust proxy', true)
.set('view engine', 'pug')
@@ -450,33 +443,6 @@ aws.initConfig(awsProps)
return options;
}
function escapeLine(req, line) {
const userAgent = req.get('User-Agent');
if (userAgent.includes('Discordbot/2.0')) {
return line.replace(/&/g, '&')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
} else if (userAgent === 'Twitterbot/1.0') {
// TODO: Escape to something Twitter likes
return line;
} else if (userAgent.includes('Slackbot-LinkExpanding 1.0')) {
// TODO: Escape to something Slack likes
return line;
}
return line;
}
function filterCode(req, code, lang) {
if (lang.previewFilter) {
return _.chain(code.split('\n'))
.filter(line => !line.match(lang.previewFilter))
.map(line => escapeLine(req, line))
.value()
.join('\n');
}
return code;
}
function renderGoldenLayout(config, metadata, req, res) {
staticHeaders(res);
contentPolicyHeader(res);
@@ -487,113 +453,6 @@ aws.initConfig(awsProps)
}, req.query));
}
function renderClientState(clientstate, metadata, req, res) {
const config = getGoldenLayoutFromClientState(clientstate);
renderGoldenLayout(config, metadata, req, res);
}
function getMetaDataFromLink(req, link, config) {
const metadata = {
ogDescription: null,
ogAuthor: null,
ogTitle: "Compiler Explorer"
};
if (link) {
metadata.ogDescription = link.specialMetadata ? link.specialMetadata.description.S : null;
metadata.ogAuthor = link.specialMetadata ? link.specialMetadata.author.S : null;
metadata.ogTitle = link.specialMetadata ? link.specialMetadata.title.S : "Compiler Explorer";
}
if (!metadata.ogDescription) {
const sources = utils.glGetMainContents(config.content);
if (sources.editors.length === 1) {
const editor = sources.editors[0];
const lang = languages[editor.language];
if (lang) {
metadata.ogDescription = filterCode(req, editor.source, lang);
metadata.ogTitle += ` - ${lang.name}`;
if (sources.compilers.length === 1) {
const compilerId = sources.compilers[0].compiler;
const compiler = apiHandler.compilers.find(c => c.id === compilerId);
if (compiler) {
metadata.ogTitle += ` (${compiler.name})`;
}
}
} else {
metadata.ogDescription = editor.source;
}
}
} else if (metadata.ogAuthor && metadata.ogAuthor !== '.') {
metadata.ogDescription += `\nAuthor(s): ${metadata.ogAuthor}`;
}
return metadata;
}
function storedStateHandlerResetLayout(req, res, next) {
const id = req.params.id;
storageHandler.expandId(id)
.then(result => {
let config = JSON.parse(result.config);
if (config.content) {
const normalizer = new clientStateNormalizer();
normalizer.fromGoldenLayout(config);
config = normalizer.normalized;
} else {
config = new clientState.State(config);
}
const metadata = getMetaDataFromLink(req, result, config);
renderClientState(config, metadata, req, res);
})
.catch(err => {
logger.warn(`Exception thrown when expanding ${id}`);
logger.debug('Exception value:', err);
next({
statusCode: 404,
message: `ID "${id}" could not be found`
});
});
}
function unstoredStateHandler(req, res) {
const state = JSON.parse(Buffer.from(req.params.clientstatebase64, 'base64').toString());
const config = getGoldenLayoutFromClientState(new clientState.State(state));
const metadata = getMetaDataFromLink(req, null, config);
renderGoldenLayout(config, metadata, req, res);
}
function storedStateHandler(req, res, next) {
const id = req.params.id;
storageHandler.expandId(id)
.then(result => {
let config = JSON.parse(result.config);
if (config.sessions) {
config = getGoldenLayoutFromClientState(new clientState.State(config));
}
const metadata = getMetaDataFromLink(req, result, config);
renderGoldenLayout(config, metadata, req, res);
// And finally, increment the view count
// If any errors pop up, they are just logged, but the response should still be valid
// It's really unlikely that it happens as a result of the id not being there though,
// but can be triggered with a missing implementation for a derived storage (s3/local...)
storageHandler.incrementViewCount(id).catch(err => {
logger.error(`Error incrementing view counts for ${id} - ${err}`);
});
})
.catch(err => {
logger.warn(`Could not expand ${id}: ${err}`);
next({
statusCode: 404,
message: `ID "${id}" could not be found`
});
});
}
const embeddedHandler = function (req, res) {
staticHeaders(res);
contentPolicyHeader(res);
@@ -671,13 +530,11 @@ aws.initConfig(awsProps)
.use(bodyParser.json({limit: ceProps('bodyParserLimit', maxUploadSize)}))
.use(bodyParser.text({limit: ceProps('bodyParserLimit', maxUploadSize), type: () => true}))
.use('/source', sourceHandler.handle.bind(sourceHandler))
.use('/api', apiHandler.handle)
.use('/g', oldGoogleUrlHandler)
.get('/z/:id', storedStateHandler)
.get('/resetlayout/:id', storedStateHandlerResetLayout)
.get('/clientstate/:clientstatebase64', unstoredStateHandler)
.post('/shortener', shortener);
routeApi.InitializeRoutes();
if (!defArgs.doCache) {
logger.info(" with disabled caching");
}

View File

@@ -28,7 +28,6 @@ const express = require('express'),
FormatterHandler = require('./formatting'),
utils = require('../utils'),
logger = require('../logger').logger,
clientStateClasses = require('../clientstate'),
clientStateNormalizer = require('../clientstate-normalizer').ClientStateNormalizer;
class ApiHandler {
@@ -67,7 +66,6 @@ class ApiHandler {
this.handle.get('/formats', formatter.listHandler.bind(formatter));
this.handle.get('/shortlinkinfo/:id', this.shortlinkInfoHandler.bind(this));
this.handle.get('/shortlinkinfocode/:id/:sessionid', this.shortlinkInfoCodeHandler.bind(this));
}
shortlinkInfoHandler(req, res, next) {
@@ -96,38 +94,6 @@ class ApiHandler {
});
}
shortlinkInfoCodeHandler(req, res, next) {
const id = req.params.id;
const sessionid = parseInt(req.params.sessionid);
this.storageHandler.expandId(id)
.then(result => {
const config = JSON.parse(result.config);
let clientstate = false;
if (config.content) {
const normalizer = new clientStateNormalizer();
normalizer.fromGoldenLayout(config);
clientstate = normalizer.normalized;
} else {
clientstate = new clientStateClasses.ClientState(config);
}
const session = clientstate.findSessionById(sessionid);
if (!session) throw {msg: `Session ${sessionid} doesn't exist in this shortlink`};
res.set('Content-Type', 'text/plain');
res.end(session.source);
})
.catch(err => {
logger.debug(`Exception thrown when expanding ${id}: `, err);
next({
statusCode: 404,
message: `ID "${id}/${sessionid}" could not be found`
});
});
}
handleLanguages(req, res) {
const availableLanguages = this.usedLangIds.map(val => {
let lang = this.languages[val];

203
lib/handlers/route-api.js Normal file
View File

@@ -0,0 +1,203 @@
const
_ = require('underscore'),
utils = require('../utils'),
logger = require('../logger').logger,
ApiHandler = require('./api').Handler,
clientState = require('../clientstate'),
clientStateGoldenifier = require('../clientstate-normalizer').ClientStateGoldenifier,
clientStateNormalizer = require('../clientstate-normalizer').ClientStateNormalizer;
class RouteAPI {
constructor(router, compileHandler, ceProps, storageHandler, renderConfig, renderGoldenLayout) {
this.router = router;
this.renderConfig = renderConfig;
this.renderGoldenLayout = renderGoldenLayout;
this.storageHandler = storageHandler;
this.apiHandler = new ApiHandler(compileHandler, ceProps, storageHandler);
}
InitializeRoutes() {
this.router
.use('/api', this.apiHandler.handle)
.get('/z/:id', _.bind(this.storedStateHandler, this))
.get('/z/:id/code/:session', _.bind(this.storedCodeHandler, this))
.get('/resetlayout/:id', _.bind(this.storedStateHandlerResetLayout, this))
.get('/clientstate/:clientstatebase64', _.bind(this.unstoredStateHandler, this));
}
storedCodeHandler(req, res, next) {
const id = req.params.id;
const sessionid = parseInt(req.params.session);
this.storageHandler.expandId(id)
.then(result => {
const config = JSON.parse(result.config);
let clientstate = false;
if (config.content) {
const normalizer = new clientStateNormalizer();
normalizer.fromGoldenLayout(config);
clientstate = normalizer.normalized;
} else {
clientstate = new clientState.ClientState(config);
}
const session = clientstate.findSessionById(sessionid);
if (!session) throw {msg: `Session ${sessionid} doesn't exist in this shortlink`};
res.set('Content-Type', 'text/plain');
res.end(session.source);
})
.catch(err => {
logger.debug(`Exception thrown when expanding ${id}: `, err);
next({
statusCode: 404,
message: `ID "${id}/${sessionid}" could not be found`
});
});
}
storedStateHandler(req, res, next) {
const id = req.params.id;
this.storageHandler.expandId(id)
.then(result => {
let config = JSON.parse(result.config);
if (config.sessions) {
config = this.getGoldenLayoutFromClientState(new clientState.State(config));
}
const metadata = this.getMetaDataFromLink(req, result, config);
this.renderGoldenLayout(config, metadata, req, res);
// And finally, increment the view count
// If any errors pop up, they are just logged, but the response should still be valid
// It's really unlikely that it happens as a result of the id not being there though,
// but can be triggered with a missing implementation for a derived storage (s3/local...)
this.storageHandler.incrementViewCount(id).catch(err => {
logger.error(`Error incrementing view counts for ${id} - ${err}`);
});
})
.catch(err => {
logger.warn(`Could not expand ${id}: ${err}`);
next({
statusCode: 404,
message: `ID "${id}" could not be found`
});
});
}
getGoldenLayoutFromClientState(state) {
const goldenifier = new clientStateGoldenifier();
goldenifier.fromClientState(state);
return goldenifier.golden;
}
unstoredStateHandler(req, res) {
const state = JSON.parse(Buffer.from(req.params.clientstatebase64, 'base64').toString());
const config = this.getGoldenLayoutFromClientState(new clientState.State(state));
const metadata = this.getMetaDataFromLink(req, null, config);
this.renderGoldenLayout(config, metadata, req, res);
}
renderClientState(clientstate, metadata, req, res) {
const config = this.getGoldenLayoutFromClientState(clientstate);
this.renderGoldenLayout(config, metadata, req, res);
}
storedStateHandlerResetLayout(req, res, next) {
const id = req.params.id;
this.storageHandler.expandId(id)
.then(result => {
let config = JSON.parse(result.config);
if (config.content) {
const normalizer = new clientStateNormalizer();
normalizer.fromGoldenLayout(config);
config = normalizer.normalized;
} else {
config = new clientState.State(config);
}
const metadata = this.getMetaDataFromLink(req, result, config);
this.renderClientState(config, metadata, req, res);
})
.catch(err => {
logger.warn(`Exception thrown when expanding ${id}`);
logger.debug('Exception value:', err);
next({
statusCode: 404,
message: `ID "${id}" could not be found`
});
});
}
escapeLine(req, line) {
const userAgent = req.get('User-Agent');
if (userAgent.includes('Discordbot/2.0')) {
return line.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
} else if (userAgent === 'Twitterbot/1.0') {
// TODO: Escape to something Twitter likes
return line;
} else if (userAgent.includes('Slackbot-LinkExpanding 1.0')) {
// TODO: Escape to something Slack likes
return line;
}
return line;
}
filterCode(req, code, lang) {
if (lang.previewFilter) {
return _.chain(code.split('\n'))
.filter(line => !line.match(lang.previewFilter))
.map(line => this.escapeLine(req, line))
.value()
.join('\n');
}
return code;
}
getMetaDataFromLink(req, link, config) {
const metadata = {
ogDescription: null,
ogAuthor: null,
ogTitle: "Compiler Explorer"
};
if (link) {
metadata.ogDescription = link.specialMetadata ? link.specialMetadata.description.S : null;
metadata.ogAuthor = link.specialMetadata ? link.specialMetadata.author.S : null;
metadata.ogTitle = link.specialMetadata ? link.specialMetadata.title.S : "Compiler Explorer";
}
if (!metadata.ogDescription) {
const sources = utils.glGetMainContents(config.content);
if (sources.editors.length === 1) {
const editor = sources.editors[0];
const lang = this.apiHandler.languages[editor.language];
if (lang) {
metadata.ogDescription = this.filterCode(req, editor.source, lang);
metadata.ogTitle += ` - ${lang.name}`;
if (sources.compilers.length === 1) {
const compilerId = sources.compilers[0].compiler;
const compiler = this.apiHandler.compilers.find(c => c.id === compilerId);
if (compiler) {
metadata.ogTitle += ` (${compiler.name})`;
}
}
} else {
metadata.ogDescription = editor.source;
}
}
} else if (metadata.ogAuthor && metadata.ogAuthor !== '.') {
metadata.ogDescription += `\nAuthor(s): ${metadata.ogAuthor}`;
}
return metadata;
}
}
module.exports = RouteAPI;