forked from anmonteiro/ocaml-h2
-
Notifications
You must be signed in to change notification settings - Fork 0
/
h2.mli
952 lines (779 loc) · 37.8 KB
/
h2.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
(*----------------------------------------------------------------------------
* Copyright (c) 2017 Inhabited Type LLC.
* Copyright (c) 2019 Antonio N. Monteiro.
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of the author nor the names of his contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*---------------------------------------------------------------------------*)
(** H2 is a high-performance, memory-efficient, and scalable HTTP/2
implementation for OCaml. It is based on the concepts in http/af, and
therefore uses the Angstrom and Faraday libraries to implement the parsing
and serialization layers of the HTTP/2 standard. It also preserves the same
API as http/af wherever possible.
Not unlike http/af, the user should be familiar with HTTP, and the basic
principles of memory management and vectorized IO in order to use this
library. *)
(** {2 Basic HTTP Types} *)
module Method : module type of Httpaf.Method
(** Request Method
The request method token is the primary source of request semantics; it
indicates the purpose for which the client has made this request and what is
expected by the client as a successful result.
See {{:https://tools.ietf.org/html/rfc7231#section-4} RFC7231§4} for more
details.
This module is a proxy to [Httpaf.Method] and is included in h2 for
convenience. *)
(** Response Status Codes
The status-code element is a three-digit integer code giving the result of
the attempt to understand and satisfy the request.
See {{:https://tools.ietf.org/html/rfc7231#section-6} RFC7231§6} for more
details.
This module is a strict superset of [Httpaf.Status]. Even though the HTTP/2
specification removes support for the [Switching_protocols] status code, h2
keeps it for the sake of higher level interaction between OCaml libraries
that support both HTTP/1 and HTTP/2.
See {{:https://tools.ietf.org/html/rfc7540#section-8.1.1} RFC7540§8.1.1} for
more details. *)
module Status : sig
include
module type of Httpaf.Status
with type client_error := Httpaf.Status.client_error
and type standard := Httpaf.Status.standard
and type t := Httpaf.Status.t
type client_error =
[ Httpaf.Status.client_error
| `Misdirected_request
]
(** The 4xx (Client Error) class of status code indicates that the client
seems to have erred.
See {{:https://tools.ietf.org/html/rfc7231#section-6.5} RFC7231§6.5} for
more details.
In addition to http/af, this type also includes the 421 (Misdirected
Request) tag. See
{{:https://tools.ietf.org/html/rfc7540#section-9.1.2} RFC7540§9.1.2} for
more details. *)
type standard =
[ Httpaf.Status.standard
| client_error
]
(** The status codes defined in the HTTP/1.1 RFCs, excluding the
[Switching Protocols] status and including the [Misdirected Request] as
per the HTTP/2 RFC.
See {{:https://tools.ietf.org/html/rfc7540#section-8.1.1} RFC7540§8.1.1}
and {{:https://tools.ietf.org/html/rfc7540#section-9.1.2} RFC7540§9.1.2}
for more details. *)
type t =
[ standard
| `Code of int
]
(** The standard codes along with support for custom codes. *)
val default_reason_phrase : standard -> string
(** [default_reason_phrase standard] is the example reason phrase provided by
RFC7231 for the [standard] status code. The RFC allows servers to use
reason phrases besides these in responses. *)
val to_code : t -> int
(** [to_code t] is the integer representation of [t]. *)
val of_code : int -> t
(** [of_code i] is the [t] representation of [i]. [of_code] raises [Failure]
if [i] is not a positive three-digit number. *)
val unsafe_of_code : int -> t
(** [unsafe_of_code i] is equivalent to [of_code i], except it accepts any
positive code, regardless of the number of digits it has. On negative
codes, it will still raise [Failure]. *)
val is_informational : t -> bool
(** [is_informational t] is [true] iff [t] belongs to the Informational class
of status codes. *)
val is_successful : t -> bool
(** [is_successful t] is [true] iff [t] belongs to the Successful class of
status codes. *)
val is_redirection : t -> bool
(** [is_redirection t] is [true] iff [t] belongs to the Redirection class of
status codes. *)
val is_client_error : t -> bool
(** [is_client_error t] is [true] iff [t] belongs to the Client Error class of
status codes. *)
val is_server_error : t -> bool
(** [is_server_error t] is [true] iff [t] belongs to the Server Error class of
status codes. *)
val is_error : t -> bool
(** [is_server_error t] is [true] iff [t] belongs to the Client Error or
Server Error class of status codes. *)
val to_string : t -> string
val of_string : string -> t
val pp_hum : Format.formatter -> t -> unit
end
(** Header Fields
Each header field consists of a lowercase {b field name} and a
{b field value}. Per the HTTP/2 specification, header field names {b must}
be converted to lowercase prior to their encoding in HTTP/2 (see
{{:https://tools.ietf.org/html/rfc7540#section-8.1.2} RFC7540§8.1.2} for
more details). h2 does {b not} convert field names to lowercase; it is
therefore the responsibility of the caller of the functions contained in
this module to use lowercase names for header fields.
The order in which header fields {i with differing field names} are received
is not significant, except for pseudo-header fields, which {b must} appear
in header blocks before regular fields (see
{{:https://tools.ietf.org/html/rfc7540#section-8.1.2.1} RFC7540§8.1.2.1} for
more details).
A sender MUST NOT generate multiple header fields with the same field name
in a message unless either the entire field value for that header field is
defined as a comma-separated list or the header field is a well-known
exception, e.g., [Set-Cookie].
A recipient MAY combine multiple header fields with the same field name into
one "field-name: field-value" pair, without changing the semantics of the
message, by appending each subsequent field value to the combined field
value in order, separated by a comma.
{i The order in which header fields with the same field name are received is
therefore significant to the interpretation of the combined field value};
a proxy MUST NOT change the order of these field values when forwarding a
message.
{i Note.} Unless otherwise specified, all operations preserve header field
order and all reference to equality on names is assumed to be
case-insensitive.
See {{:https://tools.ietf.org/html/rfc7230#section-3.2} RFC7230§3.2} for
more details. *)
module Headers : sig
type t
(** The type of a group of header fields. *)
type name = string
(** The type of a lowercase header name. *)
type value = string
(** The type of a header value. *)
(** {3 Constructor} *)
val empty : t
(** [empty] is the empty collection of header fields. *)
val of_list : (name * value) list -> t
(** [of_list assoc] is a collection of header fields defined by the
association list [assoc]. [of_list] assumes the order of header fields in
[assoc] is the intended transmission order. The following equations should
hold:
- [to_list (of_list lst) = lst]
- [get (of_list [("k", "v1"); ("k", "v2")]) "k" = Some "v2"]. *)
val of_rev_list : (name * value) list -> t
(** [of_list assoc] is a collection of header fields defined by the
association list [assoc]. [of_list] assumes the order of header fields in
[assoc] is the {i reverse} of the intended trasmission order. The
following equations should hold:
- [to_list (of_rev_list lst) = List.rev lst]
- [get (of_rev_list [("k", "v1"); ("k", "v2")]) "k" = Some "v1"]. *)
val to_list : t -> (name * value) list
(** [to_list t] is the association list of header fields contained in [t] in
transmission order. *)
val to_rev_list : t -> (name * value) list
(** [to_rev_list t] is the association list of header fields contained in [t]
in {i reverse} transmission order. *)
val add : t -> ?sensitive:bool -> name -> value -> t
(** [add t ?sensitive name value] is a collection of header fields that is the
same as [t] except with [(name, value)] added at the end of the
trasmission order. Additionally, [sensitive] specifies whether this header
field should not be compressed by HPACK and instead encoded as a
never-indexed literal (see
{{:https://tools.ietf.org/html/rfc7541#section-7.1.3} RFC7541§7.1.3} for
more details).
The following equations should hold:
- [get (add t name value) name = Some value] *)
val add_unless_exists : t -> ?sensitive:bool -> name -> value -> t
(** [add_unless_exists t ?sensitive name value] is a collection of header
fields that is the same as [t] if [t] already inclues [name], and
otherwise is equivalent to [add t ?sensitive name value]. *)
val add_list : t -> (name * value) list -> t
(** [add_list t assoc] is a collection of header fields that is the same as
[t] except with all the header fields in [assoc] added to the end of the
transmission order, in reverse order. *)
val add_multi : t -> (name * value list) list -> t
(** [add_multi t assoc] is the same as
{[
add_list
t
(List.concat_map assoc ~f:(fun (name, values) ->
List.map values ~f:(fun value -> name, value)))
]}
but is implemented more efficiently. For example,
{[
add_multi t [ "name1", [ "x", "y" ]; "name2", [ "p", "q" ] ]
= add_list [ "name1", "x"; "name1", "y"; "name2", "p"; "name2", "q" ]
]} *)
val remove : t -> name -> t
(** [remove t name] is a collection of header fields that contains all the
header fields of [t] except those that have a header-field name that are
equal to [name]. If [t] contains multiple header fields whose name is
[name], they will all be removed. *)
val replace : t -> ?sensitive:bool -> name -> value -> t
(** [replace t ?sensitive name value] is a collection of header fields that is
the same as [t] except with all header fields with a name equal to [name]
removed and replaced with a single header field whose name is [name] and
whose value is [value]. This new header field will appear in the
transmission order where the first occurrence of a header field with a
name matching [name] was found.
If no header field with a name equal to [name] is present in [t], then the
result is simply [t], unchanged. *)
(** {3 Destructors} *)
val mem : t -> name -> bool
(** [mem t name] is [true] iff [t] includes a header field with a name that is
equal to [name]. *)
val get : t -> name -> value option
(** [get t name] returns the last header from [t] with name [name], or [None]
if no such header is present. *)
val get_exn : t -> name -> value
(** [get t name] returns the last header from [t] with name [name], or raises
[Not_found] if no such header is present. *)
val get_multi : t -> name -> value list
(** [get_multi t name] is the list of header values in [t] whose names are
equal to [name]. The returned list is in transmission order. *)
(** {3 Iteration} *)
val iter : f:(name -> value -> unit) -> t -> unit
val fold : f:(name -> value -> 'a -> 'a) -> init:'a -> t -> 'a
(** {3 Utilities} *)
val to_string : t -> string
val pp_hum : Format.formatter -> t -> unit
end
(** {2 Message Body} *)
module Body : sig
module Reader : sig
type t
val schedule_read :
t
-> on_eof:(unit -> unit)
-> on_read:(Bigstringaf.t -> off:int -> len:int -> unit)
-> unit
(** [schedule_read t ~on_eof ~on_read] will setup [on_read] and [on_eof] as
callbacks for when bytes are available in [t] for the application to
consume, or when the input channel has been closed and no further bytes
will be received by the application.
Once either of these callbacks have been called, they become inactive.
The application is responsible for scheduling subsequent reads, either
within the [on_read] callback or by some other mechanism. *)
val close : t -> unit
(** [close t] closes [t], indicating that any subsequent input received
should be discarded. *)
val is_closed : t -> bool
(** [is_closed t] is [true] if {!close} has been called on [t] and [false]
otherwise. A closed [t] may still have pending output. *)
end
module Writer : sig
type t
val write_char : t -> char -> unit
(** [write_char w char] copies [char] into an internal buffer. If possible,
this write will be combined with previous and/or subsequent writes
before transmission. *)
val write_string : t -> ?off:int -> ?len:int -> string -> unit
(** [write_string w ?off ?len str] copies [str] into an internal buffer. If
possible, this write will be combined with previous and/or subsequent
writes before transmission. *)
val write_bigstring : t -> ?off:int -> ?len:int -> Bigstringaf.t -> unit
(** [write_bigstring w ?off ?len bs] copies [bs] into an internal buffer. If
possible, this write will be combined with previous and/or subsequent
writes before transmission. *)
val schedule_bigstring : t -> ?off:int -> ?len:int -> Bigstringaf.t -> unit
(** [schedule_bigstring w ?off ?len bs] schedules [bs] to be transmitted at
the next opportunity without performing a copy. [bs] should not be
modified until a subsequent call to {!flush} has successfully completed. *)
val flush : t -> (unit -> unit) -> unit
(** [flush t f] makes all bytes in [t] available for writing to the awaiting
output channel. Once those bytes have reached that output channel, [f]
will be called.
The type of the output channel is runtime-dependent, as are guarantees
about whether those packets have been queued for delivery or have
actually been received by the intended recipient. *)
val close : t -> unit
(** [close t] closes [t], causing subsequent write calls to raise. If [t] is
writable, this will cause any pending output to become available to the
output channel. *)
val is_closed : t -> bool
(** [is_closed t] is [true] if {!close} has been called on [t] and [false]
otherwise. A closed [t] may still have pending output. *)
end
end
(** {2 Message Types} *)
(** Request
A client-initiated HTTP message. *)
module Request : sig
type t =
{ meth : Method.t
; target : string
; scheme : string
; headers : Headers.t
}
val create :
?headers:Headers.t (** default is {!Headers.empty} *)
-> scheme:string
-> Method.t
-> string
-> t
(** [create ?headers ~scheme meth target] creates an HTTP request with the
given parameters. In HTTP/2, the [:scheme] pseudo-header field is required
and includes the scheme portion of the target URI. The [headers] parameter
is optional, however clients will want to include the [:authority]
pseudo-header field in most cases. The [:authority] pseudo-header field
includes the authority portion of the target URI, and should be used
instead of the [Host] header field in HTTP/2.
See
{{:https://tools.ietf.org/html/rfc7540#section-8.1.2.3} RFC7540§8.1.2.4}
for more details. *)
val body_length :
t
-> [ `Error of [ `Bad_request ] | `Fixed of int64 | `Unknown ]
(** [body_length t] is the length of the message body accompanying [t].
See {{:https://tools.ietf.org/html/rfc7230#section-3.3.3} RFC7230§3.3.3}
for more details. *)
val pp_hum : Format.formatter -> t -> unit
end
(** Response
A server-generated message to a {!Request.t}. *)
module Response : sig
type t =
{ status : Status.t
; headers : Headers.t
}
val create :
?headers:Headers.t (** default is {!Headers.empty} *)
-> Status.t
-> t
(** [create ?headers status] creates an HTTP response with the given
parameters. Unlike the [Response] type in http/af, h2 does not define a
way for responses to carry reason phrases or protocol version.
See
{{:https://tools.ietf.org/html/rfc7540#section-8.1.2.4} RFC7540§8.1.2.4}
for more details. *)
val body_length :
request_method:Method.standard
-> t
-> [ `Error of [ `Bad_request ] | `Fixed of int64 | `Unknown ]
(** [body_length ~request_method t] is the length of the message body
accompanying [t] assuming it is a response to a request whose method was
[request_method].
See {{:https://tools.ietf.org/html/rfc7230#section-3.3.3} RFC7230§3.3.3}
for more details. *)
val pp_hum : Format.formatter -> t -> unit
end
module IOVec : module type of Httpaf.IOVec
(** IOVec *)
(** {2 Request Descriptor} *)
module Reqd : sig
type error =
[ `Bad_request
| `Internal_server_error
| `Exn of exn
]
type t
val request : t -> Request.t
val request_body : t -> Body.Reader.t
val response : t -> Response.t option
val response_exn : t -> Response.t
(** {3 Responding}
The following functions will initiate a response for the corresponding
request in [t]. When the response is fully transmitted to the wire, the
stream completes.
From {{:https://tools.ietf.org/html/rfc7540#section-8.1} RFC7540§8.1}: An
HTTP request/response exchange fully consumes a single stream. *)
val respond_with_string : t -> Response.t -> string -> unit
val respond_with_bigstring : t -> Response.t -> Bigstringaf.t -> unit
val respond_with_streaming :
t
-> ?flush_headers_immediately:bool
-> Response.t
-> Body.Writer.t
val schedule_trailers : t -> Headers.t -> unit
(** [schedule_trailers reqd trailers] schedules a list of trailers to be sent
before the stream is closed, concluding the HTTP message. Should only be
used after {!respond_with_streaming}. Raises [Failure] if trailers have
already been scheduled. See
{{:https://tools.ietf.org/html/rfc7540#section-8.1} RFC7540§8.1} for more
information *)
(** {3 Pushing}
HTTP/2 allows a server to pre-emptively send (or "push") responses (along
with corresponding "promised" requests) to a client in association with a
previous client-initiated request. This can be useful when the server
knows the client will need to have those responses available in order to
fully process the response to the original request.
{4 {b An additional note regarding server push:}}
In HTTP/2, PUSH_PROMISE frames must only be sent in the open or
half-closed ("remote") stream states. In practice, this means that calling
{!Reqd.push} must happen before the entire response body for the
associated client-initiated request has been written to the wire. As such,
it is dangerous to start a server pushed response in association with
either {!Reqd.respond_with_string} or {!Reqd.respond_with_bigstring}, as
the entire body for the response that they produce is sent to the output
channel immediately, causing the corresponding stream to enter the closed
state.
See {{:https://tools.ietf.org/html/rfc7540#section-8.2} RFC7540§8.2} for
more details. *)
val push :
t
-> Request.t
-> ( t
, [ `Push_disabled | `Stream_cant_push | `Stream_ids_exhausted ] )
result
(** [push reqd request] creates a new ("pushed") request descriptor that
allows responding to the "promised" [request]. As per the HTTP/2
specification, [request] must be cacheable, safe, and must not include a
request body (see
{{:https://tools.ietf.org/html/rfc7540.html#section-8.2} RFC7540§8.2} for
more details). {b Note}: h2 will not validate [request] against these
assumptions.
This function returns [Error `Push_disabled] when the value of
[SETTINGS_ENABLE_PUSH] is set to [0] (see
{{:https://tools.ietf.org/html/rfc7540.html#section-6.5.2} RFC7540§8.2}
for more details), [Error `Stream_cant_push] when trying to initiate a
push stream from a stream that has been obtained from pushing, or
[Error `Stream_ids_exhausted] when the connection has exhausted the range
of identifiers available for pushed streams and cannot push on that
connection anymore. *)
(** {3 Exception Handling} *)
val error_code : t -> error option
val report_exn : t -> exn -> unit
val try_with : t -> (unit -> unit) -> (unit, exn) result
end
(** {2 Errors} *)
module Error_code : sig
type t =
(* From RFC7540§7:
* NO_ERROR (0x0): The associated condition is not a result of an
* error. *)
| NoError
(* From RFC7540§7:
* PROTOCOL_ERROR (0x1): The endpoint detected an unspecific protocol
* error. This error is for use when a more specific error code is not
* available. *)
| ProtocolError
(* From RFC7540§7:
* INTERNAL_ERROR (0x2): The endpoint encountered an unexpected internal
* error. *)
| InternalError
(* From RFC7540§7:
* FLOW_CONTROL_ERROR (0x3): The endpoint detected that its peer violated
* the flow-control protocol. *)
| FlowControlError
(* From RFC7540§7:
* SETTINGS_TIMEOUT (0x4): The endpoint sent a SETTINGS frame but did not
* receive a response in a timely manner. *)
| SettingsTimeout
(* From RFC7540§7:
* STREAM_CLOSED (0x5): The endpoint received a frame after a stream was
* half-closed. *)
| StreamClosed
(* From RFC7540§7:
* FRAME_SIZE_ERROR (0x6): The endpoint received a frame with an invalid
* size. *)
| FrameSizeError
(* From RFC7540§7:
* REFUSED_STREAM (0x7): The endpoint refused the stream prior to
* performing any application processing (see Section 8.1.4 for
* details). *)
| RefusedStream
(* From RFC7540§7:
* CANCEL (0x8): Used by the endpoint to indicate that the stream is no
* longer needed. *)
| Cancel
(* From RFC7540§7:
* COMPRESSION_ERROR (0x9): The endpoint is unable to maintain the header
* compression context for the connection. *)
| CompressionError
(* From RFC7540§7:
* CONNECT_ERROR (0xa): The connection established in response to a
* CONNECT request (Section 8.3) was reset or abnormally closed. *)
| ConnectError
(* From RFC7540§7:
* ENHANCE_YOUR_CALM (0xb): The endpoint detected that its peer is
* exhibiting a behavior that might be generating excessive load. *)
| EnhanceYourCalm
(* From RFC7540§7:
* INADEQUATE_SECURITY (0xc): The underlying transport has properties
* that do not meet minimum security requirements (see Section 9.2). *)
| InadequateSecurity
(* From RFC7540§7:
* HTTP_1_1_REQUIRED (0xd): The endpoint requires that HTTP/1.1 be used
* instead of HTTP/2. *)
| HTTP_1_1_Required
(* From RFC7540§7:
* Unknown or unsupported error codes MUST NOT trigger any special
* behavior. These MAY be treated by an implementation as being
* equivalent to INTERNAL_ERROR. *)
| UnknownError_code of int32
val to_string : t -> string
val pp_hum : Format.formatter -> t -> unit
end
(* TODO: needs docs *)
module Settings : sig
type t =
{ header_table_size : int
; enable_push : bool
; max_concurrent_streams : int32
; initial_window_size : int32
; max_frame_size : int
; max_header_list_size : int option
}
val default : t
val of_base64 : string -> (t, string) result
(** {{:https://tools.ietf.org/html/rfc7540#section-3.2.1} RFC7540§3.2.1} *)
val to_base64 : t -> (string, string) result
(** {{:https://tools.ietf.org/html/rfc7540#section-3.2.1} RFC7540§3.2.1} *)
val pp_hum : Format.formatter -> t -> unit
end
(** {2 HTTP/2 Configuration} *)
module Config : sig
type t =
{ read_buffer_size : int
(** [read_buffer_size] specifies the size of the largest frame payload that
the sender is willing to receive, in octets. Defaults to [16384] *)
; request_body_buffer_size : int (** Defaults to [4096] *)
; response_body_buffer_size : int (** Defaults to [4096] *)
; enable_server_push : bool (** Defaults to [true] *)
; max_concurrent_streams : int32
(** [max_concurrent_streams] specifies the maximum number of streams that
the sender will allow the peer to initiate. Defaults to [2^31 - 1] *)
; initial_window_size : int32
(** [initial_window_size] specifies the initial window size for flow control
tokens. Defaults to [65535] *)
}
val default : t
(** [default] is a configuration record with all parameters set to their
default values. *)
val to_settings : t -> Settings.t
end
(** {2 Server Connection} *)
module Server_connection : sig
type t
type error =
[ `Bad_request
| `Internal_server_error
| `Exn of exn
]
type request_handler = Reqd.t -> unit
type error_handler =
?request:Request.t -> error -> (Headers.t -> Body.Writer.t) -> unit
val create :
?config:Config.t
-> ?error_handler:error_handler
-> request_handler
-> t
(** [create ?config ?error_handler ~request_handler] creates a connection
handler that will service individual requests with [request_handler]. *)
val create_h2c :
?config:Config.t
-> ?error_handler:error_handler
-> http_request:Httpaf.Request.t
-> ?request_body:Bigstringaf.t IOVec.t list
-> request_handler
-> (t, string) result
(** [create ?config ?error_handler ~http_request ~request_handler] creates a
connection handler that will take over the communication channel from a
HTTP/1.1 connection, and service individual HTTP/2.0 requests with
[request_handler]. Upon successful creation, it returns the connection,
otherwise an error message is returned with an explanation of the failure
that caused the connection setup to not succeed.
This function is intended to be used in HTTP/1.1 upgrade handlers to set
up a new [h2c] (HTTP/2.0 over TCP) connection without prior knowledge.
See {{:https://tools.ietf.org/html/rfc7540#section-3.2} RFC7540§3.2} for
more details. *)
val next_read_operation : t -> [> `Read | `Close ]
(** [next_read_operation t] returns a value describing the next operation that
the caller should conduct on behalf of the connection. *)
val read : t -> Bigstringaf.t -> off:int -> len:int -> int
(** [read t bigstring ~off ~len] reads bytes of input from the provided range
of [bigstring] and returns the number of bytes consumed by the connection.
{!read} should be called after {!next_read_operation} returns a [`Read]
value and additional input is available for the connection to consume. *)
val read_eof : t -> Bigstringaf.t -> off:int -> len:int -> int
(** [read t bigstring ~off ~len] reads bytes of input from the provided range
of [bigstring] and returns the number of bytes consumed by the connection.
{!read} should be called after {!next_read_operation} returns a [`Read]
and an EOF has been received from the communication channel. The
connection will attempt to consume any buffered input and then shutdown
the HTTP parser for the connection. *)
val next_write_operation :
t
-> [ `Write of Bigstringaf.t IOVec.t list | `Yield | `Close of int ]
(** [next_write_operation t] returns a value describing the next operation
that the caller should conduct on behalf of the connection. *)
val report_write_result : t -> [ `Ok of int | `Closed ] -> unit
(** [report_write_result t result] reports the result of the latest write
attempt to the connection. {!report_write_result} should be called after a
call to {!next_write_operation} that returns a [`Write buffer] value.
- [`Ok n] indicates that the caller successfully wrote [n] bytes of output
from the buffer that the caller was provided by {!next_write_operation}.
- [`Closed] indicates that the output destination will no longer accept
bytes from the write processor. *)
val yield_writer : t -> (unit -> unit) -> unit
(** [yield_writer t continue] registers with the connection to call [continue]
when writing should resume. {!yield_writer} should be called after
{!next_write_operation} returns a [`Yield] value. *)
val yield_reader : t -> (unit -> unit) -> unit
(** [yield_reader t continue] immediately calls [continue]. This function *
shouldn't generally be called and it's only here to simplify adhering * to
the Gluten [RUNTIME] module type. *)
val report_exn : t -> exn -> unit
(** [report_exn t exn] reports that an error [exn] has been caught and that it
has been attributed to [t]. Calling this function will switch [t] into an
error state. Depending on the state [t] is transitioning from, it may call
its error handler before terminating the connection. *)
val is_closed : t -> bool
(** [is_closed t] is [true] if both the read and write processors have been
shutdown. When this is the case {!next_read_operation} will return
[`Close _] and {!next_write_operation} will do the same will return a
[`Write _] until all buffered output has been flushed, at which point it
will return [`Close]. *)
(* val error_code : t -> error option *)
(** [error_code t] returns the [error_code] that caused the connection to
close, if one exists. *)
(**/**)
val shutdown : t -> unit
(**/**)
end
(** {2 Client Connection} *)
module Client_connection : sig
type t
type error =
[ `Malformed_response of string
| `Invalid_response_body_length of Response.t
| `Protocol_error of Error_code.t * string
| `Exn of exn
]
type trailers_handler = Headers.t -> unit
type response_handler = Response.t -> Body.Reader.t -> unit
type error_handler = error -> unit
val create :
?config:Config.t
-> ?push_handler:(Request.t -> (response_handler, unit) result)
-> error_handler:error_handler
-> unit
-> t
(** [create ?config ?push_handler ~error_handler] creates a connection that
can be used to interact with servers over the HTTP/2 protocol.
[error_handler] will be called for {e connection-level} errors. HTTP/2 is
multiplexed over a single TCP connection and distinguishes
connection-level errors from stream-level errors. See See
{{:https://tools.ietf.org/html/rfc7540#section-5.4} RFC7540§5.4} for more
details.
If present, [push_handler] will be called upon the receipt of PUSH_PROMISE
frames with the request promised by the server. This function should
return [Ok response_handler] if the client wishes to accept the pushed
request. In this case, [response_handler] will be called once the server
respondes to the pushed request. Returning [Error ()] will signal to h2
that the client is choosing to reject the request that the server is
pushing, and its stream will be closed, as per the following excerpt from
the HTTP/2 specification:
From RFC7540§6.6: Recipients of PUSH_PROMISE frames can choose to reject
promised streams by returning a RST_STREAM referencing the promised stream
identifier back to the sender of the PUSH_PROMISE. *)
val create_h2c :
?config:Config.t
-> ?push_handler:(Request.t -> (response_handler, unit) result)
-> http_request:Httpaf.Request.t
-> error_handler:error_handler
-> response_handler * error_handler
-> (t, string) result
val request :
t
-> ?flush_headers_immediately:bool
-> ?trailers_handler:trailers_handler
-> Request.t
-> error_handler:error_handler
-> response_handler:response_handler
-> Body.Writer.t
(** [request connection ?trailers_handler req ~error_handler
~response_handler]
opens a new HTTP/2 stream with [req] and returns a request body that can
be written to. Once a response arrives, [response_handler] will be called
with its headers and body. [error_handler] will be called for
{e stream-level} errors. If there are any trailers they will be parsed and
passed to [trailers_handler].
HTTP/2 is multiplexed over a single TCP connection and distinguishes
connection-level errors from stream-level errors. See
{{:https://tools.ietf.org/html/rfc7540#section-5.4} RFC7540§5.4} for more
details. *)
val ping :
t
-> ?payload:Bigstringaf.t
-> ?off:int
-> ((unit, [ `EOF ]) result -> unit)
-> unit
(** [ping connection ?payload ?off f] sends an HTTP/2 PING frame and registers
[f] to be called when the server has sent an acknowledgement for it. A
custom [payload] (and offset into that payload) for the PING frame may
also be provided. If not, a payload with all bytes set to zero will be
used. Note that a PING frame's payload {b must} be 8 octets in length.
In HTTP/2, the PING frame is a mechanism for measuring a minimal
round-trip time from the sender, as well as determining whether an idle
connection is still functional. See
{{:https://tools.ietf.org/html/rfc7540#section-5.4} RFC7540§5.4} for more
details. *)
val shutdown : t -> unit
(** [shutdown connection] initiates the graceful shutdown of [connection], and
sends an HTTP/2 GOAWAY frame with NO_ERROR on the output channel (See
{{:https://tools.ietf.org/html/rfc7540#section-6.8} RFC7540§6.8} for more
details). *)
val next_read_operation : t -> [> `Read | `Close ]
(** [next_read_operation t] returns a value describing the next operation that
the caller should conduct on behalf of the connection. *)
val read : t -> Bigstringaf.t -> off:int -> len:int -> int
(** [read t bigstring ~off ~len] reads bytes of input from the provided range
of [bigstring] and returns the number of bytes consumed by the connection.
{!read} should be called after {!next_read_operation} returns a [`Read]
value and additional input is available for the connection to consume. *)
val read_eof : t -> Bigstringaf.t -> off:int -> len:int -> int
(** [read t bigstring ~off ~len] reads bytes of input from the provided range
of [bigstring] and returns the number of bytes consumed by the connection.
{!read} should be called after {!next_read_operation} returns a [`Read]
and an EOF has been received from the communication channel. The
connection will attempt to consume any buffered input and then shutdown
the HTTP parser for the connection. *)
val next_write_operation :
t
-> [ `Write of Bigstringaf.t IOVec.t list | `Yield | `Close of int ]
(** [next_write_operation t] returns a value describing the next operation
that the caller should conduct on behalf of the connection. *)
val report_write_result : t -> [ `Ok of int | `Closed ] -> unit
(** [report_write_result t result] reports the result of the latest write
attempt to the connection. {!report_write_result} should be called after a
call to {!next_write_operation} that returns a [`Write buffer] value.
- [`Ok n] indicates that the caller successfully wrote [n] bytes of output
from the buffer that the caller was provided by {!next_write_operation}.
- [`Closed] indicates that the output destination will no longer accept
bytes from the write processor. *)
val yield_writer : t -> (unit -> unit) -> unit
(** [yield_writer t continue] registers with the connection to call [continue]
when writing should resume. {!yield_writer} should be called after
{!next_write_operation} returns a [`Yield] value. *)
val yield_reader : t -> (unit -> unit) -> unit
(** [yield_reader t continue] immediately calls [continue]. This function *
shouldn't generally be called and it's only here to simplify adhering * to
the Gluten [RUNTIME] module type. *)
val report_exn : t -> exn -> unit
(** [report_exn t exn] reports that an error [exn] has been caught and that it
has been attributed to [t]. Calling this function will switch [t] into an
error state. Depending on the state [t] is transitioning from, it may call
its (connection-level) error handler before terminating the connection. *)
val is_closed : t -> bool
(** [is_closed t] is [true] if both the read and write processors have been
shutdown. When this is the case {!next_read_operation} will return
[`Close _] and {!next_write_operation} will do the same will return a
[`Write _] until all buffered output has been flushed, at which point it
will return [`Close]. *)
end