-
Notifications
You must be signed in to change notification settings - Fork 16
/
Copy pathdraft-toomim-httpbis-braid-http-04.txt
1552 lines (1106 loc) · 58.8 KB
/
draft-toomim-httpbis-braid-http-04.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
Internet-Draft M. Toomim
Expires: Mar 10, 2024 Invisible College
Intended status: Proposed Standard G. Little
Invisible College
R. Walker
Invisible College
B. Bellomy
Invisible College
J. Gentle
Invisible College
Nov 19, 2023
\=/====\\ |//===\\= /=\ =\==\|\=/== =|====\==
||/ |\\ ||\ |\\ /|| \|\ |// //| \\\
|\\ |// |\\ |// //| \\\ \\\ /\/ |||
\=|====|= |/====/=\ /=\/====|=\ =\= \\= =/=
//\ /\\ //| |\\ |/| ||| \\\ ||| |//
||| ||| |\\ |// |\/ \|/ /|\ |=\ |\\
=\=\==/=/ ==| |\= ||= /== ===/=|=\=== |==\===//
Braid-HTTP: Synchronization for HTTP
draft-toomim-httpbis-braid-http-04
Abstract
Braid is a set of extensions that generalize HTTP from a state
*transfer* protocol into a full state *synchronization* protocol.
Braid is composed of four independent extensions to HTTP:
1. VERSIONING of resource history
2. UPDATES sent as patches
3. SUBSCRIPTIONS to updates over time
4. MERGE-TYPES that specify OT or CRDT behavior
Each extension provides a distinct value for a stand-alone use-case.
However, they can compose together to support the full power of CRDTs
and Operational Transforms on web resources. This allows multiple
writers to make simultaneous mutations to arbitrary content-types,
under arbitrary network delays and partitions, while guaranteeing
consistency across multiple clients and servers. This improves web
caching and network performance, and enables natively peer-to-peer,
collaboratively-editable, local-first web applications.
Status of this Memo
This Internet-Draft is submitted in full conformance with the
provisions of BCP 78 and BCP 79.
Internet-Drafts are working documents of the Internet Engineering
Task Force (IETF), its areas, and its working groups. Note that
other groups may also distribute working documents as
Internet-Drafts. The list of current Internet-Drafts is at
http://datatracker.ietf.org/drafts/current/.
Internet-Drafts are draft documents valid for a maximum of six months
and may be updated, replaced, or obsoleted by other documents at any
time. It is inappropriate to use Internet-Drafts as reference
material or to cite them other than as "work in progress."
The list of current Internet-Drafts can be accessed at
https://www.ietf.org/1id-abstracts.html
The list of Internet-Draft Shadow Directories can be accessed at
https://www.ietf.org/shadow.html
Table of Contents
1. Introduction ..................................................4
1.1. HTTP applications need State Synchronization ................4
1.2. Braid-HTTP is four extensions to HTTP .......................4
2. Versioning for Resources ......................................6
2.1. Comparison with ETag ........................................7
2.2. PUT a new version ...........................................7
2.3. GET a specific version ......................................8
2.4. GET a range of historical versions ..........................9
2.5. Rules for Version and Parents headers ......................10
3. Updates as Patches or Snapshots ..............................11
3.1. PUT an update as a patch ...................................12
3.2. GET an update as a patch ...................................13
3.3. PUT an update as a set of patches ..........................14
3.4. Using Patches: 1 for safe Partial PUTs .....................15
3.5. PUT an update with a custom patch-type .....................16
4. Subscriptions for GET ........................................17
4.1. Creating a Subscription ....................................18
4.2. Sending multiple updates per GET ...........................19
4.3. Continuing a Subscription ..................................20
4.4. Signaling "all caught up" ..................................21
4.5. Errors .....................................................21
5. Design Goals..................................................22
6. Example Use Cases ............................................22
6.1. Basic Examples .............................................22
6.1.1. Subscribing to the current temperature ...................23
6.1.2. Versioning of source code ................................24
6.1.3. Patches can append to server logs ........................24
6.2. Combination examples .......................................25
6.2.1. Resumeable uploads .......................................25
6.2.1.1. Version-Type: bytestream ...............................25
6.2.1.2. Protocol for resuming uploads ..........................25
6.2.2. Dynamic resources: animating a PNG .......................27
6.2.3. Dynamic proxies and caches ...............................28
6.2.4. Serverless chat example ..................................28
7. Related Work .................................................29
7.1. Existing IETF Standards ....................................29
7.3. IETF Work in Progress ......................................29
7.3. Web Frameworks .............................................30
8. IANA Considerations ..........................................31
8.1. Header Field Registration ..................................31
9. Security Considerations ......................................31
10. Conventions .................................................31
11. Copyright Notice .............................................32
12. References ...................................................32
12.1. Normative References .......................................32
12.2. Informative References .....................................33
13. Acknowledgements .............................................34
14. Authors' Addresses ...........................................35
1. Introduction
1.1. HTTP applications need state Synchronization, not just Transfer
HTTP [RFC9110] transfers a static version of state within a single
request and response. If the state changes, HTTP does not
automatically update clients with the new versions. This design
satisficed when webpages were mostly static and written by hand;
however today's websites are dynamic, generated from layers of state
in databases, and provide realtime updates across multiple clients
and servers. Programmers today need to *synchronize*, not just
*transfer* state, and to do this, they must work around HTTP.
The web has a long history of such workarounds. The original web
required users to click reload when a page changed. Javascript and
XMLHTTPRequest [XHR] made it possible to update just part of a page,
running a GET request behind the scenes. However, a GET request
still could not push server-initiated updates. To work around this,
web programmers would poll the resource with repeated GETs, which was
inefficient. Long-polling was invented to reduce redundant requests,
but still requires the client to initiate a round-trip for each
update. Server-Sent Events [SSE] finally created a standard for the
server to push events, but SSE provides semantics of an event-stream,
not an update-stream, and SSE programmers must encode the semantics
of updating a resource within the event stream. Today there is still
no standard to push updates to a resource's state.
In practice, web programmers today often give up on using standards
for "data that changes", and instead send custom messages over a
WebSocket -- a hand-rolled synchronization protocol. Unfortunately,
this forfeits the benefits of HTTP and ReST, such as caching and a
uniform interface [REST]. As the web becomes increasingly dynamic,
web applications are forced to implement additional layers of
non-standard Javascript frameworks to synchronize changes to state.
1.2. Braid-HTTP is four extensions to HTTP
State synchronization implementations come in many forms. The
simplest perform one-off transfers of state from one computer to
another, but as implementations advance, they may develop support for
pushed updates, delivery guarantees, multiple writers, multiple types
of edits, expressed as diffs or patches, with automatic conflict
resolution, offline modes, merge semantics over multiple content
types, and/or function over different network topologies and
conditions. Different systems have different needs, and
implementations today come in multiple variations, with different
network protocols.
Fortunately, it turns out that any implementation's network protocol
for these features can be decomposed into the same simple set of four
extensions to HTTP semantics:
1. Versioning (Section 2)
Each resource has a history of changes, ordered in time.
2. Updates as Patches or Snapshots (Section 3)
Each resource can express updates as either *patches* or
*snapshots*; in bidirectional client->server and server->client
messages.
3. Subscriptions (Section 4)
A Subscribe header can be added to GET requests. The server
responds by pushing updates to the client while the request is
open.
4. Merge Types [MERGE-TYPES]
If multiple clients and servers simultaneously mutate the same
resource, they can guarantee a consistent resulting state by
implementing the same Merge Type.
The first three extensions are improvements to existing HTTP
features, which are valuable in HTTP on their own, aside from state
synchronization. (For examples, see Section 6.) However, by
composing these extensions together, HTTP can generalize into a full
synchronization protocol, and ReST into a synchronization
architecture:
HTTP: HyperText *Transfer* Protocol
becomes: HyperState *Synchronization* Protocol
ReST: Representational State *Transfer*
becomes: Representational State *Synchronization*
Together, they allow an arbitrary set of clients and servers to make
arbitrary mutations to arbitrary resources, under arbitrary network
delays and partitions, and merge all mutations consistently,
receiving updates as soon as they reconnect. This enables caches to
support dynamic content, web applications to feature an offline mode,
and textareas to support collaborative editing.
The extensions are explained in the following sections, in turn.
2. Versioning for Resources
Each Braid resource has a current version, and a version history.
Versions are specified as a set of one or more strings (called
"version IDs") in the Structured Headers [RFC8941] format. Each
version ID must be unique, to differentiate distinct changes at
distinct points in time.
To specify the version of content in a request or response body, a
Version header MAY be included in a request for a PUT, PATCH or POST,
or in the response to a GET:
Version: "dkn7ov2vwg"
Every version also has a set of parents, denoting the version(s)
immediately before the version, that it derives from. Any version
can be recreated by first merging its parents, and then applying the
its update onto that merger. Parents are specified with a Parents
header in a PUT/PATCH/POST request or GET response:
Parents: "ajtva12kid", "cmdpvkpll2"
The full graph of parents forms a Directed Acyclic Graph (DAG),
representing the partial order of all versions. A version A is known
to have occurred before a version B if and only if A is an ancestor
of B in the partial order. Braid time is a DAG, rather than a line.
A Version header is also allowed to contain multiple IDs, to describe
the version of a merger:
Version: "dkn7ov2vwg", "v2vwgdkn7o"
However, any single mutation SHOULD create only a single version ID,
and mergers themselves need not be announced over the network when
created. Version headers with multiple IDs are only needed in a few
cases, such as when requesting or providing a snapshot of a merger.
For any two version IDs A and B that are specified in a Version or
Parents header, A cannot be a descendent of B or vice versa. The
ordering of version IDs within the header carries no meaning.
If a client or server does not specify a Version for a resource it
transfers, the recipient MAY generate and assign it new version IDs.
If a client or server does not specify a Parents header when
transferring a new version, the recipient MAY presume that the most
recent versions it has (the frontier of time) are the parents of the
new version. It MAY also ignore or reject the update.
2.1. Comparison with ETag
The Version header is similar to an ETag, but has two differences:
1. ETags are sensitive to Content-Encoding. If you send the same
version with a GZip Content-Encoding, it will have a different
ETag, but the same Version.
2. A Version marks a unique point in causal graph time -- not unique
content. If a resource is changed from version A to B, and then
to C, such that the contents at A are the same as the contents at
C, then it is possible for versions A and C to have the same ETag,
even though they have different Versions. This can break a CRDT
or OT merge algorithm.
Versions can be used in a variety of requests, as we explain next.
2.2. PUT a new version
When a PUT request changes the state of a resource, it can specify
the new version of the resource, the parent version IDs that it was
directly built upon, and the way multiple simultaneous changes should
be merged (the "Merge-Type"):
Request:
PUT /chat
Version: "ej4lhb9z78" | Update
Parents: "oakwn5b8qh", "uc9zwhw7mf" |
Content-Type: application/json |
Merge-Type: sync9 |
Content-Length: 64 |
|
[{"text": "Hi, everyone!", | | Snapshot
"author": {"link": "/user/tommy"}}] | |
Response:
HTTP/1.1 200 OK
Merge-Types are specified in [MERGE-TYPES]. The Version and Parents
headers are optional. If Version is omitted, the recipient may
assign new version IDs. If Parents is omitted, the recipient may
assume that its current version is the version's parents.
As will be explained in Section 3, we call the set of data that
updates a resource from one version to another an "update". An
update consists of a set of headers and a body. In this example, the
update includes a snapshot of the entire new value of the resource.
However, one can also specify the update as a set of patches.
2.3. GET a specific version
A server can allow clients to request historical versions of a
resource in GET requests by responding to the Version and Parents
headers. A client can specify a specific version that it wants with
the Version header:
Request:
GET /chat
Version: "ej4lhb9z78"
Response:
HTTP/1.1 200 OK
Version: "ej4lhb9z78" | Update
Parents: "oakwn5b8qh", "uc9zwhw7mf" |
Content-Type: application/json |
Merge-Type: sync9 |
Content-Length: 64 |
|
[{"text": "Hi, everyone!", | | Snapshot
"author": {"link": "/user/tommy"}}] | |
2.4. GET a range of historical versions
A client can request a range of history by including a Parents and a
Version header together. The Parents marks the beginning of the
range (the oldest versions) and the Version marks the end of the
range (the newest versions) that it requests.
Request:
GET /chat
Version: "3"
Parents: "1a", "1b"
Response:
HTTP/1.1 200 OK
Current-Version: "3"
Version: "2" | Update
Parents: "1a", "1b" |
Content-Type: application/json |
Merge-Type: sync9 |
Content-Length: 64 |
|
[{"text": "Hi, everyone!", | | Snapshot
"author": {"link": "/user/tommy"}}] | |
Version: "3" | Update
Parents: "2" |
Content-Type: application/json |
Merge-Type: sync9 |
Content-Length: 117 |
|
[{"text": "Hi, everyone!", | | Snapshot
"author": {"link": "/user/tommy"}} | |
{"text": "Yo!", | |
"author": {"link": "/user/yobot"}] | |
To express a range of updates, the response body contains a sequence
of updates; each with its own content-length. The format of this
sequence is defined in the upcoming (Section 4.2) on Subscriptions.
2.5. Rules for Version and Parents headers
If a GET request contains a Version header:
- The Subscribe header (Section 4) MUST be absent.
- If the Parents header is absent, the server SHOULD return a
single response, containing the requested version of the resource
in its body, with the Version response header set to the same
version.
- If the server does not support historical versions, it MAY ignore
the Version header and respond as usual, but MUST NOT include the
Version header in its response.
If a GET request contains a Parents header:
- The server SHOULD send the set of versions updating the Parents
to the specified Version. If no Version is specified, then it
should update the client to the server's current version.
- If the request contains a Subscribe header, then it SHOULD
additionally leave the request open and subscribe the client to
future updates. Otherwise, it should close the connection
after sending the updates.
- If the server does not support historical versions, then it MAY
ignore the Parents header, but MUST NOT include the Parents
header in its response.
A server MAY refactor or rebase the version history that it provides
to a client, so long as it does not affect the resulting state, or
the result of merges using the history. (Rules for specifying
constraints on such rebases are out of scope for this draft.)
A server does not need to honor historical version requests for all
documents, for all history. If a server no longer has the historical
context needed to honor a request, it may respond using an error code
that will be defined in a subsequent version of this draft.
3. Updates as Patches or Snapshots
Whereas today's HTTP sends the current version of a resource as a
"snapshot" in the body of a GET response or PUT request, and allows
clients to send a "patch" in a PATCH request, a general state
synchronization protocol needs updates that travel in both directions
(both server->client and client->server) and across multiple methods.
This section describes a general form for bidirectional updates.
Updates can be sent as snapshots or patches. When sent as patches, a
single update can contain a single patch, or multiple patches.
Unlike the PATCH method, these updates can be sent idempotently by
including versioning information -- a client or server that receives
the same update twice, for the same version, can discard the second
update, and thus maintain idempotence.
There are two reasons to send an update as a patch:
- Patches can be smaller and more efficient
- Patches articulate *how* changes occur, which enables Merge-Types
to intelligently merge, e.g. in collaborative editing.
There are two ways to express patches:
- Custom patch Content-Types: As defined in HTTP PATCH [RFC5789], a
custom patch format can be specified as a Content-Type. Any such
patch can be included in any Braid-HTTP update, by adding its
Content-Type to its update or patch header.
- Range Patches: If a Content-Range header is specified on the
update or patch, then it defines the region of the document that
is being replaced by the content, as specified in [RANGE-PATCH].
Every patch MUST include either a Content-Type or a Content-Range.
Finally, it is possible to include multiple patches within a single
update by including a "Patches: N" header, and setting the body to a
concatenation of that many patches.
These scenarios are elaborated below.
3.1. PUT an update as a patch
A single Range Patch (see [RANGE-PATCH]) can be constructed as a
Partial PUT [RFC9110] -- a PUT with a Content-Range header:
Request:
PUT /chat
Version: "g09ur8z74r" | Update
Parents: "ej4lhb9z78" |
Content-Type: application/json |
Merge-Type: sync9 |
Content-Length: 53 | | Patch
Content-Range: json .messages[1:1] | |
| |
[{"text": "Yo!", | |
"author": {"link": "/user/yobot"}] | |
Response:
HTTP/1.1 200 OK
Note that Partial PUTs can result in data loss if sent to a server
that does not support them. Legacy servers ignore headers they do
not understand, and will interpret the patch as the entire resource,
replacing the resource's state with just the region being patched.
To avoid this, only use Partial PUTs on servers that you know support
them, or do feature detection (to be defined in a subsequent draft)
to determine what they support. You can also reformulate a Partial
PUT with Patches: 1 as in (Section 3.4) below, as a safe alternative.
3.2. GET an update as a patch
A GET response can also express a patch, using Content-Range:
Request:
GET /chat
Version: "g09ur8z74r"
Parents: "ej4lhb9z78"
Response:
HTTP/1.1 200 OK
Content-Type: application/json | Update
Merge-Type: sync9 |
Content-Length: 53 | | Patch
Content-Range: json .messages[1:1] | |
| |
[{"text": "Yo!", | |
"author": {"link": "/user/yobot"}] | |
Or a custom patch type, using Content-Type:
Response:
HTTP/1.1 200 OK
Merge-Type: sync9 | Update
Content-Length: 53 | | Patch
Content-Type: application/json-patch+json | |
| |
[ | |
{"op": "test", "path": "/a/b/c", "value": "foo"}, | |
{"op": "remove", "path": "/a/b/c"}, | |
{"op": "add", "path": "/a/b/c", "value": []}, | |
{"op": "replace", "path": "/a/b/c", "value": 42}, | |
{"op": "move", "from": "/a/b", "path": "/a/d"}, | |
{"op": "copy", "from": "/a/d", "path": "/a/d/e"} | |
] | |
3.3. PUT an update as a set of patches
To format an update as a set of patches, include a header called
"Patches" and assign it to the number of patches included, and format
those patches in the body as a sequence separated by blank lines:
Request:
PUT /chat
Version: "g09ur8z74r" | Update
Parents: "ej4lhb9z78" |
Content-Length: 189 |
Content-Type: application/json |
Merge-Type: sync9 |
Patches: 2 |
|
Content-Length: 53 | | Patch
Content-Range: json .messages[1:1] | |
| |
[{"text": "Yo!", | |
"author": {"link": "/user/yobot"}] | |
|
Content-Length: 40 | | Patch
Content-Range: json .latest_change | |
| |
{"type": "date", "value": 1573952202370} | |
Response:
HTTP/1.1 200 OK
To distinguish the boundaries between patches in an update, each
patch MUST include the following header:
Content-Length: N
This length determines where each patch ends, and next begins.
3.4. Using Patches: 1 for safe Partial PUTs
The "Patches" header also provides a safe alternative to Partial
PUTs. Instead of specifying the patch in the request body of the
PUT, the request can:
- set the "Patches: 1" header
- move the "Content-Length" and "Content-Range" headers into patch
headers on the request body
For example, the Partial PUT example of Section 3.1 would translate
like this:
Request:
PUT /chat
Version: "g09ur8z74r" | Update
Parents: "ej4lhb9z78" |
Content-Type: application/json |
Merge-Type: sync9 |
Patches: 1 |
|
Content-Length: 53 | | Patch
Content-Range: json .messages[1:1] | |
| |
[{"text": "Yo!", | |
"author": {"link": "/user/yobot"}] | |
Response:
HTTP/1.1 200 OK
Because this PUT request has no Content-Length, legacy servers will
not know where the request body ends, and will not recognize a
complete PUT. (Any server that accepts a PUT before it is complete
also risks data corruption from network failures during a PUT.)
Servers that understand Patches: 1, on the other hand, know to look
at the patch inside to find the true Content-Length, and will be able
to complete the request.
3.5. PUT an update with a custom patch-type
Since PATCH is not idempotent, a client may want to send a patch
idempotently using a PUT. The client SHOULD include a Version and
Parents header to ensure idempotency. The server SHOULD discard
duplicate patches (for the same Version) to satisfy idempotence.
Request:
PUT /chat
Version: "up12vyc5ib" | Update
Parents: "2bcbi84nsp" |
Content-Length: 371 |
Merge-Type: sync9 |
Patches: 1 |
|
Content-Length: 288 | | Patch
Content-Type: application/json-patch+json | |
| |
[ | |
{"op": "test", "path": "/a/b/c", "value": "foo"}, | |
{"op": "remove", "path": "/a/b/c"}, | |
{"op": "add", "path": "/a/b/c", "value": []}, | |
{"op": "replace", "path": "/a/b/c", "value": 42}, | |
{"op": "move", "from": "/a/b", "path": "/a/d"}, | |
{"op": "copy", "from": "/a/d", "path": "/a/d/e"} | |
] | |
Response:
HTTP/1.1 200 OK
4. Subscriptions for GET
If a GET request includes the Subscribe header, the server can
"subscribe" the client to the resource, which means it promises to
keep it up-to-date with the updates necessary to describe new
versions as it learns about them.
The server will first send the current version, and then stream the
updates for future versions. Each update can express the new content
either as a snapshot, or a set of patches, as in Section 3.
Request:
GET /chat
Subscribe:
Response:
HTTP/1.1 209 Subscription
Subscribe:
Version: "ej4lhb9z78" | Update
Parents: "oakwn5b8qh", "uc9zwhw7mf" |
Content-Type: application/json |
Merge-Type: sync9 |
Content-Length: 64 |
|
[{"text": "Hi, everyone!", | | Snapshot
"author": {"link": "/user/tommy"}}] | |
Version: "g09ur8z74r" | Update
Parents: "ej4lhb9z78" |
Content-Type: application/json |
Merge-Type: sync9 |
Patches: 1 |
|
Content-Length: 53 | | Patch
Content-Range: json .messages[1:1] | |
| |
[{"text": "Yo!", | |
"author": {"link": "/user/yobot"}] | |
Version: "2bcbi84nsp" | Update
Parents: "g09ur8z74r" |
Content-Type: application/json |
Merge-Type: sync9 |
Patches: 1 |
|
Content-Length: 58 | | Patch
Content-Range: json .messages[2:2] | |
| |
[{"text": "Hi, Tommy!", | |
"author": {"link": "/user/sal"}}] | |
Version: "up12vyc5ib" | Update
Parents: "2bcbi84nsp" |
Content-Type: application/json |
Merge-Type: sync9 |
Patches: 1 |
|
Content-Length: 288 | | Patch
Content-Type: application/json-patch+json | |
| |
[ | |
{"op": "test", "path": "/a/b/c", "value": "foo"}, | |
{"op": "remove", "path": "/a/b/c"}, | |
{"op": "add", "path": "/a/b/c", "value": []}, | |
{"op": "replace", "path": "/a/b/c", "value": 42}, | |
{"op": "move", "from": "/a/b", "path": "/a/d"}, | |
{"op": "copy", "from": "/a/d", "path": "/a/d/e"} | |
] | |
Note the blank line after the "Subscribe:" header. This allows
subscriptions to be smuggled through existing HTTP clients (such as
browser fetch() APIs), which will interpret the sequence of updates
as a long response body of unspecified length. Application
programmers can then parse the updates via polyfill libraries on top
of existing client libraries. In future versions of this draft, it
could be advantageous to remove the blank line for upgraded clients,
which can interpret the initial response headers and body as the
headers and body for the first update, directly.
It is RECOMMENDED that updates do not change the Merge-Type for a
resource, because there is no defined semantics for merging updates
of different Merge-Types. If a client observes a change in
Merge-Type from a server, it is suggested to reload the resource.
4.1. Creating a Subscription
A client requests a subscription by issuing a GET request with a
Subscribe header:
Subscribe: <Parameters>
<Parameters> may be blank, set to "true", or contain arbitrary data,
and is reserved for future use.
This header modifies the normal GET method's semantics, to request a
subscription to future updates to the data, rather than only
returning the current version of the representation data.
A server implementing Subscribe MUST include a Subscribe header in
its response. The server then SHOULD keep the connection open, and
send updates over it.
In general, a server that implements subscriptions promises to keep
its subscribed clients up-to-date by sending changes until the
connection is closed. Once closed, a subscription can be resumed by
the client issuing a subsequent GET request on the same document.
4.2. Sending multiple updates per GET
To send multiple updates, a server concatenates multiple updates into
a single response body. Each update MUST include headers and a body,
and MUST specify the end of its body by including at least one of the
following headers:
- Content-Length: N
- Patches: N
The body may be zero-length. A server MAY separate each update with
one or more blank lines. These lines do not count towards
Content-Length. They can by used to visually separate updates, or to
guide the behavior of certain proxies or clients:
1. Certain clients or proxies close inactive connections. A
server signal that a connection is still active by
periodically sending additional blank lines between updates.
2. Some clients (e.g. Firefox) only flush incoming data after a
receiving a chunk of a certain size. A server can ensure
small updates get flushed by padding them with blank lines.
4.3. Continuing a Subscription
Once closed, a Braid subscription may be restarted by the client
issuing a new subscription request.
When the client reconnects, it may specify its last known version
using the Parents header. The server SHOULD then send only the
updates since that version.
Example:
Initial request:
GET /chat
Subscribe:
Initial response:
HTTP/1.1 209 Subscription
Subscribe:
Version: "ej4lhb9z78" | Update
Content-Type: application/json |
Content-Length: 64 |
|
[{"text": "Hi, everyone!", | | Snapshot
"author": {"link": "/user/tommy"}}] | |
<Client disconnects>
Reconnection request:
GET /chat
Subscribe:
Parents: "ej4lhb9z78"
Reconnection response:
HTTP/1.1 209 Subscription
Subscribe:
Version: "g09ur8z74r" | Update
Parents: "ej4lhb9z78" |
Content-Type: application/json |
Merge-Type: sync9 |
Patches: 1 |
|
Content-Length: 53 | | Patch
Content-Range: json .messages[1:1] | |
| |
[{"text": "Yo!", | |
"author": {"link": "/user/yobot"}] | |
4.4. Signaling "all caught up"
When starting or resuming a subscription, the server can indicate
which version is current by specifying a "Current-Version" header
before starting the stream of versions. This should contain the
frontier of time -- the leaves of the currently-known time DAG. The
client can use this information to determine when it has caught up
with the server's version at the time it received the client's
request.
Request:
GET /chat
Subscribe:
Response:
HTTP/1.1 209 Subscription
Subscribe:
Current-Version: "ej4lhb9z78" <-- Current Version
Version: "b9z78ej4lh" | Updates
Content-Type: application/json |
Merge-Type: sync9 |
Content-Length: 2 |
|
[] |
|
Version: "ej4lhb9z78" | <-- Current Version
Parents: "b9z78ej4lh" |
Content-Type: application/json |
Merge-Type: sync9 |
Content-Length: 64 |
|
[{"text": "Hi, everyone!", |
"author": {"link": "/user/tommy"}}] V
<-- Now caught up
4.5. Errors
If a server has dropped the history that a client requests, the
server can return a 410 GONE response, to tell the client "sorry, I
don't have the history necessary to synchronize with you."
5. Design Goals
This spec is designed to be:
1. Backwards-compatible with existing HTTP
2. Easy to implement simple synchronizers with. For instance, it
should be easy to write a read-only synchronizer for an
append-only log.
3. Possible to implement arbitrary synchronization algorithms. For
instance, these extensions support any Operational Transform or
CRDT algorithm.
6. Example Use Cases
The first three Braid extensions (Versions, Patches, and
Subscriptions) are useful independently -- without Merge-Types, and
with or without each other.
The first section 6.1. gives examples of independent uses. Then
6.2. puts them together into more advanced combinations.
6.1. Basic Examples
First, we give examples of Subscriptions, Versions, and Patches used
individually.
6.1.1. Subscribing to the current temperature
Subscriptions are useful independently of versioning, patches, or
merging. Suppose that a web server hosts the current temperature:
Request:
GET /temperature
Response:
HTTP/1.1 200 OK
Content-Length: 4
70 F
Braid Subscriptions enable a client can stay up-to-date as the
temperature changes:
Request:
GET /temperature
Subscribe: true
Response:
HTTP/1.1 209 Subscription
Subscribe: true
Content-Length: 4
70 F
Content-Length: 4
72 F
Content-Length: 4
73 F
Content-Length: 4
71 F
6.1.2. Versioning of source code
Source code is often hosted at URIs with embedded versions, such as: