Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions config/http/notifications/streaming-http.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^7.0.0/components/context.jsonld",
"import": [
"css:config/http/notifications/base/description.json",
"css:config/http/notifications/base/handler.json",
"css:config/http/notifications/base/http.json",
"css:config/http/notifications/base/storage.json",
Expand Down
12 changes: 6 additions & 6 deletions config/http/notifications/streaming-http/http.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^7.0.0/components/context.jsonld",
"@graph": [
{
"comment": "Path prefix used by streaming HTTP receiveFrom endpoints",
"@id": "urn:solid-server:default:variable:streamingHTTPReceiveFromPrefix",
"valueRaw": ".notifications/StreamingHTTPChannel2023/"
"@id": "urn:solid-server:default:StreamingHTTP2023Route",
"@type": "RelativePathInteractionRoute",
"base": { "@id": "urn:solid-server:default:NotificationRoute" },
"relativePath": "/StreamingHTTPChannel2023/"
},
{
"comment": "Creates updatesViaStreamingHttp2023 Link relations",
"@id": "urn:solid-server:default:StreamingHttpMetadataWriter",
"@type": "StreamingHttpMetadataWriter",
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" },
"pathPrefix": { "@id": "urn:solid-server:default:variable:streamingHTTPReceiveFromPrefix" }
"route": { "@id": "urn:solid-server:default:StreamingHTTP2023Route" }
},
{
"comment": "Allows discovery of the corresponding streaming HTTP channel",
Expand All @@ -32,7 +32,7 @@
"@id": "urn:solid-server:default:StreamingHttp2023RequestHandler",
"@type": "StreamingHttpRequestHandler",
"streamMap": { "@id": "urn:solid-server:default:StreamingHttpMap" },
"pathPrefix": { "@id": "urn:solid-server:default:variable:streamingHTTPReceiveFromPrefix" },
"route": { "@id": "urn:solid-server:default:StreamingHTTP2023Route" },
"generator": { "@id": "urn:solid-server:default:BaseNotificationGenerator" },
"serializer": { "@id": "urn:solid-server:default:BaseNotificationSerializer" },
"credentialsExtractor": { "@id": "urn:solid-server:default:CredentialsExtractor" },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { getLoggerFor } from '../../../logging/LogUtil';
import type { HttpResponse } from '../../HttpResponse';
import { addHeader } from '../../../util/HeaderUtil';
import { joinUrl } from '../../../util/PathUtil';
import type { InteractionRoute } from '../../../identity/interaction/routing/InteractionRoute';
import type { RepresentationMetadata } from '../../../http/representation/RepresentationMetadata';
import { MetadataWriter } from '../../../http/output/metadata/MetadataWriter';

Expand All @@ -12,15 +14,14 @@ export class StreamingHttpMetadataWriter extends MetadataWriter {
protected readonly logger = getLoggerFor(this);

public constructor(
private readonly baseUrl: string,
private readonly pathPrefix: string,
private readonly route: InteractionRoute,
) {
super();
}

public async handle(input: { response: HttpResponse; metadata: RepresentationMetadata }): Promise<void> {
const resourcePath = input.metadata.identifier.value.replace(this.baseUrl, '');
const receiveFrom = `${this.baseUrl}${this.pathPrefix}${resourcePath}`;
const encodedUrl = encodeURIComponent(input.metadata.identifier.value);
const receiveFrom = joinUrl(this.route.getPath(), encodedUrl);
const link = `<${receiveFrom}>; rel="http://www.w3.org/ns/solid/terms#updatesViaStreamingHttp2023"`;
this.logger.debug('Adding updatesViaStreamingHttp2023 to the Link header');
addHeader(input.response, 'Link', link);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { AccessMode } from '../../../authorization/permissions/Permissions';
import { OkResponseDescription } from '../../../http/output/response/OkResponseDescription';
import type { ResponseDescription } from '../../../http/output/response/ResponseDescription';
import { BasicRepresentation } from '../../../http/representation/BasicRepresentation';
import type { InteractionRoute } from '../../../identity/interaction/routing/InteractionRoute';
import { getLoggerFor } from '../../../logging/LogUtil';
import type { OperationHttpHandlerInput } from '../../OperationHttpHandler';
import { OperationHttpHandler } from '../../OperationHttpHandler';
Expand All @@ -28,7 +29,7 @@ export class StreamingHttpRequestHandler extends OperationHttpHandler {

public constructor(
private readonly streamMap: StreamingHttpMap,
private readonly pathPrefix: string,
private readonly route: InteractionRoute,
private readonly generator: NotificationGenerator,
private readonly serializer: NotificationSerializer,
private readonly credentialsExtractor: CredentialsExtractor,
Expand All @@ -39,7 +40,8 @@ export class StreamingHttpRequestHandler extends OperationHttpHandler {
}

public async handle({ operation, request }: OperationHttpHandlerInput): Promise<ResponseDescription> {
const topic = operation.target.path.replace(this.pathPrefix, '');
const encodedUrl = operation.target.path.replace(this.route.getPath(), '');
const topic = decodeURIComponent(encodedUrl);

// Verify if the client is allowed to connect
const credentials = await this.credentialsExtractor.handleSafe(request);
Expand Down
12 changes: 8 additions & 4 deletions test/integration/StreamingHttpChannel2023.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import namedNode = DataFactory.namedNode;

const port = getPort('StreamingHTTPChannel2023');
const baseUrl = `http://localhost:${port}/`;
const pathPrefix = '.notifications/StreamingHTTPChannel2023';

const rootFilePath = getTestFolder('StreamingHTTPChannel2023');
const stores: [string, any][] = [
Expand All @@ -38,13 +39,16 @@ async function readChunk(reader: ReadableStreamDefaultReader): Promise<Store> {
return new Store(parser.parse(notification));
}

function endpoint(topic: string): string {
return joinUrl(baseUrl, pathPrefix, encodeURIComponent(topic));
}

describe.each(stores)('A server supporting StreamingHTTPChannel2023 using %s', (name, { configs, teardown }): void => {
let app: App;
let store: ResourceStore;
const webId = 'http://example.com/card/#me';
const topic = joinUrl(baseUrl, '/foo');
const pathPrefix = '.notifications/StreamingHTTPChannel2023';
const receiveFrom = joinUrl(baseUrl, pathPrefix, '/foo');
const receiveFrom = endpoint(topic);

beforeAll(async(): Promise<void> => {
const variables = {
Expand Down Expand Up @@ -246,7 +250,7 @@ describe.each(stores)('A server supporting StreamingHTTPChannel2023 using %s', (

it('prevents connecting to channels of restricted topics.', async(): Promise<void> => {
const restricted = joinUrl(baseUrl, '/restricted');
const restrictedReceiveFrom = joinUrl(baseUrl, pathPrefix, '/restricted');
const restrictedReceiveFrom = endpoint(restricted);
await store.setRepresentation({ path: restricted }, new BasicRepresentation('new', 'text/plain'));

// Only allow our WebID to read
Expand Down Expand Up @@ -285,7 +289,7 @@ describe.each(stores)('A server supporting StreamingHTTPChannel2023 using %s', (

it('emits container notifications if contents get added or removed.', async(): Promise<void> => {
const resource = joinUrl(baseUrl, '/resource');
const baseReceiveFrom = joinUrl(baseUrl, pathPrefix, '/');
const baseReceiveFrom = endpoint(joinUrl(baseUrl, '/'));

// Connecting to the base URL, which is the parent container
const streamingResponse = await fetch(baseReceiveFrom);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,24 @@ import { createResponse } from 'node-mocks-http';
import {
StreamingHttpMetadataWriter,
} from '../../../../../src/server/notifications/StreamingHttpChannel2023/StreamingHttpMetadataWriter';
import {
AbsolutePathInteractionRoute,
} from '../../../../../src/identity/interaction/routing/AbsolutePathInteractionRoute';
import { RepresentationMetadata } from '../../../../../src/http/representation/RepresentationMetadata';
import type { HttpResponse } from '../../../../../src/server/HttpResponse';
import type { ResourceIdentifier } from '../../../../../src/http/representation/ResourceIdentifier';

describe('A StreamingHttpMetadataWriter', (): void => {
const baseUrl = 'http://example.org/';
const pathPrefix = '.notifications/StreamingHTTPChannel2023/';
const writer = new StreamingHttpMetadataWriter(baseUrl, pathPrefix);
const path = 'http://example.org/.notifications/StreamingHTTPChannel2023/';
const route = new AbsolutePathInteractionRoute(path);
const writer = new StreamingHttpMetadataWriter(route);
const rel = 'http://www.w3.org/ns/solid/terms#updatesViaStreamingHttp2023';

it('adds the correct link header.', async(): Promise<void> => {
const topic: ResourceIdentifier = { path: 'http://example.com/foo' };
const response = createResponse() as HttpResponse;
const metadata = new RepresentationMetadata({ path: 'http://example.org/foo/bar/baz' });
const metadata = new RepresentationMetadata(topic);
await expect(writer.handle({ response, metadata })).resolves.toBeUndefined();
expect(response.getHeaders()).toEqual({ link: `<http://example.org/.notifications/StreamingHTTPChannel2023/foo/bar/baz>; rel="${rel}"` });
expect(response.getHeaders()).toEqual({ link: `<http://example.org/.notifications/StreamingHTTPChannel2023/${encodeURIComponent(topic.path)}>; rel="${rel}"` });
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import { getLoggerFor } from '../../../../../src/logging/LogUtil';
import {
StreamingHttpRequestHandler,
} from '../../../../../src/server/notifications/StreamingHttpChannel2023/StreamingHttpRequestHandler';
import { AbsolutePathInteractionRoute, StreamingHttpMap } from '../../../../../src';
import type { NotificationGenerator, NotificationSerializer } from '../../../../../src';
import { StreamingHttpMap } from '../../../../../src';
import type { Notification } from '../../../../../src/server/notifications/Notification';
import { flushPromises } from '../../../../util/Util';

Expand All @@ -31,7 +31,7 @@ jest.mock('../../../../../src/logging/LogUtil', (): any => {
describe('A StreamingHttpRequestHandler', (): void => {
const logger: jest.Mocked<Logger> = getLoggerFor('mock') as any;
const topic: ResourceIdentifier = { path: 'http://example.com/foo' };
const pathPrefix = '.notifications/StreamingHTTPChannel2023/';
const path = 'http://example.com/.notifications/StreamingHTTPChannel2023/';
const channel: NotificationChannel = {
id: 'id',
topic: topic.path,
Expand All @@ -52,6 +52,7 @@ describe('A StreamingHttpRequestHandler', (): void => {
const request: HttpRequest = {} as any;
const response: HttpResponse = {} as any;
let representation: BasicRepresentation;
let route: AbsolutePathInteractionRoute;
let streamMap: StreamingHttpMap;
let operation: Operation;
let generator: jest.Mocked<NotificationGenerator>;
Expand All @@ -64,12 +65,14 @@ describe('A StreamingHttpRequestHandler', (): void => {
beforeEach(async(): Promise<void> => {
operation = {
method: 'GET',
target: { path: 'http://example.com/.notifications/StreamingHTTPChannel2023/foo' },
target: { path: `${path}${encodeURIComponent(topic.path)}` },
body: new BasicRepresentation(),
preferences: {},
};
representation = new BasicRepresentation(chunk, 'text/plain');

route = new AbsolutePathInteractionRoute(path);

streamMap = new StreamingHttpMap();

generator = {
Expand All @@ -95,7 +98,7 @@ describe('A StreamingHttpRequestHandler', (): void => {

handler = new StreamingHttpRequestHandler(
streamMap,
pathPrefix,
route,
generator,
serializer,
credentialsExtractor,
Expand Down Expand Up @@ -151,7 +154,7 @@ describe('A StreamingHttpRequestHandler', (): void => {
} as any;
handler = new StreamingHttpRequestHandler(
streamMap,
pathPrefix,
route,
generator,
serializer,
credentialsExtractor,
Expand Down