Skip to content

Commit

Permalink
Extract <link rel=preconnect|prefetch|prerender|preload|dns-prefetch …
Browse files Browse the repository at this point in the history
…href=...> as headers
  • Loading branch information
papandreou committed Sep 14, 2016
1 parent f8bce65 commit ed99a42
Show file tree
Hide file tree
Showing 2 changed files with 230 additions and 6 deletions.
40 changes: 34 additions & 6 deletions lib/expressExtractHeaders.js
Expand Up @@ -43,13 +43,41 @@ module.exports = function (config) {
var stillParsing = true;
var htmlParser = new HtmlParser({
onopentag: function (name, attribs) {
if (stillParsing && name === 'meta') {
var httpEquiv = attribs['http-equiv'];
if (typeof httpEquiv === 'string') {
headers[httpEquiv] = attribs.content || '';
if (/^(?:X-Frame-Options|Content-Security-Policy(?:-Report-Only)?)$/i.test(httpEquiv)) {
omitRanges.push([htmlParser.startIndex, htmlParser.endIndex + 1]);
if (stillParsing) {
if (name === 'meta') {
var httpEquiv = attribs['http-equiv'];
if (typeof httpEquiv === 'string') {
headers[httpEquiv] = attribs.content || '';
if (/^(?:X-Frame-Options|Content-Security-Policy(?:-Report-Only)?)$/i.test(httpEquiv)) {
omitRanges.push([htmlParser.startIndex, htmlParser.endIndex + 1]);
}
}
} else if (name === 'link') {
var rel = attribs.rel;
var href = attribs.href;
var as = attribs.as;
var pr = attribs.pr;
var crossorigin = attribs.crossorigin;
if (typeof href === 'string' && /^(?:preconnect|prefetch|prerender|preload|dns-prefetch)$/i.test(rel)) {
var linkHeaderValue = '<' + href + '>; rel=' + rel;
if (as) {
linkHeaderValue += '; as=' + as;
}
if (pr) {
linkHeaderValue += '; pr=' + pr;
}
if (typeof crossorigin === 'string') {
linkHeaderValue += '; crossorigin' + (crossorigin ? '=' + crossorigin : '');
}
if (Array.isArray(headers.link)) {
headers.link.push(linkHeaderValue);
} else if (headers.link) {
headers.link = [headers.link, linkHeaderValue];
} else {
headers.link = linkHeaderValue;
}
}

}
}
},
Expand Down
196 changes: 196 additions & 0 deletions test/expressExtractHeaders.js
Expand Up @@ -64,6 +64,202 @@ describe('expressExtractHeaders', function () {
);
});

it('should extract <link rel="preconnect">', function () {
var responseHtml =
'<!DOCTYPE html>\n<html><head><link rel="preconnect" href="//example.com"></head><body>foo</body></html>';
return expect(
express()
.use(expressExtractHeaders())
.use(function (req, res) {
res.send(responseHtml);
}),
'to yield exchange', {
request: '/',
response: {
headers: {
'Content-Type': 'text/html; charset=utf-8',
Link: '<//example.com>; rel=preconnect'
}
}
}
);
});

it('should extract two link elements', function () {
var responseHtml =
'<!DOCTYPE html>\n<html><head><link rel="preconnect" href="//example.com"><link rel="prefetch" href="//foobar.com"></head><body>foo</body></html>';
return expect(
express()
.use(expressExtractHeaders())
.use(function (req, res) {
res.send(responseHtml);
}),
'to yield exchange', {
request: '/',
response: {
headers: {
'Content-Type': 'text/html; charset=utf-8',
Link: [
'<//example.com>; rel=preconnect',
'<//foobar.com>; rel=prefetch'
]
}
}
}
);
});

it('should extract three link elements', function () {
var responseHtml =
'<!DOCTYPE html>\n<html><head><link rel="prefetch" href="//example.com/logo-hires.jpg" as="image"><link rel="preconnect" href="//example.com"><link rel="prefetch" href="//foobar.com"></head><body>foo</body></html>';
return expect(
express()
.use(expressExtractHeaders())
.use(function (req, res) {
res.send(responseHtml);
}),
'to yield exchange', {
request: '/',
response: {
headers: {
'Content-Type': 'text/html; charset=utf-8',
Link: [
'<//example.com/logo-hires.jpg>; rel=prefetch; as=image',
'<//example.com>; rel=preconnect',
'<//foobar.com>; rel=prefetch'
]
}
}
}
);
});

it('should ignore unsupported link elements based on the rel attribute', function () {
var responseHtml =
'<!DOCTYPE html>\n<html><head><link rel="foobar" href="//example.com/logo-hires.jpg" as="image"></head><body>foo</body></html>';
return expect(
express()
.use(expressExtractHeaders())
.use(function (req, res) {
res.send(responseHtml);
}),
'to yield exchange', {
request: '/',
response: {
headers: {
'Content-Type': 'text/html; charset=utf-8',
Link: undefined
}
}
}
);
});

it('should extract <LINK REL="preconnect" HREF=...>', function () {
var responseHtml =
'<!DOCTYPE html>\n<html><head><LINK REL="preconnect" HREF="//example.com"></head><body>foo</body></html>';
return expect(
express()
.use(expressExtractHeaders())
.use(function (req, res) {
res.send(responseHtml);
}),
'to yield exchange', {
request: '/',
response: {
headers: {
'Content-Type': 'text/html; charset=utf-8',
Link: '<//example.com>; rel=preconnect'
}
}
}
);
});

it('should extract the "as" attribute correctly', function () {
var responseHtml =
'<!DOCTYPE html>\n<html><head><link rel="prefetch" href="//example.com/logo-hires.jpg" as="image"></head><body>foo</body></html>';
return expect(
express()
.use(expressExtractHeaders())
.use(function (req, res) {
res.send(responseHtml);
}),
'to yield exchange', {
request: '/',
response: {
headers: {
'Content-Type': 'text/html; charset=utf-8',
Link: '<//example.com/logo-hires.jpg>; rel=prefetch; as=image'
}
}
}
);
});

it('should extract the "pr" attribute correctly', function () {
var responseHtml =
'<!DOCTYPE html>\n<html><head><link rel="prerender" href="//example.com/next-page.html" pr="0.75"></head><body>foo</body></html>';
return expect(
express()
.use(expressExtractHeaders())
.use(function (req, res) {
res.send(responseHtml);
}),
'to yield exchange', {
request: '/',
response: {
headers: {
'Content-Type': 'text/html; charset=utf-8',
Link: '<//example.com/next-page.html>; rel=prerender; pr=0.75'
}
}
}
);
});

it('should extract the "crossorigin" attribute when it has a value', function () {
var responseHtml =
'<!DOCTYPE html>\n<html><head><link rel="prefetch" href="//example.com/next-page.html" as="html" crossorigin="use-credentials"></head><body>foo</body></html>';
return expect(
express()
.use(expressExtractHeaders())
.use(function (req, res) {
res.send(responseHtml);
}),
'to yield exchange', {
request: '/',
response: {
headers: {
'Content-Type': 'text/html; charset=utf-8',
Link: '<//example.com/next-page.html>; rel=prefetch; as=html; crossorigin=use-credentials'
}
}
}
);
});

it('should extract the "crossorigin" attribute when it has no value', function () {
var responseHtml =
'<!DOCTYPE html>\n<html><head><link rel="prefetch" href="//example.com/next-page.html" as="html" crossorigin></head><body>foo</body></html>';
return expect(
express()
.use(expressExtractHeaders())
.use(function (req, res) {
res.send(responseHtml);
}),
'to yield exchange', {
request: '/',
response: {
headers: {
'Content-Type': 'text/html; charset=utf-8',
Link: '<//example.com/next-page.html>; rel=prefetch; as=html; crossorigin'
}
}
}
);
});

it('should specify response headers based on <META> tags in the response body', function () {
var responseHtml =
'<!DOCTYPE html>\n<html><head><META HTTP-EQUIV="Foo" CONTENT="Bar"></head><body>foo</body></html>';
Expand Down

0 comments on commit ed99a42

Please sign in to comment.