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