From 16905598879cb9a706c7cdfbedc9270baf9b0ab8 Mon Sep 17 00:00:00 2001 From: Richard Petersen <richard.petersen@open-xchange.com> Date: Mon, 12 Sep 2022 17:03:19 +0200 Subject: [PATCH] Fix: UI-Middleware responds with a 404 when the trailing slash is not found Root cause: Express automatically redirected, fastify does not Solution: Manually do the redirect --- spec/app_root_test.js | 87 ++++++++++++++++++++++++++++++++++++++ src/create-app.js | 7 ++- src/plugins/serve-files.js | 3 +- 3 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 spec/app_root_test.js diff --git a/spec/app_root_test.js b/spec/app_root_test.js new file mode 100644 index 0000000..f80fa82 --- /dev/null +++ b/spec/app_root_test.js @@ -0,0 +1,87 @@ +import request from 'supertest' +import { generateSimpleViteManifest, mockApp, mockConfig, mockFetch, mockRedis } from './util.js' +import { expect } from 'chai' +import * as td from 'testdouble' +import RedisMock from 'ioredis-mock' + +describe('With different app root', function () { + let app + + beforeEach(async function () { + let count = 0 + mockConfig({ urls: ['http://ui-server/'] }) + mockRedis() + mockFetch({ + 'http://ui-server': { + '/manifest.json': generateSimpleViteManifest({ + 'example.js': { imports: ['test.txt'] }, + 'test.txt': { }, + 'main.css': {}, + 'index.html': { + file: 'index.html.js', + isEntry: true, + imports: ['example.js'], + css: ['main.css'] + }, + 'image.png': {} + }), + '/example.js': () => new Response('this is example', { headers: { 'content-type': 'application/javascript' } }), + '/test.txt': () => new Response('this is test', { headers: { 'content-type': 'text/plain' } }), + '/index.html.js': () => new Response('this is index.html.js', { headers: { 'content-type': 'application/javascript' } }), + '/index.html': () => new Response('<html><head></head><body>it\'s me</body></html>', { headers: { 'content-type': 'text/html' } }), + '/main.css': () => new Response('.foo { color: #000; }', { headers: { 'content-type': 'text/css' } }), + '/favicon.ico': 'not really a favicon, though', + '/test.svg': () => { + if (count > 0) { + return new Response(null, { status: 404 }) + } + count++ + return new Response('<svg></svg>', { headers: { 'content-type': 'image/svg' } }) + } + } + }) + process.env.APP_ROOT = '/appsuite/' + app = await mockApp() + }) + + afterEach(async function () { + process.env.APP_ROOT = '/' + await new RedisMock().flushdb() + td.reset() + }) + + it('serves files defined in manifest.json file', async function () { + const response = await request(app.server).get('/appsuite/example.js') + expect(response.statusCode).to.equal(200) + expect(response.headers['content-type']).to.equal('application/javascript') + expect(response.text).to.equal('this is example') + const response2 = await request(app.server).get('/appsuite/test.txt') + expect(response2.statusCode).to.equal(200) + expect(response2.headers['content-type']).to.equal('text/plain') + expect(response2.text).to.equal('this is test') + }) + + it('serves / as index.html', async function () { + const response = await request(app.server).get('/appsuite/') + expect(response.statusCode).to.equal(200) + expect(response.headers['content-type']).to.equal('text/html') + expect(response.text).to.equal('<html><head></head><body>it\'s me</body></html>') + }) + + it('serves approot without slash as index.html', async function () { + const response = await request(app.server).get('/appsuite') + expect(response.statusCode).to.equal(302) + expect(response.headers.location).to.equal('/appsuite/') + }) + + it('directly fetches files not referenced in manifest.json files from the upstream servers', async function () { + const response = await request(app.server).get('/appsuite/favicon.ico') + expect(response.statusCode).to.equal(200) + expect(response.text).to.equal('not really a favicon, though') + }) + + it('returns 404 if a file misses the app-root', async function () { + const response = await request(app.server).get('/example.js') + expect(response.statusCode).to.equal(404) + }) +}) diff --git a/src/create-app.js b/src/create-app.js index 1b685de..e5c231b 100644 --- a/src/create-app.js +++ b/src/create-app.js @@ -68,8 +68,13 @@ export async function createApp (basePath) { await app.register(manifestsPlugin, { prefix: process.env.APP_ROOT }) await app.register(metadataPlugin, { prefix: process.env.APP_ROOT }) await app.register(redirectsPlugin, { prefix: process.env.APP_ROOT }) + if (process.env.APP_ROOT.length > 1) { + app.get(process.env.APP_ROOT.slice(0, -1), async (req, res) => { + res.redirect(process.env.APP_ROOT) + }) + } - await app.register(serveFilePlugin, { prefix: process.env.APP_ROOT }) + await app.register(serveFilePlugin) return app } diff --git a/src/plugins/serve-files.js b/src/plugins/serve-files.js index 4e64051..11bfaac 100644 --- a/src/plugins/serve-files.js +++ b/src/plugins/serve-files.js @@ -6,7 +6,8 @@ export default async function serveFilePlugin (fastify, options) { fastify.get('*', async (req, reply) => { try { const version = reply.version - const url = req.urlData('path').substr((options.prefix || '/').length - 1) + const url = req.urlData('path').substr(process.env.APP_ROOT.length - 1) + const path = url === '/' ? '/index.html' : url const { body, headers } = await getFile({ version, path }) -- GitLab