hyper/ffi/client.rs
1use std::ffi::c_int;
2use std::ptr;
3use std::sync::Arc;
4
5use crate::client::conn;
6use crate::rt::Executor as _;
7
8use super::error::hyper_code;
9use super::http_types::{hyper_request, hyper_response};
10use super::io::hyper_io;
11use super::task::{hyper_executor, hyper_task, hyper_task_return_type, AsTaskType, WeakExec};
12
13/// An options builder to configure an HTTP client connection.
14///
15/// Methods:
16///
17/// - hyper_clientconn_options_new: Creates a new set of HTTP clientconn options to be used in a handshake.
18/// - hyper_clientconn_options_exec: Set the client background task executor.
19/// - hyper_clientconn_options_http2: Set whether to use HTTP2.
20/// - hyper_clientconn_options_set_preserve_header_case: Set whether header case is preserved.
21/// - hyper_clientconn_options_set_preserve_header_order: Set whether header order is preserved.
22/// - hyper_clientconn_options_http1_allow_multiline_headers: Set whether HTTP/1 connections accept obsolete line folding for header values.
23/// - hyper_clientconn_options_free: Free a set of HTTP clientconn options.
24pub struct hyper_clientconn_options {
25 http1_allow_obsolete_multiline_headers_in_responses: bool,
26 http1_preserve_header_case: bool,
27 http1_preserve_header_order: bool,
28 http2: bool,
29 /// Use a `Weak` to prevent cycles.
30 exec: WeakExec,
31}
32
33/// An HTTP client connection handle.
34///
35/// These are used to send one or more requests on a single connection.
36///
37/// It's possible to send multiple requests on a single connection, such
38/// as when HTTP/1 keep-alive or HTTP/2 is used.
39///
40/// To create a `hyper_clientconn`:
41///
42/// 1. Create a `hyper_io` with `hyper_io_new`.
43/// 2. Create a `hyper_clientconn_options` with `hyper_clientconn_options_new`.
44/// 3. Call `hyper_clientconn_handshake` with the `hyper_io` and `hyper_clientconn_options`.
45/// This creates a `hyper_task`.
46/// 5. Call `hyper_task_set_userdata` to assign an application-specific pointer to the task.
47/// This allows keeping track of multiple connections that may be handshaking
48/// simultaneously.
49/// 4. Add the `hyper_task` to an executor with `hyper_executor_push`.
50/// 5. Poll that executor until it yields a task of type `HYPER_TASK_CLIENTCONN`.
51/// 6. Extract the `hyper_clientconn` from the task with `hyper_task_value`.
52/// This will require a cast from `void *` to `hyper_clientconn *`.
53///
54/// This process results in a `hyper_clientconn` that permanently owns the
55/// `hyper_io`. Because the `hyper_io` in turn owns a TCP or TLS connection, that means
56/// the `hyper_clientconn` owns the connection for both the clientconn's lifetime
57/// and the connection's lifetime.
58///
59/// In other words, each connection (`hyper_io`) must have exactly one `hyper_clientconn`
60/// associated with it. That's because `hyper_clientconn_handshake` sends the
61/// [HTTP/2 Connection Preface] (for HTTP/2 connections). Since that preface can't
62/// be sent twice, handshake can't be called twice.
63///
64/// [HTTP/2 Connection Preface]: https://datatracker.ietf.org/doc/html/rfc9113#name-http-2-connection-preface
65///
66/// Methods:
67///
68/// - hyper_clientconn_handshake: Creates an HTTP client handshake task.
69/// - hyper_clientconn_send: Creates a task to send a request on the client connection.
70/// - hyper_clientconn_free: Free a hyper_clientconn *.
71pub struct hyper_clientconn {
72 tx: Tx,
73}
74
75enum Tx {
76 #[cfg(feature = "http1")]
77 Http1(conn::http1::SendRequest<crate::body::Incoming>),
78 #[cfg(feature = "http2")]
79 Http2(conn::http2::SendRequest<crate::body::Incoming>),
80}
81
82// ===== impl hyper_clientconn =====
83
84ffi_fn! {
85 /// Creates an HTTP client handshake task.
86 ///
87 /// Both the `io` and the `options` are consumed in this function call.
88 /// They should not be used or freed afterwards.
89 ///
90 /// The returned task must be polled with an executor until the handshake
91 /// completes, at which point the value can be taken.
92 ///
93 /// To avoid a memory leak, the task must eventually be consumed by
94 /// `hyper_task_free`, or taken ownership of by `hyper_executor_push`
95 /// without subsequently being given back by `hyper_executor_poll`.
96 fn hyper_clientconn_handshake(io: *mut hyper_io, options: *mut hyper_clientconn_options) -> *mut hyper_task {
97 let options = non_null! { Box::from_raw(options) ?= ptr::null_mut() };
98 let io = non_null! { Box::from_raw(io) ?= ptr::null_mut() };
99
100 Box::into_raw(hyper_task::boxed(async move {
101 #[cfg(feature = "http2")]
102 {
103 if options.http2 {
104 return conn::http2::Builder::new(options.exec.clone())
105 .handshake::<_, crate::body::Incoming>(io)
106 .await
107 .map(|(tx, conn)| {
108 options.exec.execute(Box::pin(async move {
109 let _ = conn.await;
110 }));
111 hyper_clientconn { tx: Tx::Http2(tx) }
112 });
113 }
114 }
115
116 conn::http1::Builder::new()
117 .allow_obsolete_multiline_headers_in_responses(options.http1_allow_obsolete_multiline_headers_in_responses)
118 .preserve_header_case(options.http1_preserve_header_case)
119 .preserve_header_order(options.http1_preserve_header_order)
120 .handshake::<_, crate::body::Incoming>(io)
121 .await
122 .map(|(tx, conn)| {
123 options.exec.execute(Box::pin(async move {
124 let _ = conn.await;
125 }));
126 hyper_clientconn { tx: Tx::Http1(tx) }
127 })
128 }))
129 } ?= std::ptr::null_mut()
130}
131
132ffi_fn! {
133 /// Creates a task to send a request on the client connection.
134 ///
135 /// This consumes the request. You should not use or free the request
136 /// afterwards.
137 ///
138 /// Returns a task that needs to be polled until it is ready. When ready, the
139 /// task yields a `hyper_response *`.
140 ///
141 /// To avoid a memory leak, the task must eventually be consumed by
142 /// `hyper_task_free`, or taken ownership of by `hyper_executor_push`
143 /// without subsequently being given back by `hyper_executor_poll`.
144 fn hyper_clientconn_send(conn: *mut hyper_clientconn, req: *mut hyper_request) -> *mut hyper_task {
145 let mut req = non_null! { Box::from_raw(req) ?= ptr::null_mut() };
146
147 // Update request with original-case map of headers
148 req.finalize_request();
149
150 let fut = match non_null! { &mut *conn ?= ptr::null_mut() }.tx {
151 Tx::Http1(ref mut tx) => futures_util::future::Either::Left(tx.send_request(req.0)),
152 Tx::Http2(ref mut tx) => futures_util::future::Either::Right(tx.send_request(req.0)),
153 };
154
155 let fut = async move {
156 fut.await.map(hyper_response::wrap)
157 };
158
159 Box::into_raw(hyper_task::boxed(fut))
160 } ?= std::ptr::null_mut()
161}
162
163ffi_fn! {
164 /// Free a `hyper_clientconn *`.
165 ///
166 /// This should be used for any connection once it is no longer needed.
167 fn hyper_clientconn_free(conn: *mut hyper_clientconn) {
168 drop(non_null! { Box::from_raw(conn) ?= () });
169 }
170}
171
172unsafe impl AsTaskType for hyper_clientconn {
173 fn as_task_type(&self) -> hyper_task_return_type {
174 hyper_task_return_type::HYPER_TASK_CLIENTCONN
175 }
176}
177
178// ===== impl hyper_clientconn_options =====
179
180ffi_fn! {
181 /// Creates a new set of HTTP clientconn options to be used in a handshake.
182 ///
183 /// To avoid a memory leak, the options must eventually be consumed by
184 /// `hyper_clientconn_options_free` or `hyper_clientconn_handshake`.
185 fn hyper_clientconn_options_new() -> *mut hyper_clientconn_options {
186 Box::into_raw(Box::new(hyper_clientconn_options {
187 http1_allow_obsolete_multiline_headers_in_responses: false,
188 http1_preserve_header_case: false,
189 http1_preserve_header_order: false,
190 http2: false,
191 exec: WeakExec::new(),
192 }))
193 } ?= std::ptr::null_mut()
194}
195
196ffi_fn! {
197 /// Set whether header case is preserved.
198 ///
199 /// Pass `0` to allow lowercase normalization (default), `1` to retain original case.
200 fn hyper_clientconn_options_set_preserve_header_case(opts: *mut hyper_clientconn_options, enabled: c_int) {
201 let opts = non_null! { &mut *opts ?= () };
202 opts.http1_preserve_header_case = enabled != 0;
203 }
204}
205
206ffi_fn! {
207 /// Set whether header order is preserved.
208 ///
209 /// Pass `0` to allow reordering (default), `1` to retain original ordering.
210 fn hyper_clientconn_options_set_preserve_header_order(opts: *mut hyper_clientconn_options, enabled: c_int) {
211 let opts = non_null! { &mut *opts ?= () };
212 opts.http1_preserve_header_order = enabled != 0;
213 }
214}
215
216ffi_fn! {
217 /// Free a set of HTTP clientconn options.
218 ///
219 /// This should only be used if the options aren't consumed by
220 /// `hyper_clientconn_handshake`.
221 fn hyper_clientconn_options_free(opts: *mut hyper_clientconn_options) {
222 drop(non_null! { Box::from_raw(opts) ?= () });
223 }
224}
225
226ffi_fn! {
227 /// Set the client background task executor.
228 ///
229 /// This does not consume the `options` or the `exec`.
230 fn hyper_clientconn_options_exec(opts: *mut hyper_clientconn_options, exec: *const hyper_executor) {
231 let opts = non_null! { &mut *opts ?= () };
232
233 let exec = non_null! { Arc::from_raw(exec) ?= () };
234 let weak_exec = hyper_executor::downgrade(&exec);
235 std::mem::forget(exec);
236
237 opts.exec = weak_exec;
238 }
239}
240
241ffi_fn! {
242 /// Set whether to use HTTP2.
243 ///
244 /// Pass `0` to disable, `1` to enable.
245 fn hyper_clientconn_options_http2(opts: *mut hyper_clientconn_options, enabled: c_int) -> hyper_code {
246 #[cfg(feature = "http2")]
247 {
248 let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG };
249 opts.http2 = enabled != 0;
250 hyper_code::HYPERE_OK
251 }
252
253 #[cfg(not(feature = "http2"))]
254 {
255 drop(opts);
256 drop(enabled);
257 hyper_code::HYPERE_FEATURE_NOT_ENABLED
258 }
259 }
260}
261
262ffi_fn! {
263 /// Set whether HTTP/1 connections accept obsolete line folding for header values.
264 ///
265 /// Newline codepoints (\r and \n) will be transformed to spaces when parsing.
266 ///
267 /// Pass `0` to disable, `1` to enable.
268 ///
269 fn hyper_clientconn_options_http1_allow_multiline_headers(opts: *mut hyper_clientconn_options, enabled: c_int) -> hyper_code {
270 let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG };
271 opts.http1_allow_obsolete_multiline_headers_in_responses = enabled != 0;
272 hyper_code::HYPERE_OK
273 }
274}