Merge pull request #4220 from f-hj/development

PM2 Serve v2
This commit is contained in:
Alexandre Strzelewicz 2019-04-05 11:40:26 +02:00 committed by GitHub
commit ca325ea4ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 227 additions and 24 deletions

View File

@ -961,9 +961,13 @@ commander.command('deepUpdate')
commander.command('serve [path] [port]')
.alias('expose')
.option('--port [port]', 'specify port to listen to')
.option('--spa', 'always serving index.html on inexistant sub path')
.option('--basic-auth-username [username]', 'set basic auth username')
.option('--basic-auth-password [password]', 'set basic auth password')
.option('--monitor [frontend-app]', 'frontend app monitoring (auto integrate snippet on html files)')
.description('serve a directory over http via port')
.action(function (path, port, cmd) {
pm2.serve(path, port || cmd.port, commander);
pm2.serve(path, port || cmd.port, cmd, commander);
});
commander.command('examples')

View File

@ -868,6 +868,7 @@ class API {
_startJson (file, opts, action, pipe, cb) {
var config = {};
var appConf = {};
var staticConf = [];
var deployConf = {};
var apps_info = [];
var that = this;
@ -911,6 +912,8 @@ class API {
*/
if (config.deploy)
deployConf = config.deploy;
if (config.static)
staticConf = config.static;
if (config.apps)
appConf = config.apps;
else if (config.pm2)
@ -929,6 +932,24 @@ class API {
var apps_name = [];
var proc_list = {};
// Add statics to apps
staticConf.forEach(function(serve) {
appConf.push({
name: serve.name ? serve.name : `static-page-server-${serve.port}`,
script: path.resolve(__dirname, 'API', 'Serve.js'),
env: {
PM2_SERVE_PORT: serve.port,
PM2_SERVE_PATH: serve.path,
PM2_SERVE_SPA: serve.spa,
PM2_SERVE_DIRECTORY: serve.directory,
PM2_SERVE_BASIC_AUTH: serve.basic_auth !== undefined,
PM2_SERVE_BASIC_AUTH_USERNAME: serve.basic_auth ? serve.basic_auth.username : null,
PM2_SERVE_BASIC_AUTH_PASSWORD: serve.basic_auth ? serve.basic_auth.password : null,
PM2_SERVE_MONITOR: serve.monitor
}
});
});
// Here we pick only the field we want from the CLI when starting a JSON
appConf.forEach(function(app) {
if (!app.env) { app.env = {}; }

View File

@ -467,21 +467,36 @@ module.exports = function(CLI) {
* @param {Object} opts options
* @param {String} opts.path path to be served
* @param {Number} opts.port port on which http will bind
* @param {Boolean} opts.spa single page app served
* @param {String} opts.basicAuthUsername basic auth username
* @param {String} opts.basicAuthPassword basic auth password
* @param {Object} commander commander object
* @param {Function} cb optional callback
*/
CLI.prototype.serve = function (target_path, port, opts, cb) {
CLI.prototype.serve = function (target_path, port, opts, commander, cb) {
var that = this;
var servePort = process.env.PM2_SERVE_PORT || port || 8080;
var servePath = path.resolve(process.env.PM2_SERVE_PATH || target_path || '.');
var filepath = path.resolve(path.dirname(module.filename), './Serve.js');
if (!opts.name || typeof(opts.name) == 'function')
opts.name = 'static-page-server-' + servePort;
if (typeof commander.name === 'string')
opts.name = commander.name
else
opts.name = 'static-page-server-' + servePort
if (!opts.env)
opts.env = {};
opts.env.PM2_SERVE_PORT = servePort;
opts.env.PM2_SERVE_PATH = servePath;
opts.env.PM2_SERVE_SPA = opts.spa;
if (opts.basicAuthUsername && opts.basicAuthPassword) {
opts.env.PM2_SERVE_BASIC_AUTH = 'true';
opts.env.PM2_SERVE_BASIC_AUTH_USERNAME = opts.basicAuthUsername;
opts.env.PM2_SERVE_BASIC_AUTH_PASSWORD = opts.basicAuthPassword;
}
if (opts.monitor) {
opts.env.PM2_SERVE_MONITOR = opts.monitor
}
opts.cwd = servePath;
this.start(filepath, opts, function (err, res) {

View File

@ -3,6 +3,8 @@
* Use of this source code is governed by a license that
* can be found in the LICENSE file.
*/
'use strict';
var fs = require('fs');
var http = require('http');
var url = require('url');
@ -197,18 +199,60 @@ var contentTypes = {
'ttf': 'application/font-sfnt'
};
var options = {
port: process.env.PM2_SERVE_PORT || process.argv[3] || 8080,
path: path.resolve(process.env.PM2_SERVE_PATH || process.argv[2] || '.')
path: path.resolve(process.env.PM2_SERVE_PATH || process.argv[2] || '.'),
spa: process.env.PM2_SERVE_SPA === 'true',
homepage: process.env.PM2_SERVE_HOMEPAGE || '/index.html',
basic_auth: process.env.PM2_SERVE_BASIC_AUTH === 'true' ? {
username: process.env.PM2_SERVE_BASIC_AUTH_USERNAME,
password: process.env.PM2_SERVE_BASIC_AUTH_PASSWORD
} : null,
monitor: process.env.PM2_SERVE_MONITOR
};
if (typeof options.monitor === 'string' && options.monitor !== '') {
try {
let fileContent = fs.readFileSync(path.join(process.env.PM2_HOME, 'agent.json5')).toString()
// Handle old configuration with json5
fileContent = fileContent.replace(/\s(\w+):/g, '"$1":')
// parse
let conf = JSON.parse(fileContent)
options.monitorBucket = conf.public_key
} catch (e) {
console.log('Interaction file does not exist')
}
}
// start an HTTP server
http.createServer(function (request, response) {
var file = url.parse(request.url).pathname;
if (options.basic_auth) {
if (!request.headers.authorization || request.headers.authorization.indexOf('Basic ') === -1) {
return sendBasicAuthResponse(response)
}
var user = parseBasicAuth(request.headers.authorization)
if (user.username !== options.basic_auth.username || user.password !== options.basic_auth.password) {
return sendBasicAuthResponse(response)
}
}
serveFile(request.url, request, response);
}).listen(options.port, function (err) {
if (err) {
console.error(err);
process.exit(1);
}
console.log('Exposing %s directory on port %d', options.path, options.port);
});
function serveFile(uri, request, response) {
var file = url.parse(uri || request.url).pathname;
if (file === '/' || file === '') {
file = '/index.html';
file = options.homepage;
request.wantHomepage = true;
}
var filePath = path.resolve(options.path + file);
@ -223,30 +267,72 @@ http.createServer(function (request, response) {
fs.readFile(filePath, function (error, content) {
if (error) {
console.error('[%s] Error while serving %s with content-type %s : %s',
Date.now(), filePath, contentType, error.message || error);
if ((!options.spa || request.wantHomepage)) {
console.error('[%s] Error while serving %s with content-type %s : %s',
new Date(), filePath, contentType, error.message || error);
}
if (!isNode4)
errorMeter.mark();
if (error.code === 'ENOENT') {
if (options.spa && !request.wantHomepage) {
request.wantHomepage = true;
return serveFile(`/${path.basename(file)}`, request, response);
} else if (options.spa && file !== options.homepage) {
return serveFile(options.homepage, request, response);
}
fs.readFile(options.path + '/404.html', function (err, content) {
content = err ? '404 Not Found' : content;
response.writeHead(404, { 'Content-Type': 'text/html' });
response.end(content, 'utf-8');
return response.end(content, 'utf-8');
});
} else {
response.writeHead(500);
response.end('Sorry, check with the site admin for error: ' + error.code + ' ..\n');
return;
}
} else {
response.writeHead(200, { 'Content-Type': contentType });
response.end(content, 'utf-8');
debug('[%s] Serving %s with content-type %s', Date.now(), filePath, contentType);
response.writeHead(500);
return response.end('Sorry, check with the site admin for error: ' + error.code + ' ..\n');
}
response.writeHead(200, { 'Content-Type': contentType });
if (options.monitorBucket && contentType === 'text/html') {
content = content.toString().replace('</body>', `
<script>
;(function (b,e,n,o,i,t) {
b[o]=b[o]||function(f){(b[o].c=b[o].c||[]).push(f)};
t=e.createElement(i);e=e.getElementsByTagName(i)[0];
t.async=1;t.src=n;e.parentNode.insertBefore(t,e);
}(window,document,'https://apm.pm2.io/pm2-io-apm-browser.v1.js','pm2Ready','script'))
pm2Ready(function(apm) {
apm.setBucket('${options.monitorBucket}')
apm.setApplication('${options.monitor}')
apm.reportTimings()
apm.reportIssues()
})
</script>
</body>
`);
}
response.end(content, 'utf-8');
debug('[%s] Serving %s with content-type %s', Date.now(), filePath, contentType);
});
}).listen(options.port, function (err) {
if (err) {
console.error(err);
process.exit(1);
}
function parseBasicAuth(auth) {
// auth is like `Basic Y2hhcmxlczoxMjM0NQ==`
var tmp = auth.split(' ');
var buf = Buffer.from(tmp[1], 'base64');
var plain = buf.toString();
var creds = plain.split(':');
return {
username: creds[0],
password: creds[1]
}
console.log('Exposing %s directory on port %d', options.path, options.port);
});
}
function sendBasicAuthResponse(response) {
response.writeHead(401, {
'Content-Type': 'text/html',
'WWW-Authenticate': 'Basic realm="Authentication service"'
});
return response.end('401 Unauthorized');
}

View File

@ -29,6 +29,68 @@ OUT=`cat /tmp/tmp_out.txt | grep -o "good shit" | wc -l`
[ $OUT -eq 0 ] || fail "should be offline"
success "should be offline"
echo "testing SPA"
$pm2 serve . $PORT --spa
should 'should have started serving dir' 'online' 1
curl http://localhost:$PORT/ > /tmp/tmp_out.txt
OUT=`cat /tmp/tmp_out.txt | grep -o "good shit" | wc -l`
[ $OUT -eq 1 ] || fail "should have served index file under /index.html"
success "should have served index file under /index.html"
curl http://localhost:$PORT/index.html > /tmp/tmp_out.txt
OUT=`cat /tmp/tmp_out.txt | grep -o "good shit" | wc -l`
[ $OUT -eq 1 ] || fail "should have served index file under /index.html"
success "should have served index file under /index.html"
curl http://localhost:$PORT/other.html > /tmp/tmp_out.txt
OUT=`cat /tmp/tmp_out.txt | wc -l`
[ $OUT -eq 2 ] || fail "should have served file under /other.html"
success "should have served file under /other.html"
curl http://localhost:$PORT/mangezdespommes/avecpepin/lebref > /tmp/tmp_out.txt
OUT=`cat /tmp/tmp_out.txt | grep -o "good shit" | wc -l`
[ $OUT -eq 1 ] || fail "should have served index file under /index.html"
success "should have served index file under /index.html"
curl http://localhost:$PORT/mangezdespommes/avecpepin/lebref/other.html > /tmp/tmp_out.txt
OUT=`cat /tmp/tmp_out.txt | wc -l`
[ $OUT -eq 2 ] || fail "should have served file under /other.html"
success "should have served file under /other.html"
echo "Shutting down the server"
$pm2 delete all
echo "testing basic auth"
$pm2 serve . $PORT --basic-auth-username user --basic-auth-password pass
should 'should have started serving dir' 'online' 1
curl http://user:pass@localhost:$PORT/index.html > /tmp/tmp_out.txt
OUT=`cat /tmp/tmp_out.txt | grep -o "good shit" | wc -l`
[ $OUT -eq 1 ] || fail "should have served index file under /index.html"
success "should have served index file under /index.html"
echo "Shutting down the server"
$pm2 delete all
echo "Testing with static ecosystem"
$pm2 start ecosystem-serve.json
should 'should have started serving dir' 'online' 1
curl http://user:pass@localhost:8081/index.html > /tmp/tmp_out.txt
OUT=`cat /tmp/tmp_out.txt | grep -o "good shit" | wc -l`
[ $OUT -eq 1 ] || fail "should be listening on port 8081"
success "should be listening on port 8081"
curl http://user:pass@localhost:8081/mangezdesmangues/aupakistan > /tmp/tmp_out.txt
OUT=`cat /tmp/tmp_out.txt | grep -o "good shit" | wc -l`
[ $OUT -eq 1 ] || fail "should be listening on port 8081"
success "should be listening on port 8081"
echo "Shutting down the server"
$pm2 delete all
$pm2 serve . $PORT_2
should 'should have started serving dir' 'online' 1

View File

@ -0,0 +1,13 @@
{
"static": [
{
"path": ".",
"spa": true,
"basic_auth": {
"username": "user",
"password": "pass"
},
"port": 8081
}
]
}

2
test/fixtures/serve/other.html vendored Normal file
View File

@ -0,0 +1,2 @@
iam another file
with 2 lines