From ea83ea59a11ff127c21f33d111bd0ceb75460a25 Mon Sep 17 00:00:00 2001 From: Joachim Van Herwegen Date: Mon, 17 Jul 2023 09:38:13 +0200 Subject: [PATCH] feat: Update StaticAssetHandler to allow for easier overrides --- .../app/init/initialize-prefilled-root.json | 7 +- config/app/init/initialize-root.json | 5 ++ .../app/init/initializers/prefilled-root.json | 26 ------- config/app/init/initializers/root.json | 3 +- config/app/init/static-root.json | 6 +- config/dynamic.json | 2 +- config/example-https-file.json | 2 +- config/file-acp.json | 2 +- config/file.json | 2 +- config/http/static/default.json | 31 +++++--- config/https-file-cli.json | 2 +- config/quota-file.json | 2 +- config/restrict-idp.json | 2 +- config/sparql-endpoint.json | 2 +- config/sparql-file-storage.json | 2 +- src/server/middleware/StaticAssetHandler.ts | 22 ++++-- templates/root/static/index.html | 73 +++++++++++++++++++ .../middleware/StaticAssetHandler.test.ts | 38 +++++----- 18 files changed, 155 insertions(+), 74 deletions(-) delete mode 100644 config/app/init/initializers/prefilled-root.json create mode 100644 templates/root/static/index.html diff --git a/config/app/init/initialize-prefilled-root.json b/config/app/init/initialize-prefilled-root.json index 5708f7c88..8c7040e8c 100644 --- a/config/app/init/initialize-prefilled-root.json +++ b/config/app/init/initialize-prefilled-root.json @@ -2,7 +2,7 @@ "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^6.0.0/components/context.jsonld", "import": [ "css:config/app/init/default.json", - "css:config/app/init/initializers/prefilled-root.json" + "css:config/app/init/initializers/root.json" ], "@graph": [ { @@ -12,6 +12,11 @@ "handlers": [ { "@id": "urn:solid-server:default:RootInitializer" } ] + }, + { + "@id": "urn:solid-server:default:RootFolderGenerator", + "@type": "StaticFolderGenerator", + "templateFolder": "@css:templates/root/prefilled" } ] } diff --git a/config/app/init/initialize-root.json b/config/app/init/initialize-root.json index 6fe5144aa..69cf7145c 100644 --- a/config/app/init/initialize-root.json +++ b/config/app/init/initialize-root.json @@ -12,6 +12,11 @@ "handlers": [ { "@id": "urn:solid-server:default:RootInitializer" } ] + }, + { + "@id": "urn:solid-server:default:RootFolderGenerator", + "@type": "StaticFolderGenerator", + "templateFolder": "@css:templates/root/empty" } ] } diff --git a/config/app/init/initializers/prefilled-root.json b/config/app/init/initializers/prefilled-root.json deleted file mode 100644 index cb42e499f..000000000 --- a/config/app/init/initializers/prefilled-root.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^6.0.0/components/context.jsonld", - "@graph": [ - { - "comment": "Makes sure the root container exists and contains the necessary resources.", - "@id": "urn:solid-server:default:RootInitializer", - "@type": "ConditionalHandler", - "storageKey": "rootInitialized", - "storageValue": true, - "storage": { "@id": "urn:solid-server:default:SetupStorage" }, - "source": { - "@type": "ContainerInitializer", - "args_baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" }, - "args_path": "/", - "args_store": { "@id": "urn:solid-server:default:ResourceStore" }, - "args_generator": { - "@type": "StaticFolderGenerator", - "templateFolder": "@css:templates/root/prefilled", - "resourcesGenerator": { "@id": "urn:solid-server:default:TemplatedResourcesGenerator" } - }, - "args_storageKey": "rootInitialized", - "args_storage": { "@id": "urn:solid-server:default:SetupStorage" } - } - } - ] -} diff --git a/config/app/init/initializers/root.json b/config/app/init/initializers/root.json index 6b086db0a..41ba4b300 100644 --- a/config/app/init/initializers/root.json +++ b/config/app/init/initializers/root.json @@ -9,13 +9,14 @@ "storageValue": true, "storage": { "@id": "urn:solid-server:default:SetupStorage" }, "source": { + "@id": "urn:solid-server:default:RootContainerInitializer", "@type": "ContainerInitializer", "args_baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" }, "args_path": "/", "args_store": { "@id": "urn:solid-server:default:ResourceStore" }, "args_generator": { + "@id": "urn:solid-server:default:RootFolderGenerator", "@type": "StaticFolderGenerator", - "templateFolder": "@css:templates/root/empty", "resourcesGenerator": { "@id": "urn:solid-server:default:TemplatedResourcesGenerator" } }, "args_storageKey": "rootInitialized", diff --git a/config/app/init/static-root.json b/config/app/init/static-root.json index f519656ce..ea21510f0 100644 --- a/config/app/init/static-root.json +++ b/config/app/init/static-root.json @@ -10,8 +10,10 @@ "@type": "StaticAssetHandler", "assets": [ { - "StaticAssetHandler:_assets_key": "/", - "StaticAssetHandler:_assets_value": "@css:templates/root/prefilled/base/index.html" + "@id": "urn:solid-server:default:RootStaticAsset", + "@type": "StaticAssetEntry", + "relativeUrl": "/", + "filePath": "@css:templates/root/static/index.html" } ] } diff --git a/config/dynamic.json b/config/dynamic.json index 85d0ad200..325995248 100644 --- a/config/dynamic.json +++ b/config/dynamic.json @@ -2,7 +2,7 @@ "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^6.0.0/components/context.jsonld", "import": [ "css:config/app/main/default.json", - "css:config/app/init/default.json", + "css:config/app/init/static-root.json", "css:config/app/setup/required.json", "css:config/app/variables/default.json", "css:config/http/handler/default.json", diff --git a/config/example-https-file.json b/config/example-https-file.json index 058344e5a..d902ac5ee 100644 --- a/config/example-https-file.json +++ b/config/example-https-file.json @@ -2,7 +2,7 @@ "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^6.0.0/components/context.jsonld", "import": [ "css:config/app/main/default.json", - "css:config/app/init/default.json", + "css:config/app/init/static-root.json", "css:config/app/setup/required.json", "css:config/app/variables/default.json", "css:config/http/handler/default.json", diff --git a/config/file-acp.json b/config/file-acp.json index faa66caa7..070957448 100644 --- a/config/file-acp.json +++ b/config/file-acp.json @@ -2,7 +2,7 @@ "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^6.0.0/components/context.jsonld", "import": [ "css:config/app/main/default.json", - "css:config/app/init/default.json", + "css:config/app/init/static-root.json", "css:config/app/setup/required.json", "css:config/app/variables/default.json", "css:config/http/handler/default.json", diff --git a/config/file.json b/config/file.json index e5c952447..1322a772b 100644 --- a/config/file.json +++ b/config/file.json @@ -2,7 +2,7 @@ "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^6.0.0/components/context.jsonld", "import": [ "css:config/app/main/default.json", - "css:config/app/init/default.json", + "css:config/app/init/static-root.json", "css:config/app/setup/required.json", "css:config/app/variables/default.json", "css:config/http/handler/default.json", diff --git a/config/http/static/default.json b/config/http/static/default.json index 6a4fe4ff1..200aeee8f 100644 --- a/config/http/static/default.json +++ b/config/http/static/default.json @@ -9,26 +9,35 @@ "baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" }, "assets": [ { - "StaticAssetHandler:_assets_key": "/favicon.ico", - "StaticAssetHandler:_assets_value": "@css:templates/images/favicon.ico" + "@id": "urn:solid-server:default:FaviconStaticAsset", + "@type": "StaticAssetEntry", + "relativeUrl": "/favicon.ico", + "filePath": "@css:templates/images/favicon.ico" }, { - "StaticAssetHandler:_assets_key": "/.well-known/css/styles/", - "StaticAssetHandler:_assets_value": "@css:templates/styles/" + "@id": "urn:solid-server:default:StylesStaticAsset", + "@type": "StaticAssetEntry", + "relativeUrl": "/.well-known/css/styles/", + "filePath": "@css:templates/styles/" }, { - "StaticAssetHandler:_assets_key": "/.well-known/css/fonts/", - "StaticAssetHandler:_assets_value": "@css:templates/fonts/" + "@id": "urn:solid-server:default:FontsStaticAsset", + "@type": "StaticAssetEntry", + "relativeUrl": "/.well-known/css/fonts/", + "filePath": "@css:templates/fonts/" }, { - "StaticAssetHandler:_assets_key": "/.well-known/css/images/", - "StaticAssetHandler:_assets_value": "@css:templates/images/" + "@id": "urn:solid-server:default:ImagesStaticAsset", + "@type": "StaticAssetEntry", + "relativeUrl": "/.well-known/css/images/", + "filePath": "@css:templates/images/" }, { - "StaticAssetHandler:_assets_key": "/.well-known/css/scripts/", - "StaticAssetHandler:_assets_value": "@css:templates/scripts/" + "@id": "urn:solid-server:default:ScriptsStaticAsset", + "@type": "StaticAssetEntry", + "relativeUrl": "/.well-known/css/scripts/", + "filePath": "@css:templates/scripts/" } - ] } ] diff --git a/config/https-file-cli.json b/config/https-file-cli.json index 4e088ca6f..522759a0b 100644 --- a/config/https-file-cli.json +++ b/config/https-file-cli.json @@ -2,7 +2,7 @@ "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^6.0.0/components/context.jsonld", "import": [ "css:config/app/main/default.json", - "css:config/app/init/default.json", + "css:config/app/init/static-root.json", "css:config/app/setup/required.json", "css:config/app/variables/default.json", "css:config/http/handler/default.json", diff --git a/config/quota-file.json b/config/quota-file.json index b3884c98a..d282cfa5f 100644 --- a/config/quota-file.json +++ b/config/quota-file.json @@ -2,7 +2,7 @@ "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^6.0.0/components/context.jsonld", "import": [ "css:config/app/main/default.json", - "css:config/app/init/default.json", + "css:config/app/init/static-root.json", "css:config/app/setup/required.json", "css:config/app/variables/default.json", "css:config/http/handler/default.json", diff --git a/config/restrict-idp.json b/config/restrict-idp.json index 556bd5d61..577ea0140 100644 --- a/config/restrict-idp.json +++ b/config/restrict-idp.json @@ -2,7 +2,7 @@ "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^6.0.0/components/context.jsonld", "import": [ "css:config/app/main/default.json", - "css:config/app/init/default.json", + "css:config/app/init/static-root.json", "css:config/app/setup/disabled.json", "css:config/app/variables/default.json", "css:config/http/handler/default.json", diff --git a/config/sparql-endpoint.json b/config/sparql-endpoint.json index ee7908d8a..8825ab468 100644 --- a/config/sparql-endpoint.json +++ b/config/sparql-endpoint.json @@ -2,7 +2,7 @@ "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^6.0.0/components/context.jsonld", "import": [ "css:config/app/main/default.json", - "css:config/app/init/default.json", + "css:config/app/init/static-root.json", "css:config/app/setup/required.json", "css:config/app/variables/default.json", "css:config/http/handler/default.json", diff --git a/config/sparql-file-storage.json b/config/sparql-file-storage.json index 0fb391d17..640d86a84 100644 --- a/config/sparql-file-storage.json +++ b/config/sparql-file-storage.json @@ -2,7 +2,7 @@ "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^6.0.0/components/context.jsonld", "import": [ "css:config/app/main/default.json", - "css:config/app/init/default.json", + "css:config/app/init/static-root.json", "css:config/app/setup/required.json", "css:config/app/variables/default.json", "css:config/http/handler/default.json", diff --git a/src/server/middleware/StaticAssetHandler.ts b/src/server/middleware/StaticAssetHandler.ts index 5a1377f02..5e56babb5 100644 --- a/src/server/middleware/StaticAssetHandler.ts +++ b/src/server/middleware/StaticAssetHandler.ts @@ -12,9 +12,20 @@ import type { HttpHandlerInput } from '../HttpHandler'; import { HttpHandler } from '../HttpHandler'; import type { HttpRequest } from '../HttpRequest'; +/** + * Used to link file paths with relative URLs. + * By using a separate class instead of a key/value map it is easier to replace values in Components.js. + */ +export class StaticAssetEntry { + public constructor( + public readonly relativeUrl: string, + public readonly filePath: string, + ) { } +} + /** * Handler that serves static resources on specific paths. - * Relative file paths are assumed to be relative to cwd. + * Relative file paths are assumed to be relative to the current working directory. * Relative file paths can be preceded by `@css:`, e.g. `@css:foo/bar`, * in case they need to be relative to the module root. * File paths ending in a slash assume the target is a folder and map all of its contents. @@ -27,18 +38,17 @@ export class StaticAssetHandler extends HttpHandler { /** * Creates a handler for the provided static resources. - * @param assets - A mapping from URL paths to paths, - * where URL paths ending in a slash are interpreted as entire folders. + * @param assets - A list of {@link StaticAssetEntry}. * @param baseUrl - The base URL of the server. * @param options - Cache expiration time in seconds. */ - public constructor(assets: Record, baseUrl: string, options: { expires?: number } = {}) { + public constructor(assets: StaticAssetEntry[], baseUrl: string, options: { expires?: number } = {}) { super(); this.mappings = {}; const rootPath = ensureTrailingSlash(new URL(baseUrl).pathname); - for (const [ url, path ] of Object.entries(assets)) { - this.mappings[trimLeadingSlashes(url)] = resolveAssetPath(path); + for (const { relativeUrl, filePath } of assets) { + this.mappings[trimLeadingSlashes(relativeUrl)] = resolveAssetPath(filePath); } this.pathMatcher = this.createPathMatcher(rootPath); this.expires = Number.isInteger(options.expires) ? Math.max(0, options.expires!) : 0; diff --git a/templates/root/static/index.html b/templates/root/static/index.html new file mode 100644 index 000000000..b6dc652bb --- /dev/null +++ b/templates/root/static/index.html @@ -0,0 +1,73 @@ + + + + + + Community Solid Server + + + +
+ [Solid logo] +

