diff --git a/bin/http-server b/bin/http-server index 3b9e59d2..6979da9c 100755 --- a/bin/http-server +++ b/bin/http-server @@ -13,7 +13,8 @@ var colors = require('colors/safe'), var argv = require('minimist')(process.argv.slice(2), { alias: { tls: 'ssl' - } + }, + boolean: ['isolate'], }); var ifaces = os.networkInterfaces(); @@ -35,6 +36,9 @@ if (argv.h || argv.help) { ' -s --silent Suppress log messages from output', ' --cors[=headers] Enable CORS via the "Access-Control-Allow-Origin" header', ' Optionally provide CORS headers list separated by commas', + ' --isolate Include response headers required for cross origin isolation in', + ' order to use SharedArrayBuffer and high resolution performance timers', + ' in latest Chrome. See https://web.dev/cross-origin-isolation-guide', ' -o [path] Open browser window after starting the server.', ' Optionally provide a URL path to open the browser window to.', ' -c Cache time (max-age) in seconds [3600], e.g. -c10 for 10 seconds.', @@ -152,6 +156,7 @@ function listen(port) { proxyOptions: proxyOptions, showDotfiles: argv.dotfiles, mimetypes: argv.mimetypes, + isolate: argv.isolate, username: argv.username || process.env.NODE_HTTP_SERVER_USERNAME, password: argv.password || process.env.NODE_HTTP_SERVER_PASSWORD }; diff --git a/lib/core/opts.js b/lib/core/opts.js index ec1b2cbc..61cd47a1 100644 --- a/lib/core/opts.js +++ b/lib/core/opts.js @@ -125,6 +125,11 @@ module.exports = (opts) => { } }); + if (opts.isolate) { + headers['Cross-Origin-Embedder-Policy'] = 'require-corp'; + headers['Cross-Origin-Opener-Policy'] = 'same-origin'; + } + aliases.headers.forEach((k) => { if (isDeclared(k)) { if (Array.isArray(opts[k])) { diff --git a/lib/http-server.js b/lib/http-server.js index dfe4c474..f0340c7d 100644 --- a/lib/http-server.js +++ b/lib/http-server.js @@ -109,6 +109,11 @@ function HttpServer(options) { requestHeaders: this.headers['Access-Control-Allow-Headers'].split(/\s*,\s*/) } : null)); } + + if (options.isolate) { + this.headers['Cross-Origin-Embedder-Policy'] = 'require-corp'; + this.headers['Cross-Origin-Opener-Policy'] = 'same-origin'; + } if (options.robots) { before.push(function (req, res) { diff --git a/test/isolate.test.js b/test/isolate.test.js new file mode 100644 index 00000000..d0c2728a --- /dev/null +++ b/test/isolate.test.js @@ -0,0 +1,91 @@ +'use strict'; + +const test = require('tap').test; +const server = require('../lib/core'); +const http = require('http'); +const path = require('path'); +const request = require('request'); + +const root = path.join(__dirname, 'public'); + +test('isolate defaults to false', (t) => { + t.plan(4); + + const httpServer = http.createServer( + server({ + root, + autoIndex: true, + defaultExt: 'html', + }) + ); + + httpServer.listen(() => { + const port = httpServer.address().port; + const uri = `http://localhost:${port}/subdir/index.html`; + + request.get({ uri }, (err, res) => { + t.ifError(err); + t.equal(res.statusCode, 200); + t.type(res.headers['cross-origin-embedder-policy'], 'undefined'); + t.type(res.headers['cross-origin-opener-policy'], 'undefined'); + }); + }); + t.once('end', () => { + httpServer.close(); + }); +}); + +test('isolate set to false', (t) => { + t.plan(4); + + const httpServer = http.createServer( + server({ + root, + isolate: false, + autoIndex: true, + defaultExt: 'html', + }) + ); + + httpServer.listen(() => { + const port = httpServer.address().port; + const uri = `http://localhost:${port}/subdir/index.html`; + + request.get({ uri }, (err, res) => { + t.ifError(err); + t.equal(res.statusCode, 200); + t.type(res.headers['cross-origin-embedder-policy'], 'undefined'); + t.type(res.headers['cross-origin-opener-policy'], 'undefined'); + }); + }); + t.once('end', () => { + httpServer.close(); + }); +}); + +test('isolate set to true', (t) => { + t.plan(4); + + const httpServer = http.createServer( + server({ + root, + isolate: true, + autoIndex: true, + defaultExt: 'html', + }) + ); + + httpServer.listen(() => { + const port = httpServer.address().port; + const uri = `http://localhost:${port}/subdir/index.html`; + request.get({ uri }, (err, res) => { + t.ifError(err); + t.equal(res.statusCode, 200); + t.equal(res.headers['cross-origin-embedder-policy'], 'require-corp'); + t.equal(res.headers['cross-origin-opener-policy'], 'same-origin'); + }); + }); + t.once('end', () => { + httpServer.close(); + }); +});