DEV: Fix ember-cli proxying to production sites ()

This commit is contained in:
Jarek Radosz 2021-11-23 23:31:54 +01:00 committed by GitHub
parent 73760c77d9
commit 3172e08b6d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,11 +1,12 @@
"use strict";
const express = require("express");
const bent = require("bent");
const getJSON = bent("json");
const { encode } = require("html-entities");
const cleanBaseURL = require("clean-base-url");
const path = require("path");
const fs = require("fs");
const fs = require("fs/promises");
// via https://stackoverflow.com/a/6248722/165668
function generateUID() {
@ -16,11 +17,6 @@ function generateUID() {
return firstPart + secondPart;
}
const IGNORE_PATHS = [
/\/ember-cli-live-reload\.js$/,
/\/session\/[^\/]+\/become$/,
];
function htmlTag(buffer, bootstrap) {
let classList = "";
if (bootstrap.html_classes) {
@ -184,78 +180,80 @@ async function applyBootstrap(bootstrap, template, response, baseURL) {
return template;
}
function buildFromBootstrap(assetPath, proxy, baseURL, req, response) {
// eslint-disable-next-line
return new Promise((resolve, reject) => {
fs.readFile(
path.join(process.cwd(), "dist", assetPath),
"utf8",
(err, template) => {
let url = `${proxy}${baseURL}bootstrap.json`;
let queryLoc = req.url.indexOf("?");
if (queryLoc !== -1) {
url += req.url.substr(queryLoc);
}
getJSON(url, null, req.headers)
.then((json) => {
return applyBootstrap(json.bootstrap, template, response, baseURL);
})
.then(resolve)
.catch((e) => {
reject(
`Could not get ${proxy}${baseURL}bootstrap.json\n\n${e.toString()}`
);
});
}
async function buildFromBootstrap(proxy, baseURL, req, response) {
try {
const template = await fs.readFile(
path.join(process.cwd(), "dist", "index.html"),
"utf8"
);
});
let url = `${proxy}${baseURL}bootstrap.json`;
const queryLoc = req.url.indexOf("?");
if (queryLoc !== -1) {
url += req.url.substr(queryLoc);
}
const json = await getJSON(url, null, req.headers);
return applyBootstrap(json.bootstrap, template, response, baseURL);
} catch (error) {
throw new Error(
`Could not get ${proxy}${baseURL}bootstrap.json\n\n${error}`
);
}
}
async function handleRequest(assetPath, proxy, baseURL, req, res) {
if (assetPath.endsWith("tests/index.html")) {
return;
async function handleRequest(proxy, baseURL, req, res) {
const originalHost = req.headers.host;
req.headers.host = new URL(proxy).host;
if (req.headers["Origin"]) {
req.headers["Origin"] = req.headers["Origin"]
.replace(req.headers.host, originalHost)
.replace(/^https/, "http");
}
if (assetPath.endsWith("index.html")) {
try {
// Avoid Ember CLI's proxy if doing a GET, since Discourse depends on some non-XHR
// GET requests to work.
if (req.method === "GET") {
let url = `${proxy}${req.path}`;
if (req.headers["Referer"]) {
req.headers["Referer"] = req.headers["Referer"]
.replace(req.headers.host, originalHost)
.replace(/^https/, "http");
}
let queryLoc = req.url.indexOf("?");
if (queryLoc !== -1) {
url += req.url.substr(queryLoc);
}
let url = `${proxy}${req.path}`;
const queryLoc = req.url.indexOf("?");
if (queryLoc !== -1) {
url += req.url.substr(queryLoc);
}
req.headers["X-Discourse-Ember-CLI"] = "true";
let get = bent("GET", [200, 301, 302, 303, 307, 308, 404, 403, 500]);
let response = await get(url, null, req.headers);
res.set(response.headers);
res.set("content-type", "text/html");
if (response.headers["x-discourse-bootstrap-required"] === "true") {
req.headers["X-Discourse-Asset-Path"] = req.path;
let html = await buildFromBootstrap(
assetPath,
proxy,
baseURL,
req,
response
);
return res.send(html);
}
res.status(response.status);
res.send(await response.text());
}
} catch (e) {
res.send(`
<html>
<h1>Discourse Build Error</h1>
<pre><code>${e.toString()}</code></pre>
</html>
`);
}
if (req.method === "GET") {
req.headers["X-Discourse-Ember-CLI"] = "true";
req.headers["X-Discourse-Asset-Path"] = req.path;
}
const acceptedStatusCodes = [200, 301, 302, 303, 307, 308, 404, 403, 500];
const proxyRequest = bent(req.method, acceptedStatusCodes);
const requestBody = req.method === "GET" ? null : req.body;
const response = await proxyRequest(url, requestBody, req.headers);
res.set(response.headers);
res.set("content-encoding", null);
const { location } = response.headers;
if (location) {
const newLocation = location
.replace(req.headers.host, originalHost)
.replace(/^https/, "http");
res.set("location", newLocation);
}
if (response.headers["x-discourse-bootstrap-required"] === "true") {
const html = await buildFromBootstrap(proxy, baseURL, req, response);
res.set("content-type", "text/html");
res.send(html);
} else {
res.status(response.status);
res.send(await response.text());
}
}
@ -267,12 +265,11 @@ module.exports = {
},
serverMiddleware(config) {
let proxy = config.options.proxy;
let app = config.app;
let options = config.options;
const app = config.app;
let { proxy, rootURL, baseURL } = config.options;
if (!proxy) {
// eslint-disable-next-line
// eslint-disable-next-line no-console
console.error(`
Discourse can't be run without a \`--proxy\` setting, because it needs a Rails application
to serve API requests. For example:
@ -281,31 +278,20 @@ to serve API requests. For example:
throw "--proxy argument is required";
}
let watcher = options.watcher;
baseURL = rootURL === "" ? "/" : cleanBaseURL(rootURL || baseURL);
let baseURL =
options.rootURL === ""
? "/"
: cleanBaseURL(options.rootURL || options.baseURL);
app.use(async (req, res, next) => {
app.use(express.raw({ type: "*/*" }), async (req, res, next) => {
try {
const results = await watcher;
if (this.shouldHandleRequest(req, options)) {
let assetPath = req.path.slice(baseURL.length);
let isFile = false;
try {
isFile = fs
.statSync(path.join(results.directory, assetPath))
.isFile();
} catch (err) {}
if (!isFile) {
assetPath = "index.html";
}
await handleRequest(assetPath, proxy, baseURL, req, res);
if (this.shouldHandleRequest(req)) {
await handleRequest(proxy, baseURL, req, res);
}
} catch (error) {
res.send(`
<html>
<h1>Discourse Build Error</h1>
<pre><code>${error}</code></pre>
</html>
`);
} finally {
if (!res.headersSent) {
return next();
@ -314,25 +300,17 @@ to serve API requests. For example:
});
},
shouldHandleRequest(req) {
let acceptHeaders = req.headers.accept || [];
let hasHTMLHeader = acceptHeaders.indexOf("text/html") !== -1;
if (req.method !== "GET") {
return false;
}
if (!hasHTMLHeader) {
return false;
shouldHandleRequest(request) {
if (request.get("Accept")?.includes("text/html")) {
return true;
}
if (IGNORE_PATHS.some((ip) => ip.test(req.path))) {
return false;
if (
request.get("Content-Type")?.includes("application/x-www-form-urlencoded")
) {
return true;
}
if (req.path.endsWith(".json")) {
return false;
}
let baseURLRegexp = new RegExp(`^/`);
return baseURLRegexp.test(req.path);
return false;
},
};