mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Expose Last-Modified and ETag headers
This commit is contained in:
parent
97c534b2bf
commit
77d695c8b6
@ -17,6 +17,8 @@
|
||||
"options_credentials": true,
|
||||
"options_exposedHeaders": [
|
||||
"Accept-Patch",
|
||||
"ETag",
|
||||
"Last-Modified",
|
||||
"Link",
|
||||
"Location",
|
||||
"MS-Author-Via",
|
||||
|
@ -4,6 +4,7 @@
|
||||
"files-scs:config/ldp/metadata-writer/writers/constant.json",
|
||||
"files-scs:config/ldp/metadata-writer/writers/link-rel.json",
|
||||
"files-scs:config/ldp/metadata-writer/writers/mapped.json",
|
||||
"files-scs:config/ldp/metadata-writer/writers/modified.json",
|
||||
"files-scs:config/ldp/metadata-writer/writers/wac-allow.json",
|
||||
"files-scs:config/ldp/metadata-writer/writers/www-auth.json"
|
||||
],
|
||||
@ -15,6 +16,7 @@
|
||||
"handlers": [
|
||||
{ "@id": "urn:solid-server:default:MetadataWriter_Constant" },
|
||||
{ "@id": "urn:solid-server:default:MetadataWriter_Mapped" },
|
||||
{ "@id": "urn:solid-server:default:MetadataWriter_Modified" },
|
||||
{ "@id": "urn:solid-server:default:MetadataWriter_LinkRel" },
|
||||
{ "@id": "urn:solid-server:default:MetadataWriter_WacAllow" },
|
||||
{ "@id": "urn:solid-server:default:MetadataWriter_WwwAuth" }
|
||||
|
10
config/ldp/metadata-writer/writers/modified.json
Normal file
10
config/ldp/metadata-writer/writers/modified.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld",
|
||||
"@graph": [
|
||||
{
|
||||
"comment": "Adds the Last-Modified and ETag headers.",
|
||||
"@id": "urn:solid-server:default:MetadataWriter_Modified",
|
||||
"@type": "ModifiedMetadataWriter"
|
||||
}
|
||||
]
|
||||
}
|
@ -91,6 +91,7 @@ export * from './ldp/http/metadata/LinkTypeParser';
|
||||
export * from './ldp/http/metadata/MappedMetadataWriter';
|
||||
export * from './ldp/http/metadata/MetadataParser';
|
||||
export * from './ldp/http/metadata/MetadataWriter';
|
||||
export * from './ldp/http/metadata/ModifiedMetadataWriter';
|
||||
export * from './ldp/http/metadata/SlugParser';
|
||||
export * from './ldp/http/metadata/WacAllowMetadataWriter';
|
||||
export * from './ldp/http/metadata/WwwAuthMetadataWriter';
|
||||
|
23
src/ldp/http/metadata/ModifiedMetadataWriter.ts
Normal file
23
src/ldp/http/metadata/ModifiedMetadataWriter.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import type { HttpResponse } from '../../../server/HttpResponse';
|
||||
import { getETag } from '../../../storage/Conditions';
|
||||
import { addHeader } from '../../../util/HeaderUtil';
|
||||
import { DC } from '../../../util/Vocabularies';
|
||||
import type { RepresentationMetadata } from '../../representation/RepresentationMetadata';
|
||||
import { MetadataWriter } from './MetadataWriter';
|
||||
|
||||
/**
|
||||
* A {@link MetadataWriter} that generates all the necessary headers related to the modification date of a resource.
|
||||
*/
|
||||
export class ModifiedMetadataWriter extends MetadataWriter {
|
||||
public async handle(input: { response: HttpResponse; metadata: RepresentationMetadata }): Promise<void> {
|
||||
const modified = input.metadata.get(DC.terms.modified);
|
||||
if (modified) {
|
||||
const date = new Date(modified.value);
|
||||
addHeader(input.response, 'Last-Modified', date.toUTCString());
|
||||
}
|
||||
const etag = getETag(input.metadata);
|
||||
if (etag) {
|
||||
addHeader(input.response, 'ETag', etag);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
import type { RepresentationMetadata } from '../ldp/representation/RepresentationMetadata';
|
||||
import { DC } from '../util/Vocabularies';
|
||||
|
||||
/**
|
||||
* The conditions of an HTTP conditional request.
|
||||
@ -7,11 +8,11 @@ export interface Conditions {
|
||||
/**
|
||||
* Valid if matching any of the given ETags.
|
||||
*/
|
||||
matchesEtag: string[];
|
||||
matchesETag?: string[];
|
||||
/**
|
||||
* Valid if not matching any of the given ETags.
|
||||
*/
|
||||
notMatchesEtag: string[];
|
||||
notMatchesETag?: string[];
|
||||
/**
|
||||
* Valid if modified since the given date.
|
||||
*/
|
||||
@ -27,9 +28,23 @@ export interface Conditions {
|
||||
*/
|
||||
matchesMetadata: (metadata: RepresentationMetadata) => boolean;
|
||||
/**
|
||||
* Checks validity based on the given ETag and/org date.
|
||||
* Checks validity based on the given ETag and/or date.
|
||||
* @param eTag - Condition based on ETag.
|
||||
* @param lastModified - Condition based on last modified date.
|
||||
*/
|
||||
matches: (eTag?: string, lastModified?: Date) => boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an ETag based on the last modified date of a resource.
|
||||
* @param metadata - Metadata of the resource.
|
||||
*
|
||||
* @returns the generated ETag. Undefined if no last modified date was found.
|
||||
*/
|
||||
export function getETag(metadata: RepresentationMetadata): string | undefined {
|
||||
const modified = metadata.get(DC.terms.modified);
|
||||
if (modified) {
|
||||
const date = new Date(modified.value);
|
||||
return `"${date.getTime()}"`;
|
||||
}
|
||||
}
|
||||
|
@ -99,6 +99,13 @@ describe('An http server with middleware', (): void => {
|
||||
expect(exposed.split(/\s*,\s*/u)).toContain('Accept-Patch');
|
||||
});
|
||||
|
||||
it('exposes the Last-Modified and ETag headers via CORS.', async(): Promise<void> => {
|
||||
const res = await request(server).get('/').expect(200);
|
||||
const exposed = res.header['access-control-expose-headers'];
|
||||
expect(exposed.split(/\s*,\s*/u)).toContain('ETag');
|
||||
expect(exposed.split(/\s*,\s*/u)).toContain('Last-Modified');
|
||||
});
|
||||
|
||||
it('exposes the Link header via CORS.', async(): Promise<void> => {
|
||||
const res = await request(server).get('/').expect(200);
|
||||
const exposed = res.header['access-control-expose-headers'];
|
||||
|
29
test/unit/ldp/http/metadata/ModifiedMetadataWriter.test.ts
Normal file
29
test/unit/ldp/http/metadata/ModifiedMetadataWriter.test.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { createResponse } from 'node-mocks-http';
|
||||
import { ModifiedMetadataWriter } from '../../../../../src/ldp/http/metadata/ModifiedMetadataWriter';
|
||||
import { RepresentationMetadata } from '../../../../../src/ldp/representation/RepresentationMetadata';
|
||||
import type { HttpResponse } from '../../../../../src/server/HttpResponse';
|
||||
import { updateModifiedDate } from '../../../../../src/util/ResourceUtil';
|
||||
import { DC } from '../../../../../src/util/Vocabularies';
|
||||
|
||||
describe('A ModifiedMetadataWriter', (): void => {
|
||||
const writer = new ModifiedMetadataWriter();
|
||||
|
||||
it('adds the Last-Modified and ETag header if there is dc:modified metadata.', async(): Promise<void> => {
|
||||
const response = createResponse() as HttpResponse;
|
||||
const metadata = new RepresentationMetadata();
|
||||
updateModifiedDate(metadata);
|
||||
const dateTime = metadata.get(DC.terms.modified)!.value;
|
||||
await expect(writer.handle({ response, metadata })).resolves.toBeUndefined();
|
||||
expect(response.getHeaders()).toEqual({
|
||||
'last-modified': new Date(dateTime).toUTCString(),
|
||||
etag: `"${new Date(dateTime).getTime()}"`,
|
||||
});
|
||||
});
|
||||
|
||||
it('does nothing if there is no matching metadata.', async(): Promise<void> => {
|
||||
const response = createResponse() as HttpResponse;
|
||||
const metadata = new RepresentationMetadata();
|
||||
await expect(writer.handle({ response, metadata })).resolves.toBeUndefined();
|
||||
expect(response.getHeaders()).toEqual({});
|
||||
});
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user