Hcs.H2_client

hcs · API reference

HTTP/2-specific client.

Use only when you need direct control over HTTP/2 multiplexing behavior. Prefer Client for general application code.

HTTP/2 Client implementation on the http sans-I/O codec (Http2.Client_connection).

Multiplexed HTTP/2 client over Eio with connection pooling. The transport (TCP/TLS), DNS, pooling and timeouts live here; framing/HPACK/flow-control are driven by http. The connection is driven with two fibers (reader + writer) over the same read / next_write_operation surface as the server, with an Eio.Stream wake mailbox.

module C = Http2.Client_connection

Types

type error = 
  | Connection_failed of string
  | Tls_error of string
  | Protocol_error of string
  | Timeout
  | Invalid_response of string
  | Response_body_too_large of int64
type response = {
  status : Status.t;
  headers : Http_core.Headers.t;
  body : string;
}
type request_spec = {
  meth : Method.t;
  headers : (string * string) list;
  body : string;
  url : Uri.t;
}
type config = {
  connect_timeout : float;
  read_timeout : float;
  buffer_size : int;
  max_response_body : int64 option;
  default_headers : (string * string) list;
  pool : Pool.config;
}
val default_config : config
val default_default_headers : (string * string) list
val with_timeout : float -> config -> config
val with_default_header : string -> string -> config -> config
val with_default_headers : (string * string) list -> config -> config
val with_max_response_body : int64 -> config -> config
val with_pool_config : Pool.config -> config -> config

Internal connection type

type conn = {
  flow : Eio.Flow.two_way_ty Eio.Std.r;
  mutable alive : bool;
}
type t = {
  config : config;
  pool : conn Pool.t;
  mutex : Eio.Mutex.t;
  connect : host:string ->
  port:int ->
  is_https:bool ->
  Eio.Flow.two_way_ty Eio.Std.r;
  resolve : string -> bool;
  now : unit -> float;
  with_timeout : 'a. float -> (unit -> 'a) -> 'a option;
}

Internal helpers

val writev : 
  [> Eio.Flow.sink_ty ] Eio.Flow.sink ->
  Http_core.Iovec.t list ->
  int

Write the codec's iovecs to the flow; returns the byte count written.

module Read_buffer : sig ... end
val close_conn : conn -> unit
val drive : 
  [> `Flow | `R | `W ] Eio.Flow.source ->
  C.t ->
  done_:bool ref ->
  unit

Drive one client connection with a reader and writer fiber sharing a wake mailbox (the writer parks on it when there is nothing to send; the reader signals it after each read so flow-control acks / window updates go out). done_ decides when the work is finished so the writer can stop.

val map_error : max_response_body:int64 option -> C.error -> error
val request_parts : Uri.t -> string * bool * string * int * string
val authority : host:string -> port:int -> is_https:bool -> string
val method_has_body : Method.t -> bool

Method may carry a request body (controls the default content-type).

val build_headers : 
  t ->
  has_body:bool ->
  headers:(string * string) list ->
  Http_core.Headers.t

Build the regular header set (no pseudo-headers, no content-length — the codec adds :method/:scheme/:path/:authority and frames the body).

val do_request : 
  ?body:string ->
  ?max_response_body:int64 ->
  [> `Flow | `R | `W ] Eio.Flow.source ->
  meth:Http_core.Method.t ->
  target:string ->
  scheme:string ->
  authority:string ->
  headers:Http_core.Headers.t ->
  (response, error) result

Perform one HTTP/2 request on a connected flow.

Streaming / bidirectional calls

