@adobe/helix-cli
Version:
Project Helix CLI
121 lines (110 loc) • 3.82 kB
JavaScript
/*
* Copyright 2018 Adobe. All rights reserved.
* This file is licensed to you 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 REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
import { promisify } from 'util';
import path from 'path';
import compression from 'compression';
import utils from './utils.js';
import RequestContext from './RequestContext.js';
import { asyncHandler, BaseServer } from './BaseServer.js';
import LiveReload from './LiveReload.js';
export class HelixServer extends BaseServer {
/**
* Creates a new HelixServer for the given project.
* @param {HelixProject} project
*/
constructor(project) {
super(project);
this._liveReload = null;
this._enableLiveReload = false;
this._app.use(compression());
}
withLiveReload(value) {
this._enableLiveReload = value;
return this;
}
/**
* Proxy Mode route handler
* @param {Express.Request} req request
* @param {Express.Response} res response
*/
async handleProxyModeRequest(req, res) {
const sendFile = promisify(res.sendFile).bind(res);
const ctx = new RequestContext(req, this._project);
const { log } = this;
const proxyUrl = new URL(this._project.proxyUrl);
const filePath = path.join(this._project.directory, ctx.path);
if (path.relative(this._project.directory, filePath).startsWith('..')) {
log.info(`refuse to serve file outside the project directory: ${filePath}`);
res.status(403).send('');
return;
}
const liveReload = this._liveReload;
if (liveReload) {
liveReload.startRequest(ctx.requestId, ctx.path);
}
// try to serve static
try {
log.debug('trying to serve local file', filePath);
await sendFile(filePath, {
dotfiles: 'allow',
headers: {
'access-control-allow-origin': '*',
},
});
if (liveReload) {
liveReload.registerFile(ctx.requestId, filePath);
}
return;
} catch (e) {
log.debug(`Error while delivering resource ${ctx.path} - ${e.stack || e}`);
} finally {
if (liveReload) {
liveReload.endRequest(ctx.requestId);
}
}
// use proxy
try {
const url = new URL(ctx.url, proxyUrl);
for (const [key, value] of proxyUrl.searchParams.entries()) {
url.searchParams.append(key, value);
}
await utils.proxyRequest(ctx, url.href, req, res, {
injectLiveReload: this._project.liveReload,
headHtml: this._project.headHtml,
indexer: this._project.indexer,
cacheDirectory: this._project.cacheDirectory,
file404html: this._project.file404html,
});
} catch (err) {
log.error(`Failed to proxy Franklin request ${ctx.path}: ${err.message}`);
res.status(502).send(`Failed to proxy Franklin request: ${err.message}`);
}
this.emit('request', req, res, ctx);
}
async setupApp() {
await super.setupApp();
if (this._enableLiveReload) {
this._liveReload = new LiveReload(this.log);
await this._liveReload.init(this.app, this._server);
}
const handler = asyncHandler(this.handleProxyModeRequest.bind(this));
this.app.get('*', handler);
this.app.post('*', handler);
}
async doStop() {
await super.stop();
if (this._liveReload) {
await this._liveReload.stop();
delete this._liveReload;
}
}
}