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(** HTTP CONNECT Tunneling for HTTPS via Proxy
7
8 Per RFC 9110 Section 9.3.6:
9 The CONNECT method requests that the recipient establish a tunnel
10 to the destination origin server and, if successful, thereafter restrict
11 its behavior to blind forwarding of packets in both directions.
12
13 {2 Usage}
14
15 Establish an HTTPS tunnel through an HTTP proxy:
16 {[
17 let tunnel_flow = Proxy_tunnel.connect
18 ~sw ~net
19 ~proxy:(Proxy.http "proxy.example.com")
20 ~target_host:"api.example.com"
21 ~target_port:443
22 ()
23 in
24 (* Now wrap tunnel_flow with TLS and send HTTPS requests *)
25 ]} *)
26
27(** Log source for tunnel operations *)
28val src : Logs.Src.t
29
30(** {1 Tunnel Establishment} *)
31
32val connect :
33 sw:Eio.Switch.t ->
34 net:_ Eio.Net.t ->
35 proxy:Proxy.config ->
36 target_host:string ->
37 target_port:int ->
38 unit ->
39 [`Close | `Flow | `R | `Shutdown | `W] Eio.Resource.t
40(** [connect ~sw ~net ~proxy ~target_host ~target_port ()] establishes
41 an HTTP tunnel through [proxy] to [target_host:target_port].
42
43 This performs the following steps per RFC 9110 Section 9.3.6:
44 1. Opens a TCP connection to the proxy server
45 2. Sends a CONNECT request with the target host:port
46 3. Includes Proxy-Authorization header if proxy has auth configured
47 4. Waits for a 2xx response from the proxy
48 5. Returns the raw connection for the caller to wrap with TLS
49
50 @param sw Eio switch for resource management
51 @param net Eio network capability
52 @param proxy Proxy configuration including host, port, and optional auth
53 @param target_host Destination server hostname
54 @param target_port Destination server port (typically 443 for HTTPS)
55 @raise Error.Proxy_error if the CONNECT request fails
56 @raise Error.Tcp_connect_failed if cannot connect to proxy *)
57
58val connect_with_tls :
59 sw:Eio.Switch.t ->
60 net:_ Eio.Net.t ->
61 clock:_ Eio.Time.clock ->
62 proxy:Proxy.config ->
63 target_host:string ->
64 target_port:int ->
65 ?tls_config:Tls.Config.client ->
66 unit ->
67 Eio.Flow.two_way_ty Eio.Resource.t
68(** [connect_with_tls ~sw ~net ~clock ~proxy ~target_host ~target_port ?tls_config ()]
69 establishes an HTTPS tunnel and performs TLS handshake.
70
71 This is a convenience function that combines {!connect} with TLS wrapping:
72 1. Establishes the tunnel via {!connect}
73 2. Performs TLS handshake with the target host through the tunnel
74 3. Returns the TLS-wrapped connection ready for HTTPS requests
75
76 @param sw Eio switch for resource management
77 @param net Eio network capability
78 @param clock Eio clock for TLS operations
79 @param proxy Proxy configuration
80 @param target_host Destination server hostname (used for SNI)
81 @param target_port Destination server port
82 @param tls_config Optional custom TLS configuration. If not provided,
83 uses default configuration from system CA certificates.
84 @raise Error.Proxy_error if tunnel establishment fails
85 @raise Error.Tls_handshake_failed if TLS handshake fails *)
86
87(** {1 Low-level Functions} *)
88
89val write_connect_request :
90 Eio.Buf_write.t ->
91 proxy:Proxy.config ->
92 target_host:string ->
93 target_port:int ->
94 unit
95(** [write_connect_request w ~proxy ~target_host ~target_port] writes
96 a CONNECT request to the buffer.
97
98 Format per RFC 9110 Section 9.3.6:
99 {v
100 CONNECT host:port HTTP/1.1
101 Host: host:port
102 Proxy-Authorization: Basic ... (if auth configured)
103
104 v}
105
106 This is exposed for testing and custom tunnel implementations. *)
107
108val parse_connect_response :
109 Eio.Buf_read.t ->
110 proxy:Proxy.config ->
111 target:string ->
112 unit
113(** [parse_connect_response r ~proxy ~target] reads and validates
114 the CONNECT response from the proxy.
115
116 @raise Error.Proxy_error if the response status is not 2xx *)