type call = {
  send : string -> unit; (* Enqueue one request DATA chunk (already framed by the caller). *)
  close_request : unit -> unit; (* Finish the request body: the next stream frame carries END_STREAM. *)
  recv : unit -> string option; (* Next response DATA chunk, in order; None once the response body ends. Blocks until a chunk is available. *)
  response_headers : unit -> Http_core.Headers.t; (* Block until the response HEADERS arrive (the initial metadata). *)
  trailers : unit -> Http_core.Headers.t; (* Block until the response trailers arrive (after recv yields None): gRPC's grpc-status/grpc-message travel here. *)
  cancel : unit -> unit; (* Abort the call with RST_STREAM(CANCEL). *)
}
val with_call : 
  t ->
  meth:Method.t ->
  url:Uri.t ->
  headers:(string * string) list ->
  (call -> 'a) ->
  ('a, error) result

Multiplexed persistent connection

A single HTTP/2 connection driven by a background reader/writer pair, over which many requests run concurrently as separate streams — the model gRPC expects (one connection per channel, not one per call). Each call sends its whole request body as one Fixed frame (so the codec never blocks pulling a producer, and concurrent request calls — which are synchronous — can't interleave mid-call), and reads its response incrementally. Suits unary, server-streaming and client-streaming; bidirectional streaming, which needs a request body produced over time, still uses with_call.

type mux = {
  m_cc : C.t;
  m_flow : Eio.Flow.two_way_ty Eio.Std.r;
  m_wake : unit Eio.Stream.t;
  m_scheme : string;
  m_authority : string;
  m_max_response_body : int64 option;
  mutable m_dead : bool;
  m_aborts : (int, unit -> unit) Hashtbl.t;
  mutable m_next : int;
}
val mux_signal : mux -> unit
val mux_is_alive : mux -> bool
val mux_drive : mux -> unit
val mux_connect : t -> sw:Eio.Switch.t -> url:Uri.t -> (mux, error) result
val mux_call : 
  mux ->
  meth:Method.t ->
  target:string ->
  headers:(string * string) list ->
  request_body:string ->
  (recv:(unit -> string option) ->
    response_headers:(unit -> Http_core.Headers.t) ->
    trailers:(unit -> Http_core.Headers.t) ->
    'a) ->
  ('a, error) result
val do_requests : 
  ?max_response_body:int64 ->
  [> `Flow | `R | `W ] Eio.Flow.source ->
  (Http_core.Method.t * string * string * string * Http_core.Headers.t * string)
    array ->
  (response, error) result list

Perform many HTTP/2 requests concurrently on one connection.

val create_connection : 
  t ->
  host:string ->
  port:int ->
  is_https:bool ->
  (conn, error) result
val acquire_connection : 
  t ->
  host:string ->
  port:int ->
  is_https:bool ->
  (conn, error) result
val release_connection : 
  t ->
  host:string ->
  port:int ->
  is_https:bool ->
  conn ->
  keep_alive:bool ->
  unit

Public API

val create : 
  sw:Eio.Switch.t ->
  net:[> [> `Generic ] Eio.Net.ty ] Eio.Net.t ->
  clock:[> float Eio.Time.clock_ty ] Eio.Time.clock ->
  ?config:config ->
  unit ->
  t
val close : t -> unit
val request : 
  t ->
  Method.t ->
  ?headers:(string * string) list ->
  ?body:string ->
  Uri.t ->
  (response, error) result

Perform an HTTP/2 request with any method using pooled connections.

val same_origin : 
  ('a * 'b * String.t * 'c * 'd) ->
  ('e * 'b * String.t * 'c * 'f) ->
  bool
val chunks : int -> 'a list -> 'a list list
val request_many : 
  ?max_concurrent:int ->
  t ->
  request_spec list ->
  (response, error) result list

Perform multiple HTTP/2 requests concurrently on one connection per chunk.

Convenience methods

val get : 
  t ->
  ?headers:(string * string) list ->
  Uri.t ->
  (response, error) result
val post : 
  t ->
  ?headers:(string * string) list ->
  Uri.t ->
  body:string ->
  (response, error) result
val put : 
  t ->
  ?headers:(string * string) list ->
  Uri.t ->
  body:string ->
  (response, error) result
val patch : 
  t ->
  ?headers:(string * string) list ->
  Uri.t ->
  body:string ->
  (response, error) result
val delete : 
  t ->
  ?headers:(string * string) list ->
  ?body:string ->
  Uri.t ->
  (response, error) result
val head : 
  t ->
  ?headers:(string * string) list ->
  Uri.t ->
  (response, error) result
val options : 
  t ->
  ?headers:(string * string) list ->
  Uri.t ->
  (response, error) result

Stateless API

val get' : 
  sw:Eio.Switch.t ->
  net:[> [> `Generic ] Eio.Net.ty ] Eio.Net.t ->
  clock:[> float Eio.Time.clock_ty ] Eio.Time.clock ->
  ?config:config ->
  Uri.t ->
  (response, error) result
val post' : 
  sw:Eio.Switch.t ->
  net:[> [> `Generic ] Eio.Net.ty ] Eio.Net.t ->
  clock:[> float Eio.Time.clock_ty ] Eio.Time.clock ->
  ?config:config ->
  Uri.t ->
  body:string ->
  (response, error) result