Repository: compiler-explorer/compiler-explorer
Pull request #7154 (2024-11-29 16:01:43)

Add re-usable middleware functions to http server
Just add the headers you want to the handler list :)

It's basically the addStaticHeaders function again, but this time more composable. End goal is to replace that function and all its uses with this as I go with the api improvements

This technically also fixes a cors regression from earlier patches


Patch:
diff --git a/lib/handlers/api/assembly-documentation-controller.ts b/lib/handlers/api/assembly-documentation-controller.ts
index 9893105f4..85317b6a6 100644
--- a/lib/handlers/api/assembly-documentation-controller.ts
+++ b/lib/handlers/api/assembly-documentation-controller.ts
@@ -24,15 +24,15 @@
 
 import express from 'express';
 
-import {addStaticHeaders} from '../../../app.js';
 import {getDocumentationProviderTypeByKey} from '../../asm-docs/index.js';
+import {cached, cors} from '../middleware.js';
 
 import {HttpController} from './controller.interfaces.js';
 
 export class AssemblyDocumentationController implements HttpController {
     createRouter(): express.Router {
         const router = express.Router();
-        router.get('/api/asm/:arch/:opcode', this.getOpcodeDocumentation.bind(this));
+        router.get('/api/asm/:arch/:opcode', cors, cached, this.getOpcodeDocumentation.bind(this));
         return router;
     }
 
@@ -54,12 +54,10 @@ export class AssemblyDocumentationController implements HttpController {
             const contentType = req.accepts(['text', 'json']);
             switch (contentType) {
                 case 'text': {
-                    addStaticHeaders(res);
                     res.send(information.html);
                     break;
                 }
                 case 'json': {
-                    addStaticHeaders(res);
                     res.send(information);
                     break;
                 }
diff --git a/lib/handlers/api/site-template-controller.ts b/lib/handlers/api/site-template-controller.ts
index cc9ccca7b..03333bd86 100644
--- a/lib/handlers/api/site-template-controller.ts
+++ b/lib/handlers/api/site-template-controller.ts
@@ -25,13 +25,14 @@
 import express from 'express';
 
 import {getSiteTemplates} from '../../site-templates.js';
+import {cached, cors} from '../middleware.js';
 
 import {HttpController} from './controller.interfaces.js';
 
 export class SiteTemplateController implements HttpController {
     createRouter(): express.Router {
         const router = express.Router();
-        router.get('/api/siteTemplates', this.getSiteTemplates.bind(this));
+        router.get('/api/siteTemplates', cors, cached, this.getSiteTemplates.bind(this));
         return router;
     }
 
diff --git a/lib/handlers/api/source-controller.ts b/lib/handlers/api/source-controller.ts
index b7a48f609..add1a2111 100644
--- a/lib/handlers/api/source-controller.ts
+++ b/lib/handlers/api/source-controller.ts
@@ -25,8 +25,8 @@
 import express from 'express';
 import _ from 'underscore';
 
-import {addStaticHeaders} from '../../../app.js';
 import {Source} from '../../../types/source.interfaces.js';
+import {cached, cors} from '../middleware.js';
 
 import {HttpController} from './controller.interfaces.js';
 
@@ -35,8 +35,8 @@ export class SourceController implements HttpController {
 
     createRouter(): express.Router {
         const router = express.Router();
-        router.get('/source/:source/list', this.listEntries.bind(this));
-        router.get('/source/:source/load/:language/:filename', this.loadEntry.bind(this));
+        router.get('/source/:source/list', cors, cached, this.listEntries.bind(this));
+        router.get('/source/:source/load/:language/:filename', cors, cached, this.loadEntry.bind(this));
         return router;
     }
 
@@ -50,7 +50,6 @@ export class SourceController implements HttpController {
             return;
         }
         const entries = await source.list();
-        addStaticHeaders(res);
         res.json(entries);
     }
 
@@ -64,7 +63,6 @@ export class SourceController implements HttpController {
             return;
         }
         const entry = await source.load(req.params.language, req.params.filename);
-        addStaticHeaders(res);
         res.json(entry);
     }
 
diff --git a/lib/handlers/middleware.ts b/lib/handlers/middleware.ts
new file mode 100644
index 000000000..35dbf1cd7
--- /dev/null
+++ b/lib/handlers/middleware.ts
@@ -0,0 +1,43 @@
+// Copyright (c) 2024, Compiler Explorer Authors
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+//     * Redistributions of source code must retain the above copyright notice,
+//       this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above copyright
+//       notice, this list of conditions and the following disclaimer in the
+//       documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+
+import express from 'express';
+
+import * as props from '../properties.js';
+
+const ceProps = props.propsFor('compiler-explorer');
+const staticMaxAge = ceProps('staticMaxAgeSecs', 31536000);
+
+/** Add static headers to the response */
+export const cached: express.Handler = (_, res, next) => {
+    res.set('Cache-Control', `public, max-age=${staticMaxAge}, must-revalidate`);
+    return next();
+};
+
+/** Allow any client to make cross-origin requests */
+export const cors: express.Handler = (_, res, next) => {
+    res.set('Access-Control-Allow-Origin', '*');
+    res.set('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
+    return next();
+};


Test patch:
diff --git a/test/handlers/middleware-tests.ts b/test/handlers/middleware-tests.ts
new file mode 100644
index 000000000..65b1bc9ab
--- /dev/null
+++ b/test/handlers/middleware-tests.ts
@@ -0,0 +1,50 @@
+// Copyright (c) 2024, Compiler Explorer Authors
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+//     * Redistributions of source code must retain the above copyright notice,
+//       this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above copyright
+//       notice, this list of conditions and the following disclaimer in the
+//       documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+
+import express from 'express';
+import request from 'supertest';
+import {describe, it} from 'vitest';
+
+import {cached, cors} from '../../lib/handlers/middleware.js';
+
+describe('middleware functions', () => {
+    it('adds cache controls headers with cached()', async () => {
+        const app = express();
+        app.use('/', cached, (_, res) => res.send('Hello World!'));
+        await request(app)
+            .get('/')
+            .expect(200, 'Hello World!')
+            .expect('cache-control', /public, max-age=\d+, must-revalidate/);
+    });
+
+    it('adds cors headers with cors()', async () => {
+        const app = express();
+        app.use('/', cors, (_, res) => res.send('Hello World!'));
+        await request(app)
+            .get('/')
+            .expect(200, 'Hello World!')
+            .expect('access-control-allow-origin', '*')
+            .expect('access-control-allow-headers', 'Origin, X-Requested-With, Content-Type, Accept');
+    });
+});