Skip to content

Commit d5bfc7a

Browse files
committed
Use 200-style conneg on timegate.
Browsers don't support time-based content negotiation with redirection. Since time-based content negotiation happens using an additional HTTP request header (Accept-Datetime), a CORS preflight request is needed. Unfortunately, browsers disallow redirects for such cross-origin requests that require a preflight request. Therefore, we cannot use 302-style content negotiation in the browser.
1 parent 20b46c9 commit d5bfc7a

File tree

3 files changed

+35
-28
lines changed

3 files changed

+35
-28
lines changed

config/config-defaults.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@
2626

2727
"controllers": [
2828
"SummaryController",
29+
"TimegateController",
2930
"TriplePatternFragmentsController",
3031
"AssetsController",
3132
"DereferenceController",
32-
"TimegateController",
3333
"NotFoundController"
3434
],
3535

lib/controllers/Controller.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ Controller.prototype._handleNotAcceptable = function (request, response, next) {
6969
// Finds an appropriate view using content negotiation
7070
Controller.prototype._negotiateView = function (viewName, request, response) {
7171
// Indicate that the response is content-negotiated
72-
response.setHeader('Vary', 'Accept');
72+
var vary = response.getHeader('Vary');
73+
response.setHeader('Vary', 'Accept' + (vary ? ', ' + vary : ''));
7374
// Negotiate a view
7475
var viewMatch = this._views.matchView(viewName, request);
7576
response.setHeader('Content-Type', viewMatch.responseType || viewMatch.type);

lib/controllers/TimegateController.js

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -41,34 +41,40 @@ function TimegateController(options) {
4141
}
4242
Controller.extend(TimegateController);
4343

44-
// Tries to serve the requested Timegate
44+
// Perform time negotiation if applicable
4545
TimegateController.prototype._handleRequest = function (request, response, next) {
46-
var self = this, timegateMatch = this._matcher && this._matcher.exec(request.url),
47-
datasource = timegateMatch && timegateMatch[1];
48-
if (datasource && this._timemaps[datasource]) {
49-
// retrieve Accept-Datetime & construct memento link
50-
var acceptDatetime = toDate(request.headers['accept-datetime']), // If no datetime is present, return most recent one
51-
memento = this._getClosestMemento(this._timemaps[datasource].timemap, acceptDatetime);
52-
53-
// memento invalid, go to next
54-
if (!memento) next();
55-
56-
// Construct memento and Original url
57-
var mementoUrl = url.format(_.assign(request.parsedUrl, { pathname: memento.dataSource})),
58-
parsedOriginalUrl = this._timemaps[datasource].originalBaseURL ? // If originalBaseURL is present, the original is external
59-
_.assign(url.parse(this._timemaps[datasource].originalBaseURL), { query: request.parsedUrl.query }) :
60-
_.defaults({ pathname: datasource }, request.parsedUrl),
61-
originalUrl = url.format(parsedOriginalUrl);
62-
63-
response.writeHead(303, {
64-
'Location': mementoUrl,
65-
'Vary': 'Accept, Accept-Datetime',
66-
'Link': '<' + originalUrl + '>;rel="original", <' + mementoUrl + '>;rel="memento";datetime="' + memento.interval[0].toUTCString() + '"'
67-
});
68-
response.end();
46+
var self = this, timegateMatch = this._matcher.exec(request.url),
47+
datasource = timegateMatch && timegateMatch[1],
48+
timemapDetails = datasource && this._timemaps[datasource];
49+
if (timemapDetails) {
50+
// Try to find the memento closest to the requested date
51+
var acceptDatetime = toDate(request.headers['accept-datetime']),
52+
memento = this._getClosestMemento(timemapDetails.timemap, acceptDatetime);
53+
if (memento) {
54+
// Determine the URL of the memento
55+
var mementoUrl = _.assign(request.parsedUrl, { pathname: memento.dataSource });
56+
mementoUrl = url.format(mementoUrl);
57+
58+
// Determine the URL of the original resource
59+
var originalBaseURL = timemapDetails.originalBaseURL, originalUrl;
60+
if (!originalBaseURL)
61+
originalUrl = _.defaults({ pathname: datasource }, request.parsedUrl);
62+
else
63+
originalUrl = _.assign(url.parse(originalBaseURL), { query: request.parsedUrl.query });
64+
originalUrl = url.format(originalUrl);
65+
66+
// Perform 200-style negotiation (https://tools.ietf.org/html/rfc7089#section-4.1.2)
67+
response.setHeader('Link', '<' + originalUrl + '>;rel="original",' +
68+
'<' + mementoUrl + '>;rel="memento";' +
69+
'datetime="' + memento.interval[0].toUTCString() + '"');
70+
response.setHeader('Vary', 'Accept-Datetime');
71+
response.setHeader('Content-Location', mementoUrl);
72+
// Set request URL to the memento URL, which should be handled by a next controller
73+
request.url = mementoUrl.replace(/^[^:]+:\/\/[^\/]+/, '');
74+
delete request.parsedUrl;
75+
}
6976
}
70-
else
71-
next();
77+
next();
7278
};
7379

7480
/*

0 commit comments

Comments
 (0)