From 4292dcb1d8be03c66c7103a0e831dcd45325ebde Mon Sep 17 00:00:00 2001 From: Thomas Wang Date: Thu, 24 Oct 2019 17:19:24 -0700 Subject: [PATCH 1/2] fix: throwing w/ fresh ember-cli-fastboot serve ember-cli-fastboot requires configuring host whitelist before accessing host from request. In ember-fetch, the error should be lazily thrown. --- fastboot/instance-initializers/setup-fetch.js | 8 ++- public/fetch-fastboot.js | 18 ++++-- test/fastboot-fetch-test.js | 55 +++++++++++++++++++ test/fastboot-test.js | 11 ++-- 4 files changed, 78 insertions(+), 14 deletions(-) create mode 100644 test/fastboot-fetch-test.js diff --git a/fastboot/instance-initializers/setup-fetch.js b/fastboot/instance-initializers/setup-fetch.js index 0232f95a..b5017f14 100644 --- a/fastboot/instance-initializers/setup-fetch.js +++ b/fastboot/instance-initializers/setup-fetch.js @@ -9,8 +9,12 @@ function patchFetchForRelativeURLs(instance) { const request = fastboot.get('request'); // Prember is not sending protocol const protocol = request.protocol === 'undefined:' ? 'http:' : request.protocol; - // host is cp - setupFastboot(protocol, request.get('host')); + try { + // host is cp + setupFastboot(protocol, request.get('host')); + } catch (_e) { + // Do not throw error until request with a relative url. + } } export default { diff --git a/public/fetch-fastboot.js b/public/fetch-fastboot.js index 59c3607e..428d1522 100644 --- a/public/fetch-fastboot.js +++ b/public/fetch-fastboot.js @@ -8,6 +8,14 @@ define('fetch', ['exports'], function(exports) { ); var nodeFetch = FastBoot.require('node-fetch'); + function _checkHost(host) { + if (host === null) { + throw new Error( + 'You are using using fetch with a relative URL, but host is missing from Fastboot request. Please set the hostWhitelist property in your environment.js. https://github.com/ember-fastboot/ember-cli-fastboot#host' + ); + } + } + /** * Build the absolute url if it's not, can handle: * - protocol-relative URL (//can-be-http-or-https.com/) @@ -20,19 +28,17 @@ define('fetch', ['exports'], function(exports) { */ function buildAbsoluteUrl(url, protocol, host) { if (protocolRelativeRegex.test(url)) { + _checkHost(host); url = host + url; } else if (!httpRegex.test(url)) { - if (!host) { - throw new Error( - 'You are using using fetch with a path-relative URL, but host is missing from Fastboot request. Please set the hostWhitelist property in your environment.js.' - ); - } + _checkHost(host); url = protocol + '//' + host + url; } return url; } - var FastbootHost, FastbootProtocol; + var FastbootHost = null; + var FastbootProtocol = null; class FastBootRequest extends nodeFetch.Request { constructor(input, init) { diff --git a/test/fastboot-fetch-test.js b/test/fastboot-fetch-test.js new file mode 100644 index 00000000..be70973b --- /dev/null +++ b/test/fastboot-fetch-test.js @@ -0,0 +1,55 @@ +'use strict'; +const request = require('request'); +const get = require('rsvp').denodeify(request); +const chai = require('chai'); +const expect = chai.expect; +chai.use(require('chai-fs')); + +const AddonTestApp = require('ember-cli-addon-tests').AddonTestApp; + +describe('renders in fastboot build', function() { + this.timeout(300000); + + let app; + + beforeEach(function() { + app = new AddonTestApp(); + + return app + .create('dummy', { skipNpm: true }) + .then(app => + app.editPackageJSON(pkg => { + pkg.devDependencies['ember-cli-fastboot'] = '*'; + // ember-fetch-adapter@0.4.0 has ember-fetch as dependency, we want to test + pkg.devDependencies['ember-fetch-adapter'] = '0.4.0'; + // These 2 are in ember-fetch's package.json, symlinking to dummy won't help resolve + pkg.devDependencies['abortcontroller-polyfill'] = '*'; + pkg.devDependencies['node-fetch'] = '*'; + }) + ) + .then(function() { + return app.run('npm', 'install'); + }) + .then(function() { + return app.startServer({ + command: 'serve' + }); + }); + }); + + afterEach(function() { + return app.stopServer(); + }); + + it('fetches in fastboot mode', function() { + return get({ + url: 'http://localhost:49741/', + headers: { + Accept: 'text/html' + } + }).then(function(response) { + expect(response.body).to.contain('Hello World! fetch'); + expect(response.body).to.contain('Hello World! fetch (Request)'); + }); + }); +}); diff --git a/test/fastboot-test.js b/test/fastboot-test.js index db1e5d01..73f46972 100644 --- a/test/fastboot-test.js +++ b/test/fastboot-test.js @@ -7,7 +7,7 @@ chai.use(require('chai-fs')); const AddonTestApp = require('ember-cli-addon-tests').AddonTestApp; -describe('renders in fastboot build', function() { +describe('renders in fastboot build without calling fetch', function() { this.timeout(300000); let app; @@ -16,7 +16,7 @@ describe('renders in fastboot build', function() { app = new AddonTestApp(); return app - .create('dummy', { skipNpm: true }) + .create('fresh', { skipNpm: true, noFixtures: true }) .then(app => app.editPackageJSON(pkg => { pkg.devDependencies['ember-cli-fastboot'] = '*'; @@ -44,18 +44,17 @@ describe('renders in fastboot build', function() { it('builds into dist/ember-fetch/fetch-fastboot.js ignoring sub dependency version conflict', function() { expect(app.filePath('dist/index.html')).to.be.a.file(); expect(app.filePath('dist/ember-fetch/fetch-fastboot.js')).to.be.a.file(); - expect(app.filePath('dist/assets/dummy-fastboot.js')).to.be.a.file(); + expect(app.filePath('dist/assets/fresh-fastboot.js')).to.be.a.file(); }); - it('fetches in fastboot mode', function() { + it('fresh serve works', function() { return get({ url: 'http://localhost:49741/', headers: { Accept: 'text/html' } }).then(function(response) { - expect(response.body).to.contain('Hello World! fetch'); - expect(response.body).to.contain('Hello World! fetch (Request)'); + expect(response.body).to.contain('Congratulations, you made it!'); }); }); }); From 8fdf660728054a5ad36e32444652ba0f3ac8bc25 Mon Sep 17 00:00:00 2001 From: Thomas Wang Date: Fri, 25 Oct 2019 18:05:36 -0700 Subject: [PATCH 2/2] save request instead of protocol, host --- fastboot/instance-initializers/setup-fetch.js | 13 ++----- public/fetch-fastboot.js | 38 +++++++++---------- 2 files changed, 20 insertions(+), 31 deletions(-) diff --git a/fastboot/instance-initializers/setup-fetch.js b/fastboot/instance-initializers/setup-fetch.js index b5017f14..c41a7b5b 100644 --- a/fastboot/instance-initializers/setup-fetch.js +++ b/fastboot/instance-initializers/setup-fetch.js @@ -2,19 +2,12 @@ import { setupFastboot } from 'fetch'; /** * To allow relative URLs for Fastboot mode, we need the per request information - * from the fastboot service. Then we set the protocol and host to fetch module. + * from the fastboot service. Then we save the request from fastboot info. + * On each fetch with relative url we get host and protocol from it. */ function patchFetchForRelativeURLs(instance) { const fastboot = instance.lookup('service:fastboot'); - const request = fastboot.get('request'); - // Prember is not sending protocol - const protocol = request.protocol === 'undefined:' ? 'http:' : request.protocol; - try { - // host is cp - setupFastboot(protocol, request.get('host')); - } catch (_e) { - // Do not throw error until request with a relative url. - } + setupFastboot(fastboot.get('request')); } export default { diff --git a/public/fetch-fastboot.js b/public/fetch-fastboot.js index 428d1522..d94f2f8b 100644 --- a/public/fetch-fastboot.js +++ b/public/fetch-fastboot.js @@ -8,12 +8,13 @@ define('fetch', ['exports'], function(exports) { ); var nodeFetch = FastBoot.require('node-fetch'); - function _checkHost(host) { - if (host === null) { - throw new Error( - 'You are using using fetch with a relative URL, but host is missing from Fastboot request. Please set the hostWhitelist property in your environment.js. https://github.com/ember-fastboot/ember-cli-fastboot#host' - ); + function parseRequest(request) { + if (request === null) { + throw new Error('Trying to fetch with relative url but ember-fetch hasn\'t finished loading FastBootInfo, see details at https://github.com/ember-cli/ember-fetch#relative-url'); } + // Old Prember version is not sending protocol + const protocol = request.protocol === 'undefined:' ? 'http:' : request.protocol; + return [request.get('host'), protocol]; } /** @@ -21,32 +22,28 @@ define('fetch', ['exports'], function(exports) { * - protocol-relative URL (//can-be-http-or-https.com/) * - path-relative URL (/file/under/root) * - * @param {string} url - * @param {string} protocol - * @param {string} host * @returns {string} */ - function buildAbsoluteUrl(url, protocol, host) { + function buildAbsoluteUrl(url) { if (protocolRelativeRegex.test(url)) { - _checkHost(host); + let [host,] = parseRequest(REQUEST); url = host + url; } else if (!httpRegex.test(url)) { - _checkHost(host); + let [host, protocol] = parseRequest(REQUEST); url = protocol + '//' + host + url; } return url; } - var FastbootHost = null; - var FastbootProtocol = null; + var REQUEST = null; class FastBootRequest extends nodeFetch.Request { constructor(input, init) { if (typeof input === 'string') { - input = buildAbsoluteUrl(input, FastbootProtocol, FastbootHost); + input = buildAbsoluteUrl(input); } else if (input && input.href) { // WHATWG URL or Node.js Url Object - input = buildAbsoluteUrl(input.href, FastbootProtocol, FastbootHost); + input = buildAbsoluteUrl(input.href); } super(input, init); } @@ -66,19 +63,18 @@ define('fetch', ['exports'], function(exports) { */ exports.default = function fetch(input, options) { if (input && input.href) { - input.url = buildAbsoluteUrl(input.href, FastbootProtocol, FastbootHost); + input.url = buildAbsoluteUrl(input.href); } else if (typeof input === 'string') { - input = buildAbsoluteUrl(input, FastbootProtocol, FastbootHost); + input = buildAbsoluteUrl(input); } return nodeFetch(input, options); }; /** - * Assign the local protocol and host being used for building absolute URLs + * Assign the local REQUEST object for building absolute URLs * @private */ - exports.setupFastboot = function setupFastboot(protocol, host) { - FastbootProtocol = protocol; - FastbootHost = host; + exports.setupFastboot = function setupFastboot(fastBootRequest) { + REQUEST = fastBootRequest; } exports.Request = FastBootRequest; exports.Headers = nodeFetch.Headers;