mte/unikernel/duniverse/ocaml-h2/lib/server_connection.ml

1419 lines
59 KiB
OCaml
Raw Normal View History

2025-11-11 02:07:51 +01:00
(*----------------------------------------------------------------------------
* 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 AB = Angstrom.Buffered
module Reader = Parse.Reader
module Writer = Serialize.Writer
module Scheduler = Scheduler.Make (struct
include Stream
type t = Reqd.t
let flush_write_body = Reqd.flush_response_body
let requires_output = Reqd.requires_output
end)
type request_handler = Reqd.t -> unit
type error =
[ `Bad_request
| `Internal_server_error
| `Exn of exn
]
type error_handler =
?request:Request.t -> error -> (Headers.t -> Body.Writer.t) -> unit
type t =
{ mutable settings : Settings.t
; reader : Reader.frame
; writer : Writer.t
; config : Config.t
; request_handler : request_handler
; error_handler : error_handler
; streams : Scheduler.t
(* Number of currently open client streams. Used for
* MAX_CONCURRENT_STREAMS bookkeeping *)
; mutable current_client_streams : int
; mutable max_client_stream_id : Stream_identifier.t
; mutable max_pushed_stream_id : Stream_identifier.t
; mutable receiving_headers_for_stream : Stream_identifier.t option
(* Keep track of number of SETTINGS frames that we sent and for which
* we haven't eceived an acknowledgment from the client. *)
; mutable unacked_settings : int
; mutable did_send_go_away : bool
(* From RFC7540§4.3:
* Header compression is stateful. One compression context and one
* decompression context are used for the entire connection. *)
; hpack_encoder : Hpack.Encoder.t
; hpack_decoder : Hpack.Decoder.t
}
let is_closed t = Reader.is_closed t.reader && Writer.is_closed t.writer
let wakeup_writer t = Writer.wakeup t.writer
let shutdown_reader t = Reader.force_close t.reader
let shutdown_writer t =
Writer.close t.writer;
wakeup_writer t
let shutdown t =
shutdown_reader t;
shutdown_writer t
(* Handling frames against closed streams is hard. See:
* https://docs.google.com/presentation/d/1iG_U2bKTc9CnKr0jPTrNfmxyLufx_cK2nNh9VjrKH6s
*)
let was_closed_or_implicitly_closed t stream_id =
if Stream_identifier.is_request stream_id
then Stream_identifier.(stream_id <= t.max_client_stream_id)
else Stream_identifier.(stream_id <= t.max_pushed_stream_id)
(* TODO: currently connection-level errors are not reported to the error
* handler because it is assumed that an error handler will produce a response,
* and since HTTP/2 is multiplexed, there's no matching response for a
* connection error. We should do something about it. *)
let report_error t = function
| Error.ConnectionError (error, data) ->
if not t.did_send_go_away
then (
(* From RFC7540§5.4.1:
* An endpoint that encounters a connection error SHOULD first send a
* GOAWAY frame (Section 6.8) with the stream identifier of the last
* stream that it successfully received from its peer. The GOAWAY frame
* includes an error code that indicates why the connection is
* terminating. After sending the GOAWAY frame for an error condition,
* the endpoint MUST close the TCP connection. *)
let debug_data =
if String.length data = 0
then Bigstringaf.empty
else Bigstringaf.of_string ~off:0 ~len:(String.length data) data
in
let frame_info = Writer.make_frame_info Stream_identifier.connection in
(* TODO: Only write if not already shutdown. *)
Writer.write_go_away
t.writer
frame_info
~debug_data
~last_stream_id:t.max_client_stream_id
error;
Writer.flush t.writer (fun _reason ->
(* XXX: We need to allow lower numbered streams to complete before
* shutting down. *)
shutdown t);
t.did_send_go_away <- true;
wakeup_writer t)
| StreamError (stream_id, error) ->
(match Scheduler.find t.streams stream_id with
| Some reqd -> Stream.reset_stream reqd error
| None ->
if not (was_closed_or_implicitly_closed t stream_id)
then
(* Possible if the stream was going to enter the Idle state (first time
* we saw e.g. a PRIORITY frame for it) but had e.g. a
* FRAME_SIZE_ERROR. *)
let frame_info = Writer.make_frame_info stream_id in
Writer.write_rst_stream t.writer frame_info error);
wakeup_writer t
let report_connection_error t ?(reason = "") error =
report_error t (ConnectionError (error, reason))
let report_stream_error t stream_id error =
report_error t (StreamError (stream_id, error))
let set_error_and_handle ?request t stream error error_code =
assert (request = None);
Reqd.report_error stream error error_code;
wakeup_writer t
let report_exn t exn =
if not (is_closed t)
then
let reason = Printexc.to_string exn in
report_connection_error t ~reason Error_code.InternalError
let on_close_stream t id ~active closed =
if active
then
(* From RFC7540§5.1.2:
* Streams that are in the "open" state or in either of the "half-closed"
* states count toward the maximum number of streams that an endpoint is
* permitted to open. *)
t.current_client_streams <- t.current_client_streams - 1;
Scheduler.mark_for_removal t.streams id closed
let send_window_update :
type a. t -> a Scheduler.PriorityTreeNode.node -> int32 -> unit
=
fun t stream n ->
let send_window_update_frame stream_id n =
let valid_inflow = Scheduler.add_inflow stream n in
assert valid_inflow;
let frame_info = Writer.make_frame_info stream_id in
Writer.write_window_update t.writer frame_info n
in
if Int32.compare n 0l > 0
then (
let max_window_size = Settings.WindowSize.max_window_size in
let stream_id = Scheduler.stream_id stream in
let rec loop n =
if n > max_window_size
then (
send_window_update_frame stream_id max_window_size;
loop (Int32.sub n max_window_size))
else send_window_update_frame stream_id n
in
loop n;
wakeup_writer t)
let create_push_stream t parent_stream_id =
let candidate_push_stream_id = Int32.add t.max_pushed_stream_id 2l in
if not t.settings.enable_push
then
(* From RFC7540§6.6:
* PUSH_PROMISE MUST NOT be sent if the SETTINGS_ENABLE_PUSH setting of
* the peer endpoint is set to 0. *)
Error `Push_disabled
else if Stream_identifier.(candidate_push_stream_id > max_stream_id)
then (
(* From RFC7540§5.1:
* Stream identifiers cannot be reused. Long-lived connections can result
* in an endpoint exhausting the available range of stream identifiers.
* [...] A server that is unable to establish a new stream identifier can
* send a GOAWAY frame so that the client is forced to open a new
* connection for new streams. *)
report_connection_error t Error_code.NoError;
Error `Stream_ids_exhausted)
else
let pushed_stream_id = candidate_push_stream_id in
t.max_pushed_stream_id <- pushed_stream_id;
let reqd =
Stream.create
pushed_stream_id
~max_frame_size:t.settings.max_frame_size
t.writer
t.error_handler
(on_close_stream t pushed_stream_id)
in
(* 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 _stream : Scheduler.nonroot Scheduler.node =
Scheduler.add
t.streams
~priority:
{ Priority.default_priority with
stream_dependency = parent_stream_id
}
~initial_send_window_size:t.settings.initial_window_size
~initial_recv_window_size:t.config.initial_window_size
reqd
in
Ok reqd
let handle_headers t ~end_stream stream active_stream headers =
let (Scheduler.Stream { descriptor = reqd; _ }) = stream in
(* From RFC7540§5.1.2:
* Endpoints MUST NOT exceed the limit set by their peer. An endpoint that
* receives a HEADERS frame that causes its advertised concurrent stream
* limit to be exceeded MUST treat this as a stream error (Section 5.4.2)
* of type PROTOCOL_ERROR or REFUSED_STREAM. *)
if Int32.(
compare
(of_int (t.current_client_streams + 1))
t.config.max_concurrent_streams)
> 0
then
if t.unacked_settings > 0
then
(* From RFC7540§8.1.4:
* The REFUSED_STREAM error code can be included in a RST_STREAM frame
* to indicate that the stream is being closed prior to any processing
* having occurred. Any request that was sent on the reset stream can
* be safely retried.
*
* Note: if there are pending SETTINGS to acknowledge, assume there was a
* race condition and let the client retry. *)
report_stream_error t reqd.Stream.id Error_code.RefusedStream
else report_stream_error t reqd.Stream.id Error_code.ProtocolError
else (
reqd.state <- Active (Open FullHeaders, active_stream);
(* From RFC7540§5.1.2:
* Streams that are in the "open" state or in either of the "half-closed"
* states count toward the maximum number of streams that an endpoint is
* permitted to open. *)
t.current_client_streams <- t.current_client_streams + 1;
match Headers.method_path_and_scheme_or_malformed headers with
| `Malformed ->
(* From RFC7540§8.1.2.6:
* For malformed requests, a server MAY send an HTTP response prior to
* closing or resetting the stream. *)
set_error_and_handle t reqd `Bad_request ProtocolError
| `Valid (meth, path, scheme) ->
(* Note: we don't need to check for `end_stream` flag + a non-zero body
* length, as the spec allows for non-zero content-length headers and no
* DATA frames.
*
* From RFC7540§8.1.2.6:
* A response that is defined to have no payload, as described in
* [RFC7230], Section 3.3.2, can have a non-zero content-length header
* field, even though no content is included in DATA frames. *)
(match Message.body_length headers with
| `Error e -> set_error_and_handle t reqd e ProtocolError
| `Fixed _ | `Unknown ->
let request =
Request.create
~scheme
~headers
(Httpun_types.Method.of_string meth)
path
in
let request_body =
if end_stream
then Body.Reader.empty
else
Body.Reader.create
(Bigstringaf.create t.config.request_body_buffer_size)
~done_reading:(fun len ->
let len = Int32.of_int len in
(* From RFC7540§6.9.1:
* The receiver of a frame sends a WINDOW_UPDATE frame as it
* consumes data and frees up space in flow-control windows.
* Separate WINDOW_UPDATE frames are sent for the stream- and
* connection-level flow-control windows. *)
match reqd.state with
| Active _ ->
send_window_update t t.streams len;
send_window_update t stream len
| Idle | Reserved _ | Closed _ -> ())
in
let request_info = Reqd.create_active_request request request_body in
if end_stream
then (
(* From RFC7540§5.1:
* [...] an endpoint receiving an END_STREAM flag causes the stream
* state to become "half-closed (remote)". *)
reqd.state <- Active (HalfClosed request_info, active_stream);
(* Deliver EOF to the request body, as the handler might be waiting
* on it to produce a response. *)
Body.Reader.close request_body)
else
reqd.state <- Active (Open (ActiveMessage request_info), active_stream);
t.request_handler reqd;
wakeup_writer t))
let handle_headers_block
t
?(is_trailers = false)
stream
active_stream
partial_headers
flags
headers_block
=
let open AB in
let (Scheduler.Stream { descriptor = reqd; _ }) = stream in
let end_headers = Flags.test_end_header flags in
(* From RFC7540§6.10:
* An endpoint receiving HEADERS, PUSH_PROMISE, or CONTINUATION
* frames needs to reassemble header blocks and perform decompression
* even if the frames are to be discarded *)
let parse_state' =
AB.feed partial_headers.Stream.parse_state (`Bigstring headers_block)
in
if end_headers
then (
t.receiving_headers_for_stream <- None;
let parse_state' = AB.feed parse_state' `Eof in
match parse_state' with
| Done (_, Ok headers) ->
if not is_trailers
then (
(* Note:
* the highest stream identifier that the server has seen is set here
* (as opposed to when the stream was first opened - when handling
* the first HEADERS frame) because it refers to the highest stream
* identifier that the server will process.
*
* 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. All streams up to and including the identified stream
* might have been processed in some way. *)
t.max_client_stream_id <- reqd.Stream.id;
(* `handle_headers` will take care of transitioning the stream state *)
handle_headers
t
~end_stream:partial_headers.end_stream
stream
active_stream
headers)
else if Headers.trailers_valid headers
then (
Reqd.deliver_trailer_headers reqd headers;
let request_body = Reqd.request_body reqd in
Body.Reader.close request_body)
else
(* 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). *)
set_error_and_handle t reqd `Bad_request ProtocolError
(* From RFC7540§4.3:
* A decoding error in a header block MUST be treated as a connection
* error (Section 5.4.1) of type COMPRESSION_ERROR. *)
| Done (_, Error _) | Partial _ ->
report_connection_error t Error_code.CompressionError
| Fail (_, _, message) ->
report_connection_error t ~reason:message Error_code.CompressionError)
else partial_headers.parse_state <- parse_state'
let handle_trailer_headers = handle_headers_block ~is_trailers:true
let open_stream t ~priority stream_id =
if not Stream_identifier.(stream_id > t.max_client_stream_id)
then (
(* From RFC7540§5.1.1:
* [...] The identifier of a newly established stream MUST be numerically
* greater than all streams that the initiating endpoint has opened or
* reserved. [...] An endpoint that receives an unexpected stream
* identifier MUST respond with a connection error (Section 5.4.1) of
* type PROTOCOL_ERROR. *)
report_connection_error t Error_code.ProtocolError;
None)
else
(* 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 frames can
* be sent on a stream in the "idle", "reserved (local)", "open", or
* "half-closed (remote)" state. *)
match Scheduler.get_node t.streams stream_id with
| None ->
let reqd =
Stream.create
stream_id
~max_frame_size:t.settings.max_frame_size
t.writer
t.error_handler
(on_close_stream t stream_id)
in
let stream : Scheduler.nonroot Scheduler.node =
Scheduler.add
t.streams
~priority
~initial_send_window_size:t.settings.initial_window_size
~initial_recv_window_size:t.config.initial_window_size
reqd
in
Some stream
| Some (Scheduler.Stream node as stream) ->
(* From RFC7540§6.9.2:
* Both endpoints can adjust the initial window size for new streams
* by including a value for SETTINGS_INITIAL_WINDOW_SIZE in the
* SETTINGS frame.
*
* Note: we already have the stream in the priority tree, and the
* default initial window size for new streams could have changed
* between adding the (idle) stream and opening it.
*
* Note: `inflow` doesn't change, that's set by us statically via config.
*)
node.flow <- t.settings.initial_window_size;
Some stream
let process_first_headers_block t frame_header stream headers_block =
let (Scheduler.Stream { descriptor = reqd; _ }) = stream in
let { Frame.stream_id; flags; _ } = frame_header in
let end_headers = Flags.test_end_header flags in
let headers_block_length = Bigstringaf.length headers_block in
let initial_buffer_size =
if end_headers
then headers_block_length
else
(* Conservative estimate that there's only going to be one CONTINUATION
* frame. *)
2 * headers_block_length
in
let partial_headers =
{ Stream.parse_state =
AB.parse
~initial_buffer_size
(Hpack.Decoder.decode_headers t.hpack_decoder)
; end_stream = Flags.test_end_stream flags
}
in
let active_stream =
Reqd.create_active_stream
t.hpack_encoder
t.config.response_body_buffer_size
(create_push_stream t)
in
reqd.Stream.state <-
Active (Open (PartialHeaders partial_headers), active_stream);
if not end_headers then t.receiving_headers_for_stream <- Some stream_id;
handle_headers_block
t
stream
active_stream
partial_headers
flags
headers_block
let process_trailer_headers t stream active_stream frame_header headers_block =
let (Scheduler.Stream { descriptor = reqd; _ }) = stream in
let { Frame.stream_id; flags; _ } = frame_header in
let end_stream = Flags.test_end_stream flags in
if not end_stream
then
(* From RFC7540§8.1:
* A HEADERS frame (and associated CONTINUATION frames) can only appear
* at the start or end of a stream. An endpoint that receives a HEADERS
* frame without the END_STREAM flag set after receiving a final
* (non-informational) status code MUST treat the corresponding request
* or response as malformed (Section 8.1.2.6). *)
set_error_and_handle t reqd `Bad_request ProtocolError
else
let partial_headers =
{ Stream.parse_state =
AB.parse (Hpack.Decoder.decode_headers t.hpack_decoder)
(* obviously true at this point. *)
; end_stream
}
in
active_stream.Reqd.trailers_parser <- Some partial_headers;
if not Flags.(test_end_header flags)
then t.receiving_headers_for_stream <- Some stream_id;
(* trailer headers: RFC7230§4.4 *)
handle_trailer_headers
t
stream
active_stream
partial_headers
flags
headers_block
let process_headers_frame t { Frame.frame_header; _ } ~priority headers_block =
let { Frame.stream_id; _ } = frame_header in
let { Priority.stream_dependency; _ } = priority in
if not Stream_identifier.(is_request stream_id)
then
(* From RFC7540§5.1.1:
* Streams initiated by a client MUST use odd-numbered stream
* identifiers. [...] An endpoint that receives an unexpected
* stream identifier MUST respond with a connection error
* (Section 5.4.1) of type PROTOCOL_ERROR. *)
report_connection_error t Error_code.ProtocolError
else if Stream_identifier.(stream_dependency === stream_id)
then
(* From RFC7540§5.3.1:
* A stream cannot depend on itself. An endpoint MUST treat this as a
* stream error (Section 5.4.2) of type PROTOCOL_ERROR. *)
report_stream_error t stream_id Error_code.ProtocolError
else
match Scheduler.get_node t.streams stream_id with
| None ->
(match open_stream t ~priority stream_id with
| Some reqd ->
process_first_headers_block t frame_header reqd headers_block
| None -> ())
| Some (Scheduler.Stream { descriptor = reqd; _ } as stream) ->
(match reqd.state with
| Idle ->
(* From RFC7540§6.2:
* HEADERS frames can be sent on a stream in the "idle", "reserved
* (local)", "open", or "half-closed (remote)" state. *)
(match open_stream t ~priority stream_id with
| Some reqd ->
process_first_headers_block t frame_header reqd headers_block
| None -> ())
| Active (Open (WaitingForPeer | PartialHeaders _), _) ->
(* This case is unreachable because we check that partial HEADERS
* states must be followed by CONTINUATION frames elsewhere. *)
assert false
(* if we're getting a HEADERS frame at this point, they must be
* trailers, and the END_STREAM flag needs to be set. *)
| Active (Open (FullHeaders | ActiveMessage _), active_stream) ->
process_trailer_headers
t
stream
active_stream
frame_header
headers_block;
Stream.finish_stream reqd Finished
| Active (HalfClosed _, _)
(* From RFC7540§5.1:
* half-closed (remote): [...] If an endpoint receives additional
* frames, other than WINDOW_UPDATE, PRIORITY, or RST_STREAM, for a
* stream that is in this state, it MUST respond with a stream
* error (Section 5.4.2) of type STREAM_CLOSED. *)
| Closed { reason = ResetByThem _; _ } ->
(* From RFC7540§5.1:
* closed: [...] An endpoint that receives any frame other than
* PRIORITY after receiving a RST_STREAM MUST treat that as a
* stream error (Section 5.4.2) of type STREAM_CLOSED. *)
report_stream_error t stream_id Error_code.StreamClosed
(* From RFC7540§5.1:
* reserved (local): [...] Receiving any type of frame other than
* RST_STREAM, PRIORITY, or WINDOW_UPDATE on a stream in this state
* MUST be treated as a connection error (Section 5.4.1) of type
* PROTOCOL_ERROR. *)
| Reserved _ | Closed _ ->
(* From RFC7540§5.1:
* Similarly, an endpoint that receives any frames after receiving
* a frame with the END_STREAM flag set MUST treat that as a
* connection error (Section 5.4.1) of type STREAM_CLOSED [...]. *)
report_connection_error t Error_code.StreamClosed)
let process_data_frame t { Frame.frame_header; _ } bstr =
let open Scheduler in
let { Frame.flags; stream_id; payload_length; _ } = frame_header in
if not (Stream_identifier.is_request stream_id)
then
(* From RFC7540§5.1.1:
* Streams initiated by a client MUST use odd-numbered stream
* identifiers. [...] An endpoint that receives an unexpected stream
* identifier MUST respond with a connection error (Section 5.4.1) of
* type PROTOCOL_ERROR. *)
report_connection_error t Error_code.ProtocolError
else
(* From RFC7540§6.9:
* A receiver that receives a flow-controlled frame MUST always account
* for its contribution against the connection flow-control window,
* unless the receiver treats this as a connection error (Section 5.4.1).
* This is necessary even if the frame is in error. *)
let payload_len32 = Int32.of_int payload_length in
Scheduler.deduct_inflow t.streams payload_len32;
match Scheduler.get_node t.streams stream_id with
| Some (Stream { descriptor; _ } as stream) ->
(match descriptor.state with
| Active (Open (ActiveMessage request_info), active_stream) ->
let request_body = Reqd.request_body descriptor in
request_info.request_body_bytes <-
Int64.(
add
request_info.request_body_bytes
(of_int (Bigstringaf.length bstr)));
let request = request_info.request in
if not Scheduler.(allowed_to_receive t.streams stream payload_len32)
then (
(* From RFC7540§6.9:
* A receiver MAY respond with a stream error (Section 5.4.2) or
* connection error (Section 5.4.1) of type FLOW_CONTROL_ERROR if it
* is unable to accept a frame. *)
send_window_update t t.streams payload_len32;
report_stream_error t stream_id Error_code.FlowControlError)
else (
Scheduler.deduct_inflow stream payload_len32;
match Message.body_length request.headers with
| `Fixed len
(* Getting more than the client declared *)
when Int64.compare request_info.request_body_bytes len > 0 ->
(* Give back connection-level flow-controlled bytes (we use payload
* length to include any padding bytes that the frame might have
* included - which were ignored at parse time). *)
send_window_update t t.streams payload_len32;
(* From RFC7540§8.1.2.6:
* A request or response is also malformed if the value of a
* content-length header field does not equal the sum of the
* DATA frame payload lengths that form the body. *)
set_error_and_handle t descriptor `Bad_request ProtocolError
| _ ->
let end_stream = Flags.test_end_stream flags in
if end_stream
then
(* From RFC7540§6.1:
* When set, bit 0 indicates that this frame is the last that
* the endpoint will send for the identified stream. Setting
* this flag causes the stream to enter one of the
* "half-closed" states or the "closed" state
* (Section 5.1). *)
if Reqd.requires_output descriptor
then
(* There's a potential race condition here if the request
* handler completes the response right after. *)
descriptor.state <-
Active (HalfClosed request_info, active_stream)
else Stream.finish_stream descriptor Finished;
(* From RFC7540§6.9.1:
* The receiver of a frame sends a WINDOW_UPDATE frame as it
* consumes data and frees up space in flow-control windows.
* Separate WINDOW_UPDATE frames are sent for the stream- and
* connection-level flow-control windows.
*
* Note: we send these WINDOW_UPDATE frames once the body bytes
* have been surfaced to the application. This is done in the
* record field `done_reading` of `Body.t`. *)
let faraday = Body.Reader.unsafe_faraday request_body in
if not (Faraday.is_closed faraday)
then (
Faraday.schedule_bigstring faraday bstr;
if end_stream then Body.Reader.close request_body);
Reqd.flush_request_body descriptor)
| Idle ->
(* From RFC7540§5.1:
* idle: [...] Receiving any frame other than HEADERS or PRIORITY on
* a stream in this state MUST be treated as a connection error
* (Section 5.4.1) of type PROTOCOL_ERROR. *)
report_connection_error t Error_code.ProtocolError
(* This is technically in the half-closed (local) state *)
| Closed { reason = ResetByUs NoError; _ } ->
(* From RFC7540§6.9:
* A receiver that receives a flow-controlled frame MUST always
* account for its contribution against the connection flow-control
* window, unless the receiver treats this as a connection error
* (Section 5.4.1). This is necessary even if the frame is in
* error. *)
send_window_update t t.streams payload_len32
(* From RFC7540§6.4:
* [...] after sending the RST_STREAM, the sending endpoint MUST be
* prepared to receive and process additional frames sent on the
* stream that might have been sent by the peer prior to the arrival
* of the RST_STREAM.
*
* Note: after some writer yields / wake ups, we will have stopped
* keeping state information for the stream. This functions effectively
* as a way of only accepting frames after an RST_STREAM from us up to
* a time limit. *)
| _ ->
send_window_update t t.streams payload_len32;
(* From RFC7540§6.1:
* If a DATA frame is received whose stream is not in "open" or
* "half-closed (local)" state, the recipient MUST respond with a
* stream error (Section 5.4.2) of type STREAM_CLOSED. *)
report_stream_error t stream_id Error_code.StreamClosed)
| None ->
if not (was_closed_or_implicitly_closed t stream_id)
then
(* From RFC7540§5.1:
* idle: [...] Receiving any frame other than HEADERS or PRIORITY on
* a stream in this state MUST be treated as a connection error
* (Section 5.4.1) of type PROTOCOL_ERROR. *)
report_connection_error t Error_code.ProtocolError
let process_priority_frame t { Frame.frame_header; _ } priority =
let { Frame.stream_id; _ } = frame_header in
let { Priority.stream_dependency; _ } = priority in
if not (Stream_identifier.is_request stream_id)
then
(* From RFC7540§5.1.1:
* Streams initiated by a client MUST use odd-numbered stream
* identifiers. [...] An endpoint that receives an unexpected stream
* identifier MUST respond with a connection error (Section 5.4.1) of
* type PROTOCOL_ERROR. *)
report_connection_error t Error_code.ProtocolError
else if Stream_identifier.(stream_id === stream_dependency)
then
(* From RFC7540§5.3.1:
* A stream cannot depend on itself. An endpoint MUST treat this as a
* stream error (Section 5.4.2) of type PROTOCOL_ERROR. *)
report_stream_error t stream_id Error_code.ProtocolError
else
match Scheduler.get_node t.streams stream_id with
| Some stream -> Scheduler.reprioritize_stream t.streams ~priority stream
| None ->
(* From RFC7540§5.1.1:
* Endpoints SHOULD process PRIORITY frames, though they can be ignored
* if the stream has been removed from the dependency tree (see Section
* 5.3.4).
*
* Note:
* if we're receiving a PRIORITY frame for a stream that we already
* removed from the tree (i.e. can't be found in the hash table, and
* for which the stream ID is smaller then or equal to the max stream
* id that the client has opened), don't bother processing it. *)
if not (was_closed_or_implicitly_closed t stream_id)
then
let reqd =
Stream.create
stream_id
~max_frame_size:t.settings.max_frame_size
t.writer
t.error_handler
(on_close_stream t stream_id)
in
let _stream : Scheduler.nonroot Scheduler.node =
Scheduler.add
t.streams
~priority
~initial_send_window_size:t.settings.initial_window_size
~initial_recv_window_size:t.config.initial_window_size
reqd
in
()
let process_rst_stream_frame t { Frame.frame_header; _ } error_code =
let { Frame.stream_id; _ } = frame_header in
if not (Stream_identifier.is_request stream_id)
then
(* From RFC7540§5.1.1:
* Streams initiated by a client MUST use odd-numbered stream
* identifiers. [...] An endpoint that receives an unexpected stream
* identifier MUST respond with a connection error (Section 5.4.1) of
* type PROTOCOL_ERROR. *)
report_connection_error t Error_code.ProtocolError
else
match Scheduler.find t.streams stream_id with
| Some reqd ->
(match reqd.state with
| Idle ->
(* From RFC7540§6.4:
* RST_STREAM frames MUST NOT be sent for a stream in the "idle"
* state. If a RST_STREAM frame identifying an idle stream is
* received, the recipient MUST treat this as a connection error
* (Section 5.4.1) of type PROTOCOL_ERROR. *)
report_connection_error t Error_code.ProtocolError
| _ ->
(* From RFC7540§6.4:
* The RST_STREAM frame fully terminates the referenced stream and
* causes it to enter the "closed" state. After receiving a
* RST_STREAM on a stream, the receiver MUST NOT send additional
* frames for that stream, with the exception of PRIORITY.
*
* Note:
* This match branch also accepts streams in the `Closed` state. We
* do that to comply with the following:
*
* From RFC7540§6.4:
* [...] after sending the RST_STREAM, the sending endpoint MUST be
* prepared to receive and process additional frames sent on the
* stream that might have been sent by the peer prior to the arrival
* of the RST_STREAM. *)
Stream.finish_stream reqd (ResetByThem error_code))
| None ->
(* We might have removed the stream from the hash table. If its stream
* id is smaller than or equal to the max client stream id we've seen,
* then it must have been closed. *)
if not (was_closed_or_implicitly_closed t stream_id)
then
(* From RFC7540§6.4:
* RST_STREAM frames MUST NOT be sent for a stream in the "idle"
* state. If a RST_STREAM frame identifying an idle stream is
* received, the recipient MUST treat this as a connection error
* (Section 5.4.1) of type PROTOCOL_ERROR.
*
* Note:
* If we didn't find the stream in the hash table it must be
* "idle". *)
report_connection_error t Error_code.ProtocolError
let apply_settings_list t settings =
let open Scheduler in
(* From RFC7540§6.5:
* Each parameter in a SETTINGS frame replaces any existing value for
* that parameter. Parameters are processed in the order in which they
* appear, and a receiver of a SETTINGS frame does not need to maintain
* any state other than the current value of its parameters. *)
let new_settings =
List.fold_left
(fun (acc : Settings.t) item ->
match item with
| Settings.HeaderTableSize x ->
(* From RFC7540§6.5.2:
* Allows the sender to inform the remote endpoint of the maximum
* size of the header compression table used to decode header
* blocks, in octets. *)
Hpack.Encoder.set_capacity t.hpack_encoder x;
{ acc with header_table_size = x }
| EnablePush x ->
(* We've already verified that this setting is either 0 or 1 in the
* call to `Settings.check_settings_list` above. *)
{ acc with enable_push = x = 1 }
| MaxConcurrentStreams x -> { acc with max_concurrent_streams = x }
| InitialWindowSize new_val ->
(* From RFC7540§6.9.2:
* [...] a SETTINGS frame can alter the initial flow-control
* window size for streams with active flow-control windows (that
* is, streams in the "open" or "half-closed (remote)" state).
* When the value of SETTINGS_INITIAL_WINDOW_SIZE changes, a
* receiver MUST adjust the size of all stream flow-control
* windows that it maintains by the difference between the new
* value and the old value. *)
let old_val = acc.initial_window_size in
let growth = Int32.sub new_val old_val in
let exception Local in
(match
Scheduler.iter
~f:(fun stream ->
(* From RFC7540§6.9.2:
* An endpoint MUST treat a change to
* SETTINGS_INITIAL_WINDOW_SIZE that causes any
* flow-control window to exceed the maximum size as a
* connection error (Section 5.4.1) of type
* FLOW_CONTROL_ERROR. *)
if not (Scheduler.add_flow stream growth) then raise Local)
t.streams
with
| () -> ()
| exception Local ->
report_connection_error
t
~reason:
(Format.sprintf
"Window size for stream would exceed %ld"
Settings.WindowSize.max_window_size)
Error_code.FlowControlError);
{ acc with initial_window_size = new_val }
| MaxFrameSize x ->
(* XXX: We're probably not abiding entirely by this. If we get a
* MAX_FRAME_SIZE setting we'd need to reallocate the read buffer?
* This will need support from the I/O runtimes. *)
Scheduler.iter
~f:(fun (Stream { descriptor; _ }) ->
if Reqd.requires_output descriptor
then descriptor.max_frame_size <- x)
t.streams;
{ acc with max_frame_size = x }
| MaxHeaderListSize x -> { acc with max_header_list_size = Some x })
t.settings
settings
in
t.settings <- new_settings
let write_settings_frame t ~ack settings =
let flags =
if ack then Flags.(set_ack default_flags) else Flags.default_flags
in
let frame_info = Writer.make_frame_info ~flags Stream_identifier.connection in
Writer.write_settings t.writer frame_info settings;
if not ack
then
(* Don't expected our acknowledgements to be acknowledged... *)
t.unacked_settings <- t.unacked_settings + 1
let process_settings_frame t { Frame.frame_header; _ } settings =
let { Frame.flags; _ } = frame_header in
(* We already checked that an acked SETTINGS is empty. Don't need to do
* anything else in that case *)
if Flags.(test_ack flags)
then (
t.unacked_settings <- t.unacked_settings - 1;
if t.unacked_settings < 0
then
report_connection_error
t
~reason:"Received unexpected SETTINGS frame with acknowledgement"
Error_code.ProtocolError)
else
match Settings.check_settings_list settings with
| Ok () ->
apply_settings_list t settings;
(* From RFC7540§6.5.2:
* Once all values have been processed, the recipient MUST immediately
* emit a SETTINGS frame with the ACK flag set.
*
* From RFC7540§6.5:
* ACK (0x1): [...] When this bit is set, the payload of the SETTINGS
* frame MUST be empty. *)
write_settings_frame t ~ack:true [];
wakeup_writer t
| Error error -> report_error t error
let process_ping_frame t { Frame.frame_header; _ } payload =
let { Frame.flags; _ } = frame_header in
(* From RFC7540§6.7:
* ACK (0x1): When set, bit 0 indicates that this PING frame is a PING
* response. [...] An endpoint MUST NOT respond to PING frames containing
* this flag. *)
if not (Flags.test_ack flags)
then (
(* From RFC7540§6.7:
* Receivers of a PING frame that does not include an ACK flag MUST send
* a PING frame with the ACK flag set in response, with an identical
* payload. PING responses SHOULD be given higher priority than any other
* frame. *)
let frame_info =
Writer.make_frame_info
(* From RFC7540§6.7:
* ACK (0x1): When set, bit 0 indicates that this PING frame is a
* PING response. An endpoint MUST set this flag in PING
* responses. *)
~flags:Flags.(set_ack default_flags)
Stream_identifier.connection
in
(* From RFC7540§6.7:
* Receivers of a PING frame that does not include an ACK flag MUST send
* a PING frame with the ACK flag set in response, with an identical
* payload. *)
Writer.write_ping t.writer frame_info payload;
wakeup_writer t)
let process_goaway_frame t _frame ~last_stream_id:_ ~data error =
let reason =
match Bigstringaf.length data with
| 0 -> None
| len -> Some (Bigstringaf.substring data ~off:0 ~len)
in
(* TODO(anmonteiro): I think we need to allow lower numbered streams to
* complete. *)
report_connection_error t ?reason error
let add_window_increment :
type a. t -> a Scheduler.PriorityTreeNode.node -> int32 -> unit
=
fun t stream increment ->
let open Scheduler in
let did_add = Scheduler.add_flow stream increment in
let stream_id = Scheduler.stream_id stream in
let new_flow =
match stream with
| Connection { flow; _ } -> flow
| Stream { flow; _ } -> flow
in
if did_add
then (
if Int32.compare new_flow 0l > 0
then
(* Don't bother waking up the writer if the new flow doesn't allow
* the stream to write. *)
wakeup_writer t)
else if Stream_identifier.is_connection stream_id
then
report_connection_error
t
~reason:
(Printf.sprintf
"Window size for stream would exceed %ld"
Settings.WindowSize.max_window_size)
Error_code.FlowControlError
else report_stream_error t stream_id Error_code.FlowControlError
let process_window_update_frame t { Frame.frame_header; _ } window_increment =
let open Scheduler in
let { Frame.stream_id; _ } = frame_header in
(* From RFC7540§6.9:
* The WINDOW_UPDATE frame can be specific to a stream or to the entire
* connection. In the former case, the frame's stream identifier indicates
* the affected stream; in the latter, the value "0" indicates that the
* entire connection is the subject of the frame. *)
if Stream_identifier.is_connection stream_id
then add_window_increment t t.streams window_increment
else
match Scheduler.get_node t.streams stream_id with
| Some (Stream { descriptor; _ } as stream_node) ->
(match descriptor.state with
| Idle ->
(* From RFC7540§5.1:
* idle: [...] Receiving any frame other than HEADERS or PRIORITY on
* a stream in this state MUST be treated as a connection error
* (Section 5.4.1) of type PROTOCOL_ERROR. *)
report_connection_error t Error_code.ProtocolError
| Active _
(* From RFC7540§5.1:
* reserved (local): [...] A PRIORITY or WINDOW_UPDATE frame MAY be
* received in this state. *)
| Reserved _ ->
add_window_increment t stream_node window_increment
| Closed _ ->
(* From RFC7540§6.9:
* [...] a receiver could receive a WINDOW_UPDATE frame on a
* "half-closed (remote)" or "closed" stream. A receiver MUST NOT
* treat this as an error (see Section 5.1). *)
(* From RFC7540§5.1:
* Endpoints MUST ignore WINDOW_UPDATE or RST_STREAM frames received
* in this state, though endpoints MAY choose to treat frames that
* arrive a significant time after sending END_STREAM as a connection
* error (Section 5.4.1) of type PROTOCOL_ERROR. *)
())
| None ->
if not (was_closed_or_implicitly_closed t stream_id)
then
(* From RFC7540§5.1:
* idle: [...] Receiving any frame other than HEADERS or PRIORITY on
* a stream in this state MUST be treated as a connection error
* (Section 5.4.1) of type PROTOCOL_ERROR. *)
report_connection_error t Error_code.ProtocolError
let process_continuation_frame t { Frame.frame_header; _ } headers_block =
let { Frame.stream_id; flags; _ } = frame_header in
if not (Stream_identifier.is_request stream_id)
then
(* From RFC7540§5.1.1:
* Streams initiated by a client MUST use odd-numbered stream
* identifiers. [...] An endpoint that receives an unexpected stream
* identifier MUST respond with a connection error (Section 5.4.1) of
* type PROTOCOL_ERROR. *)
report_connection_error t Error_code.ProtocolError
else
match Scheduler.get_node t.streams stream_id with
| Some (Scheduler.Stream { descriptor; _ } as stream) ->
(match descriptor.state with
| Active (Open (PartialHeaders partial_headers), active_stream) ->
handle_headers_block
t
stream
active_stream
partial_headers
flags
headers_block
| Active
( Open (ActiveMessage _)
, ({ trailers_parser = Some partial_headers; _ } as active_stream) )
->
handle_trailer_headers
t
stream
active_stream
partial_headers
flags
headers_block
| _ ->
(* TODO: maybe need to handle the case where the stream has been closed
* due to a stream error. *)
(* From RFC7540§6.10:
* A RST_STREAM is the last frame that an endpoint can send on a
* stream. The peer that sends the RST_STREAM frame MUST be prepared
* to receive any frames that were sent or enqueued for sending by
* the remote peer. These frames can be ignored, except where they
* modify connection state (such as the state maintained for header
* compression (Section 4.3) or flow control). *)
report_connection_error t Error_code.ProtocolError)
| None ->
(* From RFC7540§6.10:
* A CONTINUATION frame MUST be preceded by a HEADERS, PUSH_PROMISE or
* CONTINUATION frame without the END_HEADERS flag set. A recipient
* that observes violation of this rule MUST respond with a connection
* error (Section 5.4.1) of type PROTOCOL_ERROR. *)
report_connection_error t Error_code.ProtocolError
let default_error_handler ?request:_ error handle =
let message =
match error with
| `Exn exn -> Printexc.to_string exn
| (#Status.client_error | #Status.server_error) as error ->
Status.to_string error
in
let body = handle Headers.empty in
Body.Writer.write_string body message;
Body.Writer.close body
let write_connection_preface t =
(* Check if the settings for the connection are different than the default
* HTTP/2 settings. In the event that they are, we need to send a non-empty
* SETTINGS frame advertising our configuration. *)
let settings = Settings.settings_for_the_connection t.settings in
(* XXX(anmonteiro): same as in the client. Revert
* [t.settings.initial_window_size] to the spec-default value until we
* receive a setting for it. *)
t.settings <-
{ t.settings with
initial_window_size = Settings.default.initial_window_size
};
(* Now send the connection preface, including our settings for the
* connection.
*
* 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. *)
write_settings_frame ~ack:false t settings;
(* If a higher value for initial window size is configured, add more
* tokens to the connection (we have no streams at this point). *)
if t.config.initial_window_size > Settings.default.initial_window_size
then
let diff =
Int32.sub
t.config.initial_window_size
Settings.default.initial_window_size
in
send_window_update t t.streams diff
let create_generic ~h2c ~config ~error_handler request_handler =
let settings = Config.to_settings config in
let writer = Writer.create settings.max_frame_size in
let rec connection_preface_handler recv_frame settings_list =
let t = Lazy.force t in
(* If this connection is `h2c` (HTTP/2 over TCP), we have already written
* the server connection preface. This is only necessary if we're
* responding to a client-initiated connection, but in `h2c` the server
* writes the preface first (handled below in `create_h2c`) *)
if not h2c then write_connection_preface t;
(* Now process the client's SETTINGS frame. `process_settings_frame` will
* take care of calling `wakeup_writer`. *)
process_settings_frame t recv_frame settings_list
and frame_handler r =
let t = Lazy.force t in
match r with
| Error e -> report_error t e
| Ok ({ Frame.frame_payload; frame_header } as frame) ->
(match t.receiving_headers_for_stream with
| Some stream_id
when (not Stream_identifier.(stream_id === frame_header.stream_id))
|| frame_header.frame_type <> Continuation ->
(* From RFC7540§6.2:
* A HEADERS frame without the END_HEADERS flag set MUST be followed
* by a CONTINUATION frame for the same stream. A receiver MUST treat
* the receipt of any other type of frame or a frame on a different
* stream as a connection error (Section 5.4.1) of type
* PROTOCOL_ERROR. *)
report_connection_error
t
~reason:
"HEADERS or PUSH_PROMISE without the END_HEADERS flag set must be \
followed by a CONTINUATION frame for the same stream"
Error_code.ProtocolError
| _ ->
(match frame_payload with
| Headers (priority, headers_block) ->
process_headers_frame t frame ~priority headers_block
| Data bs -> process_data_frame t frame bs
| Priority priority -> process_priority_frame t frame priority
| RSTStream error_code -> process_rst_stream_frame t frame error_code
| Settings settings -> process_settings_frame t frame settings
| PushPromise _ ->
(* From RFC7540§8.2:
* A client cannot push. Thus, servers MUST treat the receipt of a
* PUSH_PROMISE frame as a connection error (Section 5.4.1) of type
* PROTOCOL_ERROR. *)
report_connection_error
t
~reason:"Client cannot push"
Error_code.ProtocolError
| Ping data -> process_ping_frame t frame data
| GoAway (last_stream_id, error, data) ->
process_goaway_frame t frame ~last_stream_id ~data error
| WindowUpdate window_size ->
process_window_update_frame t frame window_size
| Continuation headers_block ->
process_continuation_frame t frame headers_block
| Unknown _ ->
(* TODO: in the future we can expose a hook for handling unknown
* frames, e.g. the ALTSVC frame defined in RFC7838§4
* (https://tools.ietf.org/html/rfc7838#section-4) *)
(* From RFC7540§5.1:
* Frames of unknown types are ignored. *)
()))
and t =
lazy
{ settings
; reader =
Reader.server_frames
~max_frame_size:settings.max_frame_size
connection_preface_handler
frame_handler
; writer
; config
; request_handler
; error_handler
; streams = Scheduler.make_root ()
; current_client_streams = 0
; max_client_stream_id = 0l
; max_pushed_stream_id = 0l
; receiving_headers_for_stream = None
; did_send_go_away = false
; unacked_settings = 0
; hpack_encoder = Hpack.Encoder.(create settings.header_table_size)
; hpack_decoder = Hpack.Decoder.(create settings.header_table_size)
}
in
Lazy.force t
let create
?(config = Config.default)
?(error_handler = default_error_handler)
request_handler
=
(* `h2c` false = direct *)
create_generic ~h2c:false ~config ~error_handler request_handler
let handle_h2c_request t headers request_body_iovecs =
(* From RFC7540§3.2:
* The HTTP/1.1 request that is sent prior to upgrade is assigned a stream
* identifier of 1 (see Section 5.1.1) with default priority values
* (Section 5.3.5). *)
match open_stream ~priority:Priority.default_priority t 1l with
| Some (Scheduler.Stream { descriptor = reqd; _ } as stream) ->
let active_stream =
Reqd.create_active_stream
t.hpack_encoder
t.config.response_body_buffer_size
(create_push_stream t)
in
t.max_client_stream_id <- reqd.Stream.id;
let lengthv = Httpun_types.IOVec.lengthv request_body_iovecs in
let end_stream = lengthv = 0 in
handle_headers t ~end_stream stream active_stream headers;
let request = Reqd.request reqd in
let request_body = Reqd.request_body reqd in
let request_info = Reqd.create_active_request request request_body in
request_info.request_body_bytes <-
Int64.(add request_info.request_body_bytes (of_int lengthv));
(* From RFC7540§3.2:
* Stream 1 is implicitly "half-closed" from the client toward the server
* (see Section 5.1), since the request is completed as an HTTP/1.1
* request. After commencing the HTTP/2 connection, stream 1 is used for
* the response. *)
reqd.state <- Active (HalfClosed request_info, active_stream);
if not end_stream
then
let faraday = Body.Reader.unsafe_faraday request_body in
if not (Faraday.is_closed faraday)
then (
List.iter
(fun { Httpun_types.IOVec.buffer; off; len } ->
Faraday.schedule_bigstring faraday ~off ~len buffer)
request_body_iovecs;
(* Close the request body, we're not expecting more input. *)
Body.Reader.close request_body)
| None -> ()
(* This function is meant to be called inside an HTTP/1.1 upgrade handler.
*
* It's useful to have the request body on the server because a single request
* handler may process both upgraded (h2c) and direct connections. Given the
* following, application code that calls this function needs to buffer the
* entire request body in memory if it wants, so we can just get a
* `Bigstringaf.t IOVec.t list`
*
*
* From RFC7540§3.2:
* Requests that contain a payload body MUST be sent in their
* entirety before the client can send HTTP/2 frames. This means
* that a large request can block the use of the connection until
* it is completely sent. *)
let create_h2c
?(config = Config.default)
?(error_handler = default_error_handler)
~headers
~target
~meth
?(request_body = [])
request_handler
=
(* From RFC7540§3.2.1:
* A request that upgrades from HTTP/1.1 to HTTP/2 MUST include exactly one
* HTTP2-Settings header field.
*
* [...] A server MUST NOT upgrade the connection to HTTP/2 if this header
* field is not present or if more than one is present. A server MUST NOT
* send this header field. *)
match
Httpun_types.Headers.(
get_multi headers "http2-settings", get_multi headers "connection")
with
| [ settings ], [ connection ] when Headers.is_valid_h2c_connection connection
->
(* From RFC7540§3.2.1:
* The content of the HTTP2-Settings header field is the payload of a
* SETTINGS frame (Section 6.5), encoded as a base64url string (that is,
* the URL- and filename-safe Base64 encoding described in Section 5 of
* [RFC4648], with any trailing '=' characters omitted).
*
* [...] A server decodes and interprets these values as it would any
* other SETTINGS frame. Explicit acknowledgement of these settings
* (Section 6.5.3) is not necessary, since a 101 response serves as
* implicit acknowledgement. *)
(match Headers.of_http1 ~headers ~target ~meth with
| Ok h2_headers ->
(match Settings.of_base64 settings with
| Ok upgrade_settings ->
let settings_list =
Settings.settings_for_the_connection upgrade_settings
in
(match Settings.check_settings_list settings_list with
| Ok () ->
let t =
create_generic ~h2c:true ~config ~error_handler request_handler
in
apply_settings_list t settings_list;
(* From RFC7540§3.5:
* The first HTTP/2 frame sent by the server MUST be a server
* connection preface (Section 3.5) consisting of a SETTINGS
* frame (Section 6.5).
*
* Note: as opposed to a connection started by a client, in h2c
* we're upgrading from HTTP/1.1 so the server is reponsible for
* writing the HTTP/2 connection preface to the wire first. We
* also configure the connection with `~h2c:true` above to not
* send it a second time. *)
write_connection_preface t;
(* From RFC7540§3.2:
* A server that supports HTTP/2 accepts the upgrade with a 101
* (Switching Protocols) response. After the empty line that
* terminates the 101 response, the server can begin sending
* HTTP/2 frames. These frames MUST include a response to the
* request that initiated the upgrade *)
handle_h2c_request t h2_headers request_body;
Ok t
| Error error -> Error (Error.message error))
| Error msg -> Error msg)
| Error msg -> Error msg)
| _ ->
Error
"A request that upgrades from HTTP/1.1 to HTTP/2 MUST include exactly \
one HTTP2-Settings header field and HTTP2-Settings as a connection \
option in the Connection header field."
let next_read_operation t =
if Reader.is_closed t.reader then shutdown_reader t;
match Reader.next t.reader with
| (`Read | `Close) as operation -> operation
| `Error e ->
report_error t e;
(match e with
| ConnectionError _ ->
(* From RFC7540§5.4.1:
* A connection error is any error that prevents further processing
* of the frame layer or corrupts any connection state. *)
`Close
| StreamError _ ->
(* From RFC7540§5.4.2:
* A stream error is an error related to a specific stream that does
* not affect processing of other streams. *)
`Read)
let read t bs ~off ~len = Reader.read_with_more t.reader bs ~off ~len Incomplete
let read_eof t bs ~off ~len =
Reader.read_with_more t.reader bs ~off ~len Complete
(* XXX(anmonteiro): this function is here to please the Gluten `RUNTIME`
* interface.
*
* We don't expect this function to ever be called. H2 never issues `Yield`
* commands because the connection is multiplexed, and it's therefore always
* looking to read frames from the peer. *)
let yield_reader _t k = k ()
let next_write_operation t =
Scheduler.flush t.streams (t.max_client_stream_id, t.max_pushed_stream_id);
Writer.next t.writer
let report_write_result t result = Writer.report_result t.writer result
let yield_writer t k = Writer.on_wakeup_writer t.writer k