diff options
Diffstat (limited to 'src/launcher/request.rs')
| -rw-r--r-- | src/launcher/request.rs | 139 |
1 files changed, 139 insertions, 0 deletions
diff --git a/src/launcher/request.rs b/src/launcher/request.rs new file mode 100644 index 0000000..df89a8b --- /dev/null +++ b/src/launcher/request.rs @@ -0,0 +1,139 @@ +use std::error::Error; +use std::fmt::Display; +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; +use curl::easy::{Easy}; +use tokio::sync::oneshot; +use tokio::sync::oneshot::Receiver; +use tokio::task; +use crate::launcher::constants::USER_AGENT; + +// yeah this is basically reqwest but bad (I did not want to rely on both reqwest and curl) + +#[derive(Clone, Copy)] +enum FetchState { + Primed, + Running, + Complete +} + +pub struct EasyFetch { + easy: Option<Easy>, + state: FetchState, + response: Option<Receiver<Result<FetchResult, curl::Error>>> +} + +impl EasyFetch { + fn new(easy: Easy) -> Self { + EasyFetch { + easy: Some(easy), + state: FetchState::Primed, + response: None + } + } + + pub fn get(url: &str) -> Self { + let mut easy = Easy::new(); + easy.useragent(USER_AGENT).expect("couldn't set user agent"); + easy.get(true).expect("couldn't set request method"); + easy.url(url).expect("couldn't set url"); + + Self::new(easy) + } +} + +#[derive(Debug)] +pub struct FetchResult { + easy: Easy, + response_code: u32, + data: Vec<u8>, +} + +#[derive(Debug)] +pub struct FetchResponseError(u32); + +impl FetchResponseError { + pub fn get_code(&self) -> u32 { + self.0 + } +} + +impl Display for FetchResponseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "http response: {}", self.0) + } +} + +impl Error for FetchResponseError {} + +impl FetchResult { + pub fn get_response_code(&self) -> u32 { + self.response_code + } + + pub fn get_data(&self) -> &[u8] { + &self.data + } + + pub fn get_data_string(&self) -> String { + String::from_utf8_lossy(&self.data).to_string() + } + + pub fn error_for_status(self) -> Result<Self, FetchResponseError> { + if self.response_code / 100 == 2 { + Ok(self) + } else { + Err(FetchResponseError(self.response_code)) + } + } +} + +impl Future for EasyFetch { + type Output = Result<FetchResult, curl::Error>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { + let self_ref = self.get_mut(); + + match &self_ref.state { + FetchState::Primed => { + self_ref.state = FetchState::Running; + let mut easy = self_ref.easy.take().unwrap(); + let waker = cx.waker().clone(); + + let (tx, rx) = oneshot::channel::<Result<FetchResult, curl::Error>>(); + self_ref.response.replace(rx); + + task::spawn_blocking(move || { + let mut out_data: Vec<u8> = Vec::new(); + let mut transfer = easy.transfer(); + + transfer.write_function(|data| { + out_data.extend_from_slice(data); + Ok(data.len()) + }).expect("infallible curl operation failed"); + + let res = transfer.perform(); + drop(transfer); // have to explicitly drop to release borrow on "easy" + + out_data.shrink_to_fit(); + + tx.send(res.map(|_| FetchResult { + response_code: easy.response_code().expect("querying response code should not fail"), + data: out_data, + easy + })).expect("curl fetch reader hangup (this shouldn't happen)"); + waker.wake(); + }); + + Poll::Pending + }, + FetchState::Running => { + self_ref.state = FetchState::Complete; + Poll::Ready(self_ref.response.take().unwrap().try_recv() + .expect("curl fetch writer hangup or not ready (this shouldn't happen)")) + }, + FetchState::Complete => panic!("fetch polled after completion") + } + } +}
\ No newline at end of file |
