123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342 |
- /**
- * Copyright 2013-2022 the PM2 project authors. All rights reserved.
- * 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');
- var path = require('path');
- var debug = require('debug')('pm2:serve');
- var probe = require('@pm2/io');
- var errorMeter = probe.meter({
- name : '404/sec',
- samples : 1,
- timeframe : 60
- })
- /**
- * list of supported content types.
- */
- var contentTypes = {
- '3gp': 'video/3gpp',
- 'a': 'application/octet-stream',
- 'ai': 'application/postscript',
- 'aif': 'audio/x-aiff',
- 'aiff': 'audio/x-aiff',
- 'asc': 'application/pgp-signature',
- 'asf': 'video/x-ms-asf',
- 'asm': 'text/x-asm',
- 'asx': 'video/x-ms-asf',
- 'atom': 'application/atom+xml',
- 'au': 'audio/basic',
- 'avi': 'video/x-msvideo',
- 'bat': 'application/x-msdownload',
- 'bin': 'application/octet-stream',
- 'bmp': 'image/bmp',
- 'bz2': 'application/x-bzip2',
- 'c': 'text/x-c',
- 'cab': 'application/vnd.ms-cab-compressed',
- 'cc': 'text/x-c',
- 'chm': 'application/vnd.ms-htmlhelp',
- 'class': 'application/octet-stream',
- 'com': 'application/x-msdownload',
- 'conf': 'text/plain',
- 'cpp': 'text/x-c',
- 'crt': 'application/x-x509-ca-cert',
- 'css': 'text/css',
- 'csv': 'text/csv',
- 'cxx': 'text/x-c',
- 'deb': 'application/x-debian-package',
- 'der': 'application/x-x509-ca-cert',
- 'diff': 'text/x-diff',
- 'djv': 'image/vnd.djvu',
- 'djvu': 'image/vnd.djvu',
- 'dll': 'application/x-msdownload',
- 'dmg': 'application/octet-stream',
- 'doc': 'application/msword',
- 'dot': 'application/msword',
- 'dtd': 'application/xml-dtd',
- 'dvi': 'application/x-dvi',
- 'ear': 'application/java-archive',
- 'eml': 'message/rfc822',
- 'eps': 'application/postscript',
- 'exe': 'application/x-msdownload',
- 'f': 'text/x-fortran',
- 'f77': 'text/x-fortran',
- 'f90': 'text/x-fortran',
- 'flv': 'video/x-flv',
- 'for': 'text/x-fortran',
- 'gem': 'application/octet-stream',
- 'gemspec': 'text/x-script.ruby',
- 'gif': 'image/gif',
- 'gz': 'application/x-gzip',
- 'h': 'text/x-c',
- 'hh': 'text/x-c',
- 'htm': 'text/html',
- 'html': 'text/html',
- 'ico': 'image/vnd.microsoft.icon',
- 'ics': 'text/calendar',
- 'ifb': 'text/calendar',
- 'iso': 'application/octet-stream',
- 'jar': 'application/java-archive',
- 'java': 'text/x-java-source',
- 'jnlp': 'application/x-java-jnlp-file',
- 'jpeg': 'image/jpeg',
- 'jpg': 'image/jpeg',
- 'js': 'application/javascript',
- 'json': 'application/json',
- 'log': 'text/plain',
- 'm3u': 'audio/x-mpegurl',
- 'm4v': 'video/mp4',
- 'man': 'text/troff',
- 'mathml': 'application/mathml+xml',
- 'mbox': 'application/mbox',
- 'mdoc': 'text/troff',
- 'me': 'text/troff',
- 'mid': 'audio/midi',
- 'midi': 'audio/midi',
- 'mime': 'message/rfc822',
- 'mml': 'application/mathml+xml',
- 'mng': 'video/x-mng',
- 'mov': 'video/quicktime',
- 'mp3': 'audio/mpeg',
- 'mp4': 'video/mp4',
- 'mp4v': 'video/mp4',
- 'mpeg': 'video/mpeg',
- 'mpg': 'video/mpeg',
- 'ms': 'text/troff',
- 'msi': 'application/x-msdownload',
- 'odp': 'application/vnd.oasis.opendocument.presentation',
- 'ods': 'application/vnd.oasis.opendocument.spreadsheet',
- 'odt': 'application/vnd.oasis.opendocument.text',
- 'ogg': 'application/ogg',
- 'p': 'text/x-pascal',
- 'pas': 'text/x-pascal',
- 'pbm': 'image/x-portable-bitmap',
- 'pdf': 'application/pdf',
- 'pem': 'application/x-x509-ca-cert',
- 'pgm': 'image/x-portable-graymap',
- 'pgp': 'application/pgp-encrypted',
- 'pkg': 'application/octet-stream',
- 'pl': 'text/x-script.perl',
- 'pm': 'text/x-script.perl-module',
- 'png': 'image/png',
- 'pnm': 'image/x-portable-anymap',
- 'ppm': 'image/x-portable-pixmap',
- 'pps': 'application/vnd.ms-powerpoint',
- 'ppt': 'application/vnd.ms-powerpoint',
- 'ps': 'application/postscript',
- 'psd': 'image/vnd.adobe.photoshop',
- 'py': 'text/x-script.python',
- 'qt': 'video/quicktime',
- 'ra': 'audio/x-pn-realaudio',
- 'rake': 'text/x-script.ruby',
- 'ram': 'audio/x-pn-realaudio',
- 'rar': 'application/x-rar-compressed',
- 'rb': 'text/x-script.ruby',
- 'rdf': 'application/rdf+xml',
- 'roff': 'text/troff',
- 'rpm': 'application/x-redhat-package-manager',
- 'rss': 'application/rss+xml',
- 'rtf': 'application/rtf',
- 'ru': 'text/x-script.ruby',
- 's': 'text/x-asm',
- 'sgm': 'text/sgml',
- 'sgml': 'text/sgml',
- 'sh': 'application/x-sh',
- 'sig': 'application/pgp-signature',
- 'snd': 'audio/basic',
- 'so': 'application/octet-stream',
- 'svg': 'image/svg+xml',
- 'svgz': 'image/svg+xml',
- 'swf': 'application/x-shockwave-flash',
- 't': 'text/troff',
- 'tar': 'application/x-tar',
- 'tbz': 'application/x-bzip-compressed-tar',
- 'tcl': 'application/x-tcl',
- 'tex': 'application/x-tex',
- 'texi': 'application/x-texinfo',
- 'texinfo': 'application/x-texinfo',
- 'text': 'text/plain',
- 'tif': 'image/tiff',
- 'tiff': 'image/tiff',
- 'torrent': 'application/x-bittorrent',
- 'tr': 'text/troff',
- 'txt': 'text/plain',
- 'vcf': 'text/x-vcard',
- 'vcs': 'text/x-vcalendar',
- 'vrml': 'model/vrml',
- 'war': 'application/java-archive',
- 'wav': 'audio/x-wav',
- 'wma': 'audio/x-ms-wma',
- 'wmv': 'video/x-ms-wmv',
- 'wmx': 'video/x-ms-wmx',
- 'wrl': 'model/vrml',
- 'wsdl': 'application/wsdl+xml',
- 'xbm': 'image/x-xbitmap',
- 'xhtml': 'application/xhtml+xml',
- 'xls': 'application/vnd.ms-excel',
- 'xml': 'application/xml',
- 'xpm': 'image/x-xpixmap',
- 'xsl': 'application/xml',
- 'xslt': 'application/xslt+xml',
- 'yaml': 'text/yaml',
- 'yml': 'text/yaml',
- 'zip': 'application/zip',
- 'woff': 'application/font-woff',
- 'woff2': 'application/font-woff',
- 'otf': 'application/font-sfnt',
- 'otc': 'application/font-sfnt',
- 'ttf': 'application/font-sfnt'
- };
- var options = {
- port: process.env.PM2_SERVE_PORT || process.argv[3] || 8080,
- host: process.env.PM2_SERVE_HOST || process.argv[4] || '0.0.0.0',
- 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.port === 'string') {
- options.port = parseInt(options.port) || 8080
- }
- 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) {
- 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, options.host, function (err) {
- if (err) {
- console.error(err);
- process.exit(1);
- }
- console.log('Exposing %s directory on %s:%d', options.path, options.host, options.port);
- });
- function serveFile(uri, request, response) {
- var file = decodeURIComponent(url.parse(uri || request.url).pathname);
- if (file === '/' || file === '') {
- file = options.homepage;
- request.wantHomepage = true;
- }
- var filePath = path.resolve(options.path + file);
- // since we call filesystem directly so we need to verify that the
- // url doesn't go outside the serve path
- if (filePath.indexOf(options.path) !== 0) {
- response.writeHead(403, { 'Content-Type': 'text/html' });
- return response.end('403 Forbidden');
- }
- var contentType = contentTypes[filePath.split('.').pop().toLowerCase()] || 'text/plain';
- fs.readFile(filePath, function (error, content) {
- if (error) {
- if ((!options.spa || file === options.homepage)) {
- console.error('[%s] Error while serving %s with content-type %s : %s',
- new Date(), filePath, contentType, error.message || error);
- }
- 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' });
- return response.end(content, 'utf-8');
- });
- return;
- }
- response.writeHead(500);
- return response.end('Sorry, check with the site admin for error: ' + error.code + ' ..\n');
- }
- // Add CORS headers to allow browsers to fetch data directly
- response.writeHead(200, {
- 'Content-Type': contentType,
- 'Access-Control-Allow-Origin': '*',
- 'Access-Control-Allow-Methods': 'GET'
- });
- 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);
- });
- }
- 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]
- }
- }
- function sendBasicAuthResponse(response) {
- response.writeHead(401, {
- 'Content-Type': 'text/html',
- 'WWW-Authenticate': 'Basic realm="Authentication service"'
- });
- return response.end('401 Unauthorized');
- }
|