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