Replace the ETag header by the Last-Modified to improve performance

Reviewed By: mjesun

Differential Revision: D5823545

fbshipit-source-id: 57eac5548e626eeed05f9b454e3f54b114193eb0
This commit is contained in:
Rafael Oleza 2017-09-13 13:00:31 -07:00 committed by Facebook Github Bot
parent b64a07e38b
commit 0a1e79a820
6 changed files with 86 additions and 13 deletions

View File

@ -29,6 +29,7 @@ class DeltaPatcher {
}; };
_initialized = false; _initialized = false;
_lastNumModifiedFiles = 0; _lastNumModifiedFiles = 0;
_lastModifiedDate = new Date();
/** /**
* Applies a Delta Bundle to the current bundle. * Applies a Delta Bundle to the current bundle.
@ -55,6 +56,10 @@ class DeltaPatcher {
this._lastNumModifiedFiles = this._lastNumModifiedFiles =
deltaBundle.pre.size + deltaBundle.post.size + deltaBundle.delta.size; deltaBundle.pre.size + deltaBundle.post.size + deltaBundle.delta.size;
if (this._lastNumModifiedFiles > 0) {
this._lastModifiedDate = new Date();
}
this._patchMap(this._lastBundle.pre, deltaBundle.pre); this._patchMap(this._lastBundle.pre, deltaBundle.pre);
this._patchMap(this._lastBundle.post, deltaBundle.post); this._patchMap(this._lastBundle.post, deltaBundle.post);
this._patchMap(this._lastBundle.modules, deltaBundle.delta); this._patchMap(this._lastBundle.modules, deltaBundle.delta);
@ -72,6 +77,10 @@ class DeltaPatcher {
return this._lastNumModifiedFiles; return this._lastNumModifiedFiles;
} }
getLastModifiedDate(): Date {
return this._lastModifiedDate;
}
/** /**
* Converts the current delta bundle to a standard string bundle, ready to * Converts the current delta bundle to a standard string bundle, ready to
* be interpreted by any JS VM. * be interpreted by any JS VM.

View File

@ -21,6 +21,7 @@ const DeltaTransformer = require('../DeltaTransformer');
const DeltaBundler = require('../'); const DeltaBundler = require('../');
describe('DeltaBundler', () => { describe('DeltaBundler', () => {
const OriginalDate = global.Date;
let deltaBundler; let deltaBundler;
let bundler; let bundler;
const initialTransformerResponse = { const initialTransformerResponse = {
@ -31,6 +32,10 @@ describe('DeltaBundler', () => {
reset: true, reset: true,
}; };
function setCurrentTime(time: number) {
global.Date = jest.fn(() => new OriginalDate(time));
}
beforeEach(() => { beforeEach(() => {
DeltaTransformer.prototype.getDelta = jest DeltaTransformer.prototype.getDelta = jest
.fn() .fn()
@ -42,6 +47,8 @@ describe('DeltaBundler', () => {
bundler = new Bundler(); bundler = new Bundler();
deltaBundler = new DeltaBundler(bundler, {}); deltaBundler = new DeltaBundler(bundler, {});
setCurrentTime(1482363367000);
}); });
it('should create a new transformer to build the initial bundle', async () => { it('should create a new transformer to build the initial bundle', async () => {

View File

@ -14,11 +14,20 @@
const DeltaPatcher = require('../DeltaPatcher'); const DeltaPatcher = require('../DeltaPatcher');
const INITIAL_TIME = 1482363367000;
describe('DeltaPatcher', () => { describe('DeltaPatcher', () => {
const OriginalDate = global.Date;
let deltaPatcher; let deltaPatcher;
function setCurrentTime(time: number) {
global.Date = jest.fn(() => new OriginalDate(time));
}
beforeEach(() => { beforeEach(() => {
deltaPatcher = new DeltaPatcher(); deltaPatcher = new DeltaPatcher();
setCurrentTime(INITIAL_TIME);
}); });
it('should throw if received a non-reset delta as the initial one', () => { it('should throw if received a non-reset delta as the initial one', () => {
@ -117,4 +126,44 @@ describe('DeltaPatcher', () => {
// A deleted module counts as a modified file. // A deleted module counts as a modified file.
expect(deltaPatcher.getLastNumModifiedFiles()).toEqual(2); expect(deltaPatcher.getLastNumModifiedFiles()).toEqual(2);
}); });
it('should return the time it was last modified', () => {
deltaPatcher
.applyDelta({
reset: 1,
pre: new Map([[1, {code: 'pre'}]]),
post: new Map([[2, {code: 'post'}]]),
delta: new Map([[3, {code: 'middle'}]]),
})
.stringifyCode();
expect(deltaPatcher.getLastModifiedDate().getTime()).toEqual(INITIAL_TIME);
setCurrentTime(INITIAL_TIME + 1000);
// Apply empty delta
deltaPatcher
.applyDelta({
reset: 1,
pre: new Map(),
post: new Map(),
delta: new Map(),
})
.stringifyCode();
expect(deltaPatcher.getLastModifiedDate().getTime()).toEqual(INITIAL_TIME);
setCurrentTime(INITIAL_TIME + 2000);
deltaPatcher
.applyDelta({
reset: 1,
pre: new Map(),
post: new Map([[2, {code: 'newpost'}]]),
delta: new Map(),
})
.stringifyCode();
expect(deltaPatcher.getLastModifiedDate().getTime()).toEqual(
INITIAL_TIME + 2000,
);
});
}); });

View File

@ -6,6 +6,7 @@ Object {
;module3 ;module3
;another ;another
;post", ;post",
"lastModified": 2016-12-21T23:36:07.000Z,
"numModifiedFiles": 4, "numModifiedFiles": 4,
} }
`; `;
@ -18,6 +19,7 @@ Object {
;post ;post
;bananas ;bananas
;apples", ;apples",
"lastModified": 2016-12-21T23:36:07.000Z,
"numModifiedFiles": 5, "numModifiedFiles": 5,
} }
`; `;

View File

@ -113,7 +113,7 @@ class DeltaBundler {
async buildFullBundle( async buildFullBundle(
options: FullBuildOptions, options: FullBuildOptions,
): Promise<{bundle: string, numModifiedFiles: number}> { ): Promise<{bundle: string, numModifiedFiles: number, lastModified: Date}> {
const deltaPatcher = await this._getDeltaPatcher(options); const deltaPatcher = await this._getDeltaPatcher(options);
let bundle = deltaPatcher.stringifyCode(); let bundle = deltaPatcher.stringifyCode();
@ -123,6 +123,7 @@ class DeltaBundler {
return { return {
bundle, bundle,
lastModified: deltaPatcher.getLastModifiedDate(),
numModifiedFiles: deltaPatcher.getLastNumModifiedFiles(), numModifiedFiles: deltaPatcher.getLastNumModifiedFiles(),
}; };
} }

View File

@ -923,14 +923,13 @@ class Server {
}), }),
); );
let bundle; let result;
let numModifiedFiles;
try { try {
({bundle, numModifiedFiles} = await this._deltaBundler.buildFullBundle({ result = await this._deltaBundler.buildFullBundle({
...options, ...options,
deltaBundleId: this.optionsHash(options), deltaBundleId: this.optionsHash(options),
})); });
} catch (error) { } catch (error) {
this._handleError(res, this.optionsHash(options), error); this._handleError(res, this.optionsHash(options), error);
@ -942,18 +941,24 @@ class Server {
return; return;
} }
const etag = crypto.createHash('md5').update(bundle).digest('hex'); if (
// We avoid parsing the dates since the client should never send a more
if (req.headers['if-none-match'] === etag) { // recent date than the one returned by the Delta Bundler (if that's the
// case it's fine to return the whole bundle).
req.headers['if-modified-since'] === result.lastModified.toUTCString()
) {
debug('Responding with 304'); debug('Responding with 304');
res.writeHead(304); res.writeHead(304);
res.end(); res.end();
} else { } else {
res.setHeader(FILES_CHANGED_COUNT_HEADER, String(numModifiedFiles)); res.setHeader(
FILES_CHANGED_COUNT_HEADER,
String(result.numModifiedFiles),
);
res.setHeader('Content-Type', 'application/javascript'); res.setHeader('Content-Type', 'application/javascript');
res.setHeader('ETag', etag); res.setHeader('Last-Modified', result.lastModified.toUTCString());
res.setHeader('Content-Length', String(Buffer.byteLength(bundle))); res.setHeader('Content-Length', String(Buffer.byteLength(result.bundle)));
res.end(bundle); res.end(result.bundle);
} }
this._reporter.update({ this._reporter.update({
@ -964,7 +969,7 @@ class Server {
debug('Finished response'); debug('Finished response');
log({ log({
...createActionEndEntry(requestingBundleLogEntry), ...createActionEndEntry(requestingBundleLogEntry),
outdated_modules: numModifiedFiles, outdated_modules: result.numModifiedFiles,
bundler: 'delta', bundler: 'delta',
}); });
} }