Skip to content

Commit df594f8

Browse files
nicoburnslizidev
andauthored
Make network requests abortable (#336)
Co-authored-by: lizidev <lizidev@163.com>
1 parent 2d716be commit df594f8

File tree

3 files changed

+120
-21
lines changed

3 files changed

+120
-21
lines changed

packages/blitz-net/src/lib.rs

Lines changed: 69 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
//!
33
//! Provides an implementation of the [`blitz_traits::net::NetProvider`] trait.
44
5-
use blitz_traits::net::{Body, Bytes, NetHandler, NetProvider, NetWaker, Request};
5+
// use blitz_traits::net::{Body, Bytes, NetHandler, NetProvider, NetWaker, Request};
6+
use blitz_traits::net::{AbortSignal, Body, Bytes, NetHandler, NetProvider, NetWaker, Request};
67
use data_url::DataUrl;
7-
use std::sync::Arc;
8+
use std::{marker::PhantomData, pin::Pin, sync::Arc, task::Poll};
89
use tokio::runtime::Handle;
910

1011
#[cfg(feature = "cache")]
@@ -102,16 +103,6 @@ impl Provider {
102103
})
103104
}
104105

105-
async fn fetch_with_handler(
106-
client: Client,
107-
request: Request,
108-
handler: Box<dyn NetHandler>,
109-
) -> Result<(), ProviderError> {
110-
let (response_url, bytes) = Self::fetch_inner(client, request).await?;
111-
handler.bytes(response_url, bytes);
112-
Ok(())
113-
}
114-
115106
#[allow(clippy::type_complexity)]
116107
pub fn fetch_with_callback(
117108
&self,
@@ -155,7 +146,7 @@ impl Provider {
155146
}
156147

157148
impl NetProvider for Provider {
158-
fn fetch(&self, doc_id: usize, request: Request, handler: Box<dyn NetHandler>) {
149+
fn fetch(&self, doc_id: usize, mut request: Request, handler: Box<dyn NetHandler>) {
159150
let client = self.client.clone();
160151

161152
#[cfg(feature = "debug_log")]
@@ -166,23 +157,80 @@ impl NetProvider for Provider {
166157
#[cfg(feature = "debug_log")]
167158
let url = request.url.to_string();
168159

169-
let _res = Self::fetch_with_handler(client, request, handler).await;
170-
171-
#[cfg(feature = "debug_log")]
172-
if let Err(e) = _res {
173-
eprintln!("Error fetching {url}: {e:?}");
160+
let signal = request.signal.take();
161+
let result = if let Some(signal) = signal {
162+
AbortFetch::new(
163+
signal,
164+
Box::pin(async move { Self::fetch_inner(client, request).await }),
165+
)
166+
.await
174167
} else {
175-
println!("Success {url}");
176-
}
168+
Self::fetch_inner(client, request).await
169+
};
177170

178171
// Call the waker to notify of completed network request
179-
waker.wake(doc_id)
172+
waker.wake(doc_id);
173+
174+
match result {
175+
Ok((response_url, bytes)) => {
176+
handler.bytes(response_url, bytes);
177+
#[cfg(feature = "debug_log")]
178+
println!("Success {url}");
179+
}
180+
Err(e) => {
181+
#[cfg(feature = "debug_log")]
182+
eprintln!("Error fetching {url}: {e:?}");
183+
#[cfg(not(feature = "debug_log"))]
184+
let _ = e;
185+
}
186+
};
180187
});
181188
}
182189
}
183190

191+
/// A future that is cancellable using an AbortSignal
192+
struct AbortFetch<F, T> {
193+
signal: AbortSignal,
194+
future: F,
195+
_rt: PhantomData<T>,
196+
}
197+
198+
impl<F, T> AbortFetch<F, T> {
199+
fn new(signal: AbortSignal, future: F) -> Self {
200+
Self {
201+
signal,
202+
future,
203+
_rt: PhantomData,
204+
}
205+
}
206+
}
207+
208+
impl<F, T> Future for AbortFetch<F, T>
209+
where
210+
F: Future + Unpin + Send + 'static,
211+
F::Output: Send + Into<Result<T, ProviderError>> + 'static,
212+
T: Unpin,
213+
{
214+
type Output = Result<T, ProviderError>;
215+
216+
fn poll(
217+
mut self: std::pin::Pin<&mut Self>,
218+
cx: &mut std::task::Context<'_>,
219+
) -> std::task::Poll<Self::Output> {
220+
if self.signal.aborted() {
221+
return Poll::Ready(Err(ProviderError::Abort));
222+
}
223+
224+
match Pin::new(&mut self.future).poll(cx) {
225+
Poll::Ready(output) => Poll::Ready(output.into()),
226+
Poll::Pending => Poll::Pending,
227+
}
228+
}
229+
}
230+
184231
#[derive(Debug)]
185232
pub enum ProviderError {
233+
Abort,
186234
Io(std::io::Error),
187235
DataUrl(data_url::DataUrlError),
188236
DataUrlBase64(data_url::forgiving_base64::InvalidBase64),

packages/blitz-traits/src/navigation.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ impl NavigationOptions {
6262
content_type: self.content_type,
6363
headers: HeaderMap::new(),
6464
body: self.document_resource,
65+
signal: None,
6566
}
6667
}
6768
}

