diff --git a/README.md b/README.md index 5b2d0b3a2..011feb546 100644 --- a/README.md +++ b/README.md @@ -395,6 +395,20 @@ proxyServer.listen(8015); }; ``` +* **mergeCookies**: true/false, merges `set-cookie` header from a request passed to `httpProxy.web` and from response. + E.g.: + ``` + const http = require("http"); + const httpProxy = require("http-proxy"); + const proxy = httpProxy.createProxyServer({}); + + const gateway = http.createServer((req, res) => { + res.setHeader('set-cookie', ["gateway=true; Path=/"]); + proxy.web(req, res, {target: "http://localhost:3002"}); + }); + + gateway.listen(3003); + ``` **NOTE:** `options.ws` and `options.ssl` are optional. diff --git a/lib/http-proxy.js b/lib/http-proxy.js index 8ea278938..0a24789d6 100644 --- a/lib/http-proxy.js +++ b/lib/http-proxy.js @@ -40,6 +40,7 @@ function createProxyServer(options) { * hostRewrite: rewrites the location hostname on (201/301/302/307/308) redirects, Default: null. * autoRewrite: rewrites the location host/port on (201/301/302/307/308) redirects based on requested host/port. Default: false. * protocolRewrite: rewrites the location protocol on (201/301/302/307/308) redirects to 'http' or 'https'. Default: null. + * mergeCookies: allows to merge `set-cookie` headers from passed response and response from target. Default: false. * } * * NOTE: `options.ws` and `options.ssl` are optional. diff --git a/lib/http-proxy/common.js b/lib/http-proxy/common.js index 6513e81d8..10feca286 100644 --- a/lib/http-proxy/common.js +++ b/lib/http-proxy/common.js @@ -49,7 +49,7 @@ common.setupOutgoing = function(outgoing, options, req, forward) { if (options.auth) { outgoing.auth = options.auth; } - + if (options.ca) { outgoing.ca = options.ca; } @@ -236,6 +236,30 @@ common.rewriteCookieProperty = function rewriteCookieProperty(header, config, pr }); }; +/** + * Merges `Set-Cookie` header + * + * @param {string|[string]} setCookie + * @param {string|[string]} upstreamSetCookie + * @returns {[string]} + * + * @api private + */ +common.mergeSetCookie = function mergeCookie(setCookie, upstreamSetCookie) { + var existingCookieArray = setCookie || [], + upstreamCookieArray = upstreamSetCookie || []; + + if (!Array.isArray(existingCookieArray)) { + existingCookieArray = [existingCookieArray] + } + + if (!Array.isArray(upstreamCookieArray)) { + upstreamCookieArray = [upstreamCookieArray] + } + + return [].concat(existingCookieArray, upstreamCookieArray) +}; + /** * Check the host and see if it potentially has a port in it (keep it simple) * diff --git a/lib/http-proxy/passes/web-outgoing.js b/lib/http-proxy/passes/web-outgoing.js index 46352f6e3..e1d52241f 100644 --- a/lib/http-proxy/passes/web-outgoing.js +++ b/lib/http-proxy/passes/web-outgoing.js @@ -86,6 +86,7 @@ module.exports = { // <-- var rewriteCookieDomainConfig = options.cookieDomainRewrite, rewriteCookiePathConfig = options.cookiePathRewrite, preserveHeaderKeyCase = options.preserveHeaderKeyCase, + mergeCookiesConfig = options.mergeCookies, rawHeaderKeyMap, setHeader = function(key, header) { if (header == undefined) return; @@ -95,6 +96,9 @@ module.exports = { // <-- if (rewriteCookiePathConfig && key.toLowerCase() === 'set-cookie') { header = common.rewriteCookieProperty(header, rewriteCookiePathConfig, 'path'); } + if (mergeCookiesConfig && key.toLowerCase() === 'set-cookie') { + header = common.mergeSetCookie(res.getHeader('set-cookie'), header) + } res.setHeader(String(key).trim(), header); }; diff --git a/test/lib-http-proxy-passes-web-outgoing-test.js b/test/lib-http-proxy-passes-web-outgoing-test.js index a509cf1ae..524ad6955 100644 --- a/test/lib-http-proxy-passes-web-outgoing-test.js +++ b/test/lib-http-proxy-passes-web-outgoing-test.js @@ -261,6 +261,9 @@ describe('lib/http-proxy/passes/web-outgoing.js', function () { // Header names are lower-cased this.headers[k.toLowerCase()] = v; }, + getHeader: function (k) { + return this.headers[k.toLowerCase()] + }, headers: {} }; }); @@ -404,6 +407,34 @@ describe('lib/http-proxy/passes/web-outgoing.js', function () { expect(this.res.headers['set-cookie']) .to.contain('hello-on-my.special.domain; domain=my.special.domain; path=/'); }); + + it('appends set-cookies header to an existing one', function () { + var options = { + mergeCookies: true, + }; + + this.res.setHeader('set-cookie', ['hello; domain=my.domain; path=/']); + + httpProxy.writeHeaders({}, this.res, this.proxyRes, options); + + expect(this.res.headers['set-cookie']).to.be.an(Array); + expect(this.res.headers['set-cookie']).to.have.length(3); + }); + + it('appends set-cookies header to an existing one (set-cookie is not an array)', function () { + var options = { + mergeCookies: true, + }; + + this.proxyRes.headers = Object.assign({}, this.proxyRes.headers, {'set-cookie': 'hello1; domain=my.domain; path=/'}); + + this.res.setHeader('set-cookie', 'hello; domain=my.domain; path=/'); + + httpProxy.writeHeaders({}, this.res, this.proxyRes, options); + + expect(this.res.headers['set-cookie']).to.be.an(Array); + expect(this.res.headers['set-cookie']).to.have.length(2); + }); });