1use core::cell::UnsafeCell;
9use core::fmt::Write as _;
10
11extern crate alloc;
12use alloc::boxed::Box;
13use alloc::collections::BTreeMap;
14use alloc::string::String;
15use alloc::string::ToString;
16
17use ::log::*;
18
19use embedded_svc::io::{ErrorType, Read, Write};
20
21use crate::sys::*;
22
23use uncased::{Uncased, UncasedStr};
24
25use crate::handle::RawHandle;
26use crate::io::EspIOError;
27use crate::private::common::Newtype;
28use crate::private::cstr::*;
29use crate::tls::X509;
30
31pub use embedded_svc::http::client::{Connection, Request, Response};
32
33pub use super::*;
34
35impl From<Method> for Newtype<(esp_http_client_method_t, ())> {
36 fn from(method: Method) -> Self {
37 Self((
38 match method {
39 Method::Get => esp_http_client_method_t_HTTP_METHOD_GET,
40 Method::Post => esp_http_client_method_t_HTTP_METHOD_POST,
41 Method::Delete => esp_http_client_method_t_HTTP_METHOD_DELETE,
42 Method::Head => esp_http_client_method_t_HTTP_METHOD_HEAD,
43 Method::Put => esp_http_client_method_t_HTTP_METHOD_PUT,
44 Method::Options => esp_http_client_method_t_HTTP_METHOD_OPTIONS,
45 Method::Copy => esp_http_client_method_t_HTTP_METHOD_COPY,
46 Method::Lock => esp_http_client_method_t_HTTP_METHOD_LOCK,
47 Method::MkCol => esp_http_client_method_t_HTTP_METHOD_MKCOL,
48 Method::Move => esp_http_client_method_t_HTTP_METHOD_MOVE,
49 Method::Propfind => esp_http_client_method_t_HTTP_METHOD_PROPFIND,
50 Method::Proppatch => esp_http_client_method_t_HTTP_METHOD_PROPPATCH,
51 Method::Unlock => esp_http_client_method_t_HTTP_METHOD_UNLOCK,
52 Method::Notify => esp_http_client_method_t_HTTP_METHOD_NOTIFY,
53 Method::Subscribe => esp_http_client_method_t_HTTP_METHOD_SUBSCRIBE,
54 Method::Unsubscribe => esp_http_client_method_t_HTTP_METHOD_UNSUBSCRIBE,
55 Method::Patch => esp_http_client_method_t_HTTP_METHOD_PATCH,
56 method => panic!("Method {method:?} is not supported"),
57 },
58 (),
59 ))
60 }
61}
62
63#[derive(Default, Copy, Clone, Debug, Eq, PartialEq, Hash)]
64pub enum FollowRedirectsPolicy {
65 FollowNone,
66 #[default]
67 FollowGetHead,
68 FollowAll,
69}
70
71#[derive(Copy, Clone, Debug, Default)]
72pub struct Configuration {
73 pub buffer_size: Option<usize>,
74 pub buffer_size_tx: Option<usize>,
75 pub timeout: Option<core::time::Duration>,
76 pub follow_redirects_policy: FollowRedirectsPolicy,
77 pub client_certificate: Option<X509<'static>>,
78 pub server_certificate: Option<X509<'static>>,
79 pub private_key: Option<X509<'static>>,
80 pub use_global_ca_store: bool,
81 pub crt_bundle_attach: Option<unsafe extern "C" fn(conf: *mut core::ffi::c_void) -> esp_err_t>,
82 pub raw_request_body: bool,
83 pub keep_alive_enable: bool,
84 pub keep_alive_idle: Option<usize>,
85 pub keep_alive_interval: Option<usize>,
86 pub keep_alive_count: Option<usize>,
87}
88
89#[derive(Debug, Copy, Clone, Eq, PartialEq)]
90enum State {
91 New,
92 Request,
93 Response,
94}
95
96#[allow(clippy::type_complexity)]
97pub struct EspHttpConnection {
98 raw_client: esp_http_client_handle_t,
99 follow_redirects_policy: FollowRedirectsPolicy,
100 raw_request_body: bool,
101 event_handler: Box<Option<Box<dyn Fn(&esp_http_client_event_t) -> esp_err_t>>>,
102 state: State,
103 request_content_len: i64,
104 follow_redirects: bool,
105 headers: BTreeMap<Uncased<'static>, String>,
106 content_len_header: UnsafeCell<Option<Option<String>>>,
107}
108
109impl EspHttpConnection {
110 pub fn new(configuration: &Configuration) -> Result<Self, EspError> {
111 let event_handler = Box::new(None);
112
113 #[allow(clippy::manual_c_str_literals)]
114 let mut native_config = esp_http_client_config_t {
115 url: b"http://127.0.0.1\0".as_ptr() as *const _,
118 event_handler: Some(Self::on_events),
119 user_data: &*event_handler as *const _ as *mut core::ffi::c_void,
120
121 use_global_ca_store: configuration.use_global_ca_store,
122 crt_bundle_attach: configuration.crt_bundle_attach,
123 keep_alive_enable: configuration.keep_alive_enable,
124
125 ..Default::default()
126 };
127
128 if let Some(buffer_size) = configuration.buffer_size {
129 native_config.buffer_size = buffer_size as _;
130 };
131
132 if let Some(buffer_size_tx) = configuration.buffer_size_tx {
133 native_config.buffer_size_tx = buffer_size_tx as _;
134 }
135
136 if let Some(timeout) = configuration.timeout {
137 native_config.timeout_ms = timeout.as_millis() as _;
138 }
139
140 if let Some(cert) = configuration.server_certificate {
141 #[cfg(esp_idf_version_at_least_5_5_0)]
142 {
143 native_config.__bindgen_anon_1.cert_pem = cert.as_esp_idf_raw_ptr() as _;
144 }
145 #[cfg(not(esp_idf_version_at_least_5_5_0))]
146 {
147 native_config.cert_pem = cert.as_esp_idf_raw_ptr() as _;
148 }
149 native_config.cert_len = cert.as_esp_idf_raw_len();
150 }
151
152 if let (Some(cert), Some(private_key)) =
153 (configuration.client_certificate, configuration.private_key)
154 {
155 #[cfg(esp_idf_version_at_least_5_5_0)]
156 {
157 native_config.__bindgen_anon_2.client_cert_pem = cert.as_esp_idf_raw_ptr() as _;
158 }
159
160 #[cfg(not(esp_idf_version_at_least_5_5_0))]
161 {
162 native_config.client_cert_pem = cert.as_esp_idf_raw_ptr() as _;
163 }
164
165 native_config.client_cert_len = cert.as_esp_idf_raw_len();
166
167 native_config.client_key_pem = private_key.as_esp_idf_raw_ptr() as _;
168 native_config.client_key_len = private_key.as_esp_idf_raw_len();
169 }
170
171 if configuration.keep_alive_enable {
172 if let Some(keep_alive_idle) = configuration.keep_alive_idle {
173 native_config.keep_alive_idle = keep_alive_idle as _;
174 }
175 if let Some(keep_alive_interval) = configuration.keep_alive_interval {
176 native_config.keep_alive_interval = keep_alive_interval as _;
177 }
178 if let Some(keep_alive_count) = configuration.keep_alive_count {
179 native_config.keep_alive_count = keep_alive_count as _;
180 }
181 }
182
183 let raw_client = unsafe { esp_http_client_init(&native_config) };
184 if raw_client.is_null() {
185 Err(EspError::from_infallible::<ESP_FAIL>())
186 } else {
187 Ok(Self {
188 raw_client,
189 follow_redirects_policy: configuration.follow_redirects_policy,
190 raw_request_body: configuration.raw_request_body,
191 event_handler,
192 state: State::New,
193 request_content_len: -1,
194 follow_redirects: false,
195 headers: BTreeMap::new(),
196 content_len_header: UnsafeCell::new(None),
197 })
198 }
199 }
200
201 pub fn status(&self) -> u16 {
202 self.assert_response();
203 unsafe { esp_http_client_get_status_code(self.raw_client) as _ }
204 }
205
206 pub fn status_message(&self) -> Option<&str> {
207 self.assert_response();
208 None
209 }
210
211 pub fn header(&self, name: &str) -> Option<&str> {
212 self.assert_response();
213
214 if name.eq_ignore_ascii_case("Content-Length") {
215 if let Some(content_len_opt) =
216 unsafe { self.content_len_header.get().as_mut().unwrap() }.as_ref()
217 {
218 content_len_opt.as_ref().map(|s| s.as_str())
219 } else {
220 let content_len = unsafe { esp_http_client_get_content_length(self.raw_client) };
221 *unsafe { self.content_len_header.get().as_mut().unwrap() } = if content_len >= 0 {
222 Some(Some(content_len.to_string()))
223 } else {
224 None
225 };
226
227 unsafe { self.content_len_header.get().as_mut().unwrap() }
228 .as_ref()
229 .and_then(|s| s.as_ref().map(|s| s.as_ref()))
230 }
231 } else {
232 self.headers.get(UncasedStr::new(name)).map(|s| s.as_str())
233 }
234 }
235
236 pub fn initiate_request<'a>(
237 &'a mut self,
238 method: Method,
239 uri: &'a str,
240 headers: &'a [(&'a str, &'a str)],
241 ) -> Result<(), EspError> {
242 if self.is_response_initiated() {
244 self.flush_response()?;
245 }
246
247 self.assert_initial();
248
249 let c_uri = to_cstring_arg(uri)?;
250
251 esp!(unsafe { esp_http_client_set_url(self.raw_client, c_uri.as_ptr() as _) })?;
252 esp!(unsafe {
253 esp_http_client_set_method(
254 self.raw_client,
255 Newtype::<(esp_http_client_method_t, ())>::from(method).0 .0,
256 )
257 })?;
258
259 let mut content_len = None;
260
261 for (name, value) in headers {
262 if name.eq_ignore_ascii_case("Content-Length") {
263 if let Ok(len) = value.parse::<i64>() {
264 content_len = Some(len);
265 }
266 } else {
267 let c_name = to_cstring_arg(name)?;
268
269 let c_value = to_cstring_arg(value)?;
271
272 esp!(unsafe {
273 esp_http_client_set_header(
274 self.raw_client,
275 c_name.as_ptr() as _,
276 c_value.as_ptr() as _,
277 )
278 })?;
279 }
280 }
281
282 self.follow_redirects = match self.follow_redirects_policy {
283 FollowRedirectsPolicy::FollowAll => true,
284 FollowRedirectsPolicy::FollowGetHead => method == Method::Get || method == Method::Head,
285 _ => false,
286 };
287
288 self.request_content_len =
292 content_len.unwrap_or(if method == Method::Post { -1 } else { 0 });
293
294 esp!(unsafe { esp_http_client_open(self.raw_client, self.request_content_len as i32) })?;
295
296 self.state = State::Request;
297
298 Ok(())
299 }
300
301 pub fn is_request_initiated(&self) -> bool {
302 self.state == State::Request
303 }
304
305 pub fn initiate_response(&mut self) -> Result<(), EspError> {
306 self.assert_request();
307
308 self.flush()?;
309 self.fetch_headers()?;
310
311 self.state = State::Response;
312
313 Ok(())
314 }
315
316 pub fn is_response_initiated(&self) -> bool {
317 self.state == State::Response
318 }
319
320 pub fn split(&mut self) -> (&EspHttpConnection, &mut Self) {
321 self.assert_response();
322
323 let headers_ptr: *const EspHttpConnection = self as *const _;
324
325 let headers = unsafe { headers_ptr.as_ref().unwrap() };
326
327 (headers, self)
328 }
329
330 pub fn read(&mut self, buf: &mut [u8]) -> Result<usize, EspError> {
331 self.assert_response();
332
333 let result = Self::check(unsafe {
334 esp_http_client_read(self.raw_client, buf.as_mut_ptr() as _, buf.len() as _)
335 });
336
337 #[cfg(esp_idf_version_major = "4")]
340 {
341 match result {
342 Ok(0) if unsafe { !esp_http_client_is_complete_data_received(self.raw_client) } => {
343 Err(EspError::from_infallible::<ESP_ERR_HTTP_EAGAIN>())
345 }
346 other => other,
347 }
348 }
349 #[cfg(not(esp_idf_version_major = "4"))]
350 {
351 result
352 }
353 }
354
355 pub fn write(&mut self, buf: &[u8]) -> Result<usize, EspError> {
356 if buf.is_empty() {
357 Ok(0)
358 } else if !self.raw_request_body && self.request_content_len == -1 {
359 let mut len_buf = heapless::String::<8>::new();
361 write!(&mut len_buf, "{:x}", buf.len()).unwrap();
362
363 self.raw_write_all(len_buf.as_bytes())?;
364 self.raw_write_all(b"\r\n")?;
365
366 self.raw_write_all(buf)?;
367 self.raw_write_all(b"\r\n")?;
368
369 Ok(buf.len())
370 } else {
371 self.raw_write(buf)
372 }
373 }
374
375 pub fn write_all(&mut self, data: &[u8]) -> Result<(), EspError> {
376 let mut offset = 0;
377
378 while offset < data.len() {
379 offset += self.write(&data[offset..])?;
380 }
381
382 Ok(())
383 }
384
385 fn flush(&mut self) -> Result<(), EspError> {
386 if !self.raw_request_body && self.request_content_len == -1 {
387 self.raw_write_all(b"0\r\n\r\n")
389 } else {
390 Ok(())
391 }
392 }
393
394 fn flush_response(&mut self) -> Result<(), EspError> {
395 let mut len = 0_i32;
396 esp!(unsafe { esp_http_client_flush_response(self.raw_client, &mut len) })?;
397
398 Ok(())
399 }
400
401 fn raw_write(&mut self, buf: &[u8]) -> Result<usize, EspError> {
402 self.assert_request();
403
404 Self::check(unsafe {
405 esp_http_client_write(self.raw_client, buf.as_ptr() as _, buf.len() as _)
406 })
407 }
408
409 fn raw_write_all(&mut self, data: &[u8]) -> Result<(), EspError> {
410 let mut offset = 0;
411
412 while offset < data.len() {
413 offset += self.raw_write(&data[offset..])?;
414 }
415
416 Ok(())
417 }
418
419 fn check(result: i32) -> Result<usize, EspError> {
420 match EspError::from(result) {
421 Some(err) if result < 0 => Err(err),
422 _ => Ok(result as _),
423 }
424 }
425
426 extern "C" fn on_events(event: *mut esp_http_client_event_t) -> esp_err_t {
427 match unsafe { event.as_mut() } {
428 Some(event) => {
429 let handler = event.user_data
430 as *const Option<Box<dyn Fn(&esp_http_client_event_t) -> esp_err_t>>;
431 if let Some(handler) = unsafe { handler.as_ref() } {
432 if let Some(handler) = handler.as_ref() {
433 return handler(event);
434 }
435 }
436
437 ESP_OK as _
438 }
439 None => ESP_FAIL as _,
440 }
441 }
442
443 fn fetch_headers(&mut self) -> Result<(), EspError> {
444 self.headers.clear();
445 *self.content_len_header.get_mut() = None;
446
447 loop {
448 let headers_ptr = &mut self.headers as *mut BTreeMap<Uncased, String>;
450
451 let handler = move |event: &esp_http_client_event_t| {
452 if event.event_id == esp_http_client_event_id_t_HTTP_EVENT_ON_HEADER {
453 unsafe {
454 headers_ptr.as_mut().unwrap().insert(
457 Uncased::from(from_cstr_ptr(event.header_key).to_string()),
458 from_cstr_ptr(event.header_value).to_string(),
459 );
460 }
461 }
462
463 ESP_OK as esp_err_t
464 };
465
466 self.register_handler(handler);
467
468 let result = unsafe { esp_http_client_fetch_headers(self.raw_client) };
469
470 self.deregister_handler();
471
472 Self::check(result as _)?;
473
474 trace!("Fetched headers: {:?}", self.headers);
475
476 if self.follow_redirects {
477 let status = unsafe { esp_http_client_get_status_code(self.raw_client) as u16 };
478
479 if status::REDIRECT.contains(&status) && status != 304 {
480 info!("Got response {status}, about to follow redirect");
481
482 let mut len = 0_i32;
483 esp!(unsafe { esp_http_client_flush_response(self.raw_client, &mut len) })?;
484 esp!(unsafe {
485 esp_http_client_set_method(
486 self.raw_client,
487 esp_http_client_method_t_HTTP_METHOD_GET,
488 )
489 })?;
490 esp!(unsafe { esp_http_client_set_redirection(self.raw_client) })?;
491 esp!(unsafe {
492 esp_http_client_open(self.raw_client, self.request_content_len as i32)
493 })?;
494
495 self.headers.clear();
496
497 continue;
498 }
499 }
500
501 break;
502 }
503
504 Ok(())
505 }
506
507 fn register_handler(
508 &mut self,
509 handler: impl Fn(&esp_http_client_event_t) -> esp_err_t + 'static,
510 ) {
511 *self.event_handler = Some(Box::new(handler));
512 }
513
514 fn deregister_handler(&mut self) {
515 *self.event_handler = None;
516 }
517
518 fn assert_initial(&self) {
519 if self.state != State::New && self.state != State::Response {
520 panic!("connection is not in initial phase");
521 }
522 }
523
524 fn assert_request(&self) {
525 if self.state != State::Request {
526 panic!("connection is not in request phase");
527 }
528 }
529
530 fn assert_response(&self) {
531 if self.state != State::Response {
532 panic!("connection is not in response phase");
533 }
534 }
535}
536
537impl Drop for EspHttpConnection {
538 fn drop(&mut self) {
539 esp!(unsafe { esp_http_client_cleanup(self.raw_client) })
540 .expect("Unable to stop the client cleanly");
541 }
542}
543
544impl RawHandle for EspHttpConnection {
545 type Handle = esp_http_client_handle_t;
546
547 fn handle(&self) -> Self::Handle {
548 self.raw_client
549 }
550}
551
552impl embedded_svc::http::Status for EspHttpConnection {
553 fn status(&self) -> u16 {
554 EspHttpConnection::status(self)
555 }
556
557 fn status_message(&self) -> Option<&str> {
558 EspHttpConnection::status_message(self)
559 }
560}
561
562impl embedded_svc::http::Headers for EspHttpConnection {
563 fn header(&self, name: &str) -> Option<&str> {
564 EspHttpConnection::header(self, name)
565 }
566}
567
568impl ErrorType for EspHttpConnection {
569 type Error = EspIOError;
570}
571
572impl Read for EspHttpConnection {
573 fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
574 let size = EspHttpConnection::read(self, buf)?;
575
576 Ok(size)
577 }
578}
579
580impl Write for EspHttpConnection {
581 fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
582 let size = EspHttpConnection::write(self, buf)?;
583
584 Ok(size)
585 }
586
587 fn flush(&mut self) -> Result<(), Self::Error> {
588 self.assert_request();
589
590 Ok(())
591 }
592}
593
594impl embedded_svc::http::client::Connection for EspHttpConnection {
595 type Headers = Self;
596
597 type Read = Self;
598
599 type RawConnectionError = EspIOError;
600
601 type RawConnection = Self;
602
603 fn initiate_request<'a>(
604 &'a mut self,
605 method: Method,
606 uri: &'a str,
607 headers: &'a [(&'a str, &'a str)],
608 ) -> Result<(), Self::Error> {
609 EspHttpConnection::initiate_request(self, method, uri, headers).map_err(EspIOError)
610 }
611
612 fn is_request_initiated(&self) -> bool {
613 EspHttpConnection::is_request_initiated(self)
614 }
615
616 fn initiate_response(&mut self) -> Result<(), Self::Error> {
617 EspHttpConnection::initiate_response(self).map_err(EspIOError)
618 }
619
620 fn is_response_initiated(&self) -> bool {
621 EspHttpConnection::is_response_initiated(self)
622 }
623
624 fn split(&mut self) -> (&Self::Headers, &mut Self::Read) {
625 EspHttpConnection::split(self)
626 }
627
628 fn raw_connection(&mut self) -> Result<&mut Self::RawConnection, Self::Error> {
629 Err(EspError::from_infallible::<ESP_FAIL>().into())
630 }
631}