packages/blitz-traits/src/net.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ use serde::{
66
Serialize,
77
ser::{SerializeSeq, SerializeTuple},
88
};
9+
use std::sync::{
10+
Arc,
11+
atomic::{AtomicBool, Ordering},
12+
};
913
use std::{ops::Deref, path::PathBuf};
1014
pub use url::Url;
1115

@@ -43,6 +47,7 @@ pub struct Request {
4347
pub content_type: String,
4448
pub headers: HeaderMap,
4549
pub body: Body,
50+
pub signal: Option<AbortSignal>,
4651
}
4752
impl Request {
4853
/// A get request to the specified Url and an empty body
@@ -53,8 +58,14 @@ impl Request {
5358
content_type: String::new(),
5459
headers: HeaderMap::new(),
5560
body: Body::Empty,
61+
signal: None,
5662
}
5763
}
64+
65+
pub fn signal(mut self, signal: AbortSignal) -> Self {
66+
self.signal = Some(signal);
67+
self
68+
}
5869
}
5970

6071
#[derive(Debug, Clone)]
@@ -148,3 +159,42 @@ pub struct DummyNetProvider;
148159
impl NetProvider for DummyNetProvider {
149160
fn fetch(&self, _doc_id: usize, _request: Request, _handler: Box<dyn NetHandler>) {}
150161
}
162+
163+
/// The AbortController interface represents a controller object that
164+
/// allows you to abort one or more Web requests as and when desired.
165+
///
166+
/// https://developer.mozilla.org/en-US/docs/Web/API/AbortController
167+
#[derive(Debug, Default)]
168+
pub struct AbortController {
169+
pub signal: AbortSignal,
170+
}
171+
172+
impl AbortController {
173+
/// The abort() method of the AbortController interface aborts
174+
/// an asynchronous operation before it has completed.
175+
/// This is able to abort fetch requests.
176+
///
177+
/// https://developer.mozilla.org/en-US/docs/Web/API/AbortController/abort
178+
pub fn abort(self) {
179+
self.signal.0.store(true, Ordering::SeqCst);
180+
}
181+
}
182+
183+
/// The AbortSignal interface represents a signal object that allows you to
184+
/// communicate with an asynchronous operation (such as a fetch request) and
185+
/// abort it if required via an AbortController object.
186+
///
187+
/// https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal
188+
#[derive(Debug, Default, Clone)]
189+
pub struct AbortSignal(Arc<AtomicBool>);
190+
191+
impl AbortSignal {
192+
/// The aborted read-only property returns a value that indicates whether
193+
/// the asynchronous operations the signal is communicating with are
194+
/// aborted (true) or not (false).
195+
///
196+
/// https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/aborted
197+
pub fn aborted(&self) -> bool {
198+
self.0.load(Ordering::SeqCst)
199+
}
200+
}

0 commit comments

Comments
 (0)