summaryrefslogtreecommitdiffstats
path: root/src/launcher/request.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/launcher/request.rs')
-rw-r--r--src/launcher/request.rs139
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