-
-
Notifications
You must be signed in to change notification settings - Fork 132
/
dream.mli
2593 lines (2047 loc) · 90.9 KB
/
dream.mli
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
(* This file is part of Dream, released under the MIT license. See LICENSE.md
for details, or visit https://github.com/aantron/dream.
Copyright 2021 Anton Bachin *)
(** {1 Types}
Dream is built on just five types. The first two are the data types of
Dream. Both are abstract, even though they appear to have definitions: *)
type request = client message
(** HTTP requests, such as [GET /something HTTP/1.1]. See
{!section-requests}. *)
and response = server message
(** HTTP responses, such as [200 OK]. See {!section-responses}. *)
(** The remaining three types are for building up Web apps. *)
and handler = request -> response promise
(** Handlers are asynchronous functions from requests to responses. Example
{{:https://github.com/aantron/dream/tree/master/example/1-hello#files}
[1-hello]} \[{{:http://dream.as/1-hello} playground}\] shows the simplest
handler, an anonymous function which we pass to {!Dream.run}. This creates a
complete Web server! You can also see the Reason version in example
{{:https://github.com/aantron/dream/tree/master/example/r-hello#files}
[r-hello]}.
{[
let () =
Dream.run (fun _ ->
Dream.html "Good morning, world!")
]} *)
and middleware = handler -> handler
(** Middlewares are functions that take a {!handler}, and run some code before
or after — producing a “bigger” handler. Example
{{:https://github.com/aantron/dream/tree/master/example/2-middleware#files}
[2-middleware]} inserts the {!Dream.logger} middleware into a Web app:
{[
let () =
Dream.run
@@ Dream.logger
@@ fun _ -> Dream.html "Good morning, world!"
]}
Examples
{{:https://github.com/aantron/dream/tree/master/example/4-counter#files}
[4-counter]} \[{{:http://dream.as/4-counter} playground}\] and
{{:https://github.com/aantron/dream/tree/master/example/5-promise#files}
[5-promise]} show user-defined middlewares:
{[
let count_requests inner_handler request =
count := !count + 1;
inner_handler request
]}
In case you are wondering why the example middleware [count_requests] takes
two arguments, while the type says it should take only one, it's because:
{[
middleware
= handler -> handler
= handler -> (request -> response promise)
= handler -> request -> response promise
]} *)
and route
(** Routes tell {!Dream.router} which handler to select for each request. See
{!section-routing} and example
{{:https://github.com/aantron/dream/tree/master/example/3-router#files}
[3-router]} \[{{:http://dream.as/3-router/echo/foo} playground}\]. Routes
are created by helpers such as {!Dream.get} and {!Dream.scope}:
{[
Dream.router [
Dream.scope "/admin" [Dream.memory_sessions] [
Dream.get "/" admin_handler;
Dream.get "/logout" admin_logout_handler;
];
]
]} *)
(** {2 Algebra}
The three handler-related types have a vaguely algebraic interpretation:
- Each literal {!handler} is an atom.
- {!type-middleware} is for sequential composition (product-like).
{!Dream.no_middleware} is {b 1}.
- {!type-route} is for alternative composition (sum-like). {!Dream.no_route}
is {b 0}.
{!Dream.scope} implements a left distributive law, making Dream a ring-like
structure. *)
(** {2 Helpers} *)
and 'a message = 'a Dream_pure.Message.message
(** ['a message], pronounced “any message,” allows some functions to take either
{!type-request} or {!type-response} as arguments, because both are defined
in terms of ['a message]. For example, in {!section-headers}:
{[
val Dream.header : string -> 'a message -> string option
]} *)
and client = Dream_pure.Message.client
and server = Dream_pure.Message.server
(** Type parameters for {!message} for {!type-request} and {!type-response},
respectively. These are “phantom” types. They have no meaning other than
they are different from each other. Dream only ever creates [client message]
and [server message]. [client] and [server] are never mentioned again in the
docs. *)
(* TODO These docs need to be clarified. *)
(* TODO Hide all the Dream_pure type equalities. *)
and 'a promise = 'a Lwt.t
(** Dream uses {{:https://github.com/ocsigen/lwt} Lwt} for promises and
asynchronous I/O. See example
{{:https://github.com/aantron/dream/tree/master/example/5-promise#files}
[5-promise]} \[{{:http://dream.as/5-promise} playground}\].
Use [raise] to reject promises. If you are writing a library, you may prefer
using
{{:https://github.com/ocsigen/lwt/blob/9943ba77a5508feaea5e1fb60b011db4179f9c61/src/core/lwt.mli#L459}
[Lwt.fail]} in some places, in order to avoid clobbering your user's current
exception backtrace — though, in most cases, you should still extend it with
[raise] and [let%lwt], instead. *)
(** {1 Methods} *)
type method_ = [
| `GET
| `POST
| `PUT
| `DELETE
| `HEAD
| `CONNECT
| `OPTIONS
| `TRACE
| `PATCH
| `Method of string
]
(** HTTP request methods. See
{{:https://tools.ietf.org/html/rfc7231#section-4.3} RFC 7231 §4.2},
{{:https://tools.ietf.org/html/rfc5789#page-2} RFC 5789 §2}, and
{{:https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods} MDN}. *)
val method_to_string : [< method_ ] -> string
(** Evaluates to a string representation of the given method. For example,
[`GET] is converted to ["GET"]. *)
val string_to_method : string -> method_
(** Evaluates to the {!type-method_} corresponding to the given method
string. *)
val methods_equal : [< method_ ] -> [< method_ ] -> bool
(** Compares two methods, such that equal methods are detected even if one is
represented as a string. For example,
{[
Dream.methods_equal `GET (`Method "GET") = true
]} *)
val normalize_method : [< method_ ] -> method_
(** Converts methods represented as strings to variants. Methods generated by
Dream are always normalized.
{[
Dream.normalize_method (`Method "GET") = `GET
]} *)
(** {1:status_codes Status codes} *)
type informational = [
| `Continue
| `Switching_Protocols
]
(** Informational ([1xx]) status codes. See
{{:https://tools.ietf.org/html/rfc7231#section-6.2} RFC 7231 §6.2} and
{{:https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#information_responses}
MDN}. [101 Switching Protocols] is generated internally by
{!Dream.val-websocket}. It is usually not necessary to use it directly. *)
type successful = [
| `OK
| `Created
| `Accepted
| `Non_Authoritative_Information
| `No_Content
| `Reset_Content
| `Partial_Content
]
(** Successful ([2xx]) status codes. See
{{:https://tools.ietf.org/html/rfc7231#section-6.3} RFC 7231 §6.3},
{{:https://tools.ietf.org/html/rfc7233#section-4.1} RFC 7233 §4.1} and
{{:https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#successful_responses}
MDN}. The most common is [200 OK]. *)
type redirection = [
| `Multiple_Choices
| `Moved_Permanently
| `Found
| `See_Other
| `Not_Modified
| `Temporary_Redirect
| `Permanent_Redirect
]
(** Redirection ([3xx]) status codes. See
{{:https://tools.ietf.org/html/rfc7231#section-6.4} RFC 7231 §6.4} and
{{:https://tools.ietf.org/html/rfc7538#section-3} RFC 7538 §3}, and
{{:https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#redirection_messages}
MDN}. Use [303 See Other] to direct clients to follow up with a [GET]
request, especially after a form submission. Use [301 Moved Permanently]
for permanent redirections. *)
type client_error = [
| `Bad_Request
| `Unauthorized
| `Payment_Required
| `Forbidden
| `Not_Found
| `Method_Not_Allowed
| `Not_Acceptable
| `Proxy_Authentication_Required
| `Request_Timeout
| `Conflict
| `Gone
| `Length_Required
| `Precondition_Failed
| `Payload_Too_Large
| `URI_Too_Long
| `Unsupported_Media_Type
| `Range_Not_Satisfiable
| `Expectation_Failed
| `Misdirected_Request
| `Too_Early
| `Upgrade_Required
| `Precondition_Required
| `Too_Many_Requests
| `Request_Header_Fields_Too_Large
| `Unavailable_For_Legal_Reasons
]
(** Client error ([4xx]) status codes. The most common are [400 Bad Request],
[401 Unauthorized], [403 Forbidden], and, of course, [404 Not Found].
See
{{:https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#client_error_responses}
MDN}, and
- {{:https://tools.ietf.org/html/rfc7231#section-6.5} RFC 7231 §6.5} for
most client error status codes.
- {{:https://tools.ietf.org/html/rfc7233#section-4.4} RFC 7233 §4.4} for
[416 Range Not Satisfiable].
- {{:https://tools.ietf.org/html/rfc7540#section-9.1.2} RFC 7540 §9.1.2} for
[421 Misdirected Request].
- {{:https://tools.ietf.org/html/rfc8470#section-5.2} RFC 8470 §5.2} for
[425 Too Early].
- {{:https://tools.ietf.org/html/rfc6585} RFC 6585} for
[428 Precondition Required], [429 Too Many Requests], and [431 Request
Headers Too Large].
- {{:https://tools.ietf.org/html/rfc7725} RFC 7725} for
[451 Unavailable For Legal Reasons]. *)
type server_error = [
| `Internal_Server_Error
| `Not_Implemented
| `Bad_Gateway
| `Service_Unavailable
| `Gateway_Timeout
| `HTTP_Version_Not_Supported
]
(** Server error ([5xx]) status codes. See
{{:https://tools.ietf.org/html/rfc7231#section-6.6} RFC 7231 §6.6} and
{{:https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#server_error_responses}
MDN}. The most common of these is [500 Internal Server Error]. *)
type standard_status = [
| informational
| successful
| redirection
| client_error
| server_error
]
(** Sum of all the status codes declared above. *)
type status = [
| standard_status
| `Status of int
]
(** Status codes, including codes directly represented as integers. See the
types above for the full list and references. *)
val status_to_string : [< status ] -> string
(** Evaluates to a string representation of the given status. For example,
[`Not_Found] and [`Status 404] are both converted to ["Not Found"]. Numbers
are used for unknown status codes. For example, [`Status 567] is converted
to ["567"]. *)
val status_to_reason : [< status ] -> string option
(** Converts known status codes to their string representations. Evaluates to
[None] for unknown status codes. *)
val status_to_int : [< status ] -> int
(** Evaluates to the numeric value of the given status code. *)
val int_to_status : int -> status
(** Evaluates to the symbolic representation of the status code with the given
number. *)
val is_informational : [< status ] -> bool
(** Evaluates to [true] if the given status is either from type
{!Dream.informational}, or is in the range [`Status 100] — [`Status 199]. *)
val is_successful : [< status ] -> bool
(** Like {!Dream.is_informational}, but for type {!Dream.successful} and numeric
codes [2xx]. *)
val is_redirection : [< status ] -> bool
(** Like {!Dream.is_informational}, but for type {!Dream.redirection} and
numeric codes [3xx]. *)
val is_client_error : [< status ] -> bool
(** Like {!Dream.is_informational}, but for type {!Dream.client_error} and
numeric codes [4xx]. *)
val is_server_error : [< status ] -> bool
(** Like {!Dream.is_informational}, but for type {!Dream.server_error} and
numeric codes [5xx]. *)
val status_codes_equal : [< status ] -> [< status ] -> bool
(** Compares two status codes, such that equal codes are detected even if one is
represented as a number. For example,
{[
Dream.status_codes_equal `Not_Found (`Status 404) = true
]} *)
val normalize_status : [< status ] -> status
(** Converts status codes represented as numbers to variants. Status codes
generated by Dream are always normalized.
{[
Dream.normalize_status (`Status 404) = `Not_Found
]} *)
(** {1 Requests} *)
val client : request -> string
(** Client sending the request. For example, ["127.0.0.1:56001"]. *)
val tls : request -> bool
(** Whether the request was sent over a TLS connection. *)
val method_ : request -> method_
(** Request method. For example, [`GET]. *)
val target : request -> string
(** Request target. For example, ["/foo/bar"]. *)
(**/**)
val prefix : request -> string
(**/**)
(**/**)
val path : request -> string list
[@@ocaml.deprecated
"Router path access is being removed from the API. Comment at
https://github.com/aantron/dream/issues
"]
(** Parsed request path. For example, ["foo"; "bar"]. *)
(* TODO If not removing this, move it to section Routing. *)
(**/**)
val set_client : request -> string -> unit
(** Replaces the client. See {!Dream.val-client}. *)
(**/**)
val with_client : string -> request -> request
[@@ocaml.deprecated
"Use Dream.set_client. See
https://aantron.github.io/dream/#val-set_client
"]
(**/**)
val set_method_ : request -> [< method_ ] -> unit
(** Replaces the method. See {!Dream.type-method_}. *)
(**/**)
val with_method_ : [< method_ ] -> request -> request
[@@ocaml.deprecated
"Use Dream.set_method_. See
https://aantron.github.io/dream/#val-set_method_
"]
(**/**)
(**/**)
val with_path : string list -> request -> request
[@@ocaml.deprecated
"Router path access is being removed from the API. Comment at
https://github.com/aantron/dream/issues
"]
(** Replaces the path. See {!Dream.val-path}. *)
(**/**)
val query : request -> string -> string option
(** First query parameter with the given name. See
{{:https://tools.ietf.org/html/rfc3986#section-3.4} RFC 3986 §3.4} and
example
{{:https://github.com/aantron/dream/tree/master/example/w-query#files}
[w-query]}. *)
val queries : request -> string -> string list
(** All query parameters with the given name. *)
val all_queries : request -> (string * string) list
(** Entire query string as a name-value list. *)
(** {1 Responses} *)
val response :
?status:[< status ] ->
?code:int ->
?headers:(string * string) list ->
string -> response
(** Creates a new {!type-response} with the given string as body. [~code] and
[~status] are two ways to specify the {!type-status} code, which is [200 OK]
by default. The headers are empty by default.
Note that browsers may interpret lack of a [Content-Type:] header as if its
value were [application/octet-stream] or [text/html; charset=us-ascii],
which will prevent correct interpretation of UTF-8 strings. Either add a
[Content-Type:] header using [~headers] or {!Dream.add_header}, or use a
wrapper like {!Dream.html}. The modern [Content-Type:] for HTML is
[text/html; charset=utf-8]. See {!Dream.text_html}. *)
val respond :
?status:[< status ] ->
?code:int ->
?headers:(string * string) list ->
string -> response promise
(** Same as {!Dream.val-response}, but the new {!type-response} is wrapped in a
{!type-promise}. *)
val html :
?status:[< status ] ->
?code:int ->
?headers:(string * string) list ->
string -> response promise
(** Same as {!Dream.respond}, but adds [Content-Type: text/html; charset=utf-8].
See {!Dream.text_html}.
As your Web app develops, consider adding [Content-Security-Policy] headers,
as described in example
{{:https://github.com/aantron/dream/tree/master/example/w-content-security-policy#files}
[w-content-security-policy]}. These headers are completely optional, but
they can provide an extra layer of defense for a mature app. *)
val json :
?status:[< status ] ->
?code:int ->
?headers:(string * string) list ->
string -> response promise
(** Same as {!Dream.respond}, but adds [Content-Type: application/json]. See
{!Dream.application_json}. *)
val redirect :
?status:[< redirection ] ->
?code:int ->
?headers:(string * string) list ->
request -> string -> response promise
(** Creates a new {!type-response}. Adds a [Location:] header with the given
string. The default status code is [303 See Other], for a temporary
redirection. Use [~status:`Moved_Permanently] or [~code:301] for a permanent
redirection.
If you use [~code], be sure the number follows the pattern [3xx], or most
browsers and other clients won't actually perform a redirect.
The {!type-request} is used for retrieving the site prefix, if the string is
an absolute path. Most applications don't have a site prefix. *)
val empty :
?headers:(string * string) list ->
status -> response promise
(** Same as {!Dream.val-response} with the empty string for a body. *)
val status : response -> status
(** Response {!type-status}. For example, [`OK]. *)
val set_status : response -> status -> unit
(** Sets the response status. *)
(** {1 Headers} *)
val header : 'a message -> string -> string option
(** First header with the given name. Header names are case-insensitive. See
{{:https://tools.ietf.org/html/rfc7230#section-3.2} RFC 7230 §3.2} and
{{:https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers} MDN}. *)
val headers : 'a message -> string -> string list
(** All headers with the given name. *)
val all_headers : 'a message -> (string * string) list
(** Entire header set as name-value list. *)
val has_header : 'a message -> string -> bool
(** Whether the message has a header with the given name. *)
val add_header : 'a message -> string -> string -> unit
(** Appends a header with the given name and value. Does not remove any existing
headers with the same name. *)
(* TODO Does this fit on one line in the docs now? *)
val drop_header : 'a message -> string -> unit
(** Removes all headers with the given name. *)
val set_header : 'a message -> string -> string -> unit
(** Equivalent to {!Dream.drop_header} followed by {!Dream.add_header}. *)
(**/**)
val with_header : string -> string -> 'a message -> 'a message
[@@ocaml.deprecated
"Use Dream.set_header. See
https://aantron.github.io/dream/#val-with_header
"]
(**/**)
(** {1 Cookies}
{!Dream.set_cookie} and {!Dream.cookie} are designed for round-tripping
secure cookies. The most secure settings applicable to the current server
are inferred automatically. See example
{{:https://github.com/aantron/dream/tree/master/example/c-cookie#files}
[c-cookie]} \[{{:http://dream.as/c-cookie} playground}\].
{[
Dream.set_cookie response request "my.cookie" "foo"
Dream.cookie request "my.cookie"
]}
The {!Dream.cookie} call evaluates to [Some "foo"], but the actual cookie
that is exchanged may look like:
{v
__Host-my.cookie=AL7NLA8-so3e47uy0R5E2MpEQ0TtTWztdhq5pTEUT7KSFg; \
Path=/; Secure; HttpOnly; SameSite=Strict
v}
{!Dream.set_cookie} has a large number of optional arguments for tweaking
the inferred security settings. If you use them, pass the same arguments to
{!Dream.cookie} to automatically undo the result. *)
val set_cookie :
?prefix:[< `Host | `Secure ] option ->
?encrypt:bool ->
?expires:float ->
?max_age:float ->
?domain:string ->
?path:string option ->
?secure:bool ->
?http_only:bool ->
?same_site:[< `Strict | `Lax | `None ] option ->
response -> request -> string -> string -> unit
(** Appends a [Set-Cookie:] header to the {!type-response}. Infers the most
secure defaults from the {!type-request}.
{[
Dream.set_cookie request response "my.cookie" "value"
]}
Use the {!Dream.set_secret} middleware, or the Web app will not be able to
decrypt cookies from prior starts.
See example
{{:https://github.com/aantron/dream/tree/master/example/c-cookie#files}
[c-cookie]}.
Most of the optional arguments are for overriding inferred defaults.
[~expires] and [~max_age] are independently useful. In particular, to delete
a cookie, use [~expires:0.]
- [~prefix] sets [__Host-], [__Secure-], or no prefix, from most secure to
least. A conforming client will refuse to accept the cookie if [~domain],
[~path], and [~secure] don't match the constraints implied by the prefix.
By default, {!Dream.set_cookie} chooses the most restrictive prefix based
on the other settings and the {!type-request}. See
{{:https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-07#section-4.1.3}
RFC 6265bis §4.1.3} and
{{:https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#Cookie_prefixes}
MDN}.
- [~encrypt:false] disables cookie encryption. In that case, you must make
sure that the cookie value does not contain [=], [;], or newlines. The
easiest way to do so is to pass the value through an encoder like
{!Dream.to_base64url}. See {!Dream.set_secret}.
- [~expires] sets the [Expires=] attribute. The value is compatible with
{{:https://caml.inria.fr/pub/docs/manual-ocaml/libref/Unix.html#VALgettimeofday}
[Unix.gettimeofday]}. See
{{:https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-07#section-4.1.2.1}
RFC 6265bis §4.1.2.1} and
{{:https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#define_the_lifetime_of_a_cookie}
MDN}.
- [~max_age] sets the [Max-Age=] attribute. See
{{:https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-07#section-4.1.2.2}
RFC 6265bis §4.1.2.2} and
{{:https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#define_the_lifetime_of_a_cookie}
MDN}.
- [~domain] sets the [Domain=] attribute. See
{{:https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-07#section-4.1.2.3}
RFC 6265bis §4.1.2.3} and
{{:https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#Domain_attribute}
MDN}.
- [~path] sets the [Path=] attribute. By default, [Path=] set to the site
prefix in the {!type-request}, which is usually [/]. See
{{:https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-07#section-4.1.2.4}
RFC 6265bis §4.1.2.4} and
{{:https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#Path_attribute}
MDN}.
- [~secure] sets the [Secure] attribute. By default, [Secure] is set if
{!Dream.tls} is [true] for the {!type-request}. See
{{:https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-07#section-4.1.2.5}
RFC 6265bis §4.1.2.5} and
{{:https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies}
MDN}.
- [~http_only] sets the [HttpOnly] attribute. [HttpOnly] is set by default.
See
{{:https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-07#section-4.1.2.6}
RFC 6265bis §4.1.2.6} and
{{:https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies}
MDN}.
- [~same_site] sets the [SameSite=] attribute. [SameSite] is set to [Strict]
by default. See
{{:https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-07#section-4.1.2.7}
RFC 6265bis §4.1.2.7} and
{{:https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#SameSite_attribute}
MDN}.
{!Dream.to_set_cookie} is a “raw” version of this function that does not do
any inference. *)
val drop_cookie :
?prefix:[< `Host | `Secure ] option ->
?domain:string ->
?path:string option ->
?secure:bool ->
?http_only:bool ->
?same_site:[< `Strict | `Lax | `None ] option ->
response -> request -> string -> unit
(** Deletes the given cookie.
This function works by calling {!Dream.set_cookie}, and setting the cookie
to expire in the past. Pass all the same optional values that you would pass
to {!Dream.set_cookie} to make sure that the same cookie is deleted. *)
val cookie :
?prefix:[< `Host | `Secure ] option ->
?decrypt:bool ->
?domain:string ->
?path:string option ->
?secure:bool ->
request -> string -> string option
(** First cookie with the given name. See example
{{:https://github.com/aantron/dream/tree/master/example/c-cookie#files}
[c-cookie]}.
{[
Dream.cookie request "my.cookie"
]}
Pass the same optional arguments as to {!Dream.set_cookie} for the same
cookie. This will allow {!Dream.cookie} to infer the cookie name prefix,
implementing a transparent cookie round trip with the most secure attributes
applicable. *)
val all_cookies : request -> (string * string) list
(** All cookies, with raw names and values. *)
(** {1 Bodies} *)
val body : 'a message -> string promise
(** Retrieves the entire body. See example
{{:https://github.com/aantron/dream/tree/master/example/6-echo#files}
[6-echo]}. *)
val set_body : 'a message -> string -> unit
(** Replaces the body. *)
(**/**)
val with_body : string -> response -> response
[@@ocaml.deprecated
"Use Dream.set_body. See
https://aantron.github.io/dream/#val-set_body
"]
(**/**)
(** {1 Streams} *)
type stream
(** Gradual reading of request bodies or gradual writing of response bodies. *)
val body_stream : request -> stream
(** A stream that can be used to gradually read the request's body. *)
val stream :
?status:[< status ] ->
?code:int ->
?headers:(string * string) list ->
?close:bool ->
(stream -> unit promise) -> response promise
(** Creates a response with a {!type-stream} open for writing, and passes the
stream to the callback when it is ready. See example
{{:https://github.com/aantron/dream/tree/master/example/j-stream#files}
[j-stream]}.
{[
fun request ->
Dream.stream (fun stream ->
Dream.write stream "foo")
]}
[Dream.stream] automatically closes the stream when the callback returns or
raises an exception. Pass [~close:false] to suppress this behavior. *)
val read : stream -> string option promise
(** Retrieves a body chunk. See example
{{:https://github.com/aantron/dream/tree/master/example/j-stream#files}
[j-stream]}. *)
(* TODO Document difference between receiving a request and receiving on a
WebSocket. *)
(**/**)
val with_stream : response -> response
[@@ocaml.deprecated
"Use Dream.stream instead. See
https://aantron.github.io/dream/#val-set_stream
"]
(**/**)
val write : stream -> string -> unit promise
(** Streams out the string. The promise is fulfilled when the response can
accept more writes. *)
(* TODO Document clearly which of the writing functions can raise exceptions. *)
val flush : stream -> unit promise
(** Flushes the stream's write buffer. Data is sent to the client. *)
val close : stream -> unit promise
(** Closes the stream. *)
(** {2 Low-level streaming}
Note: this part of the API is still a work in progress. *)
type buffer =
(char, Bigarray.int8_unsigned_elt, Bigarray.c_layout) Bigarray.Array1.t
(** Byte arrays in the C heap. See
{{:http://caml.inria.fr/pub/docs/manual-ocaml/libref/Bigarray.Array1.html}
[Bigarray.Array1]}. This type is also found in several libraries installed
by Dream, so their functions can be used with {!Dream.buffer}:
- {{:https://github.com/inhabitedtype/bigstringaf/blob/353cb283aef4c261597f68154eb27a138e7ef112/lib/bigstringaf.mli}
[Bigstringaf.t]} in bigstringaf.
- {{:https://ocsigen.org/lwt/latest/api/Lwt_bytes} [Lwt_bytes.t]} in Lwt.
- {{:https://github.com/mirage/ocaml-cstruct/blob/9a8b9a79bdfa2a1b8455bc26689e0228cc6fac8e/lib/cstruct.mli#L139}
[Cstruct.buffer]} in Cstruct. *)
(* TODO Probably even close can be made optional. exn can be made optional. *)
(* TODO Argument order? *)
val read_stream :
stream ->
data:(buffer -> int -> int -> bool -> bool -> unit) ->
flush:(unit -> unit) ->
ping:(buffer -> int -> int -> unit) ->
pong:(buffer -> int -> int -> unit) ->
close:(int -> unit) ->
exn:(exn -> unit) ->
unit
(** Waits for the next stream event, and calls:
- [~data] with an offset and length, if a {!type-buffer} is received,
~ [~flush] if a flush request is received,
- [~ping] if a ping is received (WebSockets only),
- [~pong] if a pong is received (WebSockets only),
- [~close] if the stream is closed, and
- [~exn] to report an exception. *)
val write_stream :
stream ->
buffer -> int -> int ->
bool -> bool ->
close:(int -> unit) ->
exn:(exn -> unit) ->
(unit -> unit) ->
unit
(** Writes a {!type-buffer} into the stream:
{[
write_stream stream buffer offset length binary fin ~close ~exn callback
]}
[write_stream] calls one of its three callback functions, depending on what
happens with the write:
- [~close] if the stream is closed before the write completes,
- [~exn] to report an exception during or before the write,
- [callback] to report that the write has succeeded and the stream can
accept another write.
[binary] and [fin] are for WebSockets only. [binary] marks the stream as
containing binary (non-text) data, and [fin] sets the [FIN] bit, indicating
the end of a message. These two parameters are ignored by non-WebSocket
streams. *)
val flush_stream :
stream ->
close:(int -> unit) ->
exn:(exn -> unit) ->
(unit -> unit) ->
unit
(** Requests the stream be flushed. The callbacks have the same meaning as in
{!write_stream}. *)
val ping_stream :
stream ->
buffer -> int -> int ->
close:(int -> unit) ->
exn:(exn -> unit) ->
(unit -> unit) ->
unit
(** Sends a ping frame on the WebSocket stream. The buffer is typically empty,
but may contain up to 125 bytes of data. *)
val pong_stream :
stream ->
buffer -> int -> int ->
close:(int -> unit) ->
exn:(exn -> unit) ->
(unit -> unit) ->
unit
(** Like {!ping_stream}, but sends a pong event. *)
val close_stream : stream -> int -> unit
(** Closes the stream. The integer parameter is a WebSocket close code, and is
ignored by non-WebSocket streams. *)
val abort_stream : stream -> exn -> unit
(** Aborts the stream, causing all readers and writers to receive the given
exception. *)
(**/**)
val write_buffer :
?offset:int -> ?length:int -> response -> buffer -> unit promise
[@@ocaml.deprecated
"Use Dream.write_stream. See
https://aantron.github.io/dream/#val-write_stream
"]
(**/**)
(* TODO Ergonomics of this stream surface API. *)
(** {1 WebSockets} *)
type websocket
(** A WebSocket connection. See {{:https://tools.ietf.org/html/rfc6455} RFC
6455} and
{{:https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API} MDN}. *)
val websocket :
?headers:(string * string) list ->
?close:bool ->
(websocket -> unit promise) -> response promise
(** Creates a fresh [101 Switching Protocols] response. Once this response is
returned to Dream's HTTP layer, the callback is passed a new
{!type-websocket}, and the application can begin using it. See example
{{:https://github.com/aantron/dream/tree/master/example/k-websocket#files}
[k-websocket]} \[{{:http://dream.as/k-websocket} playground}\].
{[
let my_handler = fun request ->
Dream.websocket (fun websocket ->
let%lwt () = Dream.send websocket "Hello, world!");
]}
[Dream.websocket] automatically closes the WebSocket when the callback
returns or raises an exception. Pass [~close:false] to suppress this
behavior. *)
type text_or_binary = [ `Text | `Binary ]
(** See {!send} and {!receive_fragment}. *)
type end_of_message = [ `End_of_message | `Continues ]
(** See {!send} and {!receive_fragment}. *)
val send :
?text_or_binary:[< text_or_binary ] ->
?end_of_message:[< end_of_message ] ->
websocket -> string -> unit promise
(** Sends a single WebSocket message. The WebSocket is ready another message
when the promise resolves.
With [~text_or_binary:`Text], the default, the message is interpreted as a
UTF-8 string. The client will receive it transcoded to JavaScript's UTF-16
representation.
With [~text_or_binary:`Binary], the message will be received unmodified, as
either a [Blob] or an [ArrayBuffer]. See
{{:https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/binaryType}
MDN, [WebSocket.binaryType]}.
[~end_of_message] is ignored for now, as the WebSocket library underlying
Dream does not support sending message fragments yet. *)
val receive : websocket -> string option promise
(** Receives a message. If the WebSocket is closed before a complete message
arrives, the result is [None]. *)
val receive_fragment :
websocket -> (string * text_or_binary * end_of_message) option promise
(** Receives a single fragment of a message, streaming it. *)
val close_websocket : ?code:int -> websocket -> unit promise
(** Closes the WebSocket. [~code] is usually not necessary, but is needed for
some protocols based on WebSockets. See
{{:https://tools.ietf.org/html/rfc6455#section-7.4} RFC 6455 §7.4}. *)
(** {1 JSON}
Dream presently recommends using
{{:https://github.com/ocaml-community/yojson#readme} Yojson}. See also
{{:https://github.com/janestreet/ppx_yojson_conv#readme} ppx_yojson_conv}
for generating JSON parsers and serializers for OCaml data types.
See example
{{:https://github.com/aantron/dream/tree/master/example/e-json#files}
[e-json]}. *)
val origin_referrer_check : middleware
(** CSRF protection for AJAX requests. Either the method must be [`GET] or
[`HEAD], or:
- [Origin:] or [Referer:] must be present, and
- their value must match [Host:]
Responds with [400 Bad Request] if the check fails. See example
{{:https://github.com/aantron/dream/tree/master/example/e-json#security}
[e-json]}.
Implements the
{{:https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#verifying-origin-with-standard-headers}
OWASP {i Verifying Origin With Standard Headers}} CSRF defense-in-depth
technique, which is good enough for basic usage. Do not allow [`GET] or
[`HEAD] requests to trigger important side effects if relying only on
{!Dream.origin_referrer_check}.
Future extensions to this function may use [X-Forwarded-Host] or host
whitelists.
For more thorough protection, generate CSRF tokens with {!Dream.csrf_token},
send them to the client (for instance, in [<meta>] tags of a single-page
application), and require their presence in an [X-CSRF-Token:] header. *)
(** {1 Forms}
{!Dream.csrf_tag} and {!Dream.val-form} round-trip secure forms.
{!Dream.csrf_tag} is used inside a form template to generate a hidden field
with a CSRF token:
{[
<form method="POST" action="/">
<%s! Dream.csrf_tag request %>
<input name="my.field">
</form>
]}