From 62aca2c10e2c722a1add0e9ac10068f58569ea9c Mon Sep 17 00:00:00 2001 From: Michael Sprauer Date: Fri, 13 Jul 2018 15:43:31 +0200 Subject: [PATCH 01/13] [FEATURE] Proxy Backend Services fixes https://github.com/SAP/ui5-server/issues/13 Signed-off-by: Michael Sprauer --- README.md | 15 ++ lib/middleware/serveProxies.js | 133 ++++++++++++++++++ lib/server.js | 2 + package.json | 3 +- test/fixtures/application.proxy/package.json | 6 + test/fixtures/application.proxy/ui5.yaml | 8 ++ .../application.proxy/webapp/whatever.json | 1 + test/lib/server.js | 21 +++ 8 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 lib/middleware/serveProxies.js create mode 100644 test/fixtures/application.proxy/package.json create mode 100644 test/fixtures/application.proxy/ui5.yaml create mode 100644 test/fixtures/application.proxy/webapp/whatever.json diff --git a/README.md b/README.md index 1cd50058..35d5e69f 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,21 @@ If there is none, a new certificate is created and used. **Hint:** If Chrome unintentionally redirects a HTTP-URL to HTTPS, you need to delete the HSTS mapping in [chrome://net-internals/#hsts](chrome://net-internals/#hsts) by entering the domain name (e.g. localhost) and pressing "delete". +## Proxy + +You can proxy existing (OData) backend services to get around Access-Control-Allow-Origin (CORS) errors. +To do so, you can add a hash of proxied paths in your `ui5.yml`. Example: +``` +# ui5.yml +specVersion: '0.1' +metadata: + name: my-awesome-app +type: application +resources: + proxyies: + /api/v1: "http://api.of.external.service/v1" +``` + ## Contributing Please check our [Contribution Guidelines](https://github.com/SAP/ui5-tooling/blob/master/CONTRIBUTING.md). diff --git a/lib/middleware/serveProxies.js b/lib/middleware/serveProxies.js new file mode 100644 index 00000000..46e5ad82 --- /dev/null +++ b/lib/middleware/serveProxies.js @@ -0,0 +1,133 @@ +// eavily inspired by https://github.com/SAP/connect-openui5/blob/master/lib/proxy.js +const url = require("url"); +const httpProxy = require("http-proxy"); + +const env = { + noProxy: process.env.NO_PROXY || process.env.no_proxy, + httpProxy: process.env.HTTP_PROXY || process.env.http_proxy, + httpsProxy: process.env.HTTPS_PROXY || process.env.https_proxy +}; + +function getProxyUri(uri) { + if (uri.protocol === "https:" && env.httpsProxy || uri.protocol === "http:" && env.httpProxy) { + if (env.noProxy) { + const canonicalHost = uri.host.replace(/^\.*/, "."); + const port = uri.port || (uri.protocol === "https:" ? "443" : "80"); + + const patterns = env.noProxy.split(","); + for (let i = patterns.length - 1; i >= 0; i--) { + let pattern = patterns[i].trim().toLowerCase(); + + // don"t use a proxy at all + if (pattern === "*") { + return null; + } + + // Remove leading * and make sure to have exact one leading dot (.) + pattern = pattern.replace(/^[*]+/, "").replace(/^\.*/, "."); + + // add port if no specified + if (pattern.indexOf(":") === -1) { + pattern += ":" + port; + } + + // if host ends with pattern, no proxy should be used + if (canonicalHost.indexOf(pattern) === canonicalHost.length - pattern.length) { + return null; + } + } + } + + if (uri.protocol === "https:" && env.httpsProxy) { + return env.httpsProxy; + } else if (uri.protocol === "http:" && env.httpProxy) { + return env.httpProxy; + } + } + + return null; +} + +function buildRequestUrl(uri) { + let ret = uri.pathname; + if (uri.query) { + ret += "?" + uri.query; + } + return ret; +} + +function createUri(uriParam, proxyDefinitions) { + for (let path in proxyDefinitions) { + if (uriParam.startsWith(path)) { + return url.parse(proxyDefinitions[path] + uriParam.substring(path.length)); + } + } + return null; +} + +/** + * Creates and returns the middleware to serve proxied servers. + * + * @module server/middleware/serveProxies + * @param {Object} proxyDefinitions Contains proxy definitions + * @returns {function} Returns a server middleware closure. + */ +function createMiddleware(proxyDefinitions) { + let proxy = httpProxy.createProxyServer({}); + + return function serveResources(req, res, next) { + if (proxyDefinitions == undefined) { + return next(); + } + let uri = createUri(req.url, proxyDefinitions); + if (!uri || !uri.host) { + next(); + return; + } + + // change original request url to target url + req.url = buildRequestUrl(uri); + + // change original host to target host + req.headers.host = uri.host; + + // overwrite response headers + res.orgWriteHead = res.writeHead; + res.writeHead = function(...args) { + // We always filter the secure header to avoid the cookie from + // "not" beeing included in follow up requests in case of the + // proxy is running on HTTP and not HTTPS + let cookies = res.getHeader("set-cookie"); + // array == multiple cookies + if (Array.isArray(cookies)) { + for (let i = 0; i < cookies.length; i++) { + cookies[i] = cookies[i].replace("secure;", ""); + } + } else if (typeof cookies === "string" || cookies instanceof String) { + // single cookie + cookies = cookies.replace("secure;", ""); + } + + if (cookies) { + res.setHeader("set-cookie", cookies); + } + + // call original writeHead function + res.orgWriteHead(args); + }; + + // get proxy for uri (if defined in env vars) + let targetUri = getProxyUri(uri) || uri.protocol + "//" + uri.host; + + // proxy the request + proxy.proxyRequest(req, res, { + target: targetUri + }, function(err) { + if (err) { + next(err); + } + }); + }; +} + +module.exports = createMiddleware; diff --git a/lib/server.js b/lib/server.js index b6cd08d7..e991c0ae 100644 --- a/lib/server.js +++ b/lib/server.js @@ -8,6 +8,7 @@ const serveIndex = require("./middleware/serveIndex"); const discovery = require("./middleware/discovery"); const versionInfo = require("./middleware/versionInfo"); const serveThemes = require("./middleware/serveThemes"); +const serveProxies = require("./middleware/serveProxies"); const csp = require("./middleware/csp"); const ui5connect = require("connect-openui5"); const nonReadRequests = require("./middleware/nonReadRequests"); @@ -90,6 +91,7 @@ function serve(tree, {port, changePortIfInUse = false, h2 = false, key, cert, ac app.use("/proxy", ui5connect.proxy({ secure: false })); + app.use(serveProxies(tree.resources.proxies)); // 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 diff --git a/package.json b/package.json index 40c45980..09a5ad4f 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "unit-watch": "rimraf test/tmp && ava --watch", "unit-nyan": "npm run unit -- --tap | tnyan", "unit-debug": "rimraf test/tmp && cross-env DEBUG=*,-babel,-ava ava", - "unit-inspect": "cross-env DEBUG=*,-babel,-ava node --inspect-brk node_modules/ava/profile.js", + "unit-inspect": "cross-env DEBUG=*,-babel,-ava node $NODE_DEBUG_OPTION --inspect-brk node_modules/ava/profile.js ./test/lib/server.js", "coverage": "nyc npm run unit", "jsdoc": "npm run jsdoc-generate && opn jsdocs/index.html", "jsdoc-generate": "node_modules/.bin/jsdoc -c ./jsdoc.json ./lib/ || (echo 'Error during JSDoc generation! Check log.' && exit 1)", @@ -103,6 +103,7 @@ "etag": "^1.8.1", "express": "^4.16.2", "fresh": "^0.5.2", + "http-proxy": "^1.12.0", "make-dir": "^1.1.0", "mime-types": "^2.1.17", "portscanner": "^2.1.1", diff --git a/test/fixtures/application.proxy/package.json b/test/fixtures/application.proxy/package.json new file mode 100644 index 00000000..f67bccb1 --- /dev/null +++ b/test/fixtures/application.proxy/package.json @@ -0,0 +1,6 @@ +{ + "name": "application.proxy", + "version": "1.0.0", + "description": "Simple SAPUI5 based application - test for ui5-proxy configuration", + "main": "index.html" +} diff --git a/test/fixtures/application.proxy/ui5.yaml b/test/fixtures/application.proxy/ui5.yaml new file mode 100644 index 00000000..531fcfb4 --- /dev/null +++ b/test/fixtures/application.proxy/ui5.yaml @@ -0,0 +1,8 @@ +--- +specVersion: "0.1" +type: application +metadata: + name: application.proxy +resources: + proxies: + /api/v1: "http://localhost:3351" diff --git a/test/fixtures/application.proxy/webapp/whatever.json b/test/fixtures/application.proxy/webapp/whatever.json new file mode 100644 index 00000000..fd3019ef --- /dev/null +++ b/test/fixtures/application.proxy/webapp/whatever.json @@ -0,0 +1 @@ +{"all": "OK"} diff --git a/test/lib/server.js b/test/lib/server.js index 26534332..285c8579 100644 --- a/test/lib/server.js +++ b/test/lib/server.js @@ -455,3 +455,24 @@ test("Get index of resources", (t) => { }) ]); }); + +test("the built-in proxy", (t) => { + let port = 3351; + let request = supertest(`http://localhost:${port}`); + return normalizer.generateProjectTree({ + cwd: "./test/fixtures/application.proxy" + }).then((tree) => { + return server.serve(tree, { + port: port + }); + }).then((serveResult) => { + return request.get("/api/v1/whatever.json").set("set-cookie", "true").then((res) => { + if (res.error) { + t.fail(res.error.text); + } + t.deepEqual(res.statusCode, 200, "Correct HTTP status code"); + t.deepEqual(res.text, "{\"all\": \"OK\"}\n", "API response correct"); + t.pass("Server was closed properly."); + }); + }); +}); From ffb757a3131b7f88b93f044d082fba8c4658fa30 Mon Sep 17 00:00:00 2001 From: Michael Sprauer Date: Fri, 13 Jul 2018 16:15:30 +0200 Subject: [PATCH 02/13] increase test coverage I really struggle to increase coverage further, because I can't reliably modify the process.env in order to test the HTTP_PROXY or make the server answer with a cookie. Signed-off-by: Michael Sprauer --- test/fixtures/application.proxy/ui5.yaml | 1 + test/lib/server.js | 22 ++++++++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/test/fixtures/application.proxy/ui5.yaml b/test/fixtures/application.proxy/ui5.yaml index 531fcfb4..cd0db72a 100644 --- a/test/fixtures/application.proxy/ui5.yaml +++ b/test/fixtures/application.proxy/ui5.yaml @@ -6,3 +6,4 @@ metadata: resources: proxies: /api/v1: "http://localhost:3351" + /404: "http://localhost:3351/proxy/nothing" # non existent diff --git a/test/lib/server.js b/test/lib/server.js index 285c8579..c7907290 100644 --- a/test/lib/server.js +++ b/test/lib/server.js @@ -466,13 +466,31 @@ test("the built-in proxy", (t) => { port: port }); }).then((serveResult) => { - return request.get("/api/v1/whatever.json").set("set-cookie", "true").then((res) => { + return request.get("/api/v1/whatever.json").then((res) => { if (res.error) { t.fail(res.error.text); } t.deepEqual(res.statusCode, 200, "Correct HTTP status code"); t.deepEqual(res.text, "{\"all\": \"OK\"}\n", "API response correct"); - t.pass("Server was closed properly."); + t.pass("Server answered properly."); + }); + }).then((serveResult) => { // direct access; bypassing the proxy + return request.get("/whatever.json").then((res) => { + if (res.error) { + t.fail(res.error.text); + } + t.deepEqual(res.statusCode, 200, "Correct HTTP status code"); + t.deepEqual(res.text, "{\"all\": \"OK\"}\n", "API response correct"); + t.pass("Server answered properly."); + }); + }).then((serveResult) => { // proxy invalid file + return request.get("/404/not.found.json").then((res) => { + if (res.error) { + t.fail(res.error.text); + } + t.is(res.headers["content-type"], "text/html", "Correct content type"); + t.is(/(.*)<\/title>/i.exec(res.text)[1], "Index of /proxy/nothing/not.found.json", "Found correct title"); + t.pass("Server answered properly."); }); }); }); From 8e8160ccf6f86ab45152805b94e0166e7751a665 Mon Sep 17 00:00:00 2001 From: Michael Sprauer <Michael.Sprauer@sap.com> Date: Fri, 13 Jul 2018 15:43:31 +0200 Subject: [PATCH 03/13] [FEATURE] Proxy Backend Services fixes https://github.com/SAP/ui5-server/issues/13 Signed-off-by: Michael Sprauer <Michael.Sprauer@sap.com> --- README.md | 15 ++ lib/middleware/serveProxies.js | 133 ++++++++++++++++++ lib/server.js | 2 + package.json | 3 +- test/fixtures/application.proxy/package.json | 6 + test/fixtures/application.proxy/ui5.yaml | 8 ++ .../application.proxy/webapp/whatever.json | 1 + test/lib/server.js | 21 +++ 8 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 lib/middleware/serveProxies.js create mode 100644 test/fixtures/application.proxy/package.json create mode 100644 test/fixtures/application.proxy/ui5.yaml create mode 100644 test/fixtures/application.proxy/webapp/whatever.json diff --git a/README.md b/README.md index 1cd50058..35d5e69f 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,21 @@ If there is none, a new certificate is created and used. **Hint:** If Chrome unintentionally redirects a HTTP-URL to HTTPS, you need to delete the HSTS mapping in [chrome://net-internals/#hsts](chrome://net-internals/#hsts) by entering the domain name (e.g. localhost) and pressing "delete". +## Proxy + +You can proxy existing (OData) backend services to get around Access-Control-Allow-Origin (CORS) errors. +To do so, you can add a hash of proxied paths in your `ui5.yml`. Example: +``` +# ui5.yml +specVersion: '0.1' +metadata: + name: my-awesome-app +type: application +resources: + proxyies: + /api/v1: "http://api.of.external.service/v1" +``` + ## Contributing Please check our [Contribution Guidelines](https://github.com/SAP/ui5-tooling/blob/master/CONTRIBUTING.md). diff --git a/lib/middleware/serveProxies.js b/lib/middleware/serveProxies.js new file mode 100644 index 00000000..46e5ad82 --- /dev/null +++ b/lib/middleware/serveProxies.js @@ -0,0 +1,133 @@ +// eavily inspired by https://github.com/SAP/connect-openui5/blob/master/lib/proxy.js +const url = require("url"); +const httpProxy = require("http-proxy"); + +const env = { + noProxy: process.env.NO_PROXY || process.env.no_proxy, + httpProxy: process.env.HTTP_PROXY || process.env.http_proxy, + httpsProxy: process.env.HTTPS_PROXY || process.env.https_proxy +}; + +function getProxyUri(uri) { + if (uri.protocol === "https:" && env.httpsProxy || uri.protocol === "http:" && env.httpProxy) { + if (env.noProxy) { + const canonicalHost = uri.host.replace(/^\.*/, "."); + const port = uri.port || (uri.protocol === "https:" ? "443" : "80"); + + const patterns = env.noProxy.split(","); + for (let i = patterns.length - 1; i >= 0; i--) { + let pattern = patterns[i].trim().toLowerCase(); + + // don"t use a proxy at all + if (pattern === "*") { + return null; + } + + // Remove leading * and make sure to have exact one leading dot (.) + pattern = pattern.replace(/^[*]+/, "").replace(/^\.*/, "."); + + // add port if no specified + if (pattern.indexOf(":") === -1) { + pattern += ":" + port; + } + + // if host ends with pattern, no proxy should be used + if (canonicalHost.indexOf(pattern) === canonicalHost.length - pattern.length) { + return null; + } + } + } + + if (uri.protocol === "https:" && env.httpsProxy) { + return env.httpsProxy; + } else if (uri.protocol === "http:" && env.httpProxy) { + return env.httpProxy; + } + } + + return null; +} + +function buildRequestUrl(uri) { + let ret = uri.pathname; + if (uri.query) { + ret += "?" + uri.query; + } + return ret; +} + +function createUri(uriParam, proxyDefinitions) { + for (let path in proxyDefinitions) { + if (uriParam.startsWith(path)) { + return url.parse(proxyDefinitions[path] + uriParam.substring(path.length)); + } + } + return null; +} + +/** + * Creates and returns the middleware to serve proxied servers. + * + * @module server/middleware/serveProxies + * @param {Object} proxyDefinitions Contains proxy definitions + * @returns {function} Returns a server middleware closure. + */ +function createMiddleware(proxyDefinitions) { + let proxy = httpProxy.createProxyServer({}); + + return function serveResources(req, res, next) { + if (proxyDefinitions == undefined) { + return next(); + } + let uri = createUri(req.url, proxyDefinitions); + if (!uri || !uri.host) { + next(); + return; + } + + // change original request url to target url + req.url = buildRequestUrl(uri); + + // change original host to target host + req.headers.host = uri.host; + + // overwrite response headers + res.orgWriteHead = res.writeHead; + res.writeHead = function(...args) { + // We always filter the secure header to avoid the cookie from + // "not" beeing included in follow up requests in case of the + // proxy is running on HTTP and not HTTPS + let cookies = res.getHeader("set-cookie"); + // array == multiple cookies + if (Array.isArray(cookies)) { + for (let i = 0; i < cookies.length; i++) { + cookies[i] = cookies[i].replace("secure;", ""); + } + } else if (typeof cookies === "string" || cookies instanceof String) { + // single cookie + cookies = cookies.replace("secure;", ""); + } + + if (cookies) { + res.setHeader("set-cookie", cookies); + } + + // call original writeHead function + res.orgWriteHead(args); + }; + + // get proxy for uri (if defined in env vars) + let targetUri = getProxyUri(uri) || uri.protocol + "//" + uri.host; + + // proxy the request + proxy.proxyRequest(req, res, { + target: targetUri + }, function(err) { + if (err) { + next(err); + } + }); + }; +} + +module.exports = createMiddleware; diff --git a/lib/server.js b/lib/server.js index b6cd08d7..e991c0ae 100644 --- a/lib/server.js +++ b/lib/server.js @@ -8,6 +8,7 @@ const serveIndex = require("./middleware/serveIndex"); const discovery = require("./middleware/discovery"); const versionInfo = require("./middleware/versionInfo"); const serveThemes = require("./middleware/serveThemes"); +const serveProxies = require("./middleware/serveProxies"); const csp = require("./middleware/csp"); const ui5connect = require("connect-openui5"); const nonReadRequests = require("./middleware/nonReadRequests"); @@ -90,6 +91,7 @@ function serve(tree, {port, changePortIfInUse = false, h2 = false, key, cert, ac app.use("/proxy", ui5connect.proxy({ secure: false })); + app.use(serveProxies(tree.resources.proxies)); // 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 diff --git a/package.json b/package.json index 40c45980..09a5ad4f 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "unit-watch": "rimraf test/tmp && ava --watch", "unit-nyan": "npm run unit -- --tap | tnyan", "unit-debug": "rimraf test/tmp && cross-env DEBUG=*,-babel,-ava ava", - "unit-inspect": "cross-env DEBUG=*,-babel,-ava node --inspect-brk node_modules/ava/profile.js", + "unit-inspect": "cross-env DEBUG=*,-babel,-ava node $NODE_DEBUG_OPTION --inspect-brk node_modules/ava/profile.js ./test/lib/server.js", "coverage": "nyc npm run unit", "jsdoc": "npm run jsdoc-generate && opn jsdocs/index.html", "jsdoc-generate": "node_modules/.bin/jsdoc -c ./jsdoc.json ./lib/ || (echo 'Error during JSDoc generation! Check log.' && exit 1)", @@ -103,6 +103,7 @@ "etag": "^1.8.1", "express": "^4.16.2", "fresh": "^0.5.2", + "http-proxy": "^1.12.0", "make-dir": "^1.1.0", "mime-types": "^2.1.17", "portscanner": "^2.1.1", diff --git a/test/fixtures/application.proxy/package.json b/test/fixtures/application.proxy/package.json new file mode 100644 index 00000000..f67bccb1 --- /dev/null +++ b/test/fixtures/application.proxy/package.json @@ -0,0 +1,6 @@ +{ + "name": "application.proxy", + "version": "1.0.0", + "description": "Simple SAPUI5 based application - test for ui5-proxy configuration", + "main": "index.html" +} diff --git a/test/fixtures/application.proxy/ui5.yaml b/test/fixtures/application.proxy/ui5.yaml new file mode 100644 index 00000000..531fcfb4 --- /dev/null +++ b/test/fixtures/application.proxy/ui5.yaml @@ -0,0 +1,8 @@ +--- +specVersion: "0.1" +type: application +metadata: + name: application.proxy +resources: + proxies: + /api/v1: "http://localhost:3351" diff --git a/test/fixtures/application.proxy/webapp/whatever.json b/test/fixtures/application.proxy/webapp/whatever.json new file mode 100644 index 00000000..fd3019ef --- /dev/null +++ b/test/fixtures/application.proxy/webapp/whatever.json @@ -0,0 +1 @@ +{"all": "OK"} diff --git a/test/lib/server.js b/test/lib/server.js index 17517f0d..3b38b695 100644 --- a/test/lib/server.js +++ b/test/lib/server.js @@ -455,3 +455,24 @@ test("Get index of resources", (t) => { }) ]); }); + +test("the built-in proxy", (t) => { + let port = 3351; + let request = supertest(`http://localhost:${port}`); + return normalizer.generateProjectTree({ + cwd: "./test/fixtures/application.proxy" + }).then((tree) => { + return server.serve(tree, { + port: port + }); + }).then((serveResult) => { + return request.get("/api/v1/whatever.json").set("set-cookie", "true").then((res) => { + if (res.error) { + t.fail(res.error.text); + } + t.deepEqual(res.statusCode, 200, "Correct HTTP status code"); + t.deepEqual(res.text, "{\"all\": \"OK\"}\n", "API response correct"); + t.pass("Server was closed properly."); + }); + }); +}); From 0de57aa8f9cdf81c6e3dd6f8452be6cc01d4a4c2 Mon Sep 17 00:00:00 2001 From: Michael Sprauer <Michael.Sprauer@sap.com> Date: Fri, 13 Jul 2018 16:15:30 +0200 Subject: [PATCH 04/13] increase test coverage I really struggle to increase coverage further, because I can't reliably modify the process.env in order to test the HTTP_PROXY or make the server answer with a cookie. Signed-off-by: Michael Sprauer <Michael.Sprauer@sap.com> --- test/fixtures/application.proxy/ui5.yaml | 1 + test/lib/server.js | 22 ++++++++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/test/fixtures/application.proxy/ui5.yaml b/test/fixtures/application.proxy/ui5.yaml index 531fcfb4..cd0db72a 100644 --- a/test/fixtures/application.proxy/ui5.yaml +++ b/test/fixtures/application.proxy/ui5.yaml @@ -6,3 +6,4 @@ metadata: resources: proxies: /api/v1: "http://localhost:3351" + /404: "http://localhost:3351/proxy/nothing" # non existent diff --git a/test/lib/server.js b/test/lib/server.js index 3b38b695..77ab0c52 100644 --- a/test/lib/server.js +++ b/test/lib/server.js @@ -466,13 +466,31 @@ test("the built-in proxy", (t) => { port: port }); }).then((serveResult) => { - return request.get("/api/v1/whatever.json").set("set-cookie", "true").then((res) => { + return request.get("/api/v1/whatever.json").then((res) => { if (res.error) { t.fail(res.error.text); } t.deepEqual(res.statusCode, 200, "Correct HTTP status code"); t.deepEqual(res.text, "{\"all\": \"OK\"}\n", "API response correct"); - t.pass("Server was closed properly."); + t.pass("Server answered properly."); + }); + }).then((serveResult) => { // direct access; bypassing the proxy + return request.get("/whatever.json").then((res) => { + if (res.error) { + t.fail(res.error.text); + } + t.deepEqual(res.statusCode, 200, "Correct HTTP status code"); + t.deepEqual(res.text, "{\"all\": \"OK\"}\n", "API response correct"); + t.pass("Server answered properly."); + }); + }).then((serveResult) => { // proxy invalid file + return request.get("/404/not.found.json").then((res) => { + if (res.error) { + t.fail(res.error.text); + } + t.is(res.headers["content-type"], "text/html", "Correct content type"); + t.is(/<title>(.*)<\/title>/i.exec(res.text)[1], "Index of /proxy/nothing/not.found.json", "Found correct title"); + t.pass("Server answered properly."); }); }); }); From 530d47afb93a484b113f0747824e712c245dc5e8 Mon Sep 17 00:00:00 2001 From: Matthias Osswald <mat.osswald@sap.com> Date: Tue, 17 Jul 2018 09:08:23 +0000 Subject: [PATCH 05/13] Release 0.2.1 --- CHANGELOG.md | 6 +++++- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 244a40c9..4300d649 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,10 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). -A list of unreleased changes can be found [here](https://github.com/SAP/ui5-server/compare/v0.2.0...HEAD). +A list of unreleased changes can be found [here](https://github.com/SAP/ui5-server/compare/v0.2.1...HEAD). + +<a name="v0.2.1"></a> +## [v0.2.1] - 2018-07-13 <a name="v0.2.0"></a> ## [v0.2.0] - 2018-07-12 @@ -53,6 +56,7 @@ A list of unreleased changes can be found [here](https://github.com/SAP/ui5-serv - **Travis:** Add node.js 10 to test matrix [`2881261`](https://github.com/SAP/ui5-server/commit/2881261a05afd737af7c8874b91819a52b8f88df) +[v0.2.1]: https://github.com/SAP/ui5-server/compare/v0.2.0...v0.2.1 [v0.2.0]: https://github.com/SAP/ui5-server/compare/v0.1.2...v0.2.0 [v0.1.2]: https://github.com/SAP/ui5-server/compare/v0.1.1...v0.1.2 [v0.1.1]: https://github.com/SAP/ui5-server/compare/v0.1.0...v0.1.1 diff --git a/package-lock.json b/package-lock.json index 7628bb1e..e6f0df6f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@ui5/server", - "version": "0.2.0", + "version": "0.2.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 09a5ad4f..99e9c25a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ui5/server", - "version": "0.2.0", + "version": "0.2.1", "description": "UI5 Build and Development Tooling - Server", "author": "SAP SE (https://www.sap.com)", "license": "Apache-2.0", From db71d8d519b4538781bb297b88bed32a43a6f884 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <support@dependabot.com> Date: Wed, 18 Jul 2018 02:19:49 +0000 Subject: [PATCH 06/13] Bump @ui5/builder from 0.2.0 to 0.2.1 Bumps [@ui5/builder](https://github.com/SAP/ui5-builder) from 0.2.0 to 0.2.1. - [Release notes](https://github.com/SAP/ui5-builder/releases) - [Changelog](https://github.com/SAP/ui5-builder/blob/master/CHANGELOG.md) - [Commits](https://github.com/SAP/ui5-builder/compare/v0.2.0...v0.2.1) Signed-off-by: dependabot[bot] <support@dependabot.com> --- package-lock.json | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index e6f0df6f..a9ecacfb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -269,9 +269,9 @@ } }, "@ui5/builder": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@ui5/builder/-/builder-0.2.0.tgz", - "integrity": "sha512-Z7fNTCCgl+GEkMUxUjVpbG/sKVGR+O2sC6OUYB6l7isuzKi4QIdIkbEgmItsJfMS+Dwv2XdTB/3Cmeka++Gl1Q==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@ui5/builder/-/builder-0.2.1.tgz", + "integrity": "sha512-gEIoO4ocr9cEIrivU0G1S0AL7pprgF6RyXwzTt7ijatCeDPI6byYpzkK3WYwOlrw102Vwg6nZAEtBPTxR8xaKQ==", "requires": { "@ui5/fs": "^0.2.0", "@ui5/logger": "^0.2.0", @@ -1417,6 +1417,11 @@ } } }, + "base64-js": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", + "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==" + }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -1567,6 +1572,15 @@ "integrity": "sha1-/vKNqLgROgoNtEMLC2Rntpcws0o=", "dev": true }, + "buffer": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.1.0.tgz", + "integrity": "sha512-YkIRgwsZwJWTnyQrsBTWefizHh+8GYj3kbL1BTiAQ/9pwpino0G7B2gp5tx/FUBqUlvtxV85KNR3mwfAtv15Yw==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, "buffer-alloc": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", @@ -2207,9 +2221,12 @@ } }, "crc": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/crc/-/crc-3.5.0.tgz", - "integrity": "sha1-mLi6fUiWZbo5efWbITgTdBAaGWQ=" + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.7.0.tgz", + "integrity": "sha512-ZwmUex488OBjSVOMxnR/dIa1yxisBMJNEi+UxzXpKhax8MPsQtoRQtl5Qgo+W7pcSVkRXa3BEVjaniaWKtvKvw==", + "requires": { + "buffer": "^5.1.0" + } }, "crc32-stream": { "version": "2.0.0", @@ -4345,6 +4362,11 @@ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" }, + "ieee754": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.12.tgz", + "integrity": "sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==" + }, "ignore": { "version": "3.3.10", "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", From 5e31d8ac4d0aba80d33359b62faa32bd4ff4319e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <support@dependabot.com> Date: Wed, 18 Jul 2018 05:35:07 +0000 Subject: [PATCH 07/13] Bump mime-types from 2.1.18 to 2.1.19 Bumps [mime-types](https://github.com/jshttp/mime-types) from 2.1.18 to 2.1.19. - [Release notes](https://github.com/jshttp/mime-types/releases) - [Changelog](https://github.com/jshttp/mime-types/blob/master/HISTORY.md) - [Commits](https://github.com/jshttp/mime-types/compare/2.1.18...2.1.19) Signed-off-by: dependabot[bot] <support@dependabot.com> --- package-lock.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index a9ecacfb..33a5f67f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5454,16 +5454,16 @@ "optional": true }, "mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" + "version": "1.35.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.35.0.tgz", + "integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg==" }, "mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "version": "2.1.19", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.19.tgz", + "integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==", "requires": { - "mime-db": "~1.33.0" + "mime-db": "~1.35.0" } }, "mimic-fn": { From 58d7b755ccf3e573c28b5de4427c857e2ee246c4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <support@dependabot.com> Date: Mon, 16 Jul 2018 02:20:36 +0000 Subject: [PATCH 08/13] Bump compression from 1.7.2 to 1.7.3 Bumps [compression](https://github.com/expressjs/compression) from 1.7.2 to 1.7.3. - [Release notes](https://github.com/expressjs/compression/releases) - [Changelog](https://github.com/expressjs/compression/blob/master/HISTORY.md) - [Commits](https://github.com/expressjs/compression/compare/1.7.2...1.7.3) Signed-off-by: dependabot[bot] <support@dependabot.com> --- package-lock.json | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/package-lock.json b/package-lock.json index 33a5f67f..9709414e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2032,31 +2032,24 @@ }, "dependencies": { "mime-db": { - "version": "1.34.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.34.0.tgz", - "integrity": "sha1-RS0Oz/XDA0am3B5kseruDTcZ/5o=" + "version": "1.35.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.35.0.tgz", + "integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg==" } } }, "compression": { - "version": "1.7.2", - "resolved": "http://registry.npmjs.org/compression/-/compression-1.7.2.tgz", - "integrity": "sha1-qv+81qr4VLROuygDU9WtFlH1mmk=", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.3.tgz", + "integrity": "sha512-HSjyBG5N1Nnz7tF2+O7A9XUhyjru71/fwgNb7oIsEVHR0WShfs2tIS/EySLgiTe98aOK18YDlMXpzjCXY/n9mg==", "requires": { - "accepts": "~1.3.4", + "accepts": "~1.3.5", "bytes": "3.0.0", - "compressible": "~2.0.13", + "compressible": "~2.0.14", "debug": "2.6.9", "on-headers": "~1.0.1", - "safe-buffer": "5.1.1", + "safe-buffer": "5.1.2", "vary": "~1.1.2" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" - } } }, "concat-map": { From 25bf89c447fcb1f044df42c5d9b85f082fb3fca9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <support@dependabot.com> Date: Sat, 4 Aug 2018 02:23:19 +0000 Subject: [PATCH 09/13] Bump eslint from 5.1.0 to 5.3.0 Bumps [eslint](https://github.com/eslint/eslint) from 5.1.0 to 5.3.0. - [Release notes](https://github.com/eslint/eslint/releases) - [Changelog](https://github.com/eslint/eslint/blob/master/CHANGELOG.md) - [Commits](https://github.com/eslint/eslint/compare/v5.1.0...v5.3.0) Signed-off-by: dependabot[bot] <support@dependabot.com> --- package-lock.json | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9709414e..7ced01a2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2760,9 +2760,9 @@ } }, "eslint": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.1.0.tgz", - "integrity": "sha512-DyH6JsoA1KzA5+OSWFjg56DFJT+sDLO0yokaPZ9qY0UEmYrPA1gEX/G1MnVkmRDsksG4H1foIVz2ZXXM3hHYvw==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.3.0.tgz", + "integrity": "sha512-N/tCqlMKkyNvAvLu+zI9AqDasnSLt00K+Hu8kdsERliC9jYEc8ck12XtjvOXrBKu8fK6RrBcN9bat6Xk++9jAg==", "dev": true, "requires": { "ajv": "^6.5.0", @@ -2781,7 +2781,7 @@ "functional-red-black-tree": "^1.0.1", "glob": "^7.1.2", "globals": "^11.7.0", - "ignore": "^3.3.3", + "ignore": "^4.0.2", "imurmurhash": "^0.1.4", "inquirer": "^5.2.0", "is-resolvable": "^1.1.0", @@ -2796,7 +2796,7 @@ "path-is-inside": "^1.0.2", "pluralize": "^7.0.0", "progress": "^2.0.0", - "regexpp": "^1.1.0", + "regexpp": "^2.0.0", "require-uncached": "^1.0.3", "semver": "^5.5.0", "string.prototype.matchall": "^2.0.0", @@ -2858,6 +2858,12 @@ "integrity": "sha512-K8BNSPySfeShBQXsahYB/AbbWruVOTyVpgoIDnl8odPpeSfP2J5QO2oLFFdl2j7GfDCtZj2bMKar2T49itTPCg==", "dev": true }, + "ignore": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.3.tgz", + "integrity": "sha512-Z/vAH2GGIEATQnBVXMclE2IGV6i0GyVngKThcGZ5kHgHMxLo9Ow2+XHRq1aEKEej5vOF1TPJNbvX6J/anT0M7A==", + "dev": true + }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -8440,9 +8446,9 @@ } }, "regexpp": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz", - "integrity": "sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.0.tgz", + "integrity": "sha512-g2FAVtR8Uh8GO1Nv5wpxW7VFVwHcCEr4wyA8/MHiRkO8uHoR5ntAA8Uq3P1vvMTX/BeQiRVSpDGLd+Wn5HNOTA==", "dev": true }, "regexpu-core": { From cf3526f82a7f104fdbdea6fc6fd5f6f0a6090420 Mon Sep 17 00:00:00 2001 From: Michael Sprauer <Michael.Sprauer@sap.com> Date: Wed, 29 Aug 2018 14:43:58 +0200 Subject: [PATCH 10/13] remove that port from the pattern (when is that ever useful?) Signed-off-by: Michael Sprauer <Michael.Sprauer@sap.com> --- lib/middleware/serveProxies.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/middleware/serveProxies.js b/lib/middleware/serveProxies.js index 46e5ad82..cfe25fc0 100644 --- a/lib/middleware/serveProxies.js +++ b/lib/middleware/serveProxies.js @@ -1,4 +1,4 @@ -// eavily inspired by https://github.com/SAP/connect-openui5/blob/master/lib/proxy.js +// heavily inspired by https://github.com/SAP/connect-openui5/blob/master/lib/proxy.js const url = require("url"); const httpProxy = require("http-proxy"); @@ -26,11 +26,6 @@ function getProxyUri(uri) { // Remove leading * and make sure to have exact one leading dot (.) pattern = pattern.replace(/^[*]+/, "").replace(/^\.*/, "."); - // add port if no specified - if (pattern.indexOf(":") === -1) { - pattern += ":" + port; - } - // if host ends with pattern, no proxy should be used if (canonicalHost.indexOf(pattern) === canonicalHost.length - pattern.length) { return null; From 498f3ed69134a6252478a3e3bd384c16f95e534d Mon Sep 17 00:00:00 2001 From: Koen Schouten <koenschouten12@hotmail.com> Date: Fri, 27 Sep 2019 19:40:44 +0200 Subject: [PATCH 11/13] Update Readme, fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 35d5e69f..db530354 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ metadata: name: my-awesome-app type: application resources: - proxyies: + proxies: /api/v1: "http://api.of.external.service/v1" ``` From 158e57e15192d68a8fded615c19461434acb20c3 Mon Sep 17 00:00:00 2001 From: Koen Schouten <koenschouten12@hotmail.com> Date: Fri, 27 Sep 2019 19:50:50 +0200 Subject: [PATCH 12/13] Pass Basic auth to proxy --- lib/middleware/serveProxies.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/middleware/serveProxies.js b/lib/middleware/serveProxies.js index cfe25fc0..3d314f4e 100644 --- a/lib/middleware/serveProxies.js +++ b/lib/middleware/serveProxies.js @@ -68,7 +68,12 @@ function createUri(uriParam, proxyDefinitions) { * @returns {function} Returns a server middleware closure. */ function createMiddleware(proxyDefinitions) { - let proxy = httpProxy.createProxyServer({}); + let proxyServerParameters = {}; + if (proxyDefinitions.auth) + { + proxyServerParameters.auth = proxyDefinitions.auth; + } + let proxy = httpProxy.createProxyServer(proxyServerParameters); return function serveResources(req, res, next) { if (proxyDefinitions == undefined) { From 21c7dde2fa91bb94c924daa97c38d0a32fed4405 Mon Sep 17 00:00:00 2001 From: Koen Schouten <koenschouten12@hotmail.com> Date: Fri, 27 Sep 2019 19:52:28 +0200 Subject: [PATCH 13/13] Add example for ui5.yaml with Basic auth --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index db530354..ce9ad465 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ If there is none, a new certificate is created and used. ## Proxy You can proxy existing (OData) backend services to get around Access-Control-Allow-Origin (CORS) errors. -To do so, you can add a hash of proxied paths in your `ui5.yml`. Example: +To do so, you can add a hash of proxied paths in your `ui5.yml`. Example (auth is optional): ``` # ui5.yml specVersion: '0.1' @@ -48,6 +48,7 @@ type: application resources: proxies: /api/v1: "http://api.of.external.service/v1" + auth: "myproxyusername:myproxypassword" ``` ## Contributing