Community Solid Server

+
+
+

Welcome to Solid

+

+ This server implements + the Solid protocol + so you can create your own Solid Pod + and identity. +

+ +

Getting started as a user

+

+ Sign up for an account + to get started with your own Pod and WebID. +

+

+ The default configuration stores data only in memory. + If you want to keep data permanently, + choose a configuration that saves data to disk instead. +

+

+ To learn more about how this server can be used, + have a look at the + getting started tutorial. +

+ +

Getting started as a developer

+

+ The default configuration includes + the ready-to-use root Pod you're currently looking at. +
+ Besides the provided configurations, + you can also fine-tune your own custom configuration using the + configuration generator. +

+

+ You can easily choose any folder on your disk + to expose as the root Pod. +
+ Use the --help switch to learn more. +

+ +

Have a wonderful Solid experience

+

+ Learn more about Solid + at solidproject.org. +

+

+ You are warmly invited + to share your experiences + and to report any bugs you encounter. +

+
+ + + diff --git a/test/unit/server/middleware/StaticAssetHandler.test.ts b/test/unit/server/middleware/StaticAssetHandler.test.ts index c5d34aab8..ff662c68b 100644 --- a/test/unit/server/middleware/StaticAssetHandler.test.ts +++ b/test/unit/server/middleware/StaticAssetHandler.test.ts @@ -2,7 +2,7 @@ import { EventEmitter } from 'events'; import fs from 'fs'; import { PassThrough, Readable } from 'stream'; import { createResponse } from 'node-mocks-http'; -import { StaticAssetHandler } from '../../../../src/server/middleware/StaticAssetHandler'; +import { StaticAssetEntry, StaticAssetHandler } from '../../../../src/server/middleware/StaticAssetHandler'; import { InternalServerError } from '../../../../src/util/errors/InternalServerError'; import { NotFoundHttpError } from '../../../../src/util/errors/NotFoundHttpError'; import type { SystemError } from '../../../../src/util/errors/SystemError'; @@ -12,17 +12,19 @@ const createReadStream = jest.spyOn(fs, 'createReadStream') .mockImplementation((): any => Readable.from([ 'file contents' ])); describe('A StaticAssetHandler', (): void => { - const handler = new StaticAssetHandler({ - '/': '/assets/README.md', - '/foo/bar/style': '/assets/styles/bar.css', - '/foo/bar/main': '/assets/scripts/bar.js', - '/foo/bar/unknown': '/assets/bar.unknown', - '/foo/bar/cwd': 'paths/cwd.txt', - '/foo/bar/module': '@css:paths/module.txt', - '/foo/bar/document/': '/assets/document.txt', - '/foo/bar/folder/': '/assets/folders/1/', - '/foo/bar/folder/subfolder/': '/assets/folders/2/', - }, 'http://localhost:3000'); + const assets = [ + new StaticAssetEntry('/', '/assets/README.md'), + new StaticAssetEntry('/foo/bar/style', '/assets/styles/bar.css'), + new StaticAssetEntry('/foo/bar/main', '/assets/scripts/bar.js'), + new StaticAssetEntry('/foo/bar/unknown', '/assets/bar.unknown'), + new StaticAssetEntry('/foo/bar/cwd', 'paths/cwd.txt'), + new StaticAssetEntry('/foo/bar/module', '@css:paths/module.txt'), + new StaticAssetEntry('/foo/bar/document/', '/assets/document.txt'), + new StaticAssetEntry('/foo/bar/folder/', '/assets/folders/1/'), + new StaticAssetEntry('/foo/bar/folder/subfolder/', '/assets/folders/2/'), + ]; + + const handler = new StaticAssetHandler(assets, 'http://localhost:3000'); afterEach(jest.clearAllMocks); @@ -177,7 +179,7 @@ describe('A StaticAssetHandler', (): void => { }); it('requires folders to be linked to URLs ending on a slash.', async(): Promise => { - expect((): StaticAssetHandler => new StaticAssetHandler({ '/foo': '/bar/' }, 'http://example.com/')) + expect((): StaticAssetHandler => new StaticAssetHandler([ new StaticAssetEntry('/foo', '/bar/') ], 'http://example.com/')) .toThrow(InternalServerError); }); @@ -225,11 +227,11 @@ describe('A StaticAssetHandler', (): void => { it('caches responses when the expires option is set.', async(): Promise => { jest.spyOn(Date, 'now').mockReturnValue(0); - const cachedHandler = new StaticAssetHandler({ - '/foo/bar/style': '/assets/styles/bar.css', - }, 'http://localhost:3000', { - expires: 86400, - }); + const cachedHandler = new StaticAssetHandler( + [ new StaticAssetEntry('/foo/bar/style', '/assets/styles/bar.css') ], + 'http://localhost:3000', + { expires: 86400 }, + ); const request = { method: 'GET', url: '/foo/bar/style' }; const response = createResponse(); await cachedHandler.handleSafe({ request, response } as any);