A batteries included HTTP/1.1 client in OCaml
1(*---------------------------------------------------------------------------
2 Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
3 SPDX-License-Identifier: ISC
4 ---------------------------------------------------------------------------*)
5
6(** TLS configuration utilities
7
8 This module provides shared TLS configuration creation to ensure consistent
9 behavior across session-based and one-shot request modes.
10
11 Supports ALPN (Application-Layer Protocol Negotiation) for HTTP/2 upgrade
12 per {{:https://datatracker.ietf.org/doc/html/rfc9113#section-3.3}RFC 9113 Section 3.3}. *)
13
14val src : Logs.src
15(** Logs source for this module *)
16
17(** {1 TLS Version Types} *)
18
19(** Minimum TLS version configuration.
20 Per Recommendation #6: Allow enforcing minimum TLS version. *)
21type tls_version =
22 | TLS_1_2 (** TLS 1.2 minimum (default, widely compatible) *)
23 | TLS_1_3 (** TLS 1.3 minimum (most secure, may not work with older servers) *)
24
25val tls_version_to_tls : tls_version -> Tls.Core.tls_version
26(** Convert our TLS version type to the underlying library's type *)
27
28(** {1 ALPN Protocol Negotiation}
29
30 Per {{:https://datatracker.ietf.org/doc/html/rfc9113#section-3.3}RFC 9113 Section 3.3},
31 HTTP/2 connections over TLS use ALPN to negotiate the protocol. *)
32
33(** ALPN protocol identifiers. *)
34val alpn_h2 : string
35(** ALPN identifier for HTTP/2: "h2" *)
36
37val alpn_http11 : string
38(** ALPN identifier for HTTP/1.1: "http/1.1" *)
39
40(** HTTP protocol mode for ALPN negotiation. *)
41type protocol_mode =
42 | Auto (** Prefer HTTP/2 if available, fall back to HTTP/1.1 *)
43 | Http1_only (** Use HTTP/1.1 only *)
44 | Http2_only (** Require HTTP/2 *)
45
46val alpn_protocols : protocol_mode -> string list
47(** [alpn_protocols mode] returns the ALPN protocol list for the given mode.
48 - Auto: ["h2"; "http/1.1"]
49 - Http1_only: ["http/1.1"]
50 - Http2_only: ["h2"] *)
51
52(** {1 Configuration Creation} *)
53
54val create_client :
55 ?verify_tls:bool ->
56 ?min_tls_version:tls_version ->
57 ?protocol_mode:protocol_mode ->
58 host:string ->
59 unit ->
60 Tls.Config.client
61(** [create_client ~host ()] creates a TLS client configuration.
62
63 @param verify_tls If true (default), use system CA certificates for verification
64 @param min_tls_version Minimum TLS version to accept (default TLS_1_2)
65 @param protocol_mode HTTP protocol mode for ALPN (default Auto)
66 @param host Hostname for error messages
67 @return TLS client configuration
68 @raise Error.Tls_handshake_failed if configuration cannot be created *)
69
70val create_client_opt :
71 ?existing_config:Tls.Config.client ->
72 verify_tls:bool ->
73 min_tls_version:tls_version ->
74 ?protocol_mode:protocol_mode ->
75 host:string ->
76 unit ->
77 Tls.Config.client option
78(** [create_client_opt ~verify_tls ~min_tls_version ~host ()] creates a TLS
79 client configuration, or returns the existing one if provided.
80
81 @param existing_config If provided, return this instead of creating new
82 @param verify_tls If true, use system CA certificates for verification
83 @param min_tls_version Minimum TLS version to accept
84 @param protocol_mode HTTP protocol mode for ALPN (default Auto)
85 @param host Hostname for error messages
86 @return Some TLS client configuration *)
87
88(** {1 ALPN Result Extraction}
89
90 Helper functions for extracting negotiated protocol from TLS epoch. *)
91
92(** Negotiated HTTP protocol from ALPN. *)
93type negotiated_protocol =
94 | Http1_1 (** HTTP/1.1 *)
95 | Http2 (** HTTP/2 *)
96
97val get_alpn_from_epoch : Tls.Core.epoch_data -> string option
98(** [get_alpn_from_epoch epoch] extracts the negotiated ALPN protocol
99 from TLS epoch data. Returns [None] if ALPN was not negotiated. *)
100
101val negotiated_of_alpn : string -> negotiated_protocol option
102(** [negotiated_of_alpn alpn] parses ALPN result string.
103 - "h2" -> Some Http2
104 - "http/1.1" -> Some Http1_1
105 - other -> None *)
106
107val default_protocol : negotiated_protocol
108(** Default protocol (HTTP/1.1) when ALPN is not available. *)
109
110val detect_protocol : mode:protocol_mode -> string option -> negotiated_protocol
111(** [detect_protocol ~mode alpn_result] determines the protocol to use.
112 @raise Failure if Http2_only mode but HTTP/2 not negotiated *)
113
114val negotiated_to_string : negotiated_protocol -> string
115(** Convert negotiated protocol to string ("HTTP/1.1" or "HTTP/2"). *)
116
117val pp_negotiated : Format.formatter -> negotiated_protocol -> unit
118(** Pretty print negotiated protocol. *)