docs: Add notification architecture documentation

This commit is contained in:
Joachim Van Herwegen 2022-10-03 10:16:41 +02:00
parent b1f7a6a8b1
commit 7b6ddfa272
5 changed files with 214 additions and 16 deletions

View File

@ -21,6 +21,8 @@ flowchart LR
StaticAssetHandler("<strong>StaticAssetHandler</strong><br>StaticAssetHandler")
SetupHandler("<strong>SetupHandler</strong><br><i>HttpHandler</i>")
OidcHandler("<strong>OidcHandler</strong><br><i>HttpHandler</i>")
NotificationHttpHandler("<strong>NotificationHttpHandler</strong><br><i>HttpHandler</i>")
StorageDescriptionHandler("<strong>StorageDescriptionHandler</strong><br><i>HttpHandler</i>")
AuthResourceHttpHandler("<strong>AuthResourceHttpHandler</strong><br><i>HttpHandler</i>")
IdentityProviderHttpHandler("<strong>IdentityProviderHttpHandler</strong><br><i>HttpHandler</i>")
LdpHandler("<strong>LdpHandler</strong><br><i>HttpHandler</i>")
@ -28,7 +30,9 @@ flowchart LR
StaticAssetHandler --> SetupHandler
SetupHandler --> OidcHandler
OidcHandler --> AuthResourceHttpHandler
OidcHandler --> NotificationHttpHandler
NotificationHttpHandler --> StorageDescriptionHandler
StorageDescriptionHandler --> AuthResourceHttpHandler
AuthResourceHttpHandler --> IdentityProviderHttpHandler
IdentityProviderHttpHandler --> LdpHandler
```
@ -66,6 +70,19 @@ to the Solid-OIDC [specification](https://solid.github.io/solid-oidc/).
The OIDC component is configured to work on the `/.oidc/` subpath,
so this handler catches all those requests and sends them to the internal OIDC library that is used.
## NotificationHttpHandler
The `urn:solid-server:default:NotificationHttpHandler` catches all notification subscription requests.
By default these are requests targeting `/.notifications/`.
Which specific subscription type is targeted is then based on the next part of the URL.
## StorageDescriptionHandler
The `urn:solid-server:default:StorageDescriptionHandler` returns the relevant RDF data
for requests targeting a storage description resource.
It does this by knowing which URL suffix is used for such resources,
and verifying that the associated container is an actual storage container.
## AuthResourceHttpHandler
The `urn:solid-server:default:AuthResourceHttpHandler` is identical

View File

@ -108,25 +108,32 @@ to add any custom initializers that need to run.
The `ServerInitializer` is the initializer that finally starts up the server by listening to the relevant port,
once all the initialization described above is finished.
This is an example of a component that differs based on some of the choices made during configuration.
It takes as input 2 components: a `HttpServerFactory` and a `ServerListener`.
```mermaid
flowchart TD
ServerInitializer("<strong>ServerInitializer</strong><br>ServerInitializer")
ServerInitializer --> WebSocketServerFactory("<strong>ServerFactory</strong><br>WebSocketServerFactory")
WebSocketServerFactory --> BaseHttpServerFactory("<br>BaseHttpServerFactory")
BaseHttpServerFactory --> HttpHandler("<strong>HttpHandler</strong><br><i>HttpHandler</i>")
ServerInitializer --> ServerInitializerArgs
ServerInitializer2("<strong>ServerInitializer</strong><br>ServerInitializer")
ServerInitializer2 ---> BaseHttpServerFactory2("<strong>ServerFactory</strong><br>BaseHttpServerFactory")
BaseHttpServerFactory2 --> HttpHandler2("<strong>HttpHandler</strong><br><i>HttpHandler</i>")
subgraph ServerInitializerArgs[" "]
direction LR
ServerFactory("<strong>ServerFactory</strong><br>BaseServerFactory")
ServerListener("<strong>ServerListener</strong><br>ParallelHandler")
end
ServerListener --> HandlerServerListener("<strong>HandlerServerListener</strong><br>HandlerServerListener")
HandlerServerListener --> HttpHandler("<strong>HttpHandler</strong><br><i>HttpHandler</i>")
```
Depending on if the configurations necessary for websockets are imported or not,
the `urn:solid-server:default:ServerFactory` identifier will point to a different resource.
There will always be a `BaseHttpServerFactory` that starts the HTTP(S) server,
but there might also be a `WebSocketServerFactory` wrapped around it to handle websocket support.
Although not indicated here, the parameters for initializing the `BaseHttpServerFactory`
might also differ in case an HTTPS configuration is imported.
The `HttpServerFactory` is responsible for starting a server on a given port.
Depending on the configuration this can be an HTTP or an HTTPS server.
The created server emits events when it receives requests.
The `HttpHandler` it takes as input is responsible for how [HTTP requests get resolved](http-handler.md).
A `ServerListener` is a class that takes the created server as input and attaches a listener to interpret events.
One listener that is always used is the `urn:solid-server:default:HandlerServerListener`,
which calls an `HttpHandler` [to resolve HTTP requests](http-handler.md).
Sometimes it is necessary to add additional listeners,
these can then be added to the `urn:solid-server:default:ServerListener` as it is a `ParallellHandler`.
An example of this is when WebSockets are used [to handle notifications](notifications.md).

View File

@ -0,0 +1,172 @@
# Notifications
This section covers the architecture used to support Notifications protocol
as described in <https://solidproject.org/TR/notifications-protocol>.
There are 3 core architectural components to this that each have separate entry points:
* Exposing metadata to allow discovery of the subscription type.
* Handling subscriptions targeting a resource.
* Emitting notifications when there is activity on a resource.
## Discovery
Discovery is done through the storage description resource(s).
The server returns the same triples for every such resource
as the notification subscription URL is always located in the root of the server.
```mermaid
flowchart LR
StorageDescriptionHandler("<br>StorageDescriptionHandler")
StorageDescriptionHandler --> StorageDescriber("<strong>StorageDescriber</strong><br>ArrayUnionHandler")
StorageDescriber --> StorageDescriberArgs
subgraph StorageDescriberArgs[" "]
direction LR
NotificationDescriber("<br>NotificationDescriber")
NotificationDescriber2("<br>NotificationDescriber")
end
```
The server uses a `StorageDescriptionHandler` to generate the necessary RDF data
and to handle content negotiation.
To generate the data we have multiple `StorageDescriber`s,
whose results get merged together in an `ArrayUnionHandler`.
One specific instance of a `StorageDescriber` is a `NotificationSubcriber`,
that contains all the necessary presets to describe a notification subscription type.
When adding a new subscription type,
a new instance of such a class should be added to the `urn:solid-server:default:StorageDescriber`.
## Subscription
To subscribe, a client has to send a specific JSON-LD request to the URl found during discovery.
```mermaid
flowchart LR
NotificationTypeHandler("<strong>NotificationTypeHandler</strong><br>WaterfallHandler")
NotificationTypeHandler --> NotificationTypeHandlerArgs
subgraph NotificationTypeHandlerArgs[" "]
direction LR
OperationRouterHandler("<br>OperationRouterHandler") --> NotificationSubscriber("<br>NotificationSubscriber")
NotificationSubscriber --> SubscriptionType("<br><i>SubscriptionType</i>")
OperationRouterHandler2("<br>OperationRouterHandler") --> NotificationSubscriber2("<br>NotificationSubscriber")
NotificationSubscriber2 --> SubscriptionType2("<br><i>SubscriptionType</i>")
end
```
Every subscription type should have a subscription URL relative to the root notification URL,
which in our configs is set to `/.notifications/`.
For every type there is then a `OperationRouterHandler` that accepts requests to that specific URL,
after which a `NotificationSubscriber` handles all checks related to subscribing,
for which it uses a `SubscriptionType` that contains all the information necessary for a specific type.
If the subscription is valid and has authorization, the results will be saved in a `SubscriptionStorage`.
## Activity
```mermaid
flowchart TB
ListeningActivityHandler("<strong>ListeningActivityHandler</strong><br>ListeningActivityHandler")
ListeningActivityHandler --> ListeningActivityHandlerArgs
subgraph ListeningActivityHandlerArgs[" "]
SubscriptionStorage("<strong>SubscriptionStorage</strong><br><i>SubscriptionStorage</i>")
ResourceStore("<strong>ResourceStore</strong><br><i>ActivityEmitter</i>")
NotificationHandler("<strong>NotificationHandler</strong><br>WaterfallHandler")
end
NotificationHandler --> NotificationHandlerArgs
subgraph NotificationHandlerArgs[" "]
direction TB
NotificationHandler1("<br><i>NotificationHandler</i>")
NotificationHandler2("<br><i>NotificationHandler</i>")
end
```
An `ActivityEmitter` is a class that emits events every time data changes in the server.
The `MonitoringStore` is an implementation of this in the server.
The `ListeningActivityHandler` is the class that listens to these events
and makes sure relevant notifications get sent out.
It will pull the relevant subscriptions from the storage and call the stored `NotificationHandler` for each of time.
For every subscription type, a `NotificationHandler` should be added to the `WaterfallHandler`
that handles notifications for the specific type.
## WebSocketSubscription2021
To add support for WebSocketSubscription2021 notifications,
components were added as described in the documentation above.
For discovery a `NotificationDescriber` was added with the corresponding settings.
As `SubscriptionType` there is a specific `WebSocketSubscription2021` that contains all the necessary information.
### Handling notifications
As `NotificationHandler` the following architecture is used:
```mermaid
flowchart TB
TypedNotificationHandler("<br>TypedNotificationHandler")
TypedNotificationHandler --> ComposedNotificationHandler("<br>ComposedNotificationHandler")
ComposedNotificationHandler --> ComposedNotificationHandlerArgs
subgraph ComposedNotificationHandlerArgs[" "]
direction LR
BaseNotificationGenerator("<strong>BaseNotificationGenerator</strong><br><i>NotificationGenerator</i>")
BaseNotificationSerializer("<strong>BaseNotificationSerializer</strong><br><i>NotificationSerializer</i>")
WebSocket2021Emitter("<strong>WebSocket2021Emitter</strong><br>WebSocket2021Emitter")
BaseNotificationGenerator --> BaseNotificationSerializer --> WebSocket2021Emitter
end
```
A `TypedNotificationHandler` is a handler that can be used to filter out subscriptions for a specific type,
making sure only WebSocketSubscription2021 subscriptions will be handled.
A `ComposedNotificationHandler` combines 3 interfaces to handle the notifications:
* A `NotificationGenerator` converts the information into a Notification object.
* A `NotificationSerializer` converts a Notification object into a serialized Representation.
* A `NotificationEmitter` takes a Representation and sends it out in a way specific to that subscription type.
`urn:solid-server:default:BaseNotificationGenerator` is a generator that fills in the default Notification template,
and also caches the result so it can be reused by multiple subscriptions.
`urn:solid-server:default:BaseNotificationSerializer` converts the Notification to a JSON-LD representation
and handles any necessary content negotiation based on the `accept` notification feature.
A `WebSocket2021Emitter` is a specific emitter that checks the current open WebSockets
if they correspond to the subscription.
### WebSockets
```mermaid
flowchart TB
WebSocket2021Listener("<strong>WebSocket2021Listener</strong><br>WebSocket2021Listener")
WebSocket2021Listener --> WebSocket2021ListenerArgs
subgraph WebSocket2021ListenerArgs[" "]
direction LR
SubscriptionStorage("<strong>SubscriptionStorage</strong><br>SubscriptionStorage")
SequenceHandler("<br>SequenceHandler")
end
SequenceHandler --> SequenceHandlerArgs
subgraph SequenceHandlerArgs[" "]
direction TB
WebSocket2021Storer("<strong>WebSocket2021Storer</strong><br>WebSocket2021Storer")
WebSocket2021StateHandler("<strong>WebSocket2021StateHandler</strong><br>BaseStateHandler")
end
```
To detect and store WebSocket connections, the `WebSocket2021Listener` is added as a listener to the HTTP server.
For all WebSocket connections that get opened, it verifies if they correspond to an existing subscription.
If yes the information gets sent out to its stored `WebSocket2021Handler`.
In this case this is a `SequenceHandler` which contains a `WebSocket2021Storer` and a `BaseStateHandler`.
The `WebSocket2021Storer` will store the WebSocket in the same map used by the `WebSocket2021Emitter`,
so that class can later on emit events as mentioned above.
The state handler will make sure that a notification gets sent out if the subscription has a `state` feature request,
as defined in the notification specification.

View File

@ -61,3 +61,4 @@ Below are the sections that go deeper into the features of the server and how th
* [How the server is initialized and started](features/initialization.md)
* [How HTTP requests are handled](features/http-handler.md)
* [How the server handles a standard Solid request](features/protocol/overview.md)
* [How Notification components are configured](features/notifications.md)

View File

@ -96,6 +96,7 @@ nav:
- Parsing: architecture/features/protocol/parsing.md
- Authorization: architecture/features/protocol/authorization.md
- Resource Store: architecture/features/protocol/resource-store.md
- Notifications: architecture/features/notifications.md
- Contributing:
- Pull requests: contributing/making-changes.md
- Releases: contributing/release.md