chromiumoxide/handler/
httpfuture.rs1use crate::handler::commandfuture::CommandFuture;
2use crate::handler::http::HttpRequest;
3use crate::handler::sender::PageSender;
4use crate::handler::target_message_future::TargetMessageFuture;
5use crate::{ArcHttpRequest, Result};
6use chromiumoxide_cdp::cdp::browser_protocol::page::NavigateReturns;
7use chromiumoxide_types::Command;
8use futures_util::future::{Fuse, FusedFuture};
9use futures_util::FutureExt;
10use pin_project_lite::pin_project;
11use std::future::Future;
12use std::pin::Pin;
13use std::sync::Arc;
14use std::task::{Context, Poll};
15
16type ArcRequest = ArcHttpRequest;
17
18const ERR_ABORTED: &str = "net::ERR_ABORTED";
19
20pub fn navigation_continues(err: &str) -> bool {
25 err == ERR_ABORTED || err.starts_with("net::ERR_HTTP_RESPONSE_CODE_FAILURE")
26}
27
28pub fn navigate_error_text(response: &NavigateReturns) -> Option<&str> {
30 response.error_text.as_deref()
31}
32
33pin_project! {
34 pub struct HttpFuture<T: Command> {
37 #[pin]
38 command: Fuse<CommandFuture<T>>,
39 #[pin]
40 navigation: TargetMessageFuture<ArcHttpRequest>,
41 failure_check: Option<fn(&T::Response) -> Option<&str>>,
44 url: Option<String>,
46 }
47}
48
49impl<T: Command> HttpFuture<T> {
50 pub fn new(
51 sender: PageSender,
52 command: CommandFuture<T>,
53 request_timeout: std::time::Duration,
54 ) -> Self {
55 Self {
56 command: command.fuse(),
57 navigation: TargetMessageFuture::<T>::wait_for_navigation(sender, request_timeout),
58 failure_check: None,
59 url: None,
60 }
61 }
62
63 pub fn with_failure_check(
64 sender: PageSender,
65 command: CommandFuture<T>,
66 request_timeout: std::time::Duration,
67 failure_check: fn(&T::Response) -> Option<&str>,
68 url: Option<String>,
69 ) -> Self {
70 Self {
71 command: command.fuse(),
72 navigation: TargetMessageFuture::<T>::wait_for_navigation(sender, request_timeout),
73 failure_check: Some(failure_check),
74 url,
75 }
76 }
77}
78
79impl<T> Future for HttpFuture<T>
80where
81 T: Command,
82{
83 type Output = Result<ArcRequest>;
84
85 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
86 let mut this = self.project();
87
88 if this.command.is_terminated() {
91 this.navigation.poll(cx)
92 } else {
93 match this.command.poll(cx) {
94 Poll::Ready(Ok(command_response)) => {
95 if let Some(check) = *this.failure_check {
96 if let Some(err) = check(&command_response.result) {
97 if !err.is_empty() && !navigation_continues(err) {
98 let req = HttpRequest {
99 failure_text: Some(err.to_owned()),
100 is_navigation_request: true,
101 url: this.url.take(),
102 ..Default::default()
103 };
104 return Poll::Ready(Ok(Some(Arc::new(req))));
105 }
106 }
107 }
108 this.navigation.as_mut().reset_deadline();
113 this.navigation.poll(cx)
114 }
115 Poll::Ready(Err(e)) => Poll::Ready(Err(e)),
116 Poll::Pending => Poll::Pending,
117 }
118 }
119 }
120}