diff --git a/index.js b/index.js
index 763347b5..d2833a7d 100644
--- a/index.js
+++ b/index.js
@@ -5,6 +5,7 @@
const modules = {
server: "./lib/server",
sslUtil: "./lib/sslUtil",
+ proxyConfiguration: "./lib/proxyConfiguration",
middlewareRepository: "./lib/middleware/middlewareRepository"
};
diff --git a/lib/middleware/MiddlewareManager.js b/lib/middleware/MiddlewareManager.js
index 537d70fd..e3486d6d 100644
--- a/lib/middleware/MiddlewareManager.js
+++ b/lib/middleware/MiddlewareManager.js
@@ -1,5 +1,6 @@
const middlewareRepository = require("./middlewareRepository");
const MiddlewareUtil = require("./MiddlewareUtil");
+const proxyConfiguration = require("../proxyConfiguration");
/**
*
@@ -8,7 +9,8 @@ const MiddlewareUtil = require("./MiddlewareUtil");
*/
class MiddlewareManager {
constructor({tree, resources, options = {
- sendSAPTargetCSP: false
+ sendSAPTargetCSP: false,
+ useProxy: false
}}) {
if (!tree || !resources || !resources.all || !resources.rootProject || !resources.dependencies) {
throw new Error("[MiddlewareManager]: One or more mandatory parameters not provided");
@@ -84,6 +86,13 @@ class MiddlewareManager {
}
async addStandardMiddleware() {
+ const useProxy = this.options.useProxy;
+
+ let proxyConfig;
+ if (useProxy) {
+ proxyConfig = await proxyConfiguration.getConfigurationForProject(this.tree);
+ }
+
await this.addMiddleware("csp", {
wrapperCallback: ({middleware: cspModule}) => {
const oCspConfig = {
@@ -125,6 +134,22 @@ class MiddlewareManager {
});
await this.addMiddleware("compression");
await this.addMiddleware("cors");
+
+ if (useProxy) {
+ await this.addMiddleware("proxyRewrite", {
+ wrapperCallback: ({middleware: proxyRewriteModule}) => {
+ return ({resources, middlewareUtil}) => {
+ return proxyRewriteModule({
+ resources,
+ middlewareUtil,
+ configuration: proxyConfig,
+ cdnUrl: this.options.cdnUrl
+ });
+ };
+ }
+ });
+ }
+
await this.addMiddleware("discovery", {
mountPath: "/discovery"
});
@@ -143,21 +168,52 @@ class MiddlewareManager {
};
}
});
- await this.addMiddleware("connectUi5Proxy", {
- mountPath: "/proxy"
- });
+
+ if (this.options.cdnUrl) {
+ await this.addMiddleware("cdn", {
+ wrapperCallback: (cdn) => {
+ return ({resources}) => {
+ return cdn({
+ resources,
+ cdnUrl: this.options.cdnUrl
+ });
+ };
+ }
+ });
+ }
+
+ if (useProxy) {
+ await this.addMiddleware("proxy", {
+ wrapperCallback: ({middleware: proxyModule}) => {
+ return ({resources}) => {
+ return proxyModule({
+ resources,
+ configuration: proxyConfig
+ });
+ };
+ }
+ });
+ } else {
+ await this.addMiddleware("connectUi5Proxy", {
+ mountPath: "/proxy"
+ });
+ }
+
// Handle anything but read operations *before* the serveIndex middleware
// as it will reject them with a 405 (Method not allowed) instead of 404 like our old tooling
await this.addMiddleware("nonReadRequests");
- await this.addMiddleware("serveIndex", {
- wrapperCallback: ({middleware: middleware}) => {
- return ({resources, middlewareUtil}) => middleware({
- resources,
- middlewareUtil,
- simpleIndex: this.options.simpleIndex
- });
- }
- });
+ if (!useProxy) {
+ // Don't do directory listing when using a proxy. High potential for confusion
+ await this.addMiddleware("serveIndex", {
+ wrapperCallback: ({middleware: middleware}) => {
+ return ({resources, middlewareUtil}) => middleware({
+ resources,
+ middlewareUtil,
+ simpleIndex: this.options.simpleIndex
+ });
+ }
+ });
+ }
}
async addCustomMiddleware() {
diff --git a/lib/middleware/cdn.js b/lib/middleware/cdn.js
new file mode 100644
index 00000000..2b835b67
--- /dev/null
+++ b/lib/middleware/cdn.js
@@ -0,0 +1,99 @@
+const log = require("@ui5/logger").getLogger("server:middleware:cdn");
+const http = require("http");
+const https = require("https");
+
+function createMiddleware({cdnUrl}) {
+ if (!cdnUrl) {
+ throw new Error(`Missing parameter "cdnUrl"`);
+ }
+ if (cdnUrl.endsWith("/")) {
+ throw new Error(`Parameter "cdnUrl" must not end with a slash`);
+ }
+
+ return function proxy(req, res, next) {
+ if (req.method !== "GET" && req.method !== "HEAD" && req.method !== "OPTIONS") {
+ // Cannot be fulfilled by CDN
+ next();
+ return;
+ }
+
+ log.verbose(`Requesting ${req.url} from CDN ${cdnUrl}...`);
+ log.verbose(`Orig. URL: ${req.originalUrl}`);
+
+ getResource({
+ cdnUrl,
+ resourcePath: req.url,
+ resolveOnOddStatusCode: true,
+ headers: req.headers
+ }).then(({data, headers, statusCode}) => {
+ if (statusCode !== 200) {
+ // odd status code
+ log.verbose(`CDN replied with status code ${statusCode} for request ${req.url}`);
+ next();
+ return;
+ }
+ if (headers) {
+ for (const headerKey in headers) {
+ if (headers.hasOwnProperty(headerKey)) {
+ res.setHeader(headerKey, headers[headerKey]);
+ }
+ }
+ }
+
+ res.setHeader("x-ui5-tooling-proxied-from-cdn", cdnUrl);
+ res.setHeader("x-ui5-tooling-proxied-as", req.url);
+
+ res.send(data);
+ }).catch((err) => {
+ log.error(`CDN request error: ${err.message}`);
+ next(err);
+ });
+ };
+}
+
+const cache = {};
+
+function getResource({cdnUrl, resourcePath, resolveOnOddStatusCode, headers}) {
+ return new Promise((resolve, reject) => {
+ const reqUrl = cdnUrl + resourcePath;
+ if (cache[reqUrl]) {
+ resolve(cache[reqUrl]);
+ }
+ if (!cdnUrl.startsWith("http")) {
+ throw new Error(`CDN URL must start with protocol "http" or "https": ${cdnUrl}`);
+ }
+ let client = http;
+ if (cdnUrl.startsWith("https")) {
+ client = https;
+ }
+ client.get(reqUrl, (cdnResponse) => {
+ const {statusCode} = cdnResponse;
+
+ const data = [];
+ cdnResponse.on("data", (chunk) => {
+ data.push(chunk);
+ });
+ cdnResponse.on("end", () => {
+ try {
+ const result = {
+ data: Buffer.concat(data),
+ statusCode,
+ headers: cdnResponse.headers
+ };
+ cache[reqUrl] = result;
+ if (Object.keys(cache).length % 10 === 0) {
+ log.verbose(`Cache size: ${Object.keys(cache).length} entries`);
+ }
+ resolve(result);
+ } catch (err) {
+ reject(err);
+ }
+ });
+ }).on("error", (err) => {
+ reject(err);
+ });
+ });
+}
+
+module.exports = createMiddleware;
+module.exports.getResource = getResource;
diff --git a/lib/middleware/middlewareRepository.js b/lib/middleware/middlewareRepository.js
index d85955e4..a3ad719c 100644
--- a/lib/middleware/middlewareRepository.js
+++ b/lib/middleware/middlewareRepository.js
@@ -9,7 +9,9 @@ const middlewareInfos = {
connectUi5Proxy: {path: "./connectUi5Proxy"},
serveThemes: {path: "./serveThemes"},
testRunner: {path: "./testRunner"},
- nonReadRequests: {path: "./nonReadRequests"}
+ nonReadRequests: {path: "./nonReadRequests"},
+ proxy: {path: "./proxy"},
+ proxyRewrite: {path: "./proxyRewrite"}
};
function getMiddleware(middlewareName) {
diff --git a/lib/middleware/proxy.js b/lib/middleware/proxy.js
new file mode 100644
index 00000000..c3d6b176
--- /dev/null
+++ b/lib/middleware/proxy.js
@@ -0,0 +1,55 @@
+const log = require("@ui5/logger").getLogger("server:middleware:proxy");
+const httpProxy = require("http-proxy");
+
+function createMiddleware({configuration}) {
+ let agent;
+
+ if (configuration.forwardProxy) {
+ let username = configuration.forwardProxy.username;
+ let password = configuration.forwardProxy.password;
+ if (!username) {
+ // TODO prompt user for credentials
+ username = "";
+ }
+ if (!password) {
+ // TODO prompt user for credentials
+ password = "";
+ }
+ const HttpsProxyAgent = require("https-proxy-agent");
+ agent = new HttpsProxyAgent({
+ host: configuration.forwardProxy.hostname,
+ port: configuration.forwardProxy.port,
+ secureProxy: configuration.forwardProxy.useSsl,
+ auth: username + ":" + password
+ });
+ }
+
+ const proxyServer = httpProxy.createProxyServer({
+ agent: agent,
+ secure: !configuration.insecure,
+ prependPath: false,
+ xfwd: true,
+ target: configuration.destination.origin,
+ changeOrigin: true
+ });
+
+ proxyServer.on("proxyRes", function(proxyRes, req, res) {
+ res.setHeader("x-ui5-tooling-proxied-from", configuration.destination.origin);
+ });
+
+ return function proxy(req, res, next) {
+ if (req.url !== req.originalUrl) {
+ log.verbose(`Proxying "${req.url}"`); // normalized URL - used for local resolution
+ log.verbose(` as "${req.originalUrl}"`); // original URL - used for reverse proxy requests
+ } else {
+ log.verbose(`Proxying "${req.url}"`);
+ }
+ req.url = req.originalUrl; // Always use the original (non-rewritten) URL
+ proxyServer.web(req, res, (err) => {
+ log.error(`Proxy error: ${err.message}`);
+ next(err);
+ });
+ };
+}
+
+module.exports = createMiddleware;
diff --git a/lib/middleware/proxyRewrite.js b/lib/middleware/proxyRewrite.js
new file mode 100644
index 00000000..f159c824
--- /dev/null
+++ b/lib/middleware/proxyRewrite.js
@@ -0,0 +1,245 @@
+const log = require("@ui5/logger").getLogger("server:middleware:proxyRewrite");
+
+function createMiddleware({resources, middlewareUtil, configuration, cdnUrl}) {
+ const rewriteRootPaths = configuration.rewriteRootPaths;
+
+ const cacheBusterRegex = /~.*~[A-Z0-9]?\/?/;
+ const preloadRegex = /^.*(?:Component-preload\.js|library-preload\.js|library-preload\.json)$/i;
+
+ return function proxyRewrite(req, res, next) {
+ const pathname = middlewareUtil.getPathname(req);
+ const rewriteApplicable = Object.keys(rewriteRootPaths).some((resourceRootPath) => {
+ return pathname.indexOf(resourceRootPath) !== -1;
+ });
+
+ if (!rewriteApplicable) {
+ // No normalization applicable
+ next();
+ return;
+ }
+
+ log.verbose(`Normalizing ${pathname}...`);
+ // Normalize URL
+ normalizeRequestPath(pathname)
+ .catch((err) => {
+ log.error(`Failed to normalize ${pathname}. Error ${err.message}`);
+ return "";
+ })
+ .then((normalizedUrl) => {
+ req.url = req.url.replace(pathname, normalizedUrl);
+ log.verbose(`Normalized ${req.originalUrl}`);
+ log.verbose(` to ${req.url}`); // will be used for internal resolution
+ handleSpecialRequests(req, res, next);
+ });
+ };
+
+ function handleSpecialRequests(req, res, next) {
+ switch (req.url) {
+ case "special:404":
+ res.setHeader("x-ui5-tooling-special-request-handling", req.url);
+ res.status(404).end("UI5 Tooling - Proxy Rewrite Middleware: Special request handling " +
+ `blocked this request by returning status code 404 - Not Found`);
+ break;
+ case "special:empty":
+ res.setHeader("x-ui5-tooling-special-request-handling", req.url);
+ res.end("// UI5 Tooling - Proxy Rewrite Middleware: " +
+ "Special request handling blocked this request by returning no file content");
+ break;
+ case "special:sap-ui-core-bootstrap":
+ getResources(["/resources/ui5loader-autoconfig.js"]).then(async ([autoconfigLoaderResource]) => {
+ let bootstrap;
+ if (autoconfigLoaderResource) {
+ bootstrap = await getBootstrapFile("evo-core");
+ } else {
+ bootstrap = await getBootstrapFile("classic-core");
+ }
+ res.setHeader("Content-Type", "application/javascript");
+ res.setHeader("x-ui5-tooling-special-request-handling", req.url);
+ res.end(bootstrap);
+ }).catch((err) => {
+ const errMsg = `Failed to generate bootstrap file for request ${req.originalUrl} (${req.url}). ` +
+ `Error: ${err.message}`;
+ log.error(errMsg);
+ log.error(err.stack);
+ next(errMsg);
+ });
+ break;
+ case "special:flp-abap-bootstrap":
+ getBootstrapFile("flp-abap").then((bootstrap) => {
+ res.setHeader("Content-Type", "application/javascript");
+ res.setHeader("x-ui5-tooling-special-request-handling", req.url);
+ res.end(bootstrap);
+ }).catch((err) => {
+ const errMsg = `Failed to generate bootstrap file for request ${req.originalUrl} (${req.url}). ` +
+ `Error: ${err.message}`;
+ log.error(errMsg);
+ log.error(err.stack);
+ next(errMsg);
+ });
+ break;
+ default:
+ next();
+ break;
+ }
+ }
+
+ async function normalizeRequestPath(reqPath) {
+ let normalizedPath = reqPath;
+
+ // Strip off first matching rewrite root path
+ for (const rootPath in rewriteRootPaths) {
+ if (rewriteRootPaths.hasOwnProperty(rootPath)) {
+ if (normalizedPath.indexOf(rootPath) !== -1) {
+ normalizedPath = normalizedPath.substr(rootPath.length);
+ if (rewriteRootPaths[rootPath].rewriteTo) {
+ normalizedPath = rewriteRootPaths[rootPath].rewriteTo + normalizedPath;
+ }
+ break;
+ }
+ }
+ }
+ normalizedPath = normalizedPath.replace(cacheBusterRegex, "");
+ return rewriteSpecials(normalizedPath);
+ }
+
+ async function rewriteSpecials(normalizedPath) {
+ switch (normalizedPath) {
+ /* === FLP bootstrap === */
+ case "/resources/sap/fiori/core-min-0.js":
+ // Try to serve sap-ui-core.js instead
+ return "/resources/sap-ui-core.js";
+ case "/resources/sap/fiori/core-min-1.js":
+ case "/resources/sap/fiori/core-min-2.js":
+ case "/resources/sap/fiori/core-min-3.js":
+ // Send an empty file
+ return "special:empty";
+
+ /* === FLP evo bootstrap === */
+ case "/resources/sap/ushell_abap/bootstrap/evo/abap.js":
+ return "special:flp-abap-bootstrap";
+ case "/resources/sap/ushell_abap/bootstrap/evo/core-min-0.js":
+ // Try to serve sap-ui-core.js instead
+ return "/resources/sap-ui-core.js";
+ case "/resources/sap/ushell_abap/bootstrap/evo/core-min-1.js":
+ case "/resources/sap/ushell_abap/bootstrap/evo/core-min-2.js":
+ case "/resources/sap/ushell_abap/bootstrap/evo/core-min-3.js":
+ // Send an empty file
+ return "special:empty";
+
+ case "/resources/sap/fiori/core-ext-light-0.js":
+ // Try to serve sap-ui-core.js instead
+ // return "special:flp-abap-bootstrap";
+ return "special:empty";
+ case "/resources/sap/fiori/core-ext-light-1.js":
+ case "/resources/sap/fiori/core-ext-light-2.js":
+ case "/resources/sap/fiori/core-ext-light-3.js":
+ // Send an empty file
+ return "special:empty";
+
+ /* === C4C (and others?) bootstrap === */
+ case "/resources/sap/client/lib-0.js":
+ // Try to serve compiled sap-ui-core.js instead
+ return "special:sap-ui-core-bootstrap";
+ case "/resources/sap/client/lib-1.js":
+ case "/resources/sap/client/lib-2.js":
+ case "/resources/sap/client/lib-3.js":
+ case "/resources/sap/client/lib-thirdparty.js":
+ case "/resources/sap/client/lib-deprecated.js":
+ // Send an empty file
+ return "special:empty";
+ }
+
+ /* === Preloads === */
+ if (preloadRegex.test(normalizedPath)) {
+ // return "special:404";
+ return "special:empty";
+ }
+
+ return normalizedPath;
+ }
+
+ function getBootstrapFile(style) {
+ let resourceList;
+ let post;
+
+ switch (style) {
+ case "evo-core":
+ resourceList = [
+ "/resources/sap/ui/thirdparty/es6-promise.js",
+ "/resources/sap/ui/thirdparty/es6-string-methods.js",
+ "/resources/ui5loader.js",
+ "/resources/ui5loader-autoconfig.js"
+ ];
+
+ post = "\n\nsap.ui.requireSync(\"sap/ui/core/Core\"); sap.ui.getCore().boot();";
+ break;
+ case "classic-core":
+ resourceList = [
+ "/resources/sap/ui/thirdparty/jquery.js",
+ "/resources/sap/ui/thirdparty/jqueryui/jquery-ui-position.js",
+ "/resources/sap/ui/Device.js",
+ "/resources/sap/ui/thirdparty/URI.js",
+ "/resources/sap/ui/thirdparty/es6-promise.js",
+ "/resources/jquery.sap.global.js",
+ "/resources/sap/ui/core/Core.js"
+ ];
+
+ post = "\n\njQuery.sap.require(\"sap/ui/core/Core\"); " +
+ "sap.ui.getCore().boot && sap.ui.getCore().boot();";
+ break;
+ case "flp-abap":
+ resourceList = [
+ "/resources/sap/ui/thirdparty/baseuri.js",
+ "/resources/sap/ui/thirdparty/es6-promise.js",
+ "/resources/sap/ui/thirdparty/es6-string-methods.js",
+ "/resources/sap/ui/thirdparty/es6-object-assign.js",
+ "/resources/sap/ui/thirdparty/es6-shim-nopromise.js",
+ "/resources/ui5loader.js",
+ "/resources/sap/ushell/bootstrap/ui5loader-config.js",
+ "/resources/ui5loader-autoconfig.js"
+ ];
+
+ post = `sap.ui.requireSync("sap/ushell_abap/bootstrap/evo/abap-def-dev");
+sap.ui.requireSync("sap/ui/core/Core"); sap.ui.getCore().boot();
+
+// ComponentContainer Required in case of deep links.
+// Possibly because of missing require in ushell/services/Container.js?
+sap.ui.requireSync("sap/ui/core/ComponentContainer");`;
+ break;
+ default:
+ throw new Error(`Unkown bootstrap file style ${style}`);
+ }
+
+ return getResources(resourceList).then((strings) => {
+ const pre = `/* ==== Generated file ui5-evo server ${new Date().toString()}==== */\n\n`;
+ const joinedFiles = strings.join("\n\n");
+ return pre + joinedFiles + post;
+ });
+ }
+
+ function getResources(resourceList) {
+ if (cdnUrl) {
+ const cdn = require("./cdn");
+ return Promise.all(resourceList.map(async (resourcePath) => {
+ const {data, statusCode} = await cdn.getResource({cdnUrl, resourcePath});
+ if (statusCode !== 200) {
+ throw new Error(`CDN replied with status code ${statusCode} for request ${resourcePath}`);
+ }
+ return data;
+ }));
+ } else {
+ return Promise.all(resourceList.map((resourcePath) => {
+ return resources.all.byPath(resourcePath).then((resource) => {
+ if (!resource) {
+ throw new Error(`Could not find resource ${resourcePath} on local host`);
+ }
+ return resource.getBuffer().then((buffer) => {
+ return `/* Begin of ${resource.virtualPath} */\n\n` + buffer.toString();
+ });
+ });
+ }));
+ }
+ }
+}
+
+module.exports = createMiddleware;
diff --git a/lib/middleware/serveResources.js b/lib/middleware/serveResources.js
index 665b1034..6cb469f9 100644
--- a/lib/middleware/serveResources.js
+++ b/lib/middleware/serveResources.js
@@ -65,6 +65,7 @@ function createMiddleware({resources, middlewareUtil}) {
if (!res.getHeader("Content-Type")) {
res.setHeader("Content-Type", contentType);
}
+ res.setHeader("x-ui5-tooling-served-from-host", true);
// Enable ETag caching
res.setHeader("ETag", etag(resource.getStatInfo()));
diff --git a/lib/middleware/serveThemes.js b/lib/middleware/serveThemes.js
index 9c5ae5c4..951db4cd 100644
--- a/lib/middleware/serveThemes.js
+++ b/lib/middleware/serveThemes.js
@@ -1,3 +1,4 @@
+const log = require("@ui5/logger").getLogger("server:middleware:serveThemes");
const themeBuilder = require("@ui5/builder").processors.themeBuilder;
const fsInterface = require("@ui5/fs").fsInterface;
const {basename, dirname} = require("path").posix;
diff --git a/lib/proxyConfiguration.js b/lib/proxyConfiguration.js
new file mode 100644
index 00000000..f6c63bd1
--- /dev/null
+++ b/lib/proxyConfiguration.js
@@ -0,0 +1,94 @@
+const log = require("@ui5/logger").getLogger("server:proxyConfiguration");
+
+const proxyConfigurations = {};
+
+function addConfiguration(name, proxyConfig) {
+ if (!name || !proxyConfig) {
+ throw new Error(`proxyConfiguration: Function called with missing parameters`);
+ }
+ if (proxyConfigurations[name]) {
+ throw new Error(`proxyConfiguration: A configuration with name ${name} is already known`);
+ }
+ if (proxyConfig.rewriteRootPaths) {
+ throw new Error(`Proxy Configuration ${name} must not define "rewriteRootPaths"`);
+ }
+
+ if (!proxyConfig.destination) {
+ proxyConfig.destination = {};
+ }
+ proxyConfigurations[name] = proxyConfig;
+}
+
+async function getConfigurationForProject(tree) {
+ const configNames = Object.keys(proxyConfigurations);
+ if (configNames.length === 0) {
+ throw new Error(`No proxy configurations have been added yet`);
+ }
+ if (configNames.length > 1) {
+ throw new Error(`Found multiple proxy configurations. ` +
+ `This is not yet supported.`); // TODO
+ }
+
+ log.verbose(`Applying proxy configuration ${configNames[0]} to project ${tree.metadata.name}...`);
+ const config = JSON.parse(JSON.stringify(proxyConfigurations[configNames[0]]));
+ config.rewriteRootPaths = {};
+
+ if (config.destination.ui5Root && !config.appOnly) {
+ log.verbose(`Using configured "destination.ui5Root": ${config.destination.ui5Root}`);
+ config.rewriteRootPaths[config.destination.ui5Root] = {
+ rewriteTo: ""
+ };
+ }
+
+ mapProjectDependencies(tree, (project) => {
+ if (project.specVersion !== "2.2a") {
+ log.warn(`Project ${project.metadata.name} defines specification version ${project.specVersion}. ` +
+ `Some proxy configuration features require projects to define specification version 2.2a`);
+ return;
+ }
+ log.verbose(`Using ABAP URI ${project.metadata.abapUri} from metadata of project ${project.metadata.name}`);
+ let prefix = "";
+ if (project.type !== "application") {
+ if (project.resources.pathMappings["/resources/"]) {
+ // If the project defines a /resources path mapping,
+ // we expect this to match the ABAP URI deployment path
+ prefix += "/resources/";
+
+ // If this is not an application and there is no /resources path mapping, somebody does something wild
+ // and hopefully knows what he/she does
+ }
+ prefix += project.metadata.namespace;
+ }
+ config.rewriteRootPaths[project.metadata.abapUri] = {
+ rewriteTo: prefix
+ };
+ });
+
+ if (log.isLevelEnabled("verbose")) {
+ log.verbose(`Configured ${Object.keys(config.rewriteRootPaths).length} root paths to rewrite for ` +
+ `project ${tree.metadata.name};`);
+ for (const abapUri in config.rewriteRootPaths) {
+ if (config.rewriteRootPaths.hasOwnProperty(abapUri)) {
+ if (config.rewriteRootPaths[abapUri].rewriteTo) {
+ log.verbose(`Rewriting ${abapUri} to ${config.rewriteRootPaths[abapUri].rewriteTo}`);
+ } else {
+ log.verbose(`Rewriting ${abapUri}`);
+ }
+ }
+ }
+ }
+
+ return config;
+}
+
+function mapProjectDependencies(tree, handler) {
+ handler(tree);
+ tree.dependencies.map((dep) => {
+ mapProjectDependencies(dep, handler);
+ });
+}
+
+module.exports = {
+ addConfiguration,
+ getConfigurationForProject
+};
diff --git a/lib/server.js b/lib/server.js
index fd22f722..df792fee 100644
--- a/lib/server.js
+++ b/lib/server.js
@@ -68,13 +68,19 @@ function _listen(app, port, changePortIfInUse, acceptRemoteConnections) {
* @param {object} parameters.app The original express application
* @param {string} parameters.key Path to private key to be used for https
* @param {string} parameters.cert Path to certificate to be used for for https
+ * @param {string} parameters.h2 Enables HTTP/2 protocol
* @returns {object} The express application with SSL support
* @private
*/
-function _addSsl({app, key, cert}) {
- // Using spdy as http2 server as the native http2 implementation
- // from Node v8.4.0 doesn't seem to work with express
- return require("spdy").createServer({cert, key}, app);
+function _addSsl({app, key, cert, h2}) {
+ if (h2) {
+ // Using spdy as http2 server as the native http2 implementation
+ // from Node v8.4.0 doesn't seem to work with express
+ return require("spdy").createServer({cert, key}, app);
+ } else {
+ // Just a plain HTTPS server
+ return require("https").createServer({key, cert}, app);
+ }
}
/**
@@ -91,9 +97,11 @@ module.exports = {
* @param {object} options Options
* @param {number} options.port Port to listen to
* @param {boolean} [options.changePortIfInUse=false] If true, change the port if it is already in use
- * @param {boolean} [options.h2=false] Whether HTTP/2 should be used - defaults to http
- * @param {string} [options.key] Path to private key to be used for https
- * @param {string} [options.cert] Path to certificate to be used for for https
+ * @param {boolean} [options.h2=false] Whether HTTP/2 and HTTPS should be used
+ * @param {boolean} [options.useProxy=false] Whether to use a proxy configured
+ * globally in the proxyConfiguration module
+ * @param {string} [options.key] Private key to be used for https
+ * @param {string} [options.cert] Certificate to be used for for https
* @param {boolean} [options.acceptRemoteConnections=false] If true, listens to remote connections and
* not only to localhost connections
* @param {boolean} [options.sendSAPTargetCSP=false] If true, then the content security policies that SAP and UI5
@@ -101,17 +109,18 @@ module.exports = {
* *.html
file
* @param {boolean} [options.simpleIndex=false] Use a simplified view for the server directory listing
* @returns {Promise