mirror of
https://github.com/Unitech/pm2.git
synced 2025-12-08 20:35:53 +00:00
commit
ca325ea4ae
6
bin/pm2
6
bin/pm2
@ -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')
|
||||
|
||||
21
lib/API.js
21
lib/API.js
@ -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 = {}; }
|
||||
|
||||
@ -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) {
|
||||
|
||||
126
lib/API/Serve.js
126
lib/API/Serve.js
@ -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');
|
||||
}
|
||||
@ -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
|
||||
|
||||
|
||||
13
test/fixtures/serve/ecosystem-serve.json
vendored
Normal file
13
test/fixtures/serve/ecosystem-serve.json
vendored
Normal 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
2
test/fixtures/serve/other.html
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
iam another file
|
||||
with 2 lines
|
||||
Loading…
x
Reference in New Issue
Block a user