Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add callback for configuring Vary header #318

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ app.listen(80, function () {
* `maxAge`: Configures the **Access-Control-Max-Age** CORS header. Set to an integer to pass the header, otherwise it is omitted.
* `preflightContinue`: Pass the CORS preflight response to the next handler.
* `optionsSuccessStatus`: Provides a status code to use for successful `OPTIONS` requests, since some legacy browsers (IE11, various SmartTVs) choke on `204`.
* `shouldSetVaryOriginHeader`: takes a `req`, and determines whether the `Vary: Origin` header should be set for that header name. Does nothing for pre-flight OPTIONS requests. As an example, `shouldSetVaryOriginHeader(req) { !req.originalUrl.startsWith('/images')) }` would set the Vary header to `Vary: Origin` for all request URIs besides those that start with `/images`; for requests that start with `/images`, it would not set the `Vary` header. This is mostly useful for working with caching and edge services that do not support the `Vary` header, such as [Cloudflare Polish](https://developers.cloudflare.com/images/polish/compression/); it is also useful for working with CDNs in general, because the `Vary` header can cause cache fragmentation and lower cache hit ratios depending on how it is configured.

The default configuration is the equivalent of:

Expand Down
23 changes: 14 additions & 9 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
origin: '*',
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
preflightContinue: false,
optionsSuccessStatus: 204
optionsSuccessStatus: 204,
shouldSetVaryOriginHeader: function(req) { return true }
};

function isString(s) {
Expand Down Expand Up @@ -50,21 +51,25 @@
key: 'Access-Control-Allow-Origin',
value: options.origin
}]);
headers.push([{
key: 'Vary',
value: 'Origin'
}]);
if (options.shouldSetVaryOriginHeader(req)) {
headers.push([{
key: 'Vary',
value: 'Origin'
}]);
}
} else {
isAllowed = isOriginAllowed(requestOrigin, options.origin);
// reflect origin
headers.push([{
key: 'Access-Control-Allow-Origin',
value: isAllowed ? requestOrigin : false
}]);
headers.push([{
key: 'Vary',
value: 'Origin'
}]);
if (options.shouldSetVaryOriginHeader(req)) {
headers.push([{
key: 'Vary',
value: 'Origin'
}]);
}
}

return headers;
Expand Down
91 changes: 91 additions & 0 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,97 @@ var util = require('util')
})
});

describe('shouldSetVaryOriginHeader', function() {
it('Vary: "Origin" header is set by default', function (done) {
var req = fakeRequest('GET')
var res = fakeResponse()
req.host = 'http://example.com'
req.originalUrl = '/images/asdf.png'
var options = {
origin: 'http://example.com'
}

var next = function () {
// assert
assert.equal(res.statusCode, 200)
assert.equal(res.getHeader('Vary'), 'Origin')
done();
};

cors(options)(req, res, next)
})

it('Vary: "Origin" header is not set when shouldSetVaryOriginHeader(req) configured to return false', function (done) {
var req = fakeRequest('GET')
var res = fakeResponse()
req.host = 'http://example.com'
req.originalUrl = '/images/asdf.png'

var options = {
origin: ['http://example.com', 'http://foo.com'],
shouldSetVaryOriginHeader: function(req) {
return req.originalUrl.indexOf('/images') !== 0
}
}

var next = function () {
// assert
assert.equal(res.statusCode, 200)
assert.equal(res.getHeader('Vary'), undefined)
done();
};

cors(options)(req, res, next)
})

it('Vary: "Origin" header is not set when shouldSetVaryOriginHeader(req) configured to return false and origin is string', function (done) {
var req = fakeRequest('GET')
var res = fakeResponse()
req.host = 'http://example.com'
req.originalUrl = '/images/asdf.png'

var options = {
origin: 'http://example.com',
shouldSetVaryOriginHeader: function(req) {
return req.originalUrl.indexOf('/images') !== 0
}
}

var next = function () {
// assert
assert.equal(res.statusCode, 200)
assert.equal(res.getHeader('Vary'), undefined)
done();
};

cors(options)(req, res, next)
})

it('Vary: "Origin" header is set when shouldSetVaryOriginHeader(req) configured to return true', function (done) {
var req = fakeRequest('GET')
var res = fakeResponse()
req.host = 'http://example.com'

req.originalUrl = '/some/other/path'

var options = {
origin: ['http://example.com', 'http://foo.com'],
shouldSetVaryOriginHeader: function(req) {
return req.originalUrl.indexOf('/images') !== 0
}
}

var next = function () {
// assert
assert.equal(res.statusCode, 200)
assert.equal(res.getHeader('Vary'), 'Origin')
done();
};

cors(options)(req, res, next)
})
})

describe('passing static options', function () {
it('overrides defaults', function (done) {
var cb = after(1, done)
Expand Down