Skip to content

Commit cad273c

Browse files
authored
Merge pull request #187 from Accenture/event-subscriptions-docs
Refine event-subscriptions documentation
2 parents bc1744a + f73a61a commit cad273c

File tree

3 files changed

+120
-61
lines changed

3 files changed

+120
-61
lines changed

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ RUN mix release
6767
FROM erlang:21-alpine
6868

6969
LABEL org.label-schema.name="Reactive Interaction Gateway"
70-
LABEL org.label-schema.description="Asynchronous API gateway and event hub"
70+
LABEL org.label-schema.description="Reactive API Gateway and Event Hub"
7171
LABEL org.label-schema.url="https://accenture.github.io/reactive-interaction-gateway/"
7272
LABEL org.label-schema.vcs-url="https://github.com/Accenture/reactive-interaction-gateway"
7373

docs/event-subscription.md

Lines changed: 115 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -4,116 +4,171 @@ title: Event Subscription
44
sidebar_label: Event Subscription
55
---
66

7-
Important part of RIG is creating event subscriptions. RIG is holding connections between clients and itself and forwarding events to proper clients. Currently the supported options are [Server-Sent Events](https://en.wikipedia.org/wiki/Server-sent_events) and [WebSocket](https://en.wikipedia.org/wiki/WebSocket). There is multiple ways how you can create subscription for given events.
7+
There are **two ways to receive events**, either via [Server-Sent Events](https://en.wikipedia.org/wiki/Server-sent_events) (SSE) or via a [WebSocket](https://en.wikipedia.org/wiki/WebSocket) connection. We recommend SSE:
88

9-
## Manual subscriptions
9+
- With HTTP/2, there is no (practical) limit to the number of SSE connections/streams that can be created.
10+
- SSE does not require dropping the HTTP protocol, which makes it firewall-friendly.
11+
- SSE is a better fit to the architecture RIG implements (WebSocket is typically used for full-duplex connection).
1012

11-
Subscription is created by calling HTTP endpoint:
13+
**Event subscriptions** allow both backend and frontend developers to select the events they want to have forwarded towards the UI:
1214

13-
```
14-
Method: PUT
15-
Url: `/connection/sse/:connectionToken/subscriptions`
16-
Body: { "subscriptions": [{ "eventType": "example-event-type" }] }
17-
```
15+
- Frontend developers can set up **(manual) subscriptions** that refer to an event type and zero, one or more _named_ fields of an event. The fields that can be referred to have to be set up in advance using the so-called _extractor configuration_ (see below).
16+
- Backend developers/administrators can set up **automatic subscriptions**, where events are subscribed to according to the JWT the UI sends with its request.
17+
18+
Both automatic and manual subscriptions can be used at the same time - they are simply merged into a single set of subscriptions. Below there are examples for both types, but first we introduce _constraints_ and _extractors_.
19+
20+
## Constraints & Extractors
1821

19-
`connectionToken` is provided by initial SSE/WS connection call, thus it's recommended to save it somewhere. In body you provide which event types to listen to (notice it's an array). After successful you'll get all events with given event type(s).
22+
If subscribing to events by their event type is all you need, you can skip this.
2023

21-
## Constraints & extractors
24+
For a larges example and more details take a look at [the source documentation of `EventFilter`](https://accenture.github.io/reactive-interaction-gateway/source_docs/Rig.EventFilter.html).
2225

23-
To be more restrictive in who should receive what event you can leverage from constraints and extractors. Extractors are set on RIG describing how to match events to clients -- to be more specific where to look for values. As an example _check `name` field in event_. This can be set as an environment variable (string or JSON file). Constraints are set on client side providing exact value to look for in filtering -- for example `John`. Combining with extractor example RIG will try to find `John` in event -- if found event is forwarded to client.
26+
### Constraints
2427

25-
Extractor example:
28+
Constraints allow you to subscribe to events based on the value of pre-defined fields. For example, this is a subscription for "greeting" events that have the "name" field set to "John":
2629

2730
```json
2831
{
29-
"greeting": {
30-
"name": {
31-
"stable_field_index": 1,
32-
"event": {
33-
"json_pointer": "/data/name"
34-
}
32+
"subscriptions": [
33+
{
34+
"eventType": "greeting",
35+
"oneOf": [
36+
{ "name": "John" }
37+
]
3538
}
36-
}
39+
]
3740
}
3841
```
3942

40-
Environment variable:
41-
42-
```
43-
EXTRACTORS=my-extractor.json
44-
```
45-
46-
Constraints example (in subscription call):
43+
The "oneOf" property contains a list of objects. Each object may contain one or more fields and each of them must match the respective value in the event, but only one of those objects must match (that's the reason the property is called "oneOf"). This allows you to query for greeting event to either "John" or "Frank":
4744

4845
```json
4946
{
5047
"subscriptions": [
5148
{
52-
"eventType": "example",
49+
"eventType": "greeting",
5350
"oneOf": [
54-
{
55-
"name": "John"
56-
}
51+
{ "name": "John" },
52+
{ "name": "Frank" }
5753
]
5854
}
5955
]
6056
}
6157
```
6258

63-
## Auth
59+
### Basic Extractor configuration
6460

65-
Authentication can be turned on by setting environment variables:
61+
When referring to the "name" field in a subscription, we assume that RIG knows what "name" refers to in incoming events. In RIG, obtaining the value for a named field is called _extraction_ and the corresponding configuration is called _extractor configuration_. The latter can be set using the `EXTRACTORS` environment variable, which expects either a file path or a JSON encoded string.
6662

63+
Consider the following extractor configuration, `extractors.json`:
64+
65+
```json
66+
{
67+
"greeting": {
68+
"name": {
69+
"stable_field_index": 1,
70+
"event": {
71+
"json_pointer": "/data/name"
72+
}
73+
}
74+
}
75+
}
6776
```
68-
SUBSCRIPTION_CHECK=jwt_validation
69-
JWT_SECRET_KEY=secret
70-
```
7177

72-
JWT needs to be in form of `Bearer eyJh...`.
78+
This sets up an extractor for events of type "greeting". It has one field called "name"; the value of "name" for any given event is extracted using the [JSON Pointer](https://tools.ietf.org/html/rfc6901) `/data/name`.
79+
80+
We can pass this configuration to RIG using the environment variable:
81+
82+
```bash
83+
export EXTRACTORS=extractors.json
84+
```
7385

74-
## Inferred subscriptions
86+
Assuming the UI sets up a subscription like this:
7587

76-
There is also possibility to create subscription automatically (without calling subscription endpoint above) -- inferring from JWT. This behavior is triggered right away in first connection call. Event types to be subscribed to are inferred from extractors, thus it's needed to set them up.
88+
```json
89+
{ "subscriptions": [ { "eventType": "greeting", "oneOf": [ { "name": "John" } ] } ] }
90+
```
7791

78-
Extractors example:
92+
And RIG receives the following event:
7993

8094
```json
95+
{
96+
"type": "greeting",
97+
"data": {
98+
"name": "John"
99+
},
100+
...
101+
}
102+
```
103+
104+
Then RIG's greeting extractor would use the JSON pointer configured for "name" to extract the value "John" and match that against the subscription - this matches, so RIG forwards the event to the UI.
105+
106+
### Extractor configuration for automatic subscriptions
107+
108+
In order to support automatic, JWT-based subscriptions, the extractor also needs to know how to extract the value from JWT claims. Going back to the previous example, we extends the extractor configuration by an additional `jwt` property:
109+
110+
<pre><code>
81111
{
82112
"greeting": {
83113
"name": {
84114
"stable_field_index": 1,
85-
"jwt": {
86-
"json_pointer": "/username"
87-
},
88115
"event": {
89116
"json_pointer": "/data/name"
90-
}
91-
}
92-
},
93-
"example": {
94-
"fullname": {
95-
"stable_field_index": 1,
96-
"jwt": {
97-
"json_pointer": "/fullname"
98117
},
99-
"event": {
100-
"json_pointer": "/data/fullname"
101-
}
118+
<b>"jwt": {
119+
"json_pointer": "/username"
120+
}</b>
102121
}
103122
}
104123
}
105-
```
124+
</code></pre>
125+
126+
With that configuration, a frontend receives all events of type "greeting" where the value of `/data/name` equals the value of `/username` in the JWT.
127+
128+
## Manual subscriptions
129+
130+
Manual subscriptions can be set up when establishing the connection. Additionally, RIG offers an endpoint to update a connection's subscriptions after the connection has already been established.
131+
132+
The following example works similarly for both SSE and WebSocket. We use HTTPie here to make the examples a bit shorter. See the [MDN web docs article on SSE](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) for an example on how to set up SSE in a browser.
133+
134+
Example for setting up initial subscriptions:
106135

107-
This would lead to subscription to event types - `greeting` and `example`.
136+
```bash
137+
$ http --stream ':4000/_rig/v1/connection/sse?subscriptions=[{"eventType":"greeting"}]'
138+
HTTP/1.1 200 OK
139+
content-type: text/event-stream
140+
transfer-encoding: chunked
108141

109-
Environment variable:
142+
event: rig.connection.create
143+
data: {"data":{"connection_token":"g2dkAA1yaWdAMTI3LjAuMC4xAAAKNwAAAAAD","errors":[]},"id":"634b8420-010f-4430-870b-fb5ca8e02945","source":"rig","specversion":"0.2","time":"2019-03-27T11:53:18.435690+00:00","type":"rig.connection.create"}
144+
145+
event: rig.subscriptions_set
146+
data: {"data":[{"eventType":"greeting","oneOf":[]}],"id":"ec4deb26-d2a7-46ed-806d-d1beaa2560f8","source":"rig","specversion":"0.2","time":"2019-03-27T11:53:18.438281+00:00","type":"rig.subscriptions_set"}
110147

111148
```
112-
EXTRACTORS=my-extractor.json
149+
150+
Note that the `rig.subscriptions_set` event includes the passed subscription. Also, note that the `rig.connection.create` event includes a `connection_token`; this token can now be used to update the subscriptions after the connection has been established. Let's replace our initial subscription to "greeting" with a new one to "greeting2":
151+
152+
```bash
153+
$ CONN_TOKEN=g2dkAA1yaWdAMTI3LjAuMC4xAAAKTAAAAAAD
154+
$ BODY='{"subscriptions": [{"eventType":"greeting2"}]}'
155+
$ echo "${BODY}" | http put :4000/_rig/v1/connection/sse/${CONN_TOKEN}/subscriptions
156+
HTTP/1.1 204 No Content
157+
113158
```
114159

115-
Connection call (client side):
160+
After this call, the client receives a new `rig.subscriptions_set` event:
116161

117-
```js
118-
new EventSource(`/connection/sse?jwt=${jwt}`)
162+
```plain
163+
event: rig.subscriptions_set
164+
data: {"data":[{"eventType":"greeting2","oneOf":[]}],"id":"0ba84600-f5cc-4abb-b55d-f9e145cbd03d","source":"rig","specversion":"0.2","time":"2019-03-27T11:58:11.411963+00:00","type":"rig.subscriptions_set"}
119165
```
166+
167+
We see that the subscription to "greeting" has been replaced by a subscription to "greeting2".
168+
169+
Note that if you want to retain any automatic subscriptions you got along with setting up the connection, you need to make sure you _include the same HTTP Authorization header in both requests_.
170+
171+
## Subscription Authorization
172+
173+
RIG can either allow anyone to subscribe to any events, restrict subscriptions to clients that send a valid JWT, or forward subscription requests to an external endpoint and let that endpoint decide whether or not to allow a particular subscription. This can be configured using the environment variable `SUBSCRIPTION_CHECK` - please consult the [Operator's Guide](rig-ops-guide) for details.
174+

website/i18n/en.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@
2525
"title": "Frequently Asked Questions",
2626
"sidebar_label": "FAQ"
2727
},
28+
"https": {
29+
"title": "HTTPS",
30+
"sidebar_label": "HTTPS"
31+
},
2832
"intro": {
2933
"title": "Intro",
3034
"sidebar_label": "Intro"

0 commit comments

Comments
 (0)