Cached api controller pipelines (#19880)

ref ENG-761
ref https://linear.app/tryghost/issue/ENG-761

Creating these pipelines is expensive, and we don't want to do it
repeatedly for the same controller. Adding caching should reduce the
amount of time spent setting up pipelines for each usage of the `get`
helper.
This commit is contained in:
Fabien 'egg' O'Carroll 2024-03-19 00:29:41 +07:00 committed by GitHub
parent a67342b06a
commit 3f27ca5c00
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 20 additions and 12 deletions

View File

@ -160,6 +160,8 @@ const STAGES = {
}
};
const controllerMap = new Map();
/**
* @description The pipeline runs the request through all stages (validation, serialisation, permissions).
*
@ -180,12 +182,16 @@ const STAGES = {
* @return {Object}
*/
const pipeline = (apiController, apiUtils, apiType) => {
if (controllerMap.has(apiController)) {
return controllerMap.get(apiController);
}
const keys = Object.keys(apiController);
const docName = apiController.docName;
// CASE: api controllers are objects with configuration.
// We have to ensure that we expose a functional interface e.g. `api.posts.add` has to be available.
return keys.reduce((obj, method) => {
const result = keys.reduce((obj, method) => {
const apiImpl = _.cloneDeep(apiController)[method];
Object.freeze(apiImpl.headers);
@ -267,6 +273,10 @@ const pipeline = (apiController, apiUtils, apiType) => {
Object.assign(obj[method], apiImpl);
return obj;
}, {});
controllerMap.set(apiController, result);
return result;
};
module.exports = pipeline;

View File

@ -33,8 +33,8 @@ describe('Frontend behavior tests', function () {
beforeEach(function () {
sinon.stub(themeEngine.getActive(), 'config').withArgs('posts_per_page').returns(2);
const postsAPI = require('../../../core/server/api/endpoints/posts-public');
postSpy = sinon.spy(postsAPI.browse, 'query');
const postsAPI = require('../../../core/server/api/endpoints').postsPublic;
postSpy = sinon.spy(postsAPI, 'browse');
});
afterEach(function () {
@ -143,22 +143,20 @@ describe('Frontend behavior tests', function () {
});
});
it('serve tag', function () {
it('serve tag', async function () {
const req = {
method: 'GET',
url: '/tag/bacon/',
host: 'example.com'
};
return localUtils.mockExpress.invoke(app, req)
.then(function (response) {
response.statusCode.should.eql(200);
response.template.should.eql('tag');
const response = await localUtils.mockExpress.invoke(app, req);
response.statusCode.should.eql(200);
response.template.should.eql('tag');
postSpy.args[0][0].options.filter.should.eql('(tags:\'bacon\'+tags.visibility:public)+type:post');
postSpy.args[0][0].options.page.should.eql(1);
postSpy.args[0][0].options.limit.should.eql(2);
});
postSpy.args[0][0].filter.should.eql('tags:\'bacon\'+tags.visibility:public');
postSpy.args[0][0].page.should.eql(1);
postSpy.args[0][0].limit.should.eql(2);
});
it('serve tag rss', function () {