This commit is contained in:
parent
aa2ff7b2f0
commit
2f3113f55d
11742 changed files with 1223940 additions and 0 deletions
190
unikernel/duniverse/ocaml-h2/lib/body.ml
Normal file
190
unikernel/duniverse/ocaml-h2/lib/body.ml
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
(*----------------------------------------------------------------------------
|
||||
* 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.
|
||||
*---------------------------------------------------------------------------*)
|
||||
|
||||
module Reader = struct
|
||||
type t =
|
||||
{ faraday : Faraday.t
|
||||
; mutable read_scheduled : bool
|
||||
; mutable on_eof : unit -> unit
|
||||
; mutable on_read : Bigstringaf.t -> off:int -> len:int -> unit
|
||||
; done_reading : int -> unit
|
||||
}
|
||||
|
||||
let default_done_reading = Sys.opaque_identity (fun _ -> ())
|
||||
let default_on_eof = Sys.opaque_identity (fun () -> ())
|
||||
let default_on_read = Sys.opaque_identity (fun _ ~off:_ ~len:_ -> ())
|
||||
|
||||
let create buffer ~done_reading =
|
||||
{ faraday = Faraday.of_bigstring buffer
|
||||
; read_scheduled = false
|
||||
; on_eof = default_on_eof
|
||||
; on_read = default_on_read
|
||||
; done_reading
|
||||
}
|
||||
|
||||
let create_empty () =
|
||||
let t = create Bigstringaf.empty ~done_reading:default_done_reading in
|
||||
Faraday.close t.faraday;
|
||||
t
|
||||
|
||||
let empty = create_empty ()
|
||||
let is_closed t = Faraday.is_closed t.faraday
|
||||
let unsafe_faraday t = t.faraday
|
||||
|
||||
let rec do_execute_read t on_eof on_read =
|
||||
match Faraday.operation t.faraday with
|
||||
| `Yield -> ()
|
||||
| `Close ->
|
||||
t.read_scheduled <- false;
|
||||
t.on_eof <- default_on_eof;
|
||||
t.on_read <- default_on_read;
|
||||
on_eof ()
|
||||
| `Writev [] -> assert false
|
||||
| `Writev (iovec :: _) ->
|
||||
t.read_scheduled <- false;
|
||||
t.on_eof <- default_on_eof;
|
||||
t.on_read <- default_on_read;
|
||||
let { Httpun_types.IOVec.buffer; off; len } = iovec in
|
||||
Faraday.shift t.faraday len;
|
||||
on_read buffer ~off ~len;
|
||||
(* Application is done reading, we can give flow control tokens back to
|
||||
the peer. *)
|
||||
t.done_reading len;
|
||||
execute_read t
|
||||
|
||||
and execute_read t =
|
||||
if t.read_scheduled then do_execute_read t t.on_eof t.on_read
|
||||
|
||||
let schedule_read t ~on_eof ~on_read =
|
||||
if t.read_scheduled
|
||||
then failwith "Body.schedule_read: reader already scheduled";
|
||||
if not (is_closed t)
|
||||
then (
|
||||
t.read_scheduled <- true;
|
||||
t.on_eof <- on_eof;
|
||||
t.on_read <- on_read);
|
||||
do_execute_read t on_eof on_read
|
||||
|
||||
let close t =
|
||||
Faraday.close t.faraday;
|
||||
execute_read t
|
||||
|
||||
let has_pending_output t = Faraday.has_pending_output t.faraday
|
||||
end
|
||||
|
||||
module Writer = struct
|
||||
module Writer = Serialize.Writer
|
||||
|
||||
type t =
|
||||
{ faraday : Faraday.t
|
||||
; mutable buffered_bytes : int
|
||||
; writer : Serialize.Writer.t
|
||||
}
|
||||
|
||||
let create buffer ~writer =
|
||||
{ faraday = Faraday.of_bigstring buffer; buffered_bytes = 0; writer }
|
||||
|
||||
let create_empty ~writer =
|
||||
let t = create Bigstringaf.empty ~writer in
|
||||
Faraday.close t.faraday;
|
||||
t
|
||||
|
||||
let ready_to_write t = Serialize.Writer.wakeup t.writer
|
||||
|
||||
let write_char t c =
|
||||
if not (Faraday.is_closed t.faraday) then Faraday.write_char t.faraday c;
|
||||
ready_to_write t
|
||||
|
||||
let write_string t ?off ?len s =
|
||||
if not (Faraday.is_closed t.faraday)
|
||||
then Faraday.write_string ?off ?len t.faraday s;
|
||||
ready_to_write t
|
||||
|
||||
let write_bigstring t ?off ?len b =
|
||||
if not (Faraday.is_closed t.faraday)
|
||||
then Faraday.write_bigstring ?off ?len t.faraday b;
|
||||
ready_to_write t
|
||||
|
||||
let schedule_bigstring t ?off ?len (b : Bigstringaf.t) =
|
||||
if not (Faraday.is_closed t.faraday)
|
||||
then Faraday.schedule_bigstring ?off ?len t.faraday b;
|
||||
ready_to_write t
|
||||
|
||||
let flush t kontinue =
|
||||
if Serialize.Writer.is_closed t.writer
|
||||
then kontinue `Closed
|
||||
else (
|
||||
Faraday.flush_with_reason t.faraday (function
|
||||
| Drain -> kontinue `Closed
|
||||
| Nothing_pending | Shift -> kontinue `Written);
|
||||
ready_to_write t)
|
||||
|
||||
let is_closed t = Faraday.is_closed t.faraday
|
||||
let has_pending_output t = Faraday.has_pending_output t.faraday
|
||||
|
||||
let close_and_drain t =
|
||||
Faraday.close t.faraday;
|
||||
(* Resolve all pending flushes *)
|
||||
ignore (Faraday.drain t.faraday : int)
|
||||
|
||||
let close t =
|
||||
Serialize.Writer.unyield t.writer;
|
||||
Faraday.close t.faraday;
|
||||
ready_to_write t
|
||||
|
||||
let unsafe_faraday t = t.faraday
|
||||
|
||||
let transfer_to_writer t writer ~max_frame_size ~max_bytes stream_id =
|
||||
let faraday = t.faraday in
|
||||
if Serialize.Writer.is_closed t.writer
|
||||
then (
|
||||
close_and_drain t;
|
||||
0)
|
||||
else
|
||||
match Faraday.operation faraday with
|
||||
| `Yield | `Close -> 0
|
||||
| `Writev iovecs ->
|
||||
let iovecs = Httpun_types.IOVec.shiftv iovecs t.buffered_bytes in
|
||||
let lengthv = Httpun_types.IOVec.lengthv iovecs in
|
||||
let writev_len = if max_bytes < lengthv then max_bytes else lengthv in
|
||||
t.buffered_bytes <- t.buffered_bytes + writev_len;
|
||||
let frame_info = Writer.make_frame_info ~max_frame_size stream_id in
|
||||
Writer.schedule_iovecs writer frame_info ~len:writev_len iovecs;
|
||||
Writer.flush t.writer (function
|
||||
| `Closed -> close_and_drain t
|
||||
| `Written ->
|
||||
Faraday.shift faraday writev_len;
|
||||
t.buffered_bytes <- t.buffered_bytes - writev_len);
|
||||
writev_len
|
||||
end
|
||||
1492
unikernel/duniverse/ocaml-h2/lib/client_connection.ml
Normal file
1492
unikernel/duniverse/ocaml-h2/lib/client_connection.ml
Normal file
File diff suppressed because it is too large
Load diff
91
unikernel/duniverse/ocaml-h2/lib/config.ml
Normal file
91
unikernel/duniverse/ocaml-h2/lib/config.ml
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
(*----------------------------------------------------------------------------
|
||||
* 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.
|
||||
*---------------------------------------------------------------------------*)
|
||||
|
||||
(* TODO: add a config option for `Reqd` to flush max bytes at a time? *)
|
||||
type t =
|
||||
{ read_buffer_size : int
|
||||
; request_body_buffer_size : int
|
||||
; response_body_buffer_size : int
|
||||
; enable_server_push : bool
|
||||
; max_concurrent_streams : int32
|
||||
; initial_window_size : int32
|
||||
}
|
||||
|
||||
let default =
|
||||
{ (* This is effectively MAX_FRAME_SIZE, because the parser commits the frame
|
||||
* header to prevent backtracking, therefore the entire payload can fit the
|
||||
* read buffer. The default is 16384, and can't be lower than that.
|
||||
*
|
||||
* Note: h2 does not check that MAX_FRAME_SIZE is lower than 16384
|
||||
* octets. In the case that a lower value than permitted is set, peers will
|
||||
* reject the setting and close the connection with a PROTOCOL_ERROR.
|
||||
*
|
||||
* From RFC7540§6.5.2:
|
||||
* SETTINGS_MAX_FRAME_SIZE (0x5): Indicates the size of the largest frame
|
||||
* payload that the sender is willing to receive, in octets.
|
||||
* The initial value is 2^14 (16,384) octets. The value advertised by an
|
||||
* endpoint MUST be between this initial value and the maximum allowed
|
||||
* frame size (2^24-1 or 16,777,215 octets), inclusive. *)
|
||||
read_buffer_size = Settings.default.max_frame_size
|
||||
; (* Buffer size for request bodies *) request_body_buffer_size = 0x1000
|
||||
; (* Buffer size for response bodies *) response_body_buffer_size = 0x1000
|
||||
; enable_server_push = true
|
||||
; (* From RFC7540§6.5.2:
|
||||
* Indicates the maximum number of concurrent streams that the sender
|
||||
* will allow. This limit is directional: it applies to the number of
|
||||
* streams that the sender permits the receiver to create. *)
|
||||
max_concurrent_streams = Settings.default.max_concurrent_streams
|
||||
; (* Indicates the initial window size when receiving data from remote
|
||||
* streams. In other words, represents the amount of octets that the H2
|
||||
* endpoint is willing to receive from the peer. Cannot be lower than
|
||||
* 65535 (the default as per the spec). The default in H2 is 2^27, or
|
||||
* 128 MiB. *)
|
||||
(* TODO(anmonteiro): validate the default somewhere. *)
|
||||
initial_window_size = Int32.shift_left 1l 27
|
||||
}
|
||||
|
||||
let to_settings
|
||||
{ read_buffer_size
|
||||
; max_concurrent_streams
|
||||
; initial_window_size
|
||||
; enable_server_push
|
||||
; _
|
||||
}
|
||||
=
|
||||
{ Settings.default with
|
||||
max_frame_size = read_buffer_size
|
||||
; max_concurrent_streams
|
||||
; initial_window_size
|
||||
; enable_push = enable_server_push
|
||||
}
|
||||
4
unikernel/duniverse/ocaml-h2/lib/dune
Normal file
4
unikernel/duniverse/ocaml-h2/lib/dune
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
(library
|
||||
(name h2)
|
||||
(public_name h2)
|
||||
(libraries angstrom base64 faraday bigstringaf httpun-types psq hpack))
|
||||
43
unikernel/duniverse/ocaml-h2/lib/error.ml
Normal file
43
unikernel/duniverse/ocaml-h2/lib/error.ml
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
(*----------------------------------------------------------------------------
|
||||
* Copyright (c) 2019 António Nuno 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 copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 COPYRIGHT HOLDER 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.
|
||||
*---------------------------------------------------------------------------*)
|
||||
|
||||
(* From RFC7540§5.4:
|
||||
* HTTP/2 framing permits two classes of error:
|
||||
*
|
||||
* - An error condition that renders the entire connection unusable is a
|
||||
* connection error.
|
||||
* - An error in an individual stream is a stream error. *)
|
||||
type t =
|
||||
| ConnectionError of Error_code.t * string
|
||||
| StreamError of Stream_identifier.t * Error_code.t
|
||||
|
||||
let message = function ConnectionError (_, msg) -> msg | StreamError _ -> ""
|
||||
152
unikernel/duniverse/ocaml-h2/lib/error_code.ml
Normal file
152
unikernel/duniverse/ocaml-h2/lib/error_code.ml
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
(*----------------------------------------------------------------------------
|
||||
* Copyright (c) 2019 António Nuno 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 copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 COPYRIGHT HOLDER 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.
|
||||
*---------------------------------------------------------------------------*)
|
||||
|
||||
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
|
||||
|
||||
(* From RFC7540§7:
|
||||
* Error codes are 32-bit fields that are used in RST_STREAM and GOAWAY
|
||||
* frames to convey the reasons for the stream or connection error. *)
|
||||
let serialize = function
|
||||
| NoError -> 0x0l
|
||||
| ProtocolError -> 0x1l
|
||||
| InternalError -> 0x2l
|
||||
| FlowControlError -> 0x3l
|
||||
| SettingsTimeout -> 0x4l
|
||||
| StreamClosed -> 0x5l
|
||||
| FrameSizeError -> 0x6l
|
||||
| RefusedStream -> 0x7l
|
||||
| Cancel -> 0x8l
|
||||
| CompressionError -> 0x9l
|
||||
| ConnectError -> 0xal
|
||||
| EnhanceYourCalm -> 0xbl
|
||||
| InadequateSecurity -> 0xcl
|
||||
| HTTP_1_1_Required -> 0xdl
|
||||
| UnknownError_code id -> id
|
||||
|
||||
let parse = function
|
||||
| 0x0l -> NoError
|
||||
| 0x1l -> ProtocolError
|
||||
| 0x2l -> InternalError
|
||||
| 0x3l -> FlowControlError
|
||||
| 0x4l -> SettingsTimeout
|
||||
| 0x5l -> StreamClosed
|
||||
| 0x6l -> FrameSizeError
|
||||
| 0x7l -> RefusedStream
|
||||
| 0x8l -> Cancel
|
||||
| 0x9l -> CompressionError
|
||||
| 0xal -> ConnectError
|
||||
| 0xbl -> EnhanceYourCalm
|
||||
| 0xcl -> InadequateSecurity
|
||||
| 0xdl -> HTTP_1_1_Required
|
||||
| id -> UnknownError_code id
|
||||
|
||||
let to_string = function
|
||||
| NoError -> "NO_ERROR (0x0)"
|
||||
| ProtocolError -> "PROTOCOL_ERROR (0x1)"
|
||||
| InternalError -> "INTERNAL_ERROR (0x2)"
|
||||
| FlowControlError -> "FLOW_CONTROL_ERROR (0x3)"
|
||||
| SettingsTimeout -> "SETTINGS_TIMEOUT (0x4)"
|
||||
| StreamClosed -> "STREAM_CLOSED (0x5)"
|
||||
| FrameSizeError -> "FRAME_SIZE_ERROR (0x6)"
|
||||
| RefusedStream -> "REFUSED_STREAM (0x7)"
|
||||
| Cancel -> "CANCEL (0x8)"
|
||||
| CompressionError -> "COMPRESSION_ERROR (0x9)"
|
||||
| ConnectError -> "CONNECT_ERROR (0xa)"
|
||||
| EnhanceYourCalm -> "ENHANCE_YOUR_CALM (0xb)"
|
||||
| InadequateSecurity -> "INADEQUATE_SECURITY (0xc)"
|
||||
| HTTP_1_1_Required -> "HTTP_1_1_REQUIRED (0xd)"
|
||||
| UnknownError_code id -> Format.asprintf "UNKNOWN_ERROR (0x%lx)" id
|
||||
|
||||
let pp_hum formatter t = Format.fprintf formatter "%s" (to_string t)
|
||||
72
unikernel/duniverse/ocaml-h2/lib/flags.ml
Normal file
72
unikernel/duniverse/ocaml-h2/lib/flags.ml
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
(*----------------------------------------------------------------------------
|
||||
* Copyright (c) 2019 António Nuno 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 copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 COPYRIGHT HOLDER 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.
|
||||
*---------------------------------------------------------------------------*)
|
||||
|
||||
open Util
|
||||
|
||||
type t = int
|
||||
|
||||
(* From RFC7540§6.2:
|
||||
* Flags that have no defined semantics for a particular frame type MUST be
|
||||
* ignored and MUST be left unset (0x0) when sending. *)
|
||||
let default_flags = 0x0
|
||||
|
||||
(* From RFC7540§6.2:
|
||||
* END_STREAM (0x1): When set, bit 0 indicates that the header block (Section
|
||||
* 4.3) is the last that the endpoint will send for the identified stream. *)
|
||||
let test_end_stream x = test_bit x 0
|
||||
let set_end_stream x = set_bit x 0
|
||||
let clear_end_stream x = clear_bit x 0
|
||||
|
||||
(* From RFC7540§6.7:
|
||||
* ACK (0x1): When set, bit 0 indicates that this PING frame is a PING
|
||||
* response. *)
|
||||
let test_ack x = test_bit x 0
|
||||
let set_ack x = set_bit x 0
|
||||
|
||||
(* From RFC7540§6.2:
|
||||
* END_HEADERS (0x4): When set, bit 2 indicates that this frame contains an
|
||||
* entire header block (Section 4.3) and is not followed by any CONTINUATION
|
||||
* frames. *)
|
||||
let test_end_header x = test_bit x 2
|
||||
let set_end_header x = set_bit x 2
|
||||
|
||||
(* From RFC7540§6.2:
|
||||
* PADDED (0x8): When set, bit 3 indicates that the Pad Length field and any
|
||||
* padding that it describes are present. *)
|
||||
let test_padded x = test_bit x 3
|
||||
let set_padded x = set_bit x 3
|
||||
|
||||
(* From RFC7540§6.2:
|
||||
* PRIORITY (0x20): When set, bit 5 indicates that the Exclusive Flag (E),
|
||||
* Stream Dependency, and Weight fields are present; see Section 5.3. *)
|
||||
let test_priority x = test_bit x 5
|
||||
let set_priority x = set_bit x 5
|
||||
218
unikernel/duniverse/ocaml-h2/lib/frame.ml
Normal file
218
unikernel/duniverse/ocaml-h2/lib/frame.ml
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
(*----------------------------------------------------------------------------
|
||||
* Copyright (c) 2019 António Nuno 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 copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 COPYRIGHT HOLDER 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.
|
||||
*---------------------------------------------------------------------------*)
|
||||
|
||||
let connection_preface = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
|
||||
|
||||
module FrameType = struct
|
||||
type t =
|
||||
(* From RFC7540§6.1:
|
||||
* DATA frames (type=0x0) convey arbitrary, variable-length sequences
|
||||
* of octets associated with a stream. *)
|
||||
| Data
|
||||
(* From RFC7540§6.2:
|
||||
* The HEADERS frame (type=0x1) is used to open a stream (Section 5.1),
|
||||
* and additionally carries a header block fragment. *)
|
||||
| Headers
|
||||
(* From RFC7540§6.3:
|
||||
* The PRIORITY frame (type=0x2) specifies the sender-advised priority
|
||||
* of a stream (Section 5.3). *)
|
||||
| Priority
|
||||
(* From RFC7540§6.4:
|
||||
* The RST_STREAM frame (type=0x3) allows for immediate termination of
|
||||
* a stream. *)
|
||||
| RSTStream
|
||||
(* From RFC7540§6.5:
|
||||
* The SETTINGS frame (type=0x4) conveys configuration parameters that
|
||||
* affect how endpoints communicate, such as preferences and
|
||||
* constraints on peer behavior. *)
|
||||
| Settings
|
||||
(* From RFC7540§6.6:
|
||||
* The PUSH_PROMISE frame (type=0x5) is used to notify the peer
|
||||
* endpoint in advance of streams the sender intends to initiate. *)
|
||||
| PushPromise
|
||||
(* From RFC7540§6.7:
|
||||
* The PING frame (type=0x6) is a mechanism for measuring a minimal
|
||||
* round-trip time from the sender, as well as determining whether an
|
||||
* idle connection is still functional. *)
|
||||
| Ping
|
||||
(* From RFC7540§6.8:
|
||||
* The GOAWAY frame (type=0x7) is used to initiate shutdown of a
|
||||
* connection or to signal serious error conditions. *)
|
||||
| GoAway
|
||||
(* From RFC7540§6.9:
|
||||
* The WINDOW_UPDATE frame (type=0x8) is used to implement flow
|
||||
* control; [...]. *)
|
||||
| WindowUpdate
|
||||
(* From RFC7540§6.10:
|
||||
* The CONTINUATION frame (type=0x9) is used to continue a sequence of
|
||||
* header block fragments (Section 4.3). *)
|
||||
| Continuation
|
||||
(* From RFC7540§5.1:
|
||||
* Frames of unknown types are ignored. *)
|
||||
| Unknown of int
|
||||
|
||||
let serialize = function
|
||||
| Data -> 0
|
||||
| Headers -> 1
|
||||
| Priority -> 2
|
||||
| RSTStream -> 3
|
||||
| Settings -> 4
|
||||
| PushPromise -> 5
|
||||
| Ping -> 6
|
||||
| GoAway -> 7
|
||||
| WindowUpdate -> 8
|
||||
| Continuation -> 9
|
||||
| Unknown x -> x
|
||||
|
||||
let parse = function
|
||||
| 0 -> Data
|
||||
| 1 -> Headers
|
||||
| 2 -> Priority
|
||||
| 3 -> RSTStream
|
||||
| 4 -> Settings
|
||||
| 5 -> PushPromise
|
||||
| 6 -> Ping
|
||||
| 7 -> GoAway
|
||||
| 8 -> WindowUpdate
|
||||
| 9 -> Continuation
|
||||
| x -> Unknown x
|
||||
end
|
||||
|
||||
(* From RFC7540§4.1:
|
||||
* The fields of the frame header are defined as:
|
||||
*
|
||||
* Length: The length of the frame payload expressed as an unsigned 24-bit
|
||||
* integer. [...]
|
||||
*
|
||||
* Type: The 8-bit type of the frame. [...]
|
||||
*
|
||||
* Flags: An 8-bit field reserved for boolean flags specific to the frame
|
||||
* type. [...]
|
||||
*
|
||||
* Stream Identifier: A stream identifier (see Section 5.1.1) expressed as
|
||||
* an unsigned 31-bit integer. [...] *)
|
||||
type frame_header =
|
||||
{ payload_length : int
|
||||
; flags : Flags.t
|
||||
; stream_id : Stream_identifier.t
|
||||
; frame_type : FrameType.t
|
||||
}
|
||||
|
||||
(* From RFC7540§4.1:
|
||||
* The structure and content of the frame payload is dependent entirely on
|
||||
* the frame type. *)
|
||||
type frame_payload =
|
||||
(* From RFC7540§6.1:
|
||||
* The DATA frame contains the following fields:
|
||||
*
|
||||
* [...]
|
||||
*
|
||||
* Data: Application data. The amount of data is the remainder of the
|
||||
* frame payload after subtracting the length of the other fields
|
||||
* that are present. *)
|
||||
| Data of Bigstringaf.t
|
||||
(* From RFC7540§6.2:
|
||||
* The HEADERS frame payload has the following fields:
|
||||
*
|
||||
* E: A single-bit flag indicating that the stream dependency is
|
||||
* exclusive (see Section 5.3). [...]
|
||||
*
|
||||
* Stream Dependency: A 31-bit stream identifier for the stream that
|
||||
* this stream depends on (see Section 5.3). [...]
|
||||
*
|
||||
* Weight: An unsigned 8-bit integer representing a priority weight for
|
||||
* the stream (see Section 5.3). [...] This field is only
|
||||
* present if the PRIORITY flag is set.
|
||||
*
|
||||
* Header Block Fragment: A header block fragment (Section 4.3). *)
|
||||
| Headers of Priority.t * Bigstringaf.t
|
||||
(* From RFC7540§6.3:
|
||||
* The payload of a PRIORITY frame contains the following fields:
|
||||
*
|
||||
* E: A single-bit flag indicating that the stream dependency is
|
||||
* exclusive (see Section 5.3).
|
||||
*
|
||||
* Stream Dependency: A 31-bit stream identifier for the stream that this
|
||||
* stream depends on (see Section 5.3).
|
||||
*
|
||||
* Weight: An unsigned 8-bit integer representing a priority weight for
|
||||
* the stream (see Section 5.3). [...] *)
|
||||
| Priority of Priority.t
|
||||
(* From RFC7540§6.4:
|
||||
* The RST_STREAM frame contains a single unsigned, 32-bit integer
|
||||
* identifying the error code (Section 7). [...] *)
|
||||
| RSTStream of Error_code.t
|
||||
(* From RFC7540§6.5:
|
||||
* The payload of a SETTINGS frame consists of zero or more parameters,
|
||||
* each consisting of an unsigned 16-bit setting identifier and an
|
||||
* unsigned 32-bit value. *)
|
||||
| Settings of Settings.settings_list
|
||||
(* From RFC7540§6.6:
|
||||
* The PUSH_PROMISE frame includes the unsigned 31-bit identifier of the
|
||||
* stream the endpoint plans to create along with a set of headers that
|
||||
* provide additional context for the stream. *)
|
||||
| PushPromise of Stream_identifier.t * Bigstringaf.t
|
||||
(* From RFC7540§6.7:
|
||||
* In addition to the frame header, PING frames MUST contain 8 octets of
|
||||
* opaque data in the payload. A sender can include any value it chooses
|
||||
* and use those octets in any fashion. *)
|
||||
| Ping of Bigstringaf.t
|
||||
(* From RFC7540§6.8:
|
||||
* The last stream identifier in the GOAWAY frame contains the
|
||||
* highest-numbered stream identifier for which the sender of the GOAWAY
|
||||
* frame might have taken some action on or might yet take action on.
|
||||
*
|
||||
* [...] The GOAWAY frame also contains a 32-bit error code (Section 7)
|
||||
* that contains the reason for closing the connection.
|
||||
*
|
||||
* [...] Endpoints MAY append opaque data to the payload of any GOAWAY
|
||||
* frame. *)
|
||||
| GoAway of Stream_identifier.t * Error_code.t * Bigstringaf.t
|
||||
(* From RFC7540§6.9:
|
||||
* The payload of a WINDOW_UPDATE frame is one reserved bit plus an
|
||||
* unsigned 31-bit integer indicating the number of octets that the
|
||||
* sender can transmit in addition to the existing flow-control
|
||||
* window. *)
|
||||
| WindowUpdate of Settings.WindowSize.t
|
||||
(* From RFC7540§6.10:
|
||||
* The CONTINUATION frame payload contains a header block fragment
|
||||
* (Section 4.3). *)
|
||||
| Continuation of Bigstringaf.t
|
||||
| Unknown of int * Bigstringaf.t
|
||||
|
||||
(* From RFC7540§4.1:
|
||||
* All frames begin with a fixed 9-octet header followed by a variable-length
|
||||
* payload. *)
|
||||
type t =
|
||||
{ frame_header : frame_header
|
||||
; frame_payload : frame_payload
|
||||
}
|
||||
47
unikernel/duniverse/ocaml-h2/lib/h2.ml
Normal file
47
unikernel/duniverse/ocaml-h2/lib/h2.ml
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
(*----------------------------------------------------------------------------
|
||||
* 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.
|
||||
*---------------------------------------------------------------------------*)
|
||||
|
||||
module Headers = Headers
|
||||
module IOVec = Httpun_types.IOVec
|
||||
module Method = Httpun_types.Method
|
||||
module Reqd = Reqd
|
||||
module Request = Request
|
||||
module Response = Response
|
||||
module Status = Status
|
||||
module Body = Body
|
||||
module Error_code = Error_code
|
||||
module Config = Config
|
||||
module Server_connection = Server_connection
|
||||
module Client_connection = Client_connection
|
||||
module Settings = Settings
|
||||
956
unikernel/duniverse/ocaml-h2/lib/h2.mli
Normal file
956
unikernel/duniverse/ocaml-h2/lib/h2.mli
Normal file
|
|
@ -0,0 +1,956 @@
|
|||
(*----------------------------------------------------------------------------
|
||||
* 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 introduced http/af,
|
||||
and therefore uses the Angstrom and Faraday libraries to implement the
|
||||
parsing and serialization layers of the HTTP/2 standard. It preserves the
|
||||
same API as httpun wherever possible.
|
||||
|
||||
Not unlike httpun, 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 Httpun_types.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 [Httpun_types.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 [Httpun_types.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 Httpun_types.Status
|
||||
with type client_error := Httpun_types.Status.client_error
|
||||
and type standard := Httpun_types.Status.standard
|
||||
and type t := Httpun_types.Status.t
|
||||
|
||||
type client_error =
|
||||
[ Httpun_types.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 httpun, 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 =
|
||||
[ Httpun_types.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 -> ([ `Written | `Closed ] -> 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 httpun, 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 Httpun_types.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
|
||||
-> headers:Httpun_types.Headers.t
|
||||
-> target:string
|
||||
-> meth:Httpun_types.Method.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)
|
||||
-> headers:Httpun_types.Headers.t
|
||||
-> target:string
|
||||
-> meth:Httpun_types.Method.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
|
||||
363
unikernel/duniverse/ocaml-h2/lib/headers.ml
Normal file
363
unikernel/duniverse/ocaml-h2/lib/headers.ml
Normal file
|
|
@ -0,0 +1,363 @@
|
|||
(*----------------------------------------------------------------------------
|
||||
* 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.
|
||||
*---------------------------------------------------------------------------*)
|
||||
|
||||
type name = string
|
||||
type value = string
|
||||
|
||||
type header = Hpack.header =
|
||||
{ name : name
|
||||
; value : value
|
||||
; sensitive : bool
|
||||
}
|
||||
|
||||
type t = header list
|
||||
|
||||
let empty : t = []
|
||||
|
||||
let of_rev_list hs =
|
||||
List.map (fun (name, value) -> { name; value; sensitive = false }) hs
|
||||
|
||||
let of_list t = of_rev_list (List.rev t)
|
||||
let to_rev_list t = List.map (fun { name; value; _ } -> name, value) t
|
||||
let to_list t = List.rev (to_rev_list t)
|
||||
let to_hpack_list t = List.rev t
|
||||
|
||||
exception Local
|
||||
|
||||
module CI = struct
|
||||
let char_is_upper c = c >= 0x41 && c <= 0x5a
|
||||
let lower c = if char_is_upper c then c + 32 else c
|
||||
|
||||
let equal x y =
|
||||
let len = String.length x in
|
||||
len = String.length y
|
||||
&&
|
||||
match
|
||||
for i = 0 to len - 1 do
|
||||
let c1 = Char.code (String.unsafe_get x i) in
|
||||
let c2 = Char.code (String.unsafe_get y i) in
|
||||
if c1 = c2 then () else if lower c1 <> lower c2 then raise Local
|
||||
done
|
||||
with
|
||||
| () -> true
|
||||
| exception Local -> false
|
||||
|
||||
let is_lowercase x =
|
||||
let len = String.length x in
|
||||
match
|
||||
for i = 0 to len - 1 do
|
||||
let c1 = Char.code (String.unsafe_get x i) in
|
||||
if char_is_upper c1 then raise Local else ()
|
||||
done
|
||||
with
|
||||
| () -> true
|
||||
| exception Local -> false
|
||||
end
|
||||
|
||||
let rec mem t name =
|
||||
match t with
|
||||
| { name = name'; _ } :: t' -> CI.equal name name' || mem t' name
|
||||
| _ -> false
|
||||
|
||||
(* TODO: do we need to keep a list of never indexed fields? *)
|
||||
let add t ?(sensitive = false) name value = { name; value; sensitive } :: t
|
||||
let add_list t ls = of_rev_list ls @ t (* XXX(seliopou): do better here *)
|
||||
|
||||
let add_multi =
|
||||
let rec loop_outer t lss =
|
||||
match lss with [] -> t | (n, vs) :: lss' -> loop_inner t n vs lss'
|
||||
and loop_inner t n vs lss =
|
||||
match vs with
|
||||
| [] -> loop_outer t lss
|
||||
| v :: vs' ->
|
||||
loop_inner ({ name = n; value = v; sensitive = false } :: t) n vs' lss
|
||||
in
|
||||
loop_outer
|
||||
|
||||
let add_unless_exists t ?(sensitive = false) name value =
|
||||
if mem t name then t else { name; value; sensitive } :: t
|
||||
|
||||
let replace t ?(sensitive = false) name value =
|
||||
let rec loop t n nv seen =
|
||||
match t with
|
||||
| [] -> if not seen then raise Local else []
|
||||
| ({ name = n'; _ } as nv') :: t ->
|
||||
if CI.equal n n'
|
||||
then if seen then loop t n nv true else nv :: loop t n nv true
|
||||
else nv' :: loop t n nv seen
|
||||
in
|
||||
try loop t name { name; value; sensitive } false with Local -> t
|
||||
|
||||
let remove t name =
|
||||
let rec loop s n seen =
|
||||
match s with
|
||||
| [] -> if not seen then raise Local else []
|
||||
| ({ name = n'; _ } as nv') :: s' ->
|
||||
if CI.equal n n' then loop s' n true else nv' :: loop s' n seen
|
||||
in
|
||||
try loop t name false with Local -> t
|
||||
|
||||
let get t name =
|
||||
let rec loop t n =
|
||||
match t with
|
||||
| [] -> None
|
||||
| { name = n'; value; _ } :: t' ->
|
||||
if CI.equal n n' then Some value else loop t' n
|
||||
in
|
||||
loop t name
|
||||
|
||||
let get_exn t name =
|
||||
let rec loop t =
|
||||
match t with
|
||||
| [] -> raise Not_found
|
||||
| { name = n; value; _ } :: t' -> if CI.equal name n then value else loop t'
|
||||
in
|
||||
loop t
|
||||
|
||||
let get_pseudo t name = get t (":" ^ name)
|
||||
let get_pseudo_exn t name = get_exn t (":" ^ name)
|
||||
|
||||
let get_multi t name =
|
||||
let rec loop t acc =
|
||||
match t with
|
||||
| [] -> acc
|
||||
| { name = n; value; _ } :: t' ->
|
||||
if CI.equal name n then loop t' (value :: acc) else loop t' acc
|
||||
in
|
||||
loop t []
|
||||
|
||||
let get_multi_pseudo t name = get_multi t (":" ^ name)
|
||||
|
||||
module Pseudo = struct
|
||||
let reserved_request = [ ":method"; ":scheme"; ":authority"; ":path" ]
|
||||
let reserved_response = [ ":status" ]
|
||||
|
||||
(* 0x3A is the char code for `:` *)
|
||||
let is_pseudo name = Char.code (String.unsafe_get name 0) = 0x3A
|
||||
end
|
||||
|
||||
let iter ~f t = List.iter (fun { name; value; _ } -> f name value) t
|
||||
|
||||
let fold ~f ~init t =
|
||||
List.fold_left (fun acc { name; value; _ } -> f name value acc) init t
|
||||
|
||||
let exists ~f t = List.exists (fun { name; value; _ } -> f name value) t
|
||||
|
||||
let valid_headers ?(is_request = true) t =
|
||||
match get t "connection", get t "TE" with
|
||||
| Some _, _ ->
|
||||
(* From RFC7540§8.1.2.2:
|
||||
* HTTP/2 does not use the Connection header field to indicate
|
||||
* connection-specific header fields; in this protocol,
|
||||
* connection-specific metadata is conveyed by other means. An endpoint
|
||||
* MUST NOT generate an HTTP/2 message containing connection-specific
|
||||
* header fields; any message containing connection-specific header
|
||||
* fields MUST be treated as malformed (Section 8.1.2.6). *)
|
||||
false
|
||||
| _, Some value when value <> "trailers" ->
|
||||
(* From RFC7540§8.1.2.2:
|
||||
* The only exception to this is the TE header field, which MAY be
|
||||
* present in an HTTP/2 request; when it is, it MUST NOT contain any
|
||||
* value other than "trailers". *)
|
||||
false
|
||||
| _ ->
|
||||
let pseudo_ended = ref false in
|
||||
let invalid =
|
||||
exists
|
||||
~f:(fun name _ ->
|
||||
let is_pseudo = Pseudo.is_pseudo name in
|
||||
let pseudo_did_end = !pseudo_ended in
|
||||
if (not is_pseudo) && not pseudo_did_end then pseudo_ended := true;
|
||||
(* From RFC7540§8.1.2:
|
||||
* [...] header field names MUST be converted to lowercase
|
||||
* prior to their encoding in HTTP/2. A request or response
|
||||
* containing uppercase header field names MUST be treated as
|
||||
* malformed (Section 8.1.2.6). *)
|
||||
(not CI.(is_lowercase name))
|
||||
(* From RFC7540§8.1.2.1:
|
||||
* Pseudo-header fields are only valid in the context in
|
||||
* which they are defined. [...] pseudo-header fields defined
|
||||
* for responses MUST NOT appear in requests. [...] Endpoints
|
||||
* MUST treat a request or response that contains undefined
|
||||
* or invalid pseudo-header fields as malformed (Section
|
||||
* 8.1.2.6). *)
|
||||
|| (is_pseudo
|
||||
&& not
|
||||
(List.mem
|
||||
name
|
||||
(if is_request
|
||||
then Pseudo.reserved_request
|
||||
else Pseudo.reserved_response)))
|
||||
|| (* From RFC7540§8.1.2.1:
|
||||
* All pseudo-header fields MUST appear in the header block before
|
||||
* regular header fields. Any request or response that contains a
|
||||
* pseudo-header field that appears in a header block after a
|
||||
* regular header field MUST be treated as malformed (Section
|
||||
* 8.1.2.6). *)
|
||||
(is_pseudo && pseudo_did_end))
|
||||
(to_hpack_list t)
|
||||
in
|
||||
not invalid
|
||||
|
||||
let valid_request_headers t = valid_headers t
|
||||
let valid_response_headers t = valid_headers ~is_request:false t
|
||||
|
||||
let method_path_and_scheme_or_malformed t =
|
||||
match
|
||||
( get_multi_pseudo t "method"
|
||||
, get_multi_pseudo t "scheme"
|
||||
, get_multi_pseudo t "path" )
|
||||
with
|
||||
| _, [ ("http" | "https") ], [ path ] when String.length path = 0 ->
|
||||
(* From RFC7540§8.1.2.6:
|
||||
* This pseudo-header field MUST NOT be empty for http or https URIs;
|
||||
* http or https URIs that do not contain a path component MUST include a
|
||||
* value of '/'. *)
|
||||
`Malformed
|
||||
(* From RFC7540§8.1.2.3:
|
||||
* All HTTP/2 requests MUST include exactly one valid value for the
|
||||
* :method, :scheme, and :path pseudo-header fields, unless it is a
|
||||
* CONNECT request (Section 8.3). *)
|
||||
| [ ("CONNECT" as meth) ], [], [] ->
|
||||
(* From RFC7540§8.3:
|
||||
* The HTTP header field mapping works as defined in Section 8.1.2.3
|
||||
* ("Request Pseudo-Header Fields"), with a few differences.
|
||||
* Specifically:
|
||||
*
|
||||
* - The :method pseudo-header field is set to CONNECT.
|
||||
* - The :scheme and :path pseudo-header fields MUST be omitted.
|
||||
* - The :authority pseudo-header field contains the host and port to
|
||||
* connect to (equivalent to the authority-form of the request-target
|
||||
* of CONNECT requests (see [RFC7230], Section 5.3)).
|
||||
*
|
||||
* A CONNECT request that does not conform to these restrictions is
|
||||
* malformed (Section 8.1.2.6). *)
|
||||
if mem t ":authority" then `Valid (meth, "", "") else `Malformed
|
||||
| [ "CONNECT" ], _, _ -> `Malformed
|
||||
| [ meth ], [ scheme ], [ path ] ->
|
||||
if valid_request_headers t then `Valid (meth, path, scheme) else `Malformed
|
||||
| _ -> `Malformed
|
||||
|
||||
let trailers_valid t =
|
||||
let invalid =
|
||||
exists
|
||||
~f:(fun name _ ->
|
||||
(* From RFC7540§8.1.2:
|
||||
* [...] header field names MUST be converted to lowercase prior to
|
||||
* their encoding in HTTP/2. A request or response containing
|
||||
* uppercase header field names MUST be treated as malformed
|
||||
* (Section 8.1.2.6). *)
|
||||
(not (CI.is_lowercase name))
|
||||
|| (* From RFC7540§8.1.2.1:
|
||||
* Pseudo-header fields MUST NOT appear in trailers. Endpoints MUST
|
||||
* treat a request or response that contains undefined or invalid
|
||||
* pseudo-header fields as malformed (Section 8.1.2.6). *)
|
||||
Pseudo.is_pseudo name)
|
||||
t
|
||||
in
|
||||
not invalid
|
||||
|
||||
let is_valid_h2c_connection connection =
|
||||
let values = String.split_on_char ',' connection in
|
||||
let values = List.map String.trim values in
|
||||
(* From RFC7540§3.2.1:
|
||||
* [...] Since the upgrade is only intended to apply to the immediate
|
||||
* connection, a client sending the HTTP2-Settings header field MUST also
|
||||
* send HTTP2-Settings as a connection option in the Connection header
|
||||
* field to prevent it from being forwarded (see Section 6.1 of [RFC7230]).
|
||||
*)
|
||||
match
|
||||
( List.find_opt (fun x -> CI.equal x "upgrade") values
|
||||
, List.find_opt (fun x -> CI.equal x "http2-settings") values )
|
||||
with
|
||||
| Some _, Some _ -> true
|
||||
| _ -> false
|
||||
|
||||
let of_http1 ~headers ~meth ~target =
|
||||
let module Headers = Httpun_types.Headers in
|
||||
match Headers.get headers "host" with
|
||||
| Some host ->
|
||||
(* From RFC7540§8.1.2.3:
|
||||
* Clients that generate HTTP/2 requests directly SHOULD use the
|
||||
* :authority pseudo-header field instead of the Host header field. *)
|
||||
let headers =
|
||||
Headers.fold
|
||||
~f:(fun name value acc ->
|
||||
if CI.equal name "host" || CI.equal name "connection"
|
||||
then
|
||||
(* From RFC7540§8.1.2.2:
|
||||
* HTTP/2 does not use the Connection header field to indicate
|
||||
* connection-specific header fields; in this protocol,
|
||||
* connection-specific metadata is conveyed by other means. An
|
||||
* endpoint MUST NOT generate an HTTP/2 message containing
|
||||
* connection-specific header fields; any message containing
|
||||
* connection-specific header fields MUST be treated as malformed
|
||||
* (Section 8.1.2.6). *)
|
||||
acc
|
||||
else
|
||||
let name =
|
||||
(* From RFC7540§8.1.2:
|
||||
* header field names MUST be converted to lowercase prior to
|
||||
* their encoding in HTTP/2. *)
|
||||
if CI.is_lowercase name then name else String.lowercase_ascii name
|
||||
in
|
||||
(name, value) :: acc)
|
||||
~init:
|
||||
[ ":authority", host
|
||||
; ":method", Httpun_types.Method.to_string meth
|
||||
; ":path", target
|
||||
; ":scheme", "https"
|
||||
]
|
||||
headers
|
||||
in
|
||||
Ok (of_rev_list headers)
|
||||
| None -> Error "Missing `Host` header field"
|
||||
|
||||
let to_string t =
|
||||
let b = Buffer.create 128 in
|
||||
List.iter
|
||||
(fun (name, value) ->
|
||||
Buffer.add_string b name;
|
||||
Buffer.add_string b ": ";
|
||||
Buffer.add_string b value;
|
||||
Buffer.add_string b "\r\n")
|
||||
(to_list t);
|
||||
Buffer.add_string b "\r\n";
|
||||
Buffer.contents b
|
||||
|
||||
let pp_hum fmt t =
|
||||
let pp_elem fmt (name, value) = Format.fprintf fmt "@[(%S %S)@]" name value in
|
||||
Format.fprintf fmt "@[(";
|
||||
Format.pp_print_list pp_elem fmt (to_list t);
|
||||
Format.fprintf fmt ")@]"
|
||||
53
unikernel/duniverse/ocaml-h2/lib/message.ml
Normal file
53
unikernel/duniverse/ocaml-h2/lib/message.ml
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
(*----------------------------------------------------------------------------
|
||||
* 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.
|
||||
*---------------------------------------------------------------------------*)
|
||||
|
||||
(* This module contains functionality that applies to both requests and
|
||||
* responses, which are collectively referred to in the HTTP 1.1 specifications
|
||||
* as 'messages'. *)
|
||||
|
||||
let content_length_of_string s =
|
||||
match Int64.of_string s with
|
||||
| len when Int64.compare len 0L >= 0 -> `Fixed len
|
||||
| _ | (exception _) -> `Error `Bad_request
|
||||
|
||||
let body_length headers =
|
||||
match Headers.get_multi headers "content-length" with
|
||||
| [] -> `Unknown
|
||||
| [ x ] -> content_length_of_string x
|
||||
| hd :: tl ->
|
||||
(* if there are multiple content-length headers we require them all to be
|
||||
* exactly equal. *)
|
||||
if List.for_all (String.equal hd) tl
|
||||
then content_length_of_string hd
|
||||
else `Unknown
|
||||
49
unikernel/duniverse/ocaml-h2/lib/optional_thunk.ml
Normal file
49
unikernel/duniverse/ocaml-h2/lib/optional_thunk.ml
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
(*----------------------------------------------------------------------------
|
||||
* Copyright (c) 2020 Inhabited Type LLC.
|
||||
* Copyright (c) 2020 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.
|
||||
*---------------------------------------------------------------------------*)
|
||||
|
||||
type t = unit -> unit
|
||||
|
||||
let none = Sys.opaque_identity (fun () -> ())
|
||||
|
||||
let some f =
|
||||
if f == none
|
||||
then
|
||||
failwith
|
||||
"Optional_thunk: this function is not representable as a some value";
|
||||
f
|
||||
|
||||
let is_none t = t == none
|
||||
let is_some t = not (is_none t)
|
||||
let call_if_some t = t ()
|
||||
let unchecked_value t = t
|
||||
42
unikernel/duniverse/ocaml-h2/lib/optional_thunk.mli
Normal file
42
unikernel/duniverse/ocaml-h2/lib/optional_thunk.mli
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
(*----------------------------------------------------------------------------
|
||||
* Copyright (c) 2020 Inhabited Type LLC.
|
||||
* Copyright (c) 2020 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.
|
||||
*---------------------------------------------------------------------------*)
|
||||
|
||||
type t
|
||||
|
||||
val none : t
|
||||
val some : (unit -> unit) -> t
|
||||
val is_none : t -> bool
|
||||
val is_some : t -> bool
|
||||
val call_if_some : t -> unit
|
||||
val unchecked_value : t -> unit -> unit
|
||||
711
unikernel/duniverse/ocaml-h2/lib/parse.ml
Normal file
711
unikernel/duniverse/ocaml-h2/lib/parse.ml
Normal file
|
|
@ -0,0 +1,711 @@
|
|||
(*----------------------------------------------------------------------------
|
||||
* 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.
|
||||
*---------------------------------------------------------------------------*)
|
||||
|
||||
open Angstrom
|
||||
|
||||
(* We use the tail-recursive variant of `skip_many` from
|
||||
* https://github.com/inhabitedtype/angstrom/pull/219 to avoid memory leaks in
|
||||
* long-running connections. The original `skip_many` can build up a list of
|
||||
* error handlers that may never be released. *)
|
||||
let skip_many p =
|
||||
fix (fun m ->
|
||||
p >>| (fun _ -> true) <|> return false >>= function
|
||||
| true -> m
|
||||
| false -> return ())
|
||||
|
||||
let default_frame_header =
|
||||
{ Frame.payload_length = 0
|
||||
; flags = Flags.default_flags
|
||||
; stream_id = -1l
|
||||
; frame_type = Unknown (-1)
|
||||
}
|
||||
|
||||
type parse_context =
|
||||
{ mutable frame_header : Frame.frame_header
|
||||
; mutable remaining_bytes_to_skip : int
|
||||
; mutable did_report_stream_error : bool
|
||||
; (* TODO: This should change as new settings frames arrive, but we don't yet
|
||||
* resize the read buffer. *)
|
||||
max_frame_size : int
|
||||
}
|
||||
|
||||
let connection_error error_code msg =
|
||||
Error Error.(ConnectionError (error_code, msg))
|
||||
|
||||
let stream_error error_code stream_id =
|
||||
Error Error.(StreamError (stream_id, error_code))
|
||||
|
||||
let parse_uint24 o1 o2 o3 = (o1 lsl 16) lor (o2 lsl 8) lor o3
|
||||
|
||||
let frame_length =
|
||||
(* From RFC7540§4.1:
|
||||
* Length: The length of the frame payload expressed as an unsigned 24-bit
|
||||
* integer. *)
|
||||
lift3 parse_uint24 any_uint8 any_uint8 any_uint8
|
||||
|
||||
let frame_type =
|
||||
(* From RFC7540§4.1:
|
||||
* Type: The 8-bit type of the frame. The frame type determines the format
|
||||
* and semantics of the frame. Implementations MUST ignore and discard any
|
||||
* frame that has a type that is unknown. *)
|
||||
lift Frame.FrameType.parse any_uint8
|
||||
|
||||
let flags =
|
||||
(* From RFC7540§4.1:
|
||||
* Flags: An 8-bit field reserved for boolean flags specific to the frame
|
||||
* type. *)
|
||||
any_uint8
|
||||
|
||||
let parse_stream_identifier n =
|
||||
(* From RFC7540§4.1:
|
||||
* Stream Identifier: A stream identifier (see Section 5.1.1) expressed as
|
||||
* an unsigned 31-bit integer. The value 0x0 is reserved for frames that
|
||||
* are associated with the connection as a whole as opposed to an
|
||||
* individual stream. *)
|
||||
Int32.(logand n (sub (shift_left 1l 31) 1l))
|
||||
|
||||
let stream_identifier = lift parse_stream_identifier BE.any_int32
|
||||
|
||||
let parse_frame_header =
|
||||
lift4
|
||||
(fun payload_length frame_type flags stream_id ->
|
||||
{ Frame.flags; payload_length; stream_id; frame_type })
|
||||
frame_length
|
||||
frame_type
|
||||
flags
|
||||
stream_identifier
|
||||
<?> "frame_header"
|
||||
(* The parser commits after parsing the frame header so that the entire
|
||||
* underlying buffer can be used to store the payload length. This matters
|
||||
* because the size of the buffer that gets allocated is the maximum frame
|
||||
* payload negotiated by the HTTP/2 settings synchronization. The 9 octets
|
||||
* that make up the frame header are, therefore, very important in order for
|
||||
* h2 not to return a FRAME_SIZE_ERROR. *)
|
||||
<* commit
|
||||
|
||||
let parse_padded_payload { Frame.payload_length; flags; _ } parser =
|
||||
if Flags.test_padded flags
|
||||
then
|
||||
any_uint8 >>= fun pad_length ->
|
||||
(* From RFC7540§6.1:
|
||||
* Pad Length: An 8-bit field containing the length of the frame
|
||||
* padding in units of octets.
|
||||
*
|
||||
* Data: Application data. The amount of data is the remainder of the
|
||||
* frame payload after subtracting the length of the other fields that
|
||||
* are present.
|
||||
*
|
||||
* Padding: Padding octets that contain no application semantic
|
||||
* value. *)
|
||||
if pad_length >= payload_length
|
||||
then
|
||||
(* From RFC7540§6.1:
|
||||
* If the length of the padding is the length of the frame payload or
|
||||
* greater, the recipient MUST treat this as a connection error
|
||||
* (Section 5.4.1) of type PROTOCOL_ERROR. *)
|
||||
advance (payload_length - 1) >>| fun () ->
|
||||
connection_error ProtocolError "Padding size exceeds payload size"
|
||||
else
|
||||
(* Subtract the octet that contains the length of padding, and the
|
||||
* padding octets. *)
|
||||
let relevant_length = payload_length - 1 - pad_length in
|
||||
parser relevant_length <* advance pad_length
|
||||
else parser payload_length
|
||||
|
||||
let parse_data_frame ({ Frame.stream_id; payload_length; _ } as frame_header) =
|
||||
if Stream_identifier.is_connection stream_id
|
||||
then
|
||||
(* From RFC7540§6.1:
|
||||
* DATA frames MUST be associated with a stream. If a DATA frame is
|
||||
* received whose stream identifier field is 0x0, the recipient MUST
|
||||
* respond with a connection error (Section 5.4.1) of type
|
||||
* PROTOCOL_ERROR. *)
|
||||
advance payload_length >>| fun () ->
|
||||
connection_error
|
||||
ProtocolError
|
||||
"Data frames must be associated with a stream"
|
||||
else
|
||||
let parse_data length =
|
||||
lift (fun bs -> Ok (Frame.Data bs)) (take_bigstring length)
|
||||
in
|
||||
parse_padded_payload frame_header parse_data
|
||||
|
||||
let parse_priority =
|
||||
lift2
|
||||
(fun stream_dependency weight ->
|
||||
let e = Priority.test_exclusive stream_dependency in
|
||||
{ Priority.exclusive =
|
||||
e
|
||||
(* From RFC7540§6.3:
|
||||
* An unsigned 8-bit integer representing a priority weight for the
|
||||
* stream (see Section 5.3). Add one to the value to obtain a
|
||||
* weight between 1 and 256. *)
|
||||
; weight = weight + 1
|
||||
; stream_dependency = parse_stream_identifier stream_dependency
|
||||
})
|
||||
BE.any_int32
|
||||
any_uint8
|
||||
|
||||
let parse_headers_frame frame_header =
|
||||
let { Frame.payload_length; stream_id; flags; _ } = frame_header in
|
||||
if Stream_identifier.is_connection stream_id
|
||||
then
|
||||
(* From RFC7540§6.2:
|
||||
* HEADERS frames MUST be associated with a stream. If a HEADERS frame is
|
||||
* received whose stream identifier field is 0x0, the recipient MUST
|
||||
* respond with a connection error (Section 5.4.1) of type
|
||||
* PROTOCOL_ERROR. *)
|
||||
advance payload_length >>| fun () ->
|
||||
connection_error ProtocolError "HEADERS must be associated with a stream"
|
||||
else
|
||||
let parse_headers length =
|
||||
if Flags.test_priority flags
|
||||
then
|
||||
lift2
|
||||
(fun priority headers -> Ok (Frame.Headers (priority, headers)))
|
||||
parse_priority
|
||||
(* See RFC7540§6.3:
|
||||
* Stream Dependency (4 octets) + Weight (1 octet). *)
|
||||
(take_bigstring (length - 5))
|
||||
else
|
||||
lift
|
||||
(fun headers_block ->
|
||||
Ok (Frame.Headers (Priority.default_priority, headers_block)))
|
||||
(take_bigstring length)
|
||||
in
|
||||
parse_padded_payload frame_header parse_headers
|
||||
|
||||
let parse_priority_frame { Frame.payload_length; stream_id; _ } =
|
||||
if Stream_identifier.is_connection stream_id
|
||||
then
|
||||
(* From RFC7540§6.3:
|
||||
* The PRIORITY frame always identifies a stream. If a PRIORITY frame is
|
||||
* received with a stream identifier of 0x0, the recipient MUST respond
|
||||
* with a connection error (Section 5.4.1) of type PROTOCOL_ERROR. *)
|
||||
advance payload_length >>| fun () ->
|
||||
connection_error ProtocolError "PRIORITY must be associated with a stream"
|
||||
else if payload_length <> 5
|
||||
then
|
||||
(* From RFC7540§6.3:
|
||||
* A PRIORITY frame with a length other than 5 octets MUST be treated as
|
||||
* a stream error (Section 5.4.2) of type FRAME_SIZE_ERROR. *)
|
||||
advance payload_length >>| fun () -> stream_error FrameSizeError stream_id
|
||||
else lift (fun priority -> Ok (Frame.Priority priority)) parse_priority
|
||||
|
||||
let parse_error_code = lift Error_code.parse BE.any_int32
|
||||
|
||||
let parse_rst_stream_frame { Frame.payload_length; stream_id; _ } =
|
||||
if Stream_identifier.is_connection stream_id
|
||||
then
|
||||
(* From RFC7540§6.4:
|
||||
* RST_STREAM frames MUST be associated with a stream. If a RST_STREAM
|
||||
* frame is received with a stream identifier of 0x0, the recipient MUST
|
||||
* treat this as a connection error (Section 5.4.1) of type
|
||||
* PROTOCOL_ERROR. *)
|
||||
advance payload_length >>| fun () ->
|
||||
connection_error ProtocolError "RST_STREAM must be associated with a stream"
|
||||
else if payload_length <> 4
|
||||
then
|
||||
(* From RFC7540§6.4:
|
||||
* A RST_STREAM frame with a length other than 4 octets MUST be treated
|
||||
* as a connection error (Section 5.4.1) of type FRAME_SIZE_ERROR. *)
|
||||
advance payload_length >>| fun () ->
|
||||
connection_error
|
||||
FrameSizeError
|
||||
"RST_STREAM payload must be 4 octets in length"
|
||||
else lift (fun error_code -> Ok (Frame.RSTStream error_code)) parse_error_code
|
||||
|
||||
let parse_settings_frame { Frame.payload_length; stream_id; flags; _ } =
|
||||
if not (Stream_identifier.is_connection stream_id)
|
||||
then
|
||||
(* From RFC7540§6.5:
|
||||
* If an endpoint receives a SETTINGS frame whose stream identifier field
|
||||
* is anything other than 0x0, the endpoint MUST respond with a
|
||||
* connection error (Section 5.4.1) of type PROTOCOL_ERROR. *)
|
||||
advance payload_length >>| fun () ->
|
||||
connection_error
|
||||
ProtocolError
|
||||
"SETTINGS must be associated with stream id 0x0"
|
||||
else if payload_length mod 6 <> 0
|
||||
then
|
||||
(* From RFC7540§6.5:
|
||||
* A SETTINGS frame with a length other than a multiple of 6 octets MUST
|
||||
* be treated as a connection error (Section 5.4.1) of type
|
||||
* FRAME_SIZE_ERROR. *)
|
||||
advance payload_length >>| fun () ->
|
||||
connection_error
|
||||
FrameSizeError
|
||||
"SETTINGS payload size must be a multiple of 6"
|
||||
else if Flags.test_ack flags && payload_length <> 0
|
||||
then
|
||||
(* From RFC7540§6.5:
|
||||
* Receipt of a SETTINGS frame with the ACK flag set and a length field
|
||||
* value other than 0 MUST be treated as a connection error
|
||||
* (Section 5.4.1) of type FRAME_SIZE_ERROR. *)
|
||||
advance payload_length >>| fun () ->
|
||||
connection_error FrameSizeError "SETTINGS with ACK must be empty"
|
||||
else
|
||||
let num_settings = payload_length / Settings.octets_per_setting in
|
||||
Settings.parse_settings_payload num_settings >>| fun xs ->
|
||||
Ok (Frame.Settings xs)
|
||||
|
||||
let parse_push_promise_frame frame_header =
|
||||
let { Frame.payload_length; stream_id; _ } = frame_header in
|
||||
if Stream_identifier.is_connection stream_id
|
||||
then
|
||||
(* From RFC7540§6.6:
|
||||
* The stream identifier of a PUSH_PROMISE frame indicates the
|
||||
* stream it is associated with. If the stream identifier field
|
||||
* specifies the value 0x0, a recipient MUST respond with a
|
||||
* connection error (Section 5.4.1) of type PROTOCOL_ERROR. *)
|
||||
advance payload_length >>| fun () ->
|
||||
connection_error ProtocolError "PUSH must be associated with a stream"
|
||||
else
|
||||
let parse_push_promise length =
|
||||
lift2
|
||||
(fun promised_stream_id fragment ->
|
||||
if Stream_identifier.is_connection promised_stream_id
|
||||
then
|
||||
(* From RFC7540§6.6:
|
||||
* A receiver MUST treat the receipt of a PUSH_PROMISE that
|
||||
* promises an illegal stream identifier (Section 5.1.1) as a
|
||||
* connection error (Section 5.4.1) of type PROTOCOL_ERROR. *)
|
||||
connection_error
|
||||
ProtocolError
|
||||
"PUSH must not promise stream id 0x0"
|
||||
else if Stream_identifier.is_request promised_stream_id
|
||||
then
|
||||
(* From RFC7540§6.6:
|
||||
* A receiver MUST treat the receipt of a PUSH_PROMISE that
|
||||
* promises an illegal stream identifier (Section 5.1.1) as a
|
||||
* connection error (Section 5.4.1) of type PROTOCOL_ERROR.
|
||||
*
|
||||
* Note: An odd-numbered stream is an invalid stream identifier for
|
||||
* the server, and only the server can send PUSH_PROMISE frames:
|
||||
*
|
||||
* From RFC7540§8.2.1:
|
||||
* PUSH_PROMISE frames MUST NOT be sent by the client. *)
|
||||
connection_error
|
||||
ProtocolError
|
||||
"PUSH must be associated with an even-numbered stream id"
|
||||
else Ok Frame.(PushPromise (promised_stream_id, fragment)))
|
||||
stream_identifier
|
||||
(* From RFC7540§6.6:
|
||||
* The PUSH_PROMISE frame includes the unsigned 31-bit identifier of
|
||||
* the stream the endpoint plans to create along with a set of
|
||||
* headers that provide additional context for the stream. *)
|
||||
(take_bigstring (length - 4))
|
||||
in
|
||||
parse_padded_payload frame_header parse_push_promise
|
||||
|
||||
let parse_ping_frame { Frame.payload_length; stream_id; _ } =
|
||||
if not (Stream_identifier.is_connection stream_id)
|
||||
then
|
||||
(* From RFC7540§6.7:
|
||||
* PING frames are not associated with any individual stream. If a PING
|
||||
* frame is received with a stream identifier field value other than
|
||||
* 0x0, the recipient MUST respond with a connection error
|
||||
* (Section 5.4.1) of type PROTOCOL_ERROR. *)
|
||||
advance payload_length >>| fun () ->
|
||||
connection_error ProtocolError "PING must be associated with stream id 0x0"
|
||||
else if payload_length <> 8
|
||||
then
|
||||
(* From RFC7540§6.7:
|
||||
* Receipt of a PING frame with a length field value other than 8 MUST
|
||||
* be treated as a connection error (Section 5.4.1) of type
|
||||
* FRAME_SIZE_ERROR. *)
|
||||
advance payload_length >>| fun () ->
|
||||
connection_error FrameSizeError "PING payload must be 8 octets in length"
|
||||
else lift (fun bs -> Ok (Frame.Ping bs)) (take_bigstring payload_length)
|
||||
|
||||
let parse_go_away_frame { Frame.payload_length; stream_id; _ } =
|
||||
if not (Stream_identifier.is_connection stream_id)
|
||||
then
|
||||
(* From RFC7540§6.8:
|
||||
* The GOAWAY frame applies to the connection, not a specific stream. An
|
||||
* endpoint MUST treat a GOAWAY frame with a stream identifier other than
|
||||
* 0x0 as a connection error (Section 5.4.1) of type PROTOCOL_ERROR. *)
|
||||
advance payload_length >>| fun () ->
|
||||
connection_error
|
||||
ProtocolError
|
||||
"GOAWAY must be associated with stream id 0x0"
|
||||
else
|
||||
lift3
|
||||
(fun last_stream_id err debug_data ->
|
||||
Ok (Frame.GoAway (last_stream_id, err, debug_data)))
|
||||
stream_identifier
|
||||
parse_error_code
|
||||
(take_bigstring (payload_length - 8))
|
||||
|
||||
let parse_window_update_frame { Frame.stream_id; payload_length; _ } =
|
||||
(* From RFC7540§6.9:
|
||||
* A WINDOW_UPDATE frame with a length other than 4 octets MUST be treated
|
||||
* as a connection error (Section 5.4.1) of type FRAME_SIZE_ERROR. *)
|
||||
if payload_length <> 4
|
||||
then
|
||||
advance payload_length >>| fun () ->
|
||||
connection_error
|
||||
FrameSizeError
|
||||
"WINDOW_UPDATE payload must be 4 octets in length"
|
||||
else
|
||||
lift
|
||||
(fun uint ->
|
||||
(* From RFC7540§6.9:
|
||||
* The frame payload of a WINDOW_UPDATE frame is one reserved bit
|
||||
* plus an unsigned 31-bit integer indicating the number of octets
|
||||
* that the sender can transmit in addition to the existing
|
||||
* flow-control window. *)
|
||||
let window_size_increment = Util.clear_bit_int32 uint 31 in
|
||||
if Int32.equal window_size_increment 0l
|
||||
then
|
||||
if (* From RFC7540§6.9:
|
||||
* A receiver MUST treat the receipt of a WINDOW_UPDATE frame
|
||||
* with an flow-control window increment of 0 as a stream error
|
||||
* (Section 5.4.2) of type PROTOCOL_ERROR; errors on the
|
||||
* connection flow-control window MUST be treated as a connection
|
||||
* error (Section 5.4.1). *)
|
||||
Stream_identifier.is_connection stream_id
|
||||
then connection_error ProtocolError "Window update must not be 0"
|
||||
else stream_error ProtocolError stream_id
|
||||
else Ok (Frame.WindowUpdate window_size_increment))
|
||||
BE.any_int32
|
||||
|
||||
let parse_continuation_frame { Frame.payload_length; stream_id; _ } =
|
||||
if Stream_identifier.is_connection stream_id
|
||||
then
|
||||
(* From RFC7540§6.10:
|
||||
* CONTINUATION frames MUST be associated with a stream. If a
|
||||
* CONTINUATION frame is received whose stream identifier field is 0x0,
|
||||
* the recipient MUST respond with a connection error (Section 5.4.1) of
|
||||
* type PROTOCOL_ERROR. *)
|
||||
advance payload_length >>| fun () ->
|
||||
connection_error
|
||||
ProtocolError
|
||||
"CONTINUATION must be associated with a stream"
|
||||
else
|
||||
lift
|
||||
(fun block_fragment -> Ok (Frame.Continuation block_fragment))
|
||||
(take_bigstring payload_length)
|
||||
|
||||
let parse_unknown_frame typ { Frame.payload_length; _ } =
|
||||
lift
|
||||
(fun bigstring -> Ok (Frame.Unknown (typ, bigstring)))
|
||||
(take_bigstring payload_length)
|
||||
|
||||
let parse_frame_payload ({ Frame.frame_type; _ } as frame_header) =
|
||||
(match frame_type with
|
||||
| Frame.FrameType.Data -> parse_data_frame frame_header
|
||||
| Headers -> parse_headers_frame frame_header
|
||||
| Priority -> parse_priority_frame frame_header
|
||||
| RSTStream -> parse_rst_stream_frame frame_header
|
||||
| Settings -> parse_settings_frame frame_header
|
||||
| PushPromise -> parse_push_promise_frame frame_header
|
||||
| Ping -> parse_ping_frame frame_header
|
||||
| GoAway -> parse_go_away_frame frame_header
|
||||
| WindowUpdate -> parse_window_update_frame frame_header
|
||||
| Continuation -> parse_continuation_frame frame_header
|
||||
| Unknown typ -> parse_unknown_frame typ frame_header)
|
||||
<?> "frame_payload"
|
||||
|
||||
let parse_frame parse_context =
|
||||
parse_frame_header >>= fun ({ Frame.payload_length; _ } as frame_header) ->
|
||||
(* If we're parsing a new frame, we didn't yet send a stream error on it *)
|
||||
parse_context.did_report_stream_error <- false;
|
||||
parse_context.frame_header <- frame_header;
|
||||
(* h2 does unbuffered parsing and the bigarray we read input from is
|
||||
* allocated based on the maximum frame payload negotiated by HTTP/2
|
||||
* communication. If the underlying buffer is smaller than what
|
||||
* the frame can fit, we want to skip the remaining input and skip to the
|
||||
* next frame.
|
||||
*
|
||||
* From RFC7540§5.4.2:
|
||||
* A stream error is an error related to a specific stream that does
|
||||
* not affect processing of other streams. *)
|
||||
let is_frame_size_error = payload_length > parse_context.max_frame_size in
|
||||
if is_frame_size_error
|
||||
then
|
||||
parse_context.remaining_bytes_to_skip <-
|
||||
parse_context.remaining_bytes_to_skip + payload_length;
|
||||
lift
|
||||
(function
|
||||
| Ok frame_payload -> Ok { Frame.frame_header; frame_payload }
|
||||
| Error e -> Error e)
|
||||
(parse_frame_payload frame_header)
|
||||
|
||||
(* This is the client connection preface. *)
|
||||
let connection_preface =
|
||||
(* From RFC7540§3.5:
|
||||
* In HTTP/2, each endpoint is required to send a connection preface as a
|
||||
* final confirmation of the protocol in use and to establish the initial
|
||||
* settings for the HTTP/2 connection. *)
|
||||
string Frame.connection_preface <?> "connection preface"
|
||||
|
||||
module Reader = struct
|
||||
module AU = Angstrom.Unbuffered
|
||||
|
||||
type parse_error =
|
||||
(* Parse error reported by Angstrom *)
|
||||
[ `Parse of string list * string
|
||||
| (* Full error information *)
|
||||
`Error of Error.t
|
||||
| (* Just the error code, need to puzzle back connection or stream info *)
|
||||
`Error_code of
|
||||
Error_code.t
|
||||
]
|
||||
|
||||
type 'error parse_state =
|
||||
| Initial
|
||||
| Fail of 'error
|
||||
| Partial of
|
||||
(Bigstringaf.t
|
||||
-> off:int
|
||||
-> len:int
|
||||
-> AU.more
|
||||
-> (unit, 'error) result AU.state)
|
||||
|
||||
type 'error t =
|
||||
{ parser : (unit, 'error) result Angstrom.t
|
||||
; mutable parse_state : 'error parse_state
|
||||
(* The state of the parse for the current request *)
|
||||
; mutable closed : bool
|
||||
(* Whether the input source has left the building, indicating that no
|
||||
further input will be received. *)
|
||||
; parse_context : parse_context
|
||||
(* The current stream identifier being processed, in order to discern
|
||||
whether the error that needs to be assembled is a stream or connection
|
||||
error. *)
|
||||
}
|
||||
|
||||
type frame = parse_error t
|
||||
|
||||
let create parser parse_context =
|
||||
{ parser; parse_state = Initial; closed = false; parse_context }
|
||||
|
||||
let create_parse_context max_frame_size =
|
||||
{ frame_header = default_frame_header
|
||||
; remaining_bytes_to_skip = 0
|
||||
; did_report_stream_error = false
|
||||
; max_frame_size
|
||||
}
|
||||
|
||||
let settings_preface parse_context =
|
||||
(* From RFC7540§3.5:
|
||||
* [...] the connection preface starts with the string
|
||||
* PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n). This sequence MUST be followed by
|
||||
* a SETTINGS frame (Section 6.5), which MAY be empty. *)
|
||||
parse_frame parse_context >>| function
|
||||
| Ok ({ frame_payload = Frame.Settings settings_list; _ } as frame) ->
|
||||
Ok (frame, settings_list)
|
||||
| Ok { frame_payload = Frame.GoAway (_, error_code, debug_data); _ } ->
|
||||
(* From RFC7540§9.2.1:
|
||||
* An endpoint MAY immediately terminate an HTTP/2 connection that does
|
||||
* not meet these TLS requirements with a connection error (Section
|
||||
* 5.4.1) of type INADEQUATE_SECURITY.
|
||||
*
|
||||
* Note: we are liberal on purpose in this branch instead of only
|
||||
* accepting an error of type `INADEQUATE_SECURITY`. If an endpoint is
|
||||
* sending us a `GOAWAY` frame we probably did something wrong and
|
||||
* deserve to know what that is. *)
|
||||
Error
|
||||
(`Error
|
||||
Error.(ConnectionError (error_code, Bigstringaf.to_string debug_data)))
|
||||
| Ok _ ->
|
||||
(* From RFC7540§3.5:
|
||||
* Clients and servers MUST treat an invalid connection preface as a
|
||||
* connection error (Section 5.4.1) of type PROTOCOL_ERROR. A GOAWAY
|
||||
* frame (Section 6.8) MAY be omitted in this case, since an invalid
|
||||
* preface indicates that the peer is not using HTTP/2. *)
|
||||
Error
|
||||
(`Error
|
||||
Error.(ConnectionError (ProtocolError, "Invalid connection preface")))
|
||||
| Error e -> Error (`Error e)
|
||||
|
||||
let connection_preface_and_frames
|
||||
~max_frame_size
|
||||
preface_parser
|
||||
preface_handler
|
||||
frame_handler
|
||||
=
|
||||
let parse_context = create_parse_context max_frame_size in
|
||||
let parser =
|
||||
preface_parser parse_context <* commit >>= function
|
||||
| Ok (frame, settings_list) ->
|
||||
preface_handler frame settings_list;
|
||||
(* After having received a valid connection preface, we can start
|
||||
* reading other frames now. *)
|
||||
skip_many (parse_frame parse_context <* commit >>| frame_handler)
|
||||
>>| fun () -> Ok ()
|
||||
| Error _ as error -> return error
|
||||
in
|
||||
create parser parse_context
|
||||
|
||||
let client_frames preface_handler frame_handler =
|
||||
connection_preface_and_frames
|
||||
(* From RFC7540§3.5:
|
||||
* The server connection preface consists of a potentially empty
|
||||
* SETTINGS frame (Section 6.5) that MUST be the first frame the server
|
||||
* sends in the HTTP/2 connection. *)
|
||||
settings_preface
|
||||
preface_handler
|
||||
frame_handler
|
||||
|
||||
let server_frames ~max_frame_size preface_handler frame_handler =
|
||||
connection_preface_and_frames
|
||||
~max_frame_size
|
||||
(fun parse_context ->
|
||||
(* From RFC7540§3.5:
|
||||
* The client connection preface starts with a sequence of 24 octets,
|
||||
* which in hex notation is:
|
||||
*
|
||||
* 0x505249202a20485454502f322e300d0a0d0a534d0d0a0d0a
|
||||
* That is, the connection preface starts with the string
|
||||
* PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n). This sequence MUST be followed
|
||||
* by a SETTINGS frame (Section 6.5), which MAY be empty. *)
|
||||
connection_preface *> settings_preface parse_context)
|
||||
preface_handler
|
||||
frame_handler
|
||||
|
||||
let is_closed t = t.closed
|
||||
|
||||
let transition t state =
|
||||
match state with
|
||||
| AU.Done (consumed, Ok ()) ->
|
||||
t.parse_state <- Initial;
|
||||
consumed
|
||||
| Done (consumed, Error error) ->
|
||||
t.parse_state <- Fail error;
|
||||
consumed
|
||||
| Fail (consumed, marks, msg) ->
|
||||
t.parse_state <- Fail (`Parse (marks, msg));
|
||||
consumed
|
||||
| Partial { committed; continue } ->
|
||||
(* If we have bytes to skip over then it means we've spotted a
|
||||
* FRAME_SIZE_ERROR, a case where, due to our unbuffered parsing, the
|
||||
* payload length declared in a frame header is larger than the
|
||||
* underlying buffer can fit. *)
|
||||
if t.parse_context.remaining_bytes_to_skip > 0
|
||||
then t.parse_state <- Fail (`Error_code Error_code.FrameSizeError)
|
||||
else t.parse_state <- Partial continue;
|
||||
committed
|
||||
|
||||
let start t state =
|
||||
match state with
|
||||
| AU.Done _ -> failwith "h2.Parse.Reader.unable to start parser"
|
||||
| Fail (0, marks, msg) -> t.parse_state <- Fail (`Parse (marks, msg))
|
||||
| Partial { committed = 0; continue } -> t.parse_state <- Partial continue
|
||||
| Partial _ | Fail _ -> assert false
|
||||
|
||||
let rec read_with_more t bs ~off ~len more =
|
||||
let consumed =
|
||||
match t.parse_state with
|
||||
| Fail _ ->
|
||||
let parser_ctx = t.parse_context in
|
||||
let remaining_bytes = parser_ctx.remaining_bytes_to_skip in
|
||||
(* Just skip input if we need to *)
|
||||
if remaining_bytes > 0
|
||||
then (
|
||||
assert (remaining_bytes >= len);
|
||||
let remaining_bytes' = remaining_bytes - len in
|
||||
parser_ctx.remaining_bytes_to_skip <- remaining_bytes';
|
||||
assert (remaining_bytes' >= 0);
|
||||
if remaining_bytes' = 0
|
||||
then
|
||||
(* Reset the parser state to `Done` so that we can read the next
|
||||
* frame (after skipping through the bad input) *)
|
||||
t.parse_state <- Initial;
|
||||
len)
|
||||
else 0
|
||||
| Initial ->
|
||||
start t (AU.parse t.parser);
|
||||
read_with_more t bs ~off ~len more
|
||||
| Partial continue -> transition t (continue bs more ~off ~len)
|
||||
in
|
||||
(match more with Complete -> t.closed <- true | Incomplete -> ());
|
||||
consumed
|
||||
|
||||
let force_close t = t.closed <- true
|
||||
let fail_to_string marks err = String.concat " > " marks ^ ": " ^ err
|
||||
|
||||
let next_from_error t ?(msg = "") error_code =
|
||||
if t.parse_context.frame_header == default_frame_header
|
||||
then `Error Error.(ConnectionError (error_code, msg))
|
||||
else
|
||||
match t.parse_context, error_code with
|
||||
| ( { frame_header =
|
||||
{ frame_type =
|
||||
Headers | PushPromise | Continuation | Settings | Unknown _
|
||||
; _
|
||||
}
|
||||
; _
|
||||
}
|
||||
, Error_code.FrameSizeError )
|
||||
| { frame_header = { Frame.stream_id = 0x0l; _ }; _ }, _ ->
|
||||
(* From RFC7540§4.2:
|
||||
* A frame size error in a frame that could alter the state of the
|
||||
* entire connection MUST be treated as a connection error (Section
|
||||
* 5.4.1); this includes any frame carrying a header block (Section
|
||||
* 4.3) (that is, HEADERS, PUSH_PROMISE, and CONTINUATION), SETTINGS,
|
||||
* and any frame with a stream identifier of 0. *)
|
||||
`Error Error.(ConnectionError (error_code, msg))
|
||||
| { did_report_stream_error = true; _ }, _ ->
|
||||
(* If the parser is in a `Fail` state and would report a stream error,
|
||||
* just issue a `Read` operation if we've already reported that error. *)
|
||||
if t.closed then `Close else `Read
|
||||
| { frame_header = { Frame.stream_id; _ }; _ }, _ ->
|
||||
t.parse_context.did_report_stream_error <- true;
|
||||
`Error Error.(StreamError (stream_id, error_code))
|
||||
|
||||
let next t =
|
||||
match t.parse_state with
|
||||
| Fail error ->
|
||||
(match error with
|
||||
| `Error e -> `Error e
|
||||
| `Error_code error_code -> next_from_error t error_code
|
||||
| `Parse (marks, msg) ->
|
||||
let error_code =
|
||||
match marks, msg with
|
||||
| [ "frame_payload" ], "not enough input" ->
|
||||
(* From RFC7540§4.2:
|
||||
* An endpoint MUST send an error code of FRAME_SIZE_ERROR if a
|
||||
* frame exceeds the size defined in SETTINGS_MAX_FRAME_SIZE,
|
||||
* exceeds any limit defined for the frame type, or is too small
|
||||
* to contain mandatory frame data. *)
|
||||
Error_code.FrameSizeError
|
||||
| _ -> Error_code.ProtocolError
|
||||
in
|
||||
next_from_error t ~msg:(fail_to_string marks msg) error_code)
|
||||
| _ when t.closed -> `Close
|
||||
| Partial _ -> `Read
|
||||
| Initial -> if t.closed then `Close else `Read
|
||||
end
|
||||
71
unikernel/duniverse/ocaml-h2/lib/priority.ml
Normal file
71
unikernel/duniverse/ocaml-h2/lib/priority.ml
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
(*----------------------------------------------------------------------------
|
||||
* Copyright (c) 2019 António Nuno 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 copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 COPYRIGHT HOLDER 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.
|
||||
*---------------------------------------------------------------------------*)
|
||||
|
||||
open Util
|
||||
|
||||
type t =
|
||||
{ exclusive : bool
|
||||
; stream_dependency : Stream_identifier.t
|
||||
; weight : int
|
||||
}
|
||||
|
||||
(* From RFC7540§5.3.5:
|
||||
* All streams are initially assigned a non-exclusive dependency on stream
|
||||
* 0x0. Pushed streams (Section 8.2) initially depend on their associated
|
||||
* stream. In both cases, streams are assigned a default weight of 16. *)
|
||||
let default_priority =
|
||||
{ exclusive = false; stream_dependency = 0l; weight = 16 }
|
||||
|
||||
(* From RFC7540§5.4.1:
|
||||
* All dependent streams are allocated an integer weight between 1 and 256
|
||||
* (inclusive). *)
|
||||
let highest_priority =
|
||||
{ exclusive = false; stream_dependency = 0l; weight = 256 }
|
||||
|
||||
(* --- Exclusive flag ---
|
||||
*
|
||||
* From RFC7540§5.4.1:
|
||||
* +-+-------------------------------------------------------------+
|
||||
* |E| Stream Dependency (31) |
|
||||
* +-+-------------+-----------------------------------------------+
|
||||
* | Weight (8) |
|
||||
* +-+-------------+
|
||||
*)
|
||||
|
||||
let test_exclusive n = test_bit_int32 n 31
|
||||
let set_exclusive n = set_bit_int32 n 31
|
||||
let clear_exclusive n = clear_bit_int32 n 31
|
||||
|
||||
let equal p1 p2 =
|
||||
p1.weight = p2.weight
|
||||
&& Int32.equal p1.stream_dependency p2.stream_dependency
|
||||
&& p1.exclusive = p2.exclusive
|
||||
523
unikernel/duniverse/ocaml-h2/lib/reqd.ml
Normal file
523
unikernel/duniverse/ocaml-h2/lib/reqd.ml
Normal file
|
|
@ -0,0 +1,523 @@
|
|||
(*----------------------------------------------------------------------------
|
||||
* 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.
|
||||
*---------------------------------------------------------------------------*)
|
||||
|
||||
module Writer = Serialize.Writer
|
||||
|
||||
type error =
|
||||
[ `Bad_request
|
||||
| `Internal_server_error
|
||||
| `Exn of exn
|
||||
]
|
||||
|
||||
type error_handler =
|
||||
?request:Request.t -> error -> (Headers.t -> Body.Writer.t) -> unit
|
||||
|
||||
type response_state =
|
||||
| Waiting
|
||||
| Fixed of
|
||||
{ response : Response.t
|
||||
; mutable iovec :
|
||||
[ `String of string | `Bigstring of Bigstringaf.t ]
|
||||
Httpun_types.IOVec.t
|
||||
}
|
||||
| Streaming of
|
||||
{ response : Response.t
|
||||
; response_body : Body.Writer.t
|
||||
; trailers : Headers.t
|
||||
}
|
||||
| Complete of Response.t
|
||||
|
||||
type request_info =
|
||||
{ request : Request.t
|
||||
; request_body : Body.Reader.t
|
||||
; mutable request_body_bytes : int64
|
||||
}
|
||||
|
||||
type active_state = (request_info, request_info) Stream.active_state
|
||||
|
||||
type active_stream =
|
||||
{ body_buffer_size : int
|
||||
; encoder : Hpack.Encoder.t
|
||||
; mutable response_state : response_state
|
||||
(* We're not doing anything with these yet, we could probably have a
|
||||
* `Reqd.schedule_read_trailers` function that would be called once
|
||||
* trailer headers are emitted. *)
|
||||
; mutable trailers_parser : Stream.partial_headers option
|
||||
; mutable trailers : Headers.t option
|
||||
; create_push_stream :
|
||||
Stream_identifier.t
|
||||
-> (t, [ `Push_disabled | `Stream_ids_exhausted ]) result
|
||||
}
|
||||
|
||||
and state =
|
||||
(active_state, active_stream, request_info * active_stream) Stream.state
|
||||
|
||||
and t = (state, error, error_handler) Stream.t
|
||||
|
||||
let create_active_request request request_body =
|
||||
{ request; request_body; request_body_bytes = Int64.zero }
|
||||
|
||||
let create_active_stream encoder body_buffer_size create_push_stream =
|
||||
{ body_buffer_size
|
||||
; encoder
|
||||
; response_state = Waiting
|
||||
; trailers_parser = None
|
||||
; trailers = None
|
||||
; create_push_stream
|
||||
}
|
||||
|
||||
let request (t : t) =
|
||||
match t.state with
|
||||
| Idle | Active (Open (WaitingForPeer | PartialHeaders _ | FullHeaders), _) ->
|
||||
assert false
|
||||
| Active ((Open (ActiveMessage { request; _ }) | HalfClosed { request; _ }), _)
|
||||
| Reserved ({ request; _ }, _) ->
|
||||
request
|
||||
| Closed _ -> assert false
|
||||
|
||||
let request_body (t : t) =
|
||||
match t.state with
|
||||
| Idle | Active (Open (WaitingForPeer | PartialHeaders _ | FullHeaders), _) ->
|
||||
assert false
|
||||
| Active
|
||||
( ( Open (ActiveMessage { request_body; _ })
|
||||
| HalfClosed { request_body; _ } )
|
||||
, _ ) ->
|
||||
request_body
|
||||
| Reserved _ ->
|
||||
(* From RFC7540§8.1:
|
||||
* Promised requests MUST NOT include a request body. *)
|
||||
failwith
|
||||
"h2.Reqd.request_body: Promised requests must not include a request body"
|
||||
| Closed _ -> failwith "h2.Reqd.request_body: Stream already closed"
|
||||
|
||||
let response (t : t) =
|
||||
match t.state with
|
||||
| Idle | Active (Open (WaitingForPeer | PartialHeaders _), _) -> None
|
||||
| Active
|
||||
( (Open (FullHeaders | ActiveMessage _) | HalfClosed _)
|
||||
, { response_state; _ } )
|
||||
| Reserved (_, { response_state; _ }) ->
|
||||
(match response_state with
|
||||
| Waiting -> None
|
||||
| Streaming { response; _ } | Fixed { response; _ } | Complete response ->
|
||||
Some response)
|
||||
| Closed _ -> None
|
||||
|
||||
let response_exn (t : t) =
|
||||
match t.state with
|
||||
| Idle | Active (Open (WaitingForPeer | PartialHeaders _), _) ->
|
||||
failwith "h2.Reqd.response_exn: response has not started"
|
||||
| Active
|
||||
( (Open (FullHeaders | ActiveMessage _) | HalfClosed _)
|
||||
, { response_state; _ } )
|
||||
| Reserved (_, { response_state; _ }) ->
|
||||
(match response_state with
|
||||
| Waiting -> failwith "h2.Reqd.response_exn: response has not started"
|
||||
| Streaming { response; _ } | Fixed { response; _ } | Complete response ->
|
||||
response)
|
||||
| Closed _ -> assert false
|
||||
|
||||
let send_fixed_response (t : t) s response data =
|
||||
match s.response_state with
|
||||
| Waiting ->
|
||||
let iovec, length =
|
||||
match data with
|
||||
| `String s ->
|
||||
let len = String.length s in
|
||||
let iovec = { Httpun_types.IOVec.buffer = `String s; off = 0; len } in
|
||||
iovec, len
|
||||
| `Bigstring b ->
|
||||
let len = Bigstringaf.length b in
|
||||
let iovec =
|
||||
{ Httpun_types.IOVec.buffer = `Bigstring b; off = 0; len }
|
||||
in
|
||||
iovec, len
|
||||
in
|
||||
let should_send_data = length <> 0 in
|
||||
let frame_info =
|
||||
Writer.make_frame_info
|
||||
~max_frame_size:t.max_frame_size
|
||||
~flags:
|
||||
(if should_send_data
|
||||
then Flags.default_flags
|
||||
else Flags.(set_end_stream default_flags))
|
||||
t.id
|
||||
in
|
||||
Writer.write_response_headers t.writer s.encoder frame_info response;
|
||||
(* From RFC7540§8.1:
|
||||
* An HTTP request/response exchange fully consumes a single stream.
|
||||
* [...] A response starts with a HEADERS frame and ends with a frame
|
||||
* bearing END_STREAM, which places the stream in the "closed" state. *)
|
||||
if should_send_data
|
||||
then s.response_state <- Fixed { response; iovec }
|
||||
else s.response_state <- Complete response;
|
||||
Writer.wakeup t.writer
|
||||
| Streaming _ -> failwith "h2.Reqd.respond_with_*: response already started"
|
||||
| Fixed _ -> failwith "h2.Reqd.respond_with_*: response already sent"
|
||||
| Complete _ -> failwith "h2.Reqd.respond_with_*: response already complete"
|
||||
|
||||
let schedule_trailers (t : t) new_trailers =
|
||||
match t.state with
|
||||
| Idle | Active (Open (WaitingForPeer | PartialHeaders _), _) -> assert false
|
||||
| Closed _ -> failwith "h2.Reqd.schedule_trailers: stream already closed"
|
||||
| Reserved _ -> failwith "h2.Reqd.schedule_trailers: response not started"
|
||||
| Active ((Open (FullHeaders | ActiveMessage _) | HalfClosed _), stream) ->
|
||||
(match stream.response_state with
|
||||
| Streaming { response; response_body; trailers = old_trailers } ->
|
||||
if old_trailers <> Headers.empty
|
||||
then failwith "h2.Reqd.schedule_trailers: trailers already scheduled";
|
||||
stream.response_state <-
|
||||
Streaming { response; response_body; trailers = new_trailers }
|
||||
| _ ->
|
||||
failwith
|
||||
"h2.Reqd.schedule_trailers: can only send trailers in Streaming mode")
|
||||
|
||||
let unsafe_respond_with_data (t : t) response data =
|
||||
match t.state with
|
||||
| Idle | Active (Open (WaitingForPeer | PartialHeaders _), _) -> assert false
|
||||
| Active ((Open (FullHeaders | ActiveMessage _) | HalfClosed _), stream) ->
|
||||
send_fixed_response t stream response data
|
||||
| Reserved (request_info, stream) ->
|
||||
send_fixed_response t stream response data;
|
||||
(* From RFC7540§8.1:
|
||||
* reserved (local): [...] In this state, only the following transitions
|
||||
* are possible: The endpoint can send a HEADERS frame. This causes the
|
||||
* stream to open in a "half-closed (remote)" state. *)
|
||||
Writer.flush t.writer (fun _reason ->
|
||||
(* TODO(anmonteiro): different if closed? *)
|
||||
t.state <- Active (HalfClosed request_info, stream))
|
||||
| Closed _ -> assert false
|
||||
|
||||
let respond_with_string (t : t) response str =
|
||||
match t.error_code with
|
||||
| No_error -> unsafe_respond_with_data t response (`String str)
|
||||
| _ ->
|
||||
failwith
|
||||
"h2.Reqd.respond_with_string: invalid state, currently handling error"
|
||||
|
||||
let respond_with_bigstring (t : t) response bstr =
|
||||
match t.error_code with
|
||||
| No_error -> unsafe_respond_with_data t response (`Bigstring bstr)
|
||||
| _ ->
|
||||
failwith
|
||||
"h2.Reqd.respond_with_bigstring: invalid state, currently handling error"
|
||||
|
||||
let send_streaming_response ~flush_headers_immediately (t : t) s response =
|
||||
let wait_for_first_flush = not flush_headers_immediately in
|
||||
match s.response_state with
|
||||
| Waiting ->
|
||||
let frame_info =
|
||||
Writer.make_frame_info ~max_frame_size:t.max_frame_size t.id
|
||||
in
|
||||
let response_body_buffer = Bigstringaf.create s.body_buffer_size in
|
||||
let response_body =
|
||||
Body.Writer.create response_body_buffer ~writer:t.writer
|
||||
in
|
||||
Writer.write_response_headers t.writer s.encoder frame_info response;
|
||||
if wait_for_first_flush then Writer.yield t.writer;
|
||||
s.response_state <-
|
||||
Streaming { response; response_body; trailers = Headers.empty };
|
||||
Writer.wakeup t.writer;
|
||||
response_body
|
||||
| Streaming _ ->
|
||||
failwith "h2.Reqd.respond_with_streaming: response already started"
|
||||
| Fixed _ | Complete _ ->
|
||||
failwith "h2.Reqd.respond_with_streaming: response already complete"
|
||||
|
||||
let unsafe_respond_with_streaming (t : t) ~flush_headers_immediately response =
|
||||
match t.state with
|
||||
| Idle | Active (Open (WaitingForPeer | PartialHeaders _), _) -> assert false
|
||||
| Active ((Open (FullHeaders | ActiveMessage _) | HalfClosed _), stream) ->
|
||||
send_streaming_response ~flush_headers_immediately t stream response
|
||||
| Reserved (request_info, stream) ->
|
||||
let response_body =
|
||||
send_streaming_response ~flush_headers_immediately t stream response
|
||||
in
|
||||
(* From RFC7540§8.1:
|
||||
* reserved (local): [...] In this state, only the following transitions
|
||||
* are possible: The endpoint can send a HEADERS frame. This causes the
|
||||
* stream to open in a "half-closed (remote)" state. *)
|
||||
Writer.flush t.writer (fun _reason ->
|
||||
(* TODO(anmonteiro): different if closed? *)
|
||||
t.state <- Active (HalfClosed request_info, stream));
|
||||
response_body
|
||||
| Closed _ -> assert false
|
||||
|
||||
let respond_with_streaming (t : t) ?(flush_headers_immediately = false) response
|
||||
=
|
||||
match t.error_code with
|
||||
| No_error ->
|
||||
unsafe_respond_with_streaming ~flush_headers_immediately t response
|
||||
| _ ->
|
||||
failwith
|
||||
"h2.Reqd.respond_with_streaming: invalid state, currently handling error"
|
||||
|
||||
let start_push_stream (t : t) s request =
|
||||
match s.create_push_stream t.id with
|
||||
| Ok promised_reqd ->
|
||||
let frame_info =
|
||||
Writer.make_frame_info ~max_frame_size:t.max_frame_size t.id
|
||||
in
|
||||
Writer.write_push_promise
|
||||
t.writer
|
||||
s.encoder
|
||||
frame_info
|
||||
~promised_id:promised_reqd.id
|
||||
request;
|
||||
let { encoder; body_buffer_size; create_push_stream; _ } = s in
|
||||
(* From RFC7540§8.2:
|
||||
* Promised requests [...] MUST NOT include a request body. *)
|
||||
let request_info = create_active_request request Body.Reader.empty in
|
||||
let active_stream =
|
||||
create_active_stream encoder body_buffer_size create_push_stream
|
||||
in
|
||||
(* From RFC7540§8.2.1:
|
||||
* Sending a PUSH_PROMISE frame creates a new stream and puts the stream
|
||||
* into the "reserved (local)" state for the server and the "reserved
|
||||
* (remote)" state for the client.
|
||||
*
|
||||
* Note: we do this before flushing the writer because request handlers
|
||||
* might immediately call one of the `respond_with` functions and expect
|
||||
* the stream to be in the `Reserved` state. *)
|
||||
promised_reqd.state <- Reserved (request_info, active_stream);
|
||||
Writer.wakeup t.writer;
|
||||
Ok promised_reqd
|
||||
| Error e ->
|
||||
Error (e :> [ `Push_disabled | `Stream_cant_push | `Stream_ids_exhausted ])
|
||||
|
||||
(* TODO: We could easily allow the priority of the PUSH request to be
|
||||
* configurable. We should allow users of this API to define the weight (maybe
|
||||
* not strictly), dependency on the current Reqd, and exclusivity *)
|
||||
let unsafe_push (t : t) request =
|
||||
match t.state with
|
||||
| Idle | Active (Open (WaitingForPeer | PartialHeaders _), _) -> assert false
|
||||
| Active ((Open (FullHeaders | ActiveMessage _) | HalfClosed _), stream) ->
|
||||
start_push_stream t stream request
|
||||
(* Already checked in `push` *)
|
||||
| Reserved _ | Closed _ -> assert false
|
||||
|
||||
let push (t : t) request =
|
||||
match t.error_code with
|
||||
| No_error ->
|
||||
if Stream_identifier.is_pushed t.id
|
||||
then
|
||||
(* From RFC7540§6.6:
|
||||
* PUSH_PROMISE frames MUST only be sent on a peer-initiated stream that
|
||||
* is in either the "open" or "half-closed (remote)" state. *)
|
||||
Error `Stream_cant_push
|
||||
else unsafe_push t request
|
||||
| _ -> failwith "h2.Reqd.push: invalid state, currently handling error"
|
||||
|
||||
let _report_error ?request (t : t) s (error : error) error_code =
|
||||
match s.response_state, t.error_code with
|
||||
| Waiting, No_error ->
|
||||
t.error_code <- Stream.error_to_code error error_code;
|
||||
let status =
|
||||
match (error :> [ error | Status.standard ]) with
|
||||
| `Exn _ -> `Internal_server_error
|
||||
| #Status.standard as status -> status
|
||||
in
|
||||
t.error_handler ?request error (fun headers ->
|
||||
let response = Response.create ~headers status in
|
||||
unsafe_respond_with_streaming ~flush_headers_immediately:true t response)
|
||||
| Streaming { response_body; _ }, No_error ->
|
||||
Body.Writer.close response_body;
|
||||
t.error_code <- Stream.error_to_code error error_code;
|
||||
Stream.reset_stream t error_code
|
||||
| Fixed _, No_error ->
|
||||
(* Still need to send an RST_STREAM frame. Set t.error_code with
|
||||
* `error_code` and `flush_response_body` below will reset the stream after
|
||||
* flushing any remaining body bytes. *)
|
||||
t.error_code <- Stream.error_to_code error error_code;
|
||||
Stream.reset_stream t error_code
|
||||
| (Waiting | Fixed _ | Streaming _), Exn _ ->
|
||||
(* XXX(seliopou): Decide what to do in this unlikely case. There is an
|
||||
* outstanding call to the [error_handler], but an intervening exception
|
||||
* has been reported as well. *)
|
||||
failwith "h2.Reqd.report_exn: NYI"
|
||||
| (Waiting | Streaming _ | Fixed _ | Complete _), _ -> ()
|
||||
|
||||
let report_error (t : t) exn error_code =
|
||||
match t.state with
|
||||
| Idle | Reserved _ | Active (Open (WaitingForPeer | PartialHeaders _), _) ->
|
||||
assert false
|
||||
| Active (Open FullHeaders, stream) -> _report_error t stream exn error_code
|
||||
| Active
|
||||
( ( Open (ActiveMessage { request; request_body; _ })
|
||||
| HalfClosed { request; request_body; _ } )
|
||||
, stream ) ->
|
||||
Body.Reader.close request_body;
|
||||
_report_error t stream ~request exn error_code
|
||||
| Closed _ -> ()
|
||||
|
||||
let report_exn t exn = report_error t (`Exn exn) Error_code.InternalError
|
||||
|
||||
let try_with t f : (unit, exn) result =
|
||||
try
|
||||
f ();
|
||||
Ok ()
|
||||
with
|
||||
| exn ->
|
||||
report_exn t exn;
|
||||
Error exn
|
||||
|
||||
let error_code = Stream.error_code
|
||||
(* Private API, not exposed to the user through h2.mli *)
|
||||
|
||||
let requires_output (t : t) =
|
||||
match t.state with
|
||||
| Idle -> false
|
||||
| Reserved _ -> true
|
||||
| Active (Open (WaitingForPeer | PartialHeaders _), _) -> false
|
||||
| Active
|
||||
( (Open (FullHeaders | ActiveMessage _) | HalfClosed _)
|
||||
, { response_state; _ } ) ->
|
||||
(* From RFC7540§8.1:
|
||||
* A server can send a complete response prior to the client sending an
|
||||
* entire request if the response does not depend on any portion of the
|
||||
* request that has not been sent and received. *)
|
||||
(match response_state with
|
||||
| Complete _ -> false
|
||||
| Fixed { iovec = { len; _ }; _ } -> len > 0
|
||||
| Streaming _ -> true
|
||||
| Waiting -> true)
|
||||
| Closed _ -> false
|
||||
|
||||
let flush_request_body (t : t) =
|
||||
match t.state with
|
||||
| Active
|
||||
( ( Open (ActiveMessage { request_body; _ })
|
||||
| HalfClosed { request_body; _ } )
|
||||
, _ ) ->
|
||||
if Body.Reader.has_pending_output request_body
|
||||
then (
|
||||
try Body.Reader.execute_read request_body with exn -> report_exn t exn)
|
||||
| _ -> ()
|
||||
|
||||
let write_buffer_data writer ~off ~len frame_info buffer =
|
||||
match buffer with
|
||||
| `String str -> Writer.write_data writer ~off ~len frame_info str
|
||||
| `Bigstring bstr -> Writer.schedule_data writer ~off ~len frame_info bstr
|
||||
|
||||
let close_stream (t : t) =
|
||||
match t.error_code with
|
||||
| No_error ->
|
||||
(match t.state with
|
||||
| Active (Open (FullHeaders | ActiveMessage _), _) ->
|
||||
(* From RFC7540§8.1:
|
||||
* A server can send a complete response prior to the client sending an
|
||||
* entire request if the response does not depend on any portion of the
|
||||
* request that has not been sent and received. When this is true, a
|
||||
* server MAY request that the client abort transmission of a request
|
||||
* without error by sending a RST_STREAM with an error code of NO_ERROR
|
||||
* after sending a complete response (i.e., a frame with the END_STREAM
|
||||
* flag). *)
|
||||
Stream.reset_stream t Error_code.NoError
|
||||
| Active (HalfClosed _, _) ->
|
||||
Writer.flush t.writer (fun _reason -> Stream.finish_stream t Finished)
|
||||
| _ -> assert false)
|
||||
| Exn _ -> Stream.reset_stream t InternalError
|
||||
| Other { code; _ } -> Stream.reset_stream t code
|
||||
|
||||
let flush_response_body (t : t) ~max_bytes =
|
||||
match t.state with
|
||||
| Active ((Open _ | HalfClosed _), stream) ->
|
||||
(match stream.response_state with
|
||||
| Streaming { response; response_body; trailers } ->
|
||||
if Body.Writer.has_pending_output response_body && max_bytes > 0
|
||||
then
|
||||
Body.Writer.transfer_to_writer
|
||||
response_body
|
||||
t.writer
|
||||
~max_frame_size:t.max_frame_size
|
||||
~max_bytes
|
||||
t.id
|
||||
else if Body.Writer.is_closed response_body
|
||||
then (
|
||||
(* no pending output and closed, we can finalize the message and close
|
||||
the stream *)
|
||||
let frame_info =
|
||||
Writer.make_frame_info
|
||||
~max_frame_size:t.max_frame_size
|
||||
~flags:Flags.(set_end_stream default_flags)
|
||||
t.id
|
||||
in
|
||||
match trailers with
|
||||
| _ :: _ ->
|
||||
Writer.write_response_trailers
|
||||
t.writer
|
||||
stream.encoder
|
||||
frame_info
|
||||
trailers;
|
||||
close_stream t;
|
||||
stream.response_state <- Complete response;
|
||||
0
|
||||
| [] ->
|
||||
(* From RFC7540§6.9.1:
|
||||
* Frames with zero length with the END_STREAM flag set (that is,
|
||||
* an empty DATA frame) MAY be sent if there is no available space
|
||||
* in either flow-control window. *)
|
||||
Writer.schedule_data t.writer frame_info ~len:0 Bigstringaf.empty;
|
||||
close_stream t;
|
||||
stream.response_state <- Complete response;
|
||||
0)
|
||||
else (* no pending output but Body is still open *)
|
||||
0
|
||||
| Fixed ({ iovec = { buffer; off; len } as iovec; _ } as r)
|
||||
when max_bytes > 0 ->
|
||||
let is_partial_flush = max_bytes < len in
|
||||
let frame_info =
|
||||
let flags =
|
||||
if is_partial_flush
|
||||
then Flags.default_flags
|
||||
else Flags.(set_end_stream default_flags)
|
||||
in
|
||||
Writer.make_frame_info ~max_frame_size:t.max_frame_size ~flags t.id
|
||||
in
|
||||
let len_to_write = if is_partial_flush then max_bytes else len in
|
||||
write_buffer_data t.writer ~off ~len:len_to_write frame_info buffer;
|
||||
r.iovec <- Httpun_types.IOVec.shift iovec len_to_write;
|
||||
if not is_partial_flush then close_stream t;
|
||||
len_to_write
|
||||
| Fixed _ | Waiting | Complete _ -> 0)
|
||||
| _ -> 0
|
||||
|
||||
let deliver_trailer_headers (t : t) headers =
|
||||
match t.state with
|
||||
| Active (Open (PartialHeaders _ | FullHeaders), _) -> assert false
|
||||
| Active ((Open (ActiveMessage _) | HalfClosed _), stream) ->
|
||||
(* TODO: call the schedule_trailers callback *)
|
||||
stream.trailers <- Some headers
|
||||
| _ -> assert false
|
||||
57
unikernel/duniverse/ocaml-h2/lib/request.ml
Normal file
57
unikernel/duniverse/ocaml-h2/lib/request.ml
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
(*----------------------------------------------------------------------------
|
||||
* 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.
|
||||
*---------------------------------------------------------------------------*)
|
||||
|
||||
type t =
|
||||
{ meth : Httpun_types.Method.t
|
||||
; target : string
|
||||
; scheme : string
|
||||
; headers : Headers.t
|
||||
}
|
||||
|
||||
(* TODO: `:authority` pseudo-header? *)
|
||||
let create ?(headers = Headers.empty) ~scheme meth target =
|
||||
{ meth; target; scheme; headers }
|
||||
|
||||
let body_length { headers; _ } = Message.body_length headers
|
||||
|
||||
let pp_hum fmt { meth; target; scheme; headers } =
|
||||
Format.fprintf
|
||||
fmt
|
||||
"((method \"%a\") (target %S) (scheme %S) (headers %a))"
|
||||
Httpun_types.Method.pp_hum
|
||||
meth
|
||||
target
|
||||
scheme
|
||||
Headers.pp_hum
|
||||
headers
|
||||
200
unikernel/duniverse/ocaml-h2/lib/respd.ml
Normal file
200
unikernel/duniverse/ocaml-h2/lib/respd.ml
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
(*----------------------------------------------------------------------------
|
||||
* Copyright (c) 2019 António Nuno 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 copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 COPYRIGHT HOLDER 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.
|
||||
*---------------------------------------------------------------------------*)
|
||||
|
||||
(* TODO(anmonteiro): think about whether we wanna expose this module. it might
|
||||
* be helpful to expose a way to reset streams, and I think we'd need a
|
||||
* reference to the Respd *)
|
||||
|
||||
module Writer = Serialize.Writer
|
||||
|
||||
type error =
|
||||
[ `Malformed_response of string
|
||||
| `Invalid_response_body_length of Response.t
|
||||
| `Protocol_error of Error_code.t * string
|
||||
| `Exn of exn
|
||||
]
|
||||
|
||||
type error_handler = error -> unit
|
||||
type response_handler = Response.t -> Body.Reader.t -> unit
|
||||
|
||||
type response_info =
|
||||
{ response : Response.t
|
||||
; response_body : Body.Reader.t
|
||||
; mutable response_body_bytes : int64
|
||||
; mutable trailers_parser : Stream.partial_headers option
|
||||
}
|
||||
|
||||
type trailers_handler = Headers.t -> unit
|
||||
|
||||
type active_request =
|
||||
{ request : Request.t
|
||||
; request_body : Body.Writer.t
|
||||
; response_handler : response_handler
|
||||
; trailers_handler : trailers_handler
|
||||
}
|
||||
|
||||
type active_state =
|
||||
(response_info, response_info Stream.remote_state) Stream.active_state
|
||||
|
||||
type state =
|
||||
( active_state
|
||||
, active_request
|
||||
, active_request Stream.remote_state )
|
||||
Stream.state
|
||||
|
||||
type t = (state, error, error_handler) Stream.t
|
||||
|
||||
let create_active_response response response_body =
|
||||
Stream.ActiveMessage
|
||||
{ response
|
||||
; response_body
|
||||
; response_body_bytes = Int64.zero
|
||||
; trailers_parser = None
|
||||
}
|
||||
|
||||
let response_body_exn (t : t) =
|
||||
match t.state with
|
||||
| Idle | Reserved _ ->
|
||||
failwith "h2.Respd.response_exn: response has not arrived"
|
||||
| Active
|
||||
( ( Open (ActiveMessage { response_body; _ })
|
||||
| HalfClosed (ActiveMessage { response_body; _ }) )
|
||||
, _ ) ->
|
||||
response_body
|
||||
| Active ((Open _ | HalfClosed _), _) ->
|
||||
failwith "h2.Respd.response_exn: response has not arrived"
|
||||
| Closed _ -> failwith "h2.Respd.response_exn: stream already closed"
|
||||
|
||||
let close_stream (t : t) =
|
||||
(* TODO: reserved *)
|
||||
match t.state with
|
||||
| Active (HalfClosed _, _) ->
|
||||
(* easy case, just transition to the closed state. *)
|
||||
Stream.finish_stream t Finished
|
||||
| Active (Open _, _) ->
|
||||
(* Still not done sending, reset stream with no error? *)
|
||||
(* TODO: *)
|
||||
()
|
||||
| _ -> ()
|
||||
|
||||
let _report_error (t : t) ?response_body (error : error) error_code =
|
||||
match t.error_code with
|
||||
| No_error ->
|
||||
(match response_body with
|
||||
| Some response_body -> Body.Reader.close response_body
|
||||
| None -> ());
|
||||
t.error_code <- Stream.error_to_code error error_code;
|
||||
t.error_handler error
|
||||
| Exn _ | Other _ ->
|
||||
(* Already handling error.
|
||||
* TODO(anmonteiro): Log a message when we add Logs support *)
|
||||
()
|
||||
|
||||
let report_error (t : t) error error_code =
|
||||
match t.state with
|
||||
| Active
|
||||
( ( Open (ActiveMessage { response_body; _ })
|
||||
| HalfClosed (ActiveMessage { response_body; _ }) )
|
||||
, s ) ->
|
||||
Body.Writer.close s.request_body;
|
||||
_report_error t ~response_body error error_code;
|
||||
Stream.reset_stream t error_code
|
||||
| Reserved (ActiveMessage s) | Active (_, s) ->
|
||||
Body.Writer.close s.request_body;
|
||||
_report_error t error error_code;
|
||||
Stream.reset_stream t error_code
|
||||
| Reserved _ ->
|
||||
(* Streams in the reserved state don't yet have a stream-level error
|
||||
* handler registered with them *)
|
||||
()
|
||||
| Idle | Closed _ ->
|
||||
(* Not allowed to send RST_STREAM frames in these states *)
|
||||
ignore (_report_error t error error_code)
|
||||
|
||||
let requires_output (t : t) =
|
||||
match t.state with
|
||||
| Idle -> true
|
||||
| Reserved _ -> false
|
||||
| Active (Open _, _) -> true
|
||||
| Active (HalfClosed _, _) -> false
|
||||
| Closed _ -> false
|
||||
|
||||
let flush_request_body (t : t) ~max_bytes =
|
||||
match t.state with
|
||||
| Active (Open active_state, ({ request_body; _ } as s)) ->
|
||||
if Body.Writer.has_pending_output request_body && max_bytes > 0
|
||||
then
|
||||
Body.Writer.transfer_to_writer
|
||||
request_body
|
||||
t.writer
|
||||
~max_frame_size:t.max_frame_size
|
||||
~max_bytes
|
||||
t.id
|
||||
else if Body.Writer.is_closed request_body
|
||||
then (
|
||||
(* closed and no pending output *)
|
||||
(* From RFC7540§6.9.1:
|
||||
* Frames with zero length with the END_STREAM flag set (that is, an
|
||||
* empty DATA frame) MAY be sent if there is no available space in
|
||||
* either flow-control window. *)
|
||||
let frame_info =
|
||||
Writer.make_frame_info
|
||||
~max_frame_size:t.max_frame_size
|
||||
~flags:Flags.(set_end_stream default_flags)
|
||||
t.id
|
||||
in
|
||||
Writer.schedule_data t.writer frame_info ~len:0 Bigstringaf.empty;
|
||||
t.state <- Active (HalfClosed active_state, s);
|
||||
0)
|
||||
else (* not closed and no pending output *)
|
||||
0
|
||||
| _ -> 0
|
||||
|
||||
let deliver_trailer_headers (t : t) headers =
|
||||
match t.state with
|
||||
| Active
|
||||
( (Open (ActiveMessage _) | HalfClosed (ActiveMessage _))
|
||||
, { trailers_handler; _ } ) ->
|
||||
trailers_handler headers
|
||||
| _ -> assert false
|
||||
|
||||
let flush_response_body (t : t) =
|
||||
match t.state with
|
||||
| Active
|
||||
( ( Open (ActiveMessage { response_body; _ })
|
||||
| HalfClosed (ActiveMessage { response_body; _ }) )
|
||||
, _ ) ->
|
||||
if Body.Reader.has_pending_output response_body
|
||||
then (
|
||||
try Body.Reader.execute_read response_body with
|
||||
| exn -> report_error t (`Exn exn) InternalError)
|
||||
| _ -> ()
|
||||
63
unikernel/duniverse/ocaml-h2/lib/response.ml
Normal file
63
unikernel/duniverse/ocaml-h2/lib/response.ml
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
(*----------------------------------------------------------------------------
|
||||
* 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.
|
||||
*---------------------------------------------------------------------------*)
|
||||
|
||||
type t =
|
||||
{ status : Status.t
|
||||
; headers : Headers.t
|
||||
}
|
||||
|
||||
(* From RFC7540§8.1.2.4:
|
||||
* HTTP/2 does not define a way to carry the version or reason phrase that
|
||||
* is included in an HTTP/1.1 status line. *)
|
||||
let create ?(headers = Headers.empty) status = { status; headers }
|
||||
|
||||
let body_length ~request_method { headers; _ } =
|
||||
match request_method with
|
||||
| `HEAD -> `Fixed 0L
|
||||
| #Httpun_types.Method.standard -> Message.body_length headers
|
||||
|
||||
let pp_hum fmt { status; headers } =
|
||||
let reason =
|
||||
match status with
|
||||
| #Status.standard as status -> Status.default_reason_phrase status
|
||||
| `Code _ -> "Non-standard status code"
|
||||
in
|
||||
Format.fprintf
|
||||
fmt
|
||||
"((status %a) (reason %S) (headers %a))"
|
||||
Status.pp_hum
|
||||
status
|
||||
reason
|
||||
Headers.pp_hum
|
||||
headers
|
||||
571
unikernel/duniverse/ocaml-h2/lib/scheduler.ml
Normal file
571
unikernel/duniverse/ocaml-h2/lib/scheduler.ml
Normal file
|
|
@ -0,0 +1,571 @@
|
|||
(*----------------------------------------------------------------------------
|
||||
* Copyright (c) 2019 António Nuno 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 copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 COPYRIGHT HOLDER 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.
|
||||
*---------------------------------------------------------------------------*)
|
||||
|
||||
module StreamsTbl = struct
|
||||
include Hashtbl.MakeSeeded (struct
|
||||
type t = Stream_identifier.t
|
||||
|
||||
let equal = Stream_identifier.( === )
|
||||
let hash i k = Hashtbl.seeded_hash i k
|
||||
|
||||
(* Required for OCaml >= 5.0.0, but causes errors for older compilers
|
||||
because it is an unused value declaration. *)
|
||||
let[@warning "-32"] seeded_hash = hash
|
||||
end)
|
||||
|
||||
let[@inline] find_opt h key = try Some (find h key) with Not_found -> None
|
||||
end
|
||||
|
||||
module type StreamDescriptor = sig
|
||||
type t
|
||||
|
||||
val id : t -> Stream_identifier.t
|
||||
val requires_output : t -> bool
|
||||
val flush_write_body : t -> max_bytes:int -> int
|
||||
val finish_stream : t -> Stream.closed_reason -> unit
|
||||
val is_idle : t -> bool
|
||||
end
|
||||
|
||||
module Make (Streamd : StreamDescriptor) = struct
|
||||
module rec PriorityTreeNode : sig
|
||||
type root = Root
|
||||
type nonroot = NonRoot
|
||||
|
||||
type stream = nonroot node
|
||||
and parent = Parent : _ node -> parent
|
||||
|
||||
and _ node =
|
||||
(* From RFC7540§5.3.1:
|
||||
* A stream that is not dependent on any other stream is given a stream
|
||||
* dependency of 0x0. In other words, the non-existent stream 0 forms
|
||||
* the root of the tree.
|
||||
*
|
||||
* Note:
|
||||
* We use a GADT because the root of the tree doesn't have an
|
||||
* associated request descriptor. It has the added advantage of
|
||||
* allowing us to enforce that all (other) streams in the tree are
|
||||
* associated with a request descriptor. *)
|
||||
| Connection :
|
||||
{ all_streams : stream StreamsTbl.t
|
||||
; mutable t_last : int
|
||||
; mutable children : PriorityQueue.t
|
||||
; (* Connection-level flow control window.
|
||||
* outbound flow control, what we're allowed to send.
|
||||
*
|
||||
* From RFC7540§6.9.1:
|
||||
* Two flow-control windows are applicable: the stream
|
||||
* flow-control window and the connection flow-control window. *)
|
||||
mutable flow : Settings.WindowSize.t
|
||||
; (* inbound flow control, what the client is allowed to send. *)
|
||||
mutable inflow : Settings.WindowSize.t
|
||||
; mutable marked_for_removal : Stream.closed StreamsTbl.t
|
||||
}
|
||||
-> root node
|
||||
| Stream :
|
||||
{ descriptor : Streamd.t
|
||||
; mutable t_last : int
|
||||
; mutable t : int
|
||||
; mutable priority : Priority.t
|
||||
; mutable parent : parent
|
||||
; mutable children : PriorityQueue.t
|
||||
; (* Stream-level flow control window. See connection-level above.
|
||||
*
|
||||
* From RFC7540§6.9.1:
|
||||
* Two flow-control windows are applicable: the stream
|
||||
* flow-control window and the connection flow-control window. *)
|
||||
mutable flow : Settings.WindowSize.t
|
||||
; mutable inflow : Settings.WindowSize.t
|
||||
}
|
||||
-> nonroot node
|
||||
end =
|
||||
PriorityTreeNode
|
||||
|
||||
and PriorityQueue :
|
||||
(Psq.S with type k = Int32.t and type p = PriorityTreeNode.stream) =
|
||||
Psq.Make
|
||||
(Int32)
|
||||
(struct
|
||||
include PriorityTreeNode
|
||||
|
||||
type t = stream
|
||||
|
||||
let compare (Stream { t = t1; _ }) (Stream { t = t2; _ }) =
|
||||
compare t1 t2
|
||||
end)
|
||||
|
||||
include PriorityTreeNode
|
||||
|
||||
type t = root node
|
||||
|
||||
(* TODO(anmonteiro): change according to SETTINGS_MAX_CONCURRENT_STREAMS? *)
|
||||
let make_root ?(capacity = 65536) () =
|
||||
Connection
|
||||
{ t_last = 0
|
||||
; children = PriorityQueue.empty
|
||||
; all_streams = StreamsTbl.create ~random:true capacity
|
||||
; flow = Settings.WindowSize.default_initial_window_size
|
||||
; inflow = Settings.WindowSize.default_initial_window_size
|
||||
; marked_for_removal = StreamsTbl.create ~random:true 256
|
||||
}
|
||||
|
||||
let create
|
||||
~parent
|
||||
~initial_send_window_size
|
||||
~initial_recv_window_size
|
||||
descriptor
|
||||
=
|
||||
Stream
|
||||
{ descriptor
|
||||
; t_last = 0
|
||||
; t = 0
|
||||
; (* From RFC7540§5.3.5:
|
||||
* All streams are initially assigned a non-exclusive dependency on
|
||||
* stream 0x0. Pushed streams (Section 8.2) initially depend on their
|
||||
* associated stream. In both cases, streams are assigned a default
|
||||
* weight of 16. *)
|
||||
priority = Priority.default_priority
|
||||
; parent
|
||||
; children = PriorityQueue.empty
|
||||
; flow = initial_send_window_size
|
||||
; inflow = initial_recv_window_size
|
||||
}
|
||||
|
||||
let stream_id : type a. a node -> int32 = function
|
||||
| Connection _ -> Stream_identifier.connection
|
||||
| Stream { descriptor; _ } -> Streamd.id descriptor
|
||||
|
||||
let children : type a. a node -> PriorityQueue.t = function
|
||||
| Stream { children; _ } -> children
|
||||
| Connection { children; _ } -> children
|
||||
|
||||
let remove_child : type a. a node -> int32 -> unit =
|
||||
fun parent id ->
|
||||
match parent with
|
||||
| Connection ({ children; _ } as node) ->
|
||||
(* From RFC7540§5.3.1:
|
||||
* A stream that is not dependent on any other stream is given a stream
|
||||
* dependency of 0x0. In other words, the non-existent stream 0 forms
|
||||
* the root of the tree. *)
|
||||
node.children <- PriorityQueue.remove id children
|
||||
| Stream ({ children; _ } as node) ->
|
||||
node.children <- PriorityQueue.remove id children
|
||||
|
||||
let update_children : type a. a node -> PriorityQueue.t -> unit =
|
||||
fun parent updated_children ->
|
||||
match parent with
|
||||
| Connection s -> s.children <- updated_children
|
||||
| Stream s -> s.children <- updated_children
|
||||
|
||||
let set_parent stream_node ~exclusive (Parent new_parent_node as new_parent) =
|
||||
let (Stream ({ descriptor; parent = Parent old_parent_node; _ } as stream)) =
|
||||
stream_node
|
||||
in
|
||||
let stream_id = Streamd.id descriptor in
|
||||
remove_child old_parent_node stream_id;
|
||||
stream.parent <- new_parent;
|
||||
let new_children =
|
||||
let new_children = children new_parent_node in
|
||||
if exclusive
|
||||
then (
|
||||
(* From RFC7540§5.3.3:
|
||||
* Dependent streams move with their parent stream if the parent is
|
||||
* reprioritized. Setting a dependency with the exclusive flag for a
|
||||
* reprioritized stream causes all the dependencies of the new parent
|
||||
* stream to become dependent on the reprioritized stream. *)
|
||||
stream.children <-
|
||||
PriorityQueue.fold
|
||||
(fun k (Stream p as p_node) pq ->
|
||||
p.parent <- Parent stream_node;
|
||||
PriorityQueue.add k p_node pq)
|
||||
stream.children
|
||||
new_children;
|
||||
(* From RFC7540§5.3.1:
|
||||
* An exclusive flag allows for the insertion of a new level of
|
||||
* dependencies. The exclusive flag causes the stream to become the
|
||||
* sole dependency of its parent stream, causing other dependencies
|
||||
* to become dependent on the exclusive stream. *)
|
||||
PriorityQueue.sg stream_id stream_node)
|
||||
else PriorityQueue.add stream_id stream_node new_children
|
||||
in
|
||||
update_children new_parent_node new_children
|
||||
|
||||
let would_create_cycle ~new_parent (Stream { descriptor; _ }) =
|
||||
let rec inner : type a. a node -> bool = function
|
||||
| Connection _ -> false
|
||||
| Stream { parent = Parent parent; _ }
|
||||
when Stream_identifier.(stream_id parent === Streamd.id descriptor) ->
|
||||
true
|
||||
| Stream { parent = Parent parent; _ } -> inner parent
|
||||
in
|
||||
let (Parent parent_node) = new_parent in
|
||||
inner parent_node
|
||||
|
||||
let reprioritize_stream (Connection root as t) ~priority stream_node =
|
||||
let (Stream stream) = stream_node in
|
||||
let new_parent, new_priority =
|
||||
if Stream_identifier.is_connection priority.Priority.stream_dependency
|
||||
then Parent t, priority
|
||||
else
|
||||
match
|
||||
StreamsTbl.find_opt root.all_streams priority.stream_dependency
|
||||
with
|
||||
| Some parent_stream ->
|
||||
(match
|
||||
StreamsTbl.mem root.marked_for_removal priority.stream_dependency
|
||||
with
|
||||
| true ->
|
||||
(* A stream that is marked for removal is also not present in the
|
||||
tree *)
|
||||
Parent t, Priority.default_priority
|
||||
| false -> Parent parent_stream, priority)
|
||||
| None ->
|
||||
(* From RFC7540§5.3.1:
|
||||
* A dependency on a stream that is not currently in the tree —
|
||||
* such as a stream in the "idle" state — results in that stream
|
||||
* being given a default priority (Section 5.3.5). *)
|
||||
Parent t, Priority.default_priority
|
||||
in
|
||||
(* bail early if trying to set the same priority *)
|
||||
if not (Priority.equal stream.priority new_priority)
|
||||
then (
|
||||
let { Priority.stream_dependency; exclusive; _ } = new_priority in
|
||||
let (Parent current_parent_node) = stream.parent in
|
||||
let current_parent_id = stream_id current_parent_node in
|
||||
(* only need to set a different parent if the parent or exclusive status
|
||||
* changed *)
|
||||
if (not Stream_identifier.(stream_dependency === current_parent_id))
|
||||
|| exclusive <> stream.priority.exclusive
|
||||
then (
|
||||
let (Parent new_parent_node) = new_parent in
|
||||
(match new_parent_node with
|
||||
| Stream new_parent_stream ->
|
||||
if would_create_cycle ~new_parent stream_node
|
||||
then (
|
||||
(* From RFC7540§5.3.3:
|
||||
* If a stream is made dependent on one of its own dependencies,
|
||||
* the formerly dependent stream is first moved to be dependent
|
||||
* on the reprioritized stream's previous parent. The moved
|
||||
* dependency retains its weight. *)
|
||||
set_parent new_parent_node ~exclusive:false stream.parent;
|
||||
new_parent_stream.priority <-
|
||||
{ new_parent_stream.priority with
|
||||
stream_dependency = current_parent_id
|
||||
})
|
||||
| Connection _ ->
|
||||
(* The root node cannot be dependent on any other streams, so we
|
||||
* don't need to worry about it creating cycles. *)
|
||||
());
|
||||
(* From RFC7540§5.3.1:
|
||||
* When assigning a dependency on another stream, the stream is added
|
||||
* as a new dependency of the parent stream. *)
|
||||
set_parent stream_node ~exclusive new_parent);
|
||||
stream.priority <- new_priority)
|
||||
|
||||
let update_t node n =
|
||||
let (Stream ({ parent = Parent parent; descriptor; _ } as stream)) = node in
|
||||
let tlast_p =
|
||||
match parent with
|
||||
| Connection { t_last; _ } -> t_last
|
||||
| Stream { t_last; _ } -> t_last
|
||||
in
|
||||
stream.t <- tlast_p + (n * 256 / stream.priority.weight);
|
||||
let id = Streamd.id descriptor in
|
||||
remove_child parent id;
|
||||
let updated_children = PriorityQueue.add id node (children parent) in
|
||||
update_children parent updated_children
|
||||
|
||||
let update_t_last : type a. a node -> int -> unit =
|
||||
fun p_node t_last ->
|
||||
match p_node with
|
||||
| Connection p -> p.t_last <- t_last
|
||||
| Stream p -> p.t_last <- t_last
|
||||
|
||||
let add
|
||||
(Connection root as t)
|
||||
~priority
|
||||
~initial_send_window_size
|
||||
~initial_recv_window_size
|
||||
descriptor
|
||||
=
|
||||
let stream =
|
||||
create
|
||||
~parent:(Parent t)
|
||||
~initial_send_window_size
|
||||
~initial_recv_window_size
|
||||
descriptor
|
||||
in
|
||||
let stream_id = Streamd.id descriptor in
|
||||
StreamsTbl.add root.all_streams stream_id stream;
|
||||
root.children <- PriorityQueue.add stream_id stream root.children;
|
||||
if priority != Priority.default_priority
|
||||
then reprioritize_stream t ~priority stream;
|
||||
update_t stream 0;
|
||||
stream
|
||||
|
||||
let get_node (Connection root) stream_id =
|
||||
StreamsTbl.find_opt root.all_streams stream_id
|
||||
|
||||
let find t stream_id =
|
||||
match get_node t stream_id with
|
||||
| Some (Stream { descriptor; _ }) -> Some descriptor
|
||||
| None -> None
|
||||
|
||||
let iter (Connection { all_streams; _ }) ~f =
|
||||
StreamsTbl.iter (fun _id stream -> f stream) all_streams
|
||||
|
||||
let allowed_to_transmit (Connection root) (Stream stream) =
|
||||
Int32.compare root.flow 0l > 0 && Int32.compare stream.flow 0l > 0
|
||||
|
||||
let allowed_to_receive (Connection root) (Stream stream) size =
|
||||
size <= root.inflow && size <= stream.inflow
|
||||
|
||||
let write (Connection root as t) stream_node =
|
||||
let (Stream ({ descriptor; _ } as stream)) = stream_node in
|
||||
(* From RFC7540§6.9.1:
|
||||
* Two flow-control windows are applicable: the stream flow-control
|
||||
* window and the connection flow-control window. The sender MUST NOT
|
||||
* send a flow-controlled frame with a length that exceeds the space
|
||||
* available in either of the flow-control windows advertised by the
|
||||
* receiver. *)
|
||||
let allowed_bytes =
|
||||
if allowed_to_transmit t stream_node
|
||||
then min root.flow stream.flow
|
||||
else
|
||||
(* There might be a zero-length DATA frame (with the end stream flag
|
||||
set) waiting to be sent. *)
|
||||
0l
|
||||
in
|
||||
let written =
|
||||
Streamd.flush_write_body
|
||||
~max_bytes:(Int32.to_int allowed_bytes)
|
||||
descriptor
|
||||
in
|
||||
let written32 = Int32.of_int written in
|
||||
(* From RFC7540§6.9.1:
|
||||
* After sending a flow-controlled frame, the sender reduces the space
|
||||
* available in both windows by the length of the transmitted frame. *)
|
||||
root.flow <- Int32.sub root.flow written32;
|
||||
stream.flow <- Int32.sub stream.flow written32;
|
||||
written
|
||||
|
||||
let mark_for_removal (Connection root) id closed =
|
||||
StreamsTbl.replace root.marked_for_removal id closed
|
||||
|
||||
let implicitly_close_idle_stream descriptor max_seen_ids =
|
||||
let implicitly_close_stream descriptor =
|
||||
if Streamd.is_idle descriptor
|
||||
then
|
||||
(* From RFC7540§5.1.1:
|
||||
* The first use of a new stream identifier implicitly closes all
|
||||
* streams in the "idle" state that might have been initiated by
|
||||
* that peer with a lower-valued stream identifier. *)
|
||||
Streamd.finish_stream descriptor Finished
|
||||
in
|
||||
let max_client_stream_id, max_pushed_stream_id = max_seen_ids in
|
||||
let stream_id = Streamd.id descriptor in
|
||||
if Stream_identifier.is_request stream_id
|
||||
then (
|
||||
if stream_id < max_client_stream_id
|
||||
then implicitly_close_stream descriptor)
|
||||
else if stream_id < max_pushed_stream_id
|
||||
then implicitly_close_stream descriptor
|
||||
|
||||
(* Scheduling algorithm from https://goo.gl/3sSHXJ (based on nghttp2):
|
||||
*
|
||||
* 1 def schedule(p):
|
||||
* 2 if stream #p has data to send:
|
||||
* 3 send data for #p, update nsent[p]
|
||||
* 4 return
|
||||
* 5 if #p's queue is empty:
|
||||
* 6 return
|
||||
* 7 pop #i from queue
|
||||
* 8 update t_last[p] = t[i]
|
||||
* 9 schedule(i)
|
||||
* 10 if #i or its descendant is "active":
|
||||
* 11 update t[i] and push it into queue again
|
||||
* 12
|
||||
* 13 schedule(0)
|
||||
*)
|
||||
let flush t max_seen_ids =
|
||||
let rec schedule : type a. a node -> int * bool = function
|
||||
| Connection _ as p_node ->
|
||||
(* The root can never send data. *)
|
||||
traverse p_node
|
||||
| Stream ({ descriptor; _ } as stream) as p_node ->
|
||||
let written =
|
||||
if Streamd.requires_output descriptor
|
||||
then
|
||||
(* In this branch, flow-control has no bearing on activity, otherwise
|
||||
* a flow-controlled stream would be considered inactive (because it
|
||||
* can't make progress at the moment) and removed from the priority
|
||||
* tree altogether. *)
|
||||
write t p_node
|
||||
else 0
|
||||
in
|
||||
if written > 0
|
||||
then
|
||||
(* We check for activity again, because the stream may have gone
|
||||
* inactive after the call to `write` above. *)
|
||||
let subtree_is_active =
|
||||
Streamd.requires_output descriptor
|
||||
|| not (PriorityQueue.is_empty stream.children)
|
||||
in
|
||||
written, subtree_is_active
|
||||
else
|
||||
(* If we haven't written anything, check if any of our children
|
||||
have. *)
|
||||
let written, subtree_is_active' = traverse p_node in
|
||||
let subtree_is_active =
|
||||
Streamd.requires_output descriptor || subtree_is_active'
|
||||
in
|
||||
(match written with
|
||||
| 0 -> written, subtree_is_active
|
||||
| written ->
|
||||
(* If there's still more to write, put the node back in the tree. *)
|
||||
if subtree_is_active then update_t p_node written;
|
||||
written, subtree_is_active)
|
||||
and traverse : type a. a node -> int * bool =
|
||||
fun p_node ->
|
||||
let rec loop remaining_children =
|
||||
match PriorityQueue.pop remaining_children with
|
||||
| Some ((id, (Stream i as i_node)), remaining_children') ->
|
||||
update_t_last p_node i.t;
|
||||
let written, subtree_is_active = schedule i_node in
|
||||
|
||||
if not subtree_is_active
|
||||
then (
|
||||
implicitly_close_idle_stream i.descriptor max_seen_ids;
|
||||
(* XXX(anmonteiro): we may not want to remove from the tree right
|
||||
* away. *)
|
||||
remove_child p_node id);
|
||||
|
||||
(match written with
|
||||
| 0 ->
|
||||
(* If this subtree didn't write anything, check the other children
|
||||
in the priority queue. *)
|
||||
loop remaining_children'
|
||||
| written ->
|
||||
(* If there's still more to write, put the node back in the tree. *)
|
||||
if subtree_is_active then update_t i_node written;
|
||||
written, subtree_is_active)
|
||||
| None ->
|
||||
(* No data written, but queue was not originally empty.
|
||||
* Therefore, we can't determine the subtree is inactive. *)
|
||||
0, true
|
||||
in
|
||||
let children = children p_node in
|
||||
match PriorityQueue.is_empty children with
|
||||
| true ->
|
||||
(* Queue is empty, see line 6 above. *)
|
||||
0, false
|
||||
| false -> loop children
|
||||
in
|
||||
|
||||
let (Connection root) = t in
|
||||
ignore (schedule t);
|
||||
StreamsTbl.iter
|
||||
(fun id closed ->
|
||||
(* When a stream completes, i.e. doesn't require more output and
|
||||
* enters the `Closed` state, we set a TTL value which represents the
|
||||
* number of writer yields that the stream has before it is removed
|
||||
* from the connection Hash Table. By doing this we avoid losing some
|
||||
* potentially useful information regarding the stream's state at the
|
||||
* cost of keeping it around for a little while longer. *)
|
||||
if closed.Stream.ttl = 0
|
||||
then (
|
||||
StreamsTbl.remove root.marked_for_removal id;
|
||||
StreamsTbl.remove root.all_streams id)
|
||||
else closed.ttl <- closed.ttl - 1)
|
||||
root.marked_for_removal
|
||||
|
||||
(* XXX(anmonteiro): Consider using `optint` for this?
|
||||
* https://github.com/mirage/optint
|
||||
*)
|
||||
let check_flow flow growth flow' =
|
||||
(* Check for overflow on 32-bit systems. *)
|
||||
Int32.compare flow' growth > 0 = (Int32.compare flow 0l > 0)
|
||||
&& Int32.compare flow' Settings.WindowSize.max_window_size <= 0
|
||||
|
||||
let add_flow : type a. a node -> int32 -> bool =
|
||||
fun t growth ->
|
||||
match t with
|
||||
| Connection ({ flow; _ } as root) ->
|
||||
let flow' = Int32.add flow growth in
|
||||
let valid_flow = check_flow flow growth flow' in
|
||||
if valid_flow then root.flow <- flow';
|
||||
valid_flow
|
||||
| Stream ({ flow; _ } as stream) ->
|
||||
let flow' = Int32.add flow growth in
|
||||
let valid_flow = check_flow flow growth flow' in
|
||||
if valid_flow then stream.flow <- flow';
|
||||
valid_flow
|
||||
|
||||
let add_inflow : type a. a node -> int32 -> bool =
|
||||
fun t growth ->
|
||||
match t with
|
||||
| Connection ({ inflow; _ } as root) ->
|
||||
let inflow' = Int32.add inflow growth in
|
||||
let valid_inflow = check_flow inflow growth inflow' in
|
||||
if valid_inflow then root.inflow <- inflow';
|
||||
valid_inflow
|
||||
| Stream ({ inflow; _ } as stream) ->
|
||||
let inflow' = Int32.add inflow growth in
|
||||
let valid_inflow = check_flow inflow growth inflow' in
|
||||
if valid_inflow then stream.inflow <- inflow';
|
||||
valid_inflow
|
||||
|
||||
let deduct_inflow : type a. a node -> int32 -> unit =
|
||||
fun t size ->
|
||||
match t with
|
||||
| Connection ({ inflow; _ } as root) ->
|
||||
(* no need to check, we verify that the peer is allowed to send. *)
|
||||
root.inflow <- Int32.sub inflow size
|
||||
| Stream ({ inflow; _ } as stream) -> stream.inflow <- Int32.sub inflow size
|
||||
|
||||
let pp_hum fmt t =
|
||||
let rec pp_hum_inner level fmt t =
|
||||
let pp_binding fmt (id, Stream { children; t; _ }) =
|
||||
Format.fprintf
|
||||
fmt
|
||||
"\n%s%ld, %d -> [%a]"
|
||||
(String.make (level * 2) ' ')
|
||||
id
|
||||
t
|
||||
(pp_hum_inner (level + 1))
|
||||
children
|
||||
in
|
||||
PriorityQueue.pp pp_binding fmt t
|
||||
in
|
||||
pp_hum_inner 0 fmt t
|
||||
|
||||
let pp_hum fmt (Connection { children; _ }) = pp_hum fmt children
|
||||
end
|
||||
627
unikernel/duniverse/ocaml-h2/lib/serialize.ml
Normal file
627
unikernel/duniverse/ocaml-h2/lib/serialize.ml
Normal file
|
|
@ -0,0 +1,627 @@
|
|||
(*----------------------------------------------------------------------------
|
||||
* 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.
|
||||
*---------------------------------------------------------------------------*)
|
||||
|
||||
open Faraday
|
||||
module IOVec = Httpun_types.IOVec
|
||||
|
||||
type frame_info =
|
||||
{ flags : Flags.t
|
||||
; stream_id : Stream_identifier.t
|
||||
; padding : Bigstringaf.t
|
||||
; max_frame_payload : int
|
||||
}
|
||||
|
||||
let write_uint24 t n =
|
||||
let write_octet t o = write_uint8 t (o land 0xff) in
|
||||
write_octet t (n lsr 16);
|
||||
write_octet t (n lsr 8);
|
||||
write_octet t n
|
||||
|
||||
let write_frame_header t frame_header =
|
||||
let { Frame.payload_length; flags; stream_id; frame_type } = frame_header in
|
||||
write_uint24 t payload_length;
|
||||
write_uint8 t (Frame.FrameType.serialize frame_type);
|
||||
write_uint8 t flags;
|
||||
BE.write_uint32 t stream_id
|
||||
|
||||
let write_frame_with_padding t info frame_type length writer =
|
||||
let header, writer =
|
||||
if Bigstringaf.length info.padding = 0
|
||||
then
|
||||
let header =
|
||||
{ Frame.payload_length = length
|
||||
; flags = info.flags
|
||||
; stream_id = info.stream_id
|
||||
; frame_type
|
||||
}
|
||||
in
|
||||
header, writer
|
||||
else
|
||||
let pad_length = Bigstringaf.length info.padding in
|
||||
let writer' t =
|
||||
write_uint8 t pad_length;
|
||||
writer t;
|
||||
schedule_bigstring ~off:0 ~len:pad_length t info.padding
|
||||
in
|
||||
let header =
|
||||
{ Frame.payload_length = length + pad_length + 1
|
||||
; flags = Flags.set_padded info.flags
|
||||
; stream_id = info.stream_id
|
||||
; frame_type
|
||||
}
|
||||
in
|
||||
header, writer'
|
||||
in
|
||||
write_frame_header t header;
|
||||
writer t
|
||||
|
||||
let write_data_frame t ?off ?len info body =
|
||||
let writer t = write_string t ?off ?len body in
|
||||
let length = match len with Some len -> len | None -> String.length body in
|
||||
write_frame_with_padding t info Data length writer
|
||||
|
||||
let schedule_data_frame t info ?off ?len bstr =
|
||||
let writer t = schedule_bigstring t ?off ?len bstr in
|
||||
let length =
|
||||
match len with Some len -> len | None -> Bigstringaf.length bstr
|
||||
in
|
||||
write_frame_with_padding t info Data length writer
|
||||
|
||||
let write_priority t { Priority.exclusive; stream_dependency; weight } =
|
||||
let stream_dependency_id =
|
||||
if exclusive
|
||||
then Priority.set_exclusive stream_dependency
|
||||
else stream_dependency
|
||||
in
|
||||
BE.write_uint32 t stream_dependency_id;
|
||||
(* From RFC7540§6.3:
|
||||
* An unsigned 8-bit integer representing a priority weight for the stream
|
||||
* (see Section 5.3). Add one to the value to obtain a weight between 1 and
|
||||
* 256.
|
||||
*
|
||||
* Note: we store priority with values from 1 to 256, so decrement here. *)
|
||||
write_uint8 t (weight - 1)
|
||||
|
||||
let bounded_schedule_iovecs t ~len iovecs =
|
||||
let rec loop t remaining iovecs =
|
||||
match remaining, iovecs with
|
||||
| 0, _ | _, [] -> ()
|
||||
| remaining, { IOVec.buffer; off; len } :: xs ->
|
||||
if remaining < len
|
||||
then schedule_bigstring t ~off ~len:remaining buffer
|
||||
else (
|
||||
schedule_bigstring t ~off ~len buffer;
|
||||
loop t (remaining - len) xs)
|
||||
in
|
||||
loop t len iovecs
|
||||
|
||||
let write_headers_frame t info ~priority ?len iovecs =
|
||||
let len = match len with Some len -> len | None -> IOVec.lengthv iovecs in
|
||||
if priority == Priority.default_priority
|
||||
then
|
||||
(* See RFC7540§6.3:
|
||||
* Just the Header Block Fragment length if no priority. *)
|
||||
let writer t = bounded_schedule_iovecs t ~len iovecs in
|
||||
write_frame_with_padding t info Headers len writer
|
||||
else
|
||||
(* See RFC7540§6.2:
|
||||
* Exclusive Bit & Stream Dependency (4 octets) + Weight (1 octet) +
|
||||
* Header Block Fragment length. *)
|
||||
let payload_length = len + 5 in
|
||||
let info' = { info with flags = Flags.set_priority info.flags } in
|
||||
let writer t =
|
||||
write_priority t priority;
|
||||
bounded_schedule_iovecs t ~len iovecs
|
||||
in
|
||||
write_frame_with_padding t info' Headers payload_length writer
|
||||
|
||||
let write_priority_frame t info priority =
|
||||
let header =
|
||||
{ Frame.flags = info.flags
|
||||
; stream_id =
|
||||
info.stream_id
|
||||
(* See RFC7540§6.3:
|
||||
* Stream Dependency (4 octets) + Weight (1 octet). *)
|
||||
; payload_length = 5
|
||||
; frame_type = Priority
|
||||
}
|
||||
in
|
||||
write_frame_header t header;
|
||||
write_priority t priority
|
||||
|
||||
let write_rst_stream_frame t info e =
|
||||
let header =
|
||||
{ Frame.flags = info.flags
|
||||
; stream_id =
|
||||
info.stream_id
|
||||
(* From RFC7540§6.4:
|
||||
* The RST_STREAM frame contains a single unsigned, 32-bit integer
|
||||
* identifying the error code (Section 7). *)
|
||||
; payload_length = 4
|
||||
; frame_type = RSTStream
|
||||
}
|
||||
in
|
||||
write_frame_header t header;
|
||||
BE.write_uint32 t (Error_code.serialize e)
|
||||
|
||||
let write_settings_frame t info settings =
|
||||
let header =
|
||||
{ Frame.flags = info.flags
|
||||
; stream_id =
|
||||
info.stream_id
|
||||
(* From RFC7540§6.5.1:
|
||||
* The payload of a SETTINGS frame consists of zero or more
|
||||
* parameters, each consisting of an unsigned 16-bit setting
|
||||
* identifier and an unsigned 32-bit value. *)
|
||||
; payload_length = List.length settings * 6
|
||||
; frame_type = Settings
|
||||
}
|
||||
in
|
||||
write_frame_header t header;
|
||||
Settings.write_settings_payload t settings
|
||||
|
||||
let write_push_promise_frame t info ~promised_id ?len iovecs =
|
||||
let len = match len with Some len -> len | None -> IOVec.lengthv iovecs in
|
||||
let payload_length =
|
||||
(* From RFC7540§6.6:
|
||||
* The PUSH_PROMISE frame includes the unsigned 31-bit identifier of the
|
||||
* stream the endpoint plans to create along with a set of headers that
|
||||
* provide additional context for the stream. *)
|
||||
4 + len
|
||||
in
|
||||
let writer t =
|
||||
BE.write_uint32 t promised_id;
|
||||
bounded_schedule_iovecs t ~len iovecs
|
||||
in
|
||||
write_frame_with_padding t info PushPromise payload_length writer
|
||||
|
||||
let default_ping_payload =
|
||||
(* From RFC7540§6.7:
|
||||
* In addition to the frame header, PING frames MUST contain 8 octets of
|
||||
* opaque data in the payload. *)
|
||||
let bstr = Bigstringaf.create 8 in
|
||||
for i = 0 to 7 do
|
||||
Bigstringaf.unsafe_set bstr i '\000'
|
||||
done;
|
||||
bstr
|
||||
|
||||
let write_ping_frame t info ?(off = 0) payload =
|
||||
(* From RFC7540§6.7:
|
||||
* In addition to the frame header, PING frames MUST contain 8 octets of
|
||||
* opaque data in the payload. *)
|
||||
let payload_length = 8 in
|
||||
let header =
|
||||
{ Frame.flags = info.flags
|
||||
; stream_id = info.stream_id
|
||||
; payload_length
|
||||
; frame_type = Ping
|
||||
}
|
||||
in
|
||||
write_frame_header t header;
|
||||
schedule_bigstring ~off ~len:payload_length t payload
|
||||
|
||||
let write_go_away_frame t info stream_id error_code debug_data =
|
||||
let debug_data_len = Bigstringaf.length debug_data in
|
||||
let header =
|
||||
{ Frame.flags = info.flags
|
||||
; stream_id =
|
||||
info.stream_id
|
||||
(* See RFC7540§6.8:
|
||||
* Last-Stream-ID (4 octets) + Error Code (4 octets) + Additional
|
||||
* Debug Data (opaque) *)
|
||||
; payload_length = 8 + debug_data_len
|
||||
; frame_type = GoAway
|
||||
}
|
||||
in
|
||||
write_frame_header t header;
|
||||
BE.write_uint32 t stream_id;
|
||||
BE.write_uint32 t (Error_code.serialize error_code);
|
||||
schedule_bigstring t ~off:0 ~len:debug_data_len debug_data
|
||||
|
||||
let write_window_update_frame t info window_size =
|
||||
let header =
|
||||
{ Frame.flags = info.flags
|
||||
; stream_id =
|
||||
info.stream_id
|
||||
(* From RFC7540§6.9:
|
||||
* The payload of a WINDOW_UPDATE frame is one reserved bit plus an
|
||||
* unsigned 31-bit integer indicating the number of octets that the
|
||||
* sender can transmit in addition to the existing flow-control
|
||||
* window. *)
|
||||
; payload_length = 4
|
||||
; frame_type = WindowUpdate
|
||||
}
|
||||
in
|
||||
write_frame_header t header;
|
||||
BE.write_uint32 t window_size
|
||||
|
||||
let write_continuation_frame t info ?len iovecs =
|
||||
let len = match len with Some len -> len | None -> IOVec.lengthv iovecs in
|
||||
let header =
|
||||
{ Frame.flags = info.flags
|
||||
; stream_id = info.stream_id
|
||||
; payload_length = len
|
||||
; frame_type = Continuation
|
||||
}
|
||||
in
|
||||
write_frame_header t header;
|
||||
bounded_schedule_iovecs t ~len iovecs
|
||||
|
||||
let write_unknown_frame t ~code info payload =
|
||||
let payload_length = Bigstringaf.length payload in
|
||||
let header =
|
||||
{ Frame.flags = info.flags
|
||||
; stream_id = info.stream_id
|
||||
; payload_length
|
||||
; frame_type = Unknown code
|
||||
}
|
||||
in
|
||||
write_frame_header t header;
|
||||
schedule_bigstring t ~off:0 ~len:payload_length payload
|
||||
|
||||
let write_connection_preface t =
|
||||
(* From RFC7540§3.5:
|
||||
* In HTTP/2, each endpoint is required to send a connection preface as a
|
||||
* final confirmation of the protocol in use and to establish the initial
|
||||
* settings for the HTTP/2 connection. [...] The client connection preface
|
||||
* starts with a sequence of 24 octets, [...] the string
|
||||
* PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n. *)
|
||||
write_string t Frame.connection_preface
|
||||
|
||||
module Writer = struct
|
||||
type t =
|
||||
{ buffer : Bigstringaf.t
|
||||
(* The buffer that the encoder uses for buffered writes. Managed by
|
||||
* the control module for the encoder. *)
|
||||
; encoder : Faraday.t
|
||||
(* The encoder that handles encoding for writes. Uses the [buffer]
|
||||
* referenced above internally. *)
|
||||
; mutable drained_bytes : int
|
||||
(* The number of bytes that were not written due to the output stream
|
||||
* being closed before all buffered output could be written. Useful
|
||||
* for detecting error cases. *)
|
||||
; mutable wakeup : Optional_thunk.t
|
||||
}
|
||||
|
||||
let create buffer_size =
|
||||
let buffer = Bigstringaf.create buffer_size in
|
||||
let encoder = Faraday.of_bigstring buffer in
|
||||
{ buffer; encoder; drained_bytes = 0; wakeup = Optional_thunk.none }
|
||||
|
||||
let faraday t = t.encoder
|
||||
|
||||
let make_frame_info
|
||||
?(padding = Bigstringaf.empty)
|
||||
?(flags = Flags.default_flags)
|
||||
?(max_frame_size = Config.default.read_buffer_size)
|
||||
stream_id
|
||||
=
|
||||
{ flags; stream_id; padding; max_frame_payload = max_frame_size }
|
||||
|
||||
let write_connection_preface t settings_list =
|
||||
write_connection_preface t.encoder;
|
||||
let frame_info = make_frame_info Stream_identifier.connection in
|
||||
(* From RFC7540§3.5:
|
||||
* This sequence MUST be followed by a SETTINGS frame (Section 6.5),
|
||||
* which MAY be empty. *)
|
||||
write_settings_frame t.encoder frame_info settings_list
|
||||
|
||||
let chunk_data_frames ?(off = 0) ~f frame_info total_length =
|
||||
let { max_frame_payload; _ } = frame_info in
|
||||
if max_frame_payload < total_length
|
||||
then
|
||||
let rec loop ~off remaining =
|
||||
if max_frame_payload < remaining
|
||||
then (
|
||||
(* Note: If we're splitting data into several frames, only the last
|
||||
* one should contain the END_STREAM flag, so unset it here if it's
|
||||
* set. *)
|
||||
let frame_info =
|
||||
{ frame_info with flags = Flags.clear_end_stream frame_info.flags }
|
||||
in
|
||||
f ~off ~len:max_frame_payload frame_info;
|
||||
loop ~off:(off + max_frame_payload) (remaining - max_frame_payload))
|
||||
else f ~off ~len:remaining frame_info
|
||||
in
|
||||
loop ~off total_length
|
||||
else f ~off ~len:total_length frame_info
|
||||
|
||||
let write_data t frame_info ?off ?len str =
|
||||
if not (is_closed t.encoder)
|
||||
then
|
||||
let total_length =
|
||||
match len with Some len -> len | None -> String.length str
|
||||
in
|
||||
chunk_data_frames
|
||||
frame_info
|
||||
?off
|
||||
total_length
|
||||
~f:(fun ~off ~len frame_info ->
|
||||
write_data_frame t.encoder frame_info ~off ~len str)
|
||||
|
||||
let schedule_data t frame_info ?off ?len bstr =
|
||||
if not (is_closed t.encoder)
|
||||
then
|
||||
let total_length =
|
||||
match len with Some len -> len | None -> Bigstringaf.length bstr
|
||||
in
|
||||
chunk_data_frames
|
||||
frame_info
|
||||
?off
|
||||
total_length
|
||||
~f:(fun ~off ~len frame_info ->
|
||||
schedule_data_frame t.encoder frame_info ~off ~len bstr)
|
||||
|
||||
(* Chunk header block fragments into HEADERS|PUSH_PROMISE + CONTINUATION
|
||||
* frames. *)
|
||||
let chunk_header_block_fragments
|
||||
t
|
||||
frame_info
|
||||
?(has_priority = false)
|
||||
~(write_frame :
|
||||
Faraday.t -> frame_info -> ?len:int -> Bigstringaf.t iovec list -> unit)
|
||||
faraday
|
||||
=
|
||||
let block_size = Faraday.pending_bytes faraday in
|
||||
let total_length =
|
||||
if has_priority
|
||||
then
|
||||
(* See RFC7540§6.2: Exclusive Bit & Stream Dependency (4 octets) +
|
||||
Weight (1 octet) + Header Block Fragment length. *)
|
||||
block_size + 5
|
||||
else block_size
|
||||
in
|
||||
let { max_frame_payload; _ } = frame_info in
|
||||
if max_frame_payload < total_length
|
||||
then (
|
||||
let headers_block_len =
|
||||
if has_priority then max_frame_payload - 5 else max_frame_payload
|
||||
in
|
||||
ignore
|
||||
(Faraday.serialize faraday (fun iovecs ->
|
||||
write_frame t.encoder frame_info ~len:headers_block_len iovecs;
|
||||
`Ok headers_block_len));
|
||||
let rec loop remaining =
|
||||
if max_frame_payload < remaining
|
||||
then (
|
||||
(* Note: Don't reuse flags from frame info as CONTINUATION frames
|
||||
* only define END_HEADERS.
|
||||
*
|
||||
* From RFC7540§6.10:
|
||||
* The CONTINUATION frame defines the following flag:
|
||||
*
|
||||
* END_HEADERS (0x4): When set, bit 2 indicates that this frame
|
||||
* ends a header block (Section 4.3). *)
|
||||
let frame_info = { frame_info with flags = Flags.default_flags } in
|
||||
ignore
|
||||
(Faraday.serialize faraday (fun iovecs ->
|
||||
write_continuation_frame
|
||||
t.encoder
|
||||
frame_info
|
||||
~len:max_frame_payload
|
||||
iovecs;
|
||||
`Ok max_frame_payload));
|
||||
loop (remaining - max_frame_payload))
|
||||
else
|
||||
let frame_info =
|
||||
{ frame_info with flags = Flags.(set_end_header default_flags) }
|
||||
in
|
||||
ignore
|
||||
(Faraday.serialize faraday (fun iovecs ->
|
||||
write_continuation_frame
|
||||
t.encoder
|
||||
frame_info
|
||||
~len:remaining
|
||||
iovecs;
|
||||
`Ok remaining))
|
||||
in
|
||||
loop (block_size - headers_block_len))
|
||||
else
|
||||
let frame_info =
|
||||
{ frame_info with flags = Flags.set_end_header frame_info.flags }
|
||||
in
|
||||
ignore
|
||||
(Faraday.serialize faraday (fun iovecs ->
|
||||
let len = IOVec.lengthv iovecs in
|
||||
write_frame t.encoder frame_info ~len iovecs;
|
||||
`Ok len))
|
||||
|
||||
let encode_headers hpack_encoder faraday headers =
|
||||
List.iter
|
||||
(fun header -> Hpack.Encoder.encode_header hpack_encoder faraday header)
|
||||
(Headers.to_hpack_list headers)
|
||||
|
||||
let write_request_like_frame t hpack_encoder ~write_frame frame_info request =
|
||||
let { Request.meth; target; scheme; headers } = request in
|
||||
let faraday = Faraday.create 0x1000 in
|
||||
Hpack.Encoder.encode_header
|
||||
hpack_encoder
|
||||
faraday
|
||||
{ Headers.name = ":method"
|
||||
; value = Httpun_types.Method.to_string meth
|
||||
; sensitive = false
|
||||
};
|
||||
if meth <> `CONNECT
|
||||
then (
|
||||
(* From RFC7540§8.3:
|
||||
* The :scheme and :path pseudo-header fields MUST be omitted. *)
|
||||
Hpack.Encoder.encode_header
|
||||
hpack_encoder
|
||||
faraday
|
||||
{ Headers.name = ":path"; value = target; sensitive = false };
|
||||
Hpack.Encoder.encode_header
|
||||
hpack_encoder
|
||||
faraday
|
||||
{ Headers.name = ":scheme"; value = scheme; sensitive = false });
|
||||
encode_headers hpack_encoder faraday headers;
|
||||
chunk_header_block_fragments t frame_info ~write_frame faraday
|
||||
|
||||
let write_request_headers t hpack_encoder ~priority frame_info request =
|
||||
if not (is_closed t.encoder)
|
||||
then
|
||||
let write_frame = write_headers_frame ~priority in
|
||||
write_request_like_frame t hpack_encoder ~write_frame frame_info request
|
||||
|
||||
let write_push_promise t hpack_encoder frame_info ~promised_id request =
|
||||
if not (is_closed t.encoder)
|
||||
then
|
||||
let write_frame = write_push_promise_frame ~promised_id in
|
||||
write_request_like_frame t hpack_encoder ~write_frame frame_info request
|
||||
|
||||
let write_response_headers t hpack_encoder frame_info response =
|
||||
if not (is_closed t.encoder)
|
||||
then (
|
||||
let { Response.status; headers; _ } = response in
|
||||
let faraday = Faraday.create 0x1000 in
|
||||
(* From RFC7540§8.1.2.4:
|
||||
* For HTTP/2 responses, a single :status pseudo-header field is defined
|
||||
* that carries the HTTP status code field (see [RFC7231], Section 6).
|
||||
* This pseudo-header field MUST be included in all responses; otherwise,
|
||||
* the response is malformed (Section 8.1.2.6). *)
|
||||
Hpack.Encoder.encode_header
|
||||
hpack_encoder
|
||||
faraday
|
||||
{ Headers.name = ":status"
|
||||
; value = Status.to_string status
|
||||
; sensitive = false
|
||||
};
|
||||
encode_headers hpack_encoder faraday headers;
|
||||
chunk_header_block_fragments
|
||||
t
|
||||
frame_info
|
||||
~write_frame:(write_headers_frame ~priority:Priority.default_priority)
|
||||
~has_priority:false
|
||||
faraday)
|
||||
|
||||
let write_response_trailers t hpack_encoder frame_info trailers =
|
||||
if not (is_closed t.encoder)
|
||||
then (
|
||||
let faraday = Faraday.create 0x1000 in
|
||||
(* From RFC7540§8.1:
|
||||
* optionally, one HEADERS frame, followed by zero or more
|
||||
* CONTINUATION frames containing the trailer-part, if present (see
|
||||
* [RFC7230], Section 4.1.2). *)
|
||||
encode_headers hpack_encoder faraday trailers;
|
||||
chunk_header_block_fragments
|
||||
t
|
||||
frame_info
|
||||
~write_frame:(write_headers_frame ~priority:Priority.default_priority)
|
||||
~has_priority:false
|
||||
faraday)
|
||||
|
||||
let write_rst_stream t frame_info e =
|
||||
if not (is_closed t.encoder)
|
||||
then write_rst_stream_frame t.encoder frame_info e
|
||||
|
||||
let write_window_update t frame_info n =
|
||||
if not (is_closed t.encoder)
|
||||
then write_window_update_frame t.encoder frame_info n
|
||||
|
||||
let schedule_iovecs t ~len frame_info iovecs =
|
||||
if not (is_closed t.encoder)
|
||||
then
|
||||
let writer t ~len ~iovecs = bounded_schedule_iovecs t ~len iovecs in
|
||||
chunk_data_frames frame_info len ~f:(fun ~off ~len frame_info ->
|
||||
write_frame_with_padding
|
||||
t.encoder
|
||||
frame_info
|
||||
Data
|
||||
len
|
||||
(writer ~iovecs:(IOVec.shiftv iovecs off) ~len))
|
||||
|
||||
let write_priority t frame_info priority =
|
||||
if not (is_closed t.encoder)
|
||||
then write_priority_frame t.encoder frame_info priority
|
||||
|
||||
let write_settings t frame_info settings =
|
||||
if not (is_closed t.encoder)
|
||||
then write_settings_frame t.encoder frame_info settings
|
||||
|
||||
let write_ping t frame_info ?off payload =
|
||||
if not (is_closed t.encoder)
|
||||
then write_ping_frame t.encoder frame_info ?off payload
|
||||
|
||||
let write_go_away t frame_info ~debug_data ~last_stream_id error =
|
||||
if not (is_closed t.encoder)
|
||||
then
|
||||
write_go_away_frame t.encoder frame_info last_stream_id error debug_data
|
||||
|
||||
let on_wakeup_writer t k =
|
||||
if Faraday.is_closed t.encoder
|
||||
then failwith "on_wakeup_writer on closed conn"
|
||||
else if Optional_thunk.is_some t.wakeup
|
||||
then failwith "on_wakeup: only one callback can be registered at a time"
|
||||
else t.wakeup <- Optional_thunk.some k
|
||||
|
||||
let wakeup t =
|
||||
let f = t.wakeup in
|
||||
t.wakeup <- Optional_thunk.none;
|
||||
Optional_thunk.call_if_some f
|
||||
|
||||
let flush t f =
|
||||
flush_with_reason t.encoder (fun reason ->
|
||||
let result =
|
||||
match reason with
|
||||
| Nothing_pending | Shift -> `Written
|
||||
| Drain -> `Closed
|
||||
in
|
||||
f result)
|
||||
|
||||
let unyield t =
|
||||
(* Faraday doesn't have a function to take the serializer out of a yield
|
||||
state. In the meantime, `flush` does it. *)
|
||||
flush t (fun _reason -> ())
|
||||
|
||||
let yield t = Faraday.yield t.encoder
|
||||
let close t = Faraday.close t.encoder
|
||||
|
||||
let close_and_drain t =
|
||||
Faraday.close t.encoder;
|
||||
let drained = Faraday.drain t.encoder in
|
||||
t.drained_bytes <- t.drained_bytes + drained
|
||||
|
||||
let is_closed t = Faraday.is_closed t.encoder
|
||||
let drained_bytes t = t.drained_bytes
|
||||
|
||||
let report_result t result =
|
||||
match result with
|
||||
| `Closed -> close_and_drain t
|
||||
| `Ok len -> shift t.encoder len
|
||||
|
||||
let next t =
|
||||
match Faraday.operation t.encoder with
|
||||
| `Close -> `Close (drained_bytes t)
|
||||
| `Yield -> `Yield
|
||||
| `Writev iovecs -> `Write iovecs
|
||||
end
|
||||
1418
unikernel/duniverse/ocaml-h2/lib/server_connection.ml
Normal file
1418
unikernel/duniverse/ocaml-h2/lib/server_connection.ml
Normal file
File diff suppressed because it is too large
Load diff
291
unikernel/duniverse/ocaml-h2/lib/settings.ml
Normal file
291
unikernel/duniverse/ocaml-h2/lib/settings.ml
Normal file
|
|
@ -0,0 +1,291 @@
|
|||
(*----------------------------------------------------------------------------
|
||||
* Copyright (c) 2019-2020 António Nuno 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 copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 COPYRIGHT HOLDER 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.
|
||||
*---------------------------------------------------------------------------*)
|
||||
|
||||
module WindowSize = struct
|
||||
type t = int32
|
||||
|
||||
(* From RFC7540§6.9.2:
|
||||
* When an HTTP/2 connection is first established, new streams are created
|
||||
* with an initial flow-control window size of 65,535 octets. *)
|
||||
let default_initial_window_size = 65535l
|
||||
|
||||
(* From RFC7540§6.9:
|
||||
* The legal range for the increment to the flow-control window is 1 to
|
||||
* 2^31-1 (2,147,483,647) octets. *)
|
||||
let max_window_size = Int32.max_int
|
||||
|
||||
(* Ideally `n` here would be an unsigned 32-bit integer, but OCaml doesn't
|
||||
* support them. We avoid introducing a new dependency on an unsigned integer
|
||||
* library by letting it overflow at parse time and checking if bit 31 is set
|
||||
* here, since * `Window.max_window_size` is never allowed to be above
|
||||
* 2^31-1 (see `max_window_size` above).
|
||||
* See http://caml.inria.fr/pub/ml-archives/caml-list/2004/07/f1c483068cc62075c916f7ad7d640ce0.fr.html
|
||||
* for more info. *)
|
||||
let is_window_overflow n = Util.test_bit_int32 n 31
|
||||
end
|
||||
|
||||
type setting =
|
||||
| HeaderTableSize of int
|
||||
| EnablePush of int
|
||||
| MaxConcurrentStreams of int32
|
||||
| InitialWindowSize of int32
|
||||
| MaxFrameSize (* this means payload size *) of int
|
||||
| MaxHeaderListSize of int
|
||||
|
||||
type settings_list = setting list
|
||||
|
||||
(* From RFC7540§6.5.1:
|
||||
* The payload of a SETTINGS frame consists of zero or more parameters,
|
||||
* each consisting of an unsigned 16-bit setting identifier and an
|
||||
* unsigned 32-bit value. *)
|
||||
let octets_per_setting = 6
|
||||
|
||||
let serialize_key = function
|
||||
| HeaderTableSize _ -> 0x1
|
||||
| EnablePush _ -> 0x2
|
||||
| MaxConcurrentStreams _ -> 0x3
|
||||
| InitialWindowSize _ -> 0x4
|
||||
| MaxFrameSize _ -> 0x5
|
||||
| MaxHeaderListSize _ -> 0x6
|
||||
|
||||
let check_value ~is_client = function
|
||||
| EnablePush v ->
|
||||
if v <> 0 && v <> 1
|
||||
then
|
||||
(* From RFC7540§6.5.2
|
||||
* The initial value is 1, which indicates that server push is
|
||||
* permitted. Any value other than 0 or 1 MUST be treated as a
|
||||
* connection error (Section 5.4.1) of type PROTOCOL_ERROR. *)
|
||||
Error
|
||||
Error.(
|
||||
ConnectionError (ProtocolError, "SETTINGS_ENABLE_PUSH must be 0 or 1"))
|
||||
else if is_client && v = 1
|
||||
then
|
||||
(* From RFC7540§8.2:
|
||||
* Clients MUST reject any attempt to change the
|
||||
* SETTINGS_ENABLE_PUSH setting to a value other than 0 by
|
||||
* treating the message as a connection error (Section 5.4.1) of
|
||||
* type PROTOCOL_ERROR. *)
|
||||
Error
|
||||
Error.(
|
||||
ConnectionError
|
||||
(ProtocolError, "Server must not try to enable SETTINGS_ENABLE_PUSH"))
|
||||
else Ok ()
|
||||
| InitialWindowSize v when WindowSize.is_window_overflow v ->
|
||||
(* From RFC7540§6.5.2
|
||||
* Values above the maximum flow-control window size of 2^31-1 MUST be
|
||||
* treated as a connection error (Section 5.4.1) of type
|
||||
* FLOW_CONTROL_ERROR. *)
|
||||
Error
|
||||
Error.(
|
||||
ConnectionError
|
||||
( FlowControlError
|
||||
, Format.sprintf
|
||||
"Window size must be less than or equal to %ld"
|
||||
WindowSize.max_window_size ))
|
||||
| MaxFrameSize v when v < 16384 || v > 16777215 ->
|
||||
(* From RFC7540§6.5.2
|
||||
* The initial value is 214 (16,384) octets. The value advertised by an
|
||||
* endpoint MUST be between this initial value and the maximum allowed
|
||||
* frame size (224-1 or 16,777,215 octets), inclusive. Values outside
|
||||
* this range MUST be treated as a connection error (Section 5.4.1) of
|
||||
* type PROTOCOL_ERROR. *)
|
||||
Error
|
||||
Error.(
|
||||
ConnectionError
|
||||
(ProtocolError, "Max frame size must be in the 16384 - 16777215 range"))
|
||||
| _ -> Ok ()
|
||||
|
||||
(* Check incoming settings and report an error if any. *)
|
||||
let check_settings_list ?(is_client = false) settings =
|
||||
let rec loop = function
|
||||
| [] -> Ok ()
|
||||
| x :: xs ->
|
||||
(match check_value ~is_client x with
|
||||
| Ok () -> loop xs
|
||||
| Error _ as err -> err)
|
||||
in
|
||||
loop settings
|
||||
|
||||
type t =
|
||||
{ header_table_size : int
|
||||
; enable_push : bool
|
||||
; max_concurrent_streams : int32
|
||||
; (* Indicates the amount tokens the peer allows an H2 endpoint to send. *)
|
||||
initial_window_size : WindowSize.t
|
||||
; max_frame_size : int
|
||||
; max_header_list_size : int option
|
||||
}
|
||||
|
||||
(* From RFC7540§11.3 *)
|
||||
let default =
|
||||
{ header_table_size = 0x1000
|
||||
; enable_push =
|
||||
true
|
||||
(* From RFC7540§6.5.2:
|
||||
* SETTINGS_MAX_CONCURRENT_STREAMS (0x3): [...] Initially, there is no
|
||||
* limit to this value. *)
|
||||
; max_concurrent_streams = Int32.max_int
|
||||
; initial_window_size = WindowSize.default_initial_window_size
|
||||
; max_frame_size = 0x4000
|
||||
; max_header_list_size = None
|
||||
}
|
||||
|
||||
let settings_for_the_connection settings =
|
||||
let settings_list =
|
||||
if settings.max_frame_size <> default.max_frame_size
|
||||
then [ MaxFrameSize settings.max_frame_size ]
|
||||
else []
|
||||
in
|
||||
let settings_list =
|
||||
if settings.max_concurrent_streams <> default.max_concurrent_streams
|
||||
then MaxConcurrentStreams settings.max_concurrent_streams :: settings_list
|
||||
else settings_list
|
||||
in
|
||||
let settings_list =
|
||||
if settings.initial_window_size <> default.initial_window_size
|
||||
then
|
||||
(* FIXME: don't convert *)
|
||||
InitialWindowSize settings.initial_window_size :: settings_list
|
||||
else settings_list
|
||||
in
|
||||
let settings_list =
|
||||
if settings.enable_push <> default.enable_push
|
||||
then EnablePush (if settings.enable_push then 1 else 0) :: settings_list
|
||||
else settings_list
|
||||
in
|
||||
settings_list
|
||||
|
||||
let parse_settings_payload num_settings =
|
||||
let open Angstrom in
|
||||
let rec parse_inner acc remaining =
|
||||
(* From RFC7540§6.5.3:
|
||||
* The values in the SETTINGS frame MUST be processed in the order
|
||||
* they appear, with no other frame processing between values. *)
|
||||
if remaining <= 0
|
||||
then return (List.rev acc)
|
||||
else
|
||||
lift2
|
||||
(fun k (v : int32) ->
|
||||
match k with
|
||||
| 0x1 -> HeaderTableSize (Int32.to_int v) :: acc
|
||||
| 0x2 -> EnablePush (Int32.to_int v) :: acc
|
||||
| 0x3 -> MaxConcurrentStreams v :: acc
|
||||
| 0x4 -> InitialWindowSize v :: acc
|
||||
| 0x5 -> MaxFrameSize (Int32.to_int v) :: acc
|
||||
| 0x6 -> MaxHeaderListSize (Int32.to_int v) :: acc
|
||||
| _ ->
|
||||
(* Note: This ignores unknown settings.
|
||||
*
|
||||
* From RFC7540§6.5.3:
|
||||
* Unsupported parameters MUST be ignored.
|
||||
*)
|
||||
acc)
|
||||
BE.any_uint16
|
||||
BE.any_int32
|
||||
>>= fun acc' -> parse_inner acc' (remaining - 1)
|
||||
in
|
||||
parse_inner [] num_settings
|
||||
|
||||
let write_settings_payload t settings_list =
|
||||
let open Faraday in
|
||||
List.iter
|
||||
(fun setting ->
|
||||
(* From RFC7540§6.5.1:
|
||||
* The payload of a SETTINGS frame consists of zero or more parameters,
|
||||
* each consisting of an unsigned 16-bit setting identifier and an
|
||||
* unsigned 32-bit value. *)
|
||||
BE.write_uint16 t (serialize_key setting);
|
||||
match setting with
|
||||
| MaxConcurrentStreams value | InitialWindowSize value ->
|
||||
BE.write_uint32 t value
|
||||
| HeaderTableSize value
|
||||
| EnablePush value
|
||||
| MaxFrameSize value
|
||||
| MaxHeaderListSize value ->
|
||||
BE.write_uint32 t (Int32.of_int value))
|
||||
settings_list
|
||||
|
||||
let of_settings_list settings =
|
||||
List.fold_left
|
||||
(fun (acc : t) item ->
|
||||
match item with
|
||||
| HeaderTableSize x -> { acc with header_table_size = x }
|
||||
| EnablePush x -> { acc with enable_push = x = 1 }
|
||||
| MaxConcurrentStreams x -> { acc with max_concurrent_streams = x }
|
||||
| InitialWindowSize new_val -> { acc with initial_window_size = new_val }
|
||||
| MaxFrameSize x -> { acc with max_frame_size = x }
|
||||
| MaxHeaderListSize x -> { acc with max_header_list_size = Some x })
|
||||
default
|
||||
settings
|
||||
|
||||
let of_base64 encoded =
|
||||
match Base64.decode ~alphabet:Base64.uri_safe_alphabet encoded with
|
||||
| Ok settings_payload ->
|
||||
let settings_payload_length =
|
||||
String.length settings_payload / octets_per_setting
|
||||
in
|
||||
(match
|
||||
Angstrom.parse_string
|
||||
~consume:All
|
||||
(parse_settings_payload settings_payload_length)
|
||||
settings_payload
|
||||
with
|
||||
| Ok settings -> Ok (of_settings_list settings)
|
||||
| Error _ as e -> e)
|
||||
| Error (`Msg msg) -> Error msg
|
||||
|
||||
let to_base64 t =
|
||||
let settings = settings_for_the_connection t in
|
||||
let faraday = Faraday.create (List.length settings * 6) in
|
||||
write_settings_payload faraday settings;
|
||||
let settings_hex = Faraday.serialize_to_string faraday in
|
||||
match Base64.encode ~alphabet:Base64.uri_safe_alphabet settings_hex with
|
||||
| Ok r -> Ok r
|
||||
| Error (`Msg msg) -> Error msg
|
||||
|
||||
let pp_hum formatter t =
|
||||
let pp_elem formatter setting =
|
||||
let key, value =
|
||||
match setting with
|
||||
| HeaderTableSize v -> "HEADER_TABLE_SIZE", Int64.of_int v
|
||||
| EnablePush v -> "ENABLE_PUSH", Int64.of_int v
|
||||
| MaxConcurrentStreams v -> "MAX_CONCURRENT_STREAMS", Int64.of_int32 v
|
||||
| InitialWindowSize v -> "INITIAL_WINDOW_SIZE", Int64.of_int32 v
|
||||
| MaxFrameSize v -> "MAX_FRAME_SIZE", Int64.of_int v
|
||||
| MaxHeaderListSize v -> "MAX_HEADER_LIST_SIZE", Int64.of_int v
|
||||
in
|
||||
Format.fprintf formatter "@[(%S %Ld)@]" key value
|
||||
in
|
||||
Format.fprintf formatter "@[(";
|
||||
Format.pp_print_list pp_elem formatter (settings_for_the_connection t);
|
||||
Format.fprintf formatter ")@]"
|
||||
123
unikernel/duniverse/ocaml-h2/lib/status.ml
Normal file
123
unikernel/duniverse/ocaml-h2/lib/status.ml
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
(*----------------------------------------------------------------------------
|
||||
* 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.
|
||||
*---------------------------------------------------------------------------*)
|
||||
|
||||
(* From RFC7540§8.1.1:
|
||||
* HTTP/2 removes support for the 101 (Switching Protocols) informational
|
||||
* status code ([RFC7231], Section 6.2.2).
|
||||
*
|
||||
* Note: While the above is true, we don't enforce in this library, as it
|
||||
* makes unifying types with httpun much easier. `H2.Status.t` is, thus, a
|
||||
* strict superset of `Httpun_types.Status.t`. *)
|
||||
|
||||
include (
|
||||
Httpun_types.Status :
|
||||
module type of Httpun_types.Status
|
||||
with type client_error := Httpun_types.Status.client_error
|
||||
and type standard := Httpun_types.Status.standard
|
||||
and type t := Httpun_types.Status.t)
|
||||
|
||||
type client_error =
|
||||
[ Httpun_types.Status.client_error
|
||||
| (* From RFC7540§9.1.2:
|
||||
* The 421 (Misdirected Request) status code indicates that the request
|
||||
* was directed at a server that is not able to produce a response. This
|
||||
* can be sent by a server that is not configured to produce responses
|
||||
* for the combination of scheme and authority that are included in the
|
||||
* request URI. *)
|
||||
`Misdirected_request
|
||||
]
|
||||
|
||||
type standard =
|
||||
[ Httpun_types.Status.standard
|
||||
| client_error
|
||||
]
|
||||
|
||||
type t =
|
||||
[ standard
|
||||
| `Code of int
|
||||
]
|
||||
|
||||
(* Note: The value for reason phrases is never actually serialized to the
|
||||
* input or output channels.
|
||||
*
|
||||
* From RFC7540§8.1.2.4:
|
||||
* HTTP/2 does not define a way to carry the version or reason phrase that is
|
||||
* included in an HTTP/1.1 status line. *)
|
||||
let default_reason_phrase = function
|
||||
| `Misdirected_request -> "Misdirected Request"
|
||||
| #Httpun_types.Status.standard as t ->
|
||||
Httpun_types.Status.default_reason_phrase t
|
||||
|
||||
let to_code = function
|
||||
| `Misdirected_request -> 421
|
||||
| #Httpun_types.Status.t as t -> Httpun_types.Status.to_code t
|
||||
|
||||
let unsafe_of_code = function
|
||||
| 421 -> `Misdirected_request
|
||||
| c -> (Httpun_types.Status.unsafe_of_code c :> t)
|
||||
|
||||
let of_code = function
|
||||
| 421 -> `Misdirected_request
|
||||
| c -> (Httpun_types.Status.of_code c :> t)
|
||||
|
||||
let is_informational = function
|
||||
| `Misdirected_request -> false
|
||||
| #Httpun_types.Status.t as t -> Httpun_types.Status.is_informational t
|
||||
|
||||
let is_successful = function
|
||||
| `Misdirected_request -> false
|
||||
| #Httpun_types.Status.t as t -> Httpun_types.Status.is_successful t
|
||||
|
||||
let is_redirection = function
|
||||
| `Misdirected_request -> false
|
||||
| #Httpun_types.Status.t as t -> Httpun_types.Status.is_redirection t
|
||||
|
||||
let is_client_error = function
|
||||
| `Misdirected_request -> true
|
||||
| #Httpun_types.Status.t as t -> Httpun_types.Status.is_client_error t
|
||||
|
||||
let is_server_error = function
|
||||
| `Misdirected_request -> false
|
||||
| #Httpun_types.Status.t as t -> Httpun_types.Status.is_server_error t
|
||||
|
||||
let is_error = function
|
||||
| `Misdirected_request -> true
|
||||
| #Httpun_types.Status.t as t -> Httpun_types.Status.is_error t
|
||||
|
||||
let to_string = function
|
||||
| `Misdirected_request -> "421"
|
||||
| #Httpun_types.Status.t as t -> Httpun_types.Status.to_string t
|
||||
|
||||
let of_string x = of_code (int_of_string x)
|
||||
let pp_hum fmt t = Format.fprintf fmt "%u" (to_code t)
|
||||
157
unikernel/duniverse/ocaml-h2/lib/stream.ml
Normal file
157
unikernel/duniverse/ocaml-h2/lib/stream.ml
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
(*----------------------------------------------------------------------------
|
||||
* Copyright (c) 2019 António Nuno 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 copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 COPYRIGHT HOLDER 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.
|
||||
*---------------------------------------------------------------------------*)
|
||||
|
||||
module AB = Angstrom.Buffered
|
||||
module Writer = Serialize.Writer
|
||||
|
||||
type partial_headers =
|
||||
{ mutable parse_state : (Headers.t, Hpack.error) result AB.state
|
||||
; end_stream : bool
|
||||
}
|
||||
|
||||
type 'active_peer remote_state =
|
||||
(* A stream is in this state when it's waiting for the peer to initiate a
|
||||
* response. In practice, it only matters for the client implementation, when
|
||||
* a client has opened a stream but is still waiting on the server to send
|
||||
* the first bytes of the response. *)
|
||||
| WaitingForPeer
|
||||
(* A PartialHeaders state is entered when the endpoint sees the first HEADERS
|
||||
* frame from the peer for a given stream. Its payload is an
|
||||
* Angstrom.Buffered parse state. *)
|
||||
| PartialHeaders of partial_headers
|
||||
(* A stream transitions from the PartialHeaders state to the FullHeaders
|
||||
* state when the endpoint has finished parsing all the bytes in a group of
|
||||
* HEADER / CONTINUATION frames that the peer has sent.
|
||||
* This state doesn't carry any payload because the stream will immediately
|
||||
* transition to the ActiveMessage state once the message has been validated
|
||||
* according to RFC7540§8.1.2. *)
|
||||
| FullHeaders
|
||||
(* The ActiveMessage state carries information about the current remote
|
||||
* message being processed by the endpoint. *)
|
||||
| ActiveMessage of 'active_peer
|
||||
|
||||
type closed_reason =
|
||||
| Finished
|
||||
(* TODO: we could abide by the following by either 1) having I/O runtime
|
||||
* support for timers or 2) by simply counting the number of frames received
|
||||
* after we've sent an RST_STREAM?
|
||||
*
|
||||
* From RFC7540§5.4.2:
|
||||
* Normally, an endpoint SHOULD NOT send more than one RST_STREAM frame for
|
||||
* any stream. However, an endpoint MAY send additional RST_STREAM frames
|
||||
* if it receives frames on a closed stream after more than a round-trip
|
||||
* time. This behavior is permitted to deal with misbehaving
|
||||
* implementations. *)
|
||||
| ResetByUs of Error_code.t
|
||||
(* Received an RST_STREAM frame from the peer. *)
|
||||
| ResetByThem of Error_code.t
|
||||
|
||||
type closed =
|
||||
{ reason : closed_reason
|
||||
(* When a stream is closed, we may want to keep it around in the hash
|
||||
* table for a while (e.g. to know whether this stream was reset by the
|
||||
* peer - some error handling code depends on that). We start with a
|
||||
* default value, and on every writer yield we decrement it. If it
|
||||
* reaches 0, the stream is finally removed from the hash table. *)
|
||||
; mutable ttl : int
|
||||
}
|
||||
|
||||
type ('opn, 'half_closed) active_state =
|
||||
| Open of 'opn remote_state
|
||||
| HalfClosed of 'half_closed
|
||||
|
||||
type ('active_state, 'active, 'reserved) state =
|
||||
| Idle
|
||||
| Reserved of 'reserved
|
||||
| Active of 'active_state * 'active
|
||||
| Closed of closed
|
||||
constraint 'active_state = (_, _) active_state
|
||||
|
||||
type 'a error_status =
|
||||
| No_error
|
||||
| Exn of exn
|
||||
| Other of
|
||||
{ error : 'a
|
||||
; code : Error_code.t
|
||||
}
|
||||
|
||||
type ('state, 'error, 'error_handler) t =
|
||||
{ id : Stream_identifier.t
|
||||
; writer : Serialize.Writer.t
|
||||
; error_handler : 'error_handler
|
||||
; mutable error_code : 'error error_status
|
||||
; mutable state : 'state
|
||||
(* The largest frame payload we're allowed to write. *)
|
||||
; mutable max_frame_size : int
|
||||
; on_close : active:bool -> closed -> unit
|
||||
}
|
||||
constraint 'state = (_, _, _) state
|
||||
|
||||
let initial_ttl = 10
|
||||
|
||||
let create id ~max_frame_size writer error_handler on_close =
|
||||
{ id
|
||||
; writer
|
||||
; error_handler
|
||||
(* From RFC7540§5.1:
|
||||
* idle: All streams start in the "idle" state. *)
|
||||
; state = Idle
|
||||
; error_code = No_error
|
||||
; max_frame_size
|
||||
; on_close
|
||||
}
|
||||
|
||||
let id { id; _ } = id
|
||||
let is_idle t = match t.state with Idle -> true | _ -> false
|
||||
let is_open t = match t.state with Active (Open _, _) -> true | _ -> false
|
||||
|
||||
let finish_stream t reason =
|
||||
let active = match t.state with Active _ -> true | _ -> false in
|
||||
let closed = { reason; ttl = initial_ttl } in
|
||||
t.on_close ~active closed;
|
||||
t.state <- Closed closed
|
||||
|
||||
let error_code t =
|
||||
match t.error_code with
|
||||
| Exn exn -> Some (`Exn exn)
|
||||
| Other { error; _ } -> Some error
|
||||
| No_error -> None
|
||||
|
||||
let error_to_code error error_code =
|
||||
match error with
|
||||
| `Exn exn -> Exn exn
|
||||
| other -> Other { error = other; code = error_code }
|
||||
|
||||
let reset_stream t error_code =
|
||||
let frame_info = Writer.make_frame_info t.id in
|
||||
Writer.write_rst_stream t.writer frame_info error_code;
|
||||
finish_stream t (ResetByUs error_code)
|
||||
65
unikernel/duniverse/ocaml-h2/lib/stream_identifier.ml
Normal file
65
unikernel/duniverse/ocaml-h2/lib/stream_identifier.ml
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
(*----------------------------------------------------------------------------
|
||||
* Copyright (c) 2019 António Nuno 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 copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 COPYRIGHT HOLDER 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.
|
||||
*---------------------------------------------------------------------------*)
|
||||
|
||||
(* From RFC7540§5.1.1:
|
||||
* Streams are identified with an unsigned 31-bit integer. *)
|
||||
type t = int32
|
||||
|
||||
let ( === ) = Int32.equal
|
||||
let[@inline] ( <= ) s1 s2 = Int32.compare s1 s2 <= 0
|
||||
let[@inline] ( > ) s1 s2 = Int32.compare s1 s2 > 0
|
||||
let[@inline] ( >= ) s1 s2 = Int32.compare s1 s2 >= 0
|
||||
|
||||
(* From RFC7540§5.1.1:
|
||||
* A stream identifier of zero (0x0) is used for connection control messages;
|
||||
* the stream identifier of zero cannot be used to establish a new stream. *)
|
||||
let connection = Int32.zero
|
||||
|
||||
(* From RFC7540§5.1.1:
|
||||
* A stream identifier of zero (0x0) is used for connection control messages;
|
||||
* the stream identifier of zero cannot be used to establish a new stream. *)
|
||||
let[@inline] is_connection id = Int32.equal id connection
|
||||
|
||||
(* From RFC7540§5.1.1:
|
||||
* Streams initiated by a client MUST use odd-numbered stream
|
||||
* identifiers [...]. *)
|
||||
let[@inline] is_request id = Int32.rem id 2l === 1l
|
||||
|
||||
(* From RFC7540§5.1.1:
|
||||
* Streams initiated by [...] the server MUST use even-numbered stream
|
||||
* identifiers. A stream identifier of zero (0x0) is used for connection
|
||||
* control messages [...]. *)
|
||||
let[@inline] is_pushed = function 0l -> false | n -> Int32.rem n 2l === 0l
|
||||
|
||||
(* From RFC7540§5.1.1:
|
||||
* Streams are identified with an unsigned 31-bit integer. *)
|
||||
let max_stream_id = Int32.max_int
|
||||
48
unikernel/duniverse/ocaml-h2/lib/util.ml
Normal file
48
unikernel/duniverse/ocaml-h2/lib/util.ml
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
(*----------------------------------------------------------------------------
|
||||
* Copyright (c) 2019 António Nuno 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 copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 COPYRIGHT HOLDER 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.
|
||||
*---------------------------------------------------------------------------*)
|
||||
|
||||
let[@inline] test_bit_int32 x i =
|
||||
let open Int32 in
|
||||
not (equal (logand x (shift_left 1l i)) 0l)
|
||||
|
||||
let[@inline] test_bit x i = x land (1 lsl i) <> 0
|
||||
let[@inline] set_bit x i = x lor (1 lsl i)
|
||||
|
||||
let[@inline] set_bit_int32 x i =
|
||||
let open Int32 in
|
||||
logor x (shift_left 1l i)
|
||||
|
||||
let[@inline] clear_bit x i = x land lnot (1 lsl i)
|
||||
|
||||
let[@inline] clear_bit_int32 x i =
|
||||
let open Int32 in
|
||||
logand x (lognot (shift_left 1l i))
|
||||
Loading…
Add table
Add a link
Reference in a new issue