branch: main
fetch.rs
5771 bytesRaw
use super::{ApiData, SomeSharedData};
use futures_util::future::Either;
use serde::{Deserialize, Serialize};
use std::time::Duration;
use worker::{
    wasm_bindgen_futures, AbortController, Delay, EncodeBody, Env, Fetch, Method, Request,
    RequestInit, Response, Result,
};

#[worker::send]
pub async fn handle_fetch(_req: Request, _env: Env, _data: SomeSharedData) -> Result<Response> {
    let req = Request::new("https://example.com", Method::Post)?;
    let resp = Fetch::Request(req).send().await?;
    let resp2 = Fetch::Url("https://example.com".parse()?).send().await?;
    Response::ok(format!(
        "received responses with codes {} and {}",
        resp.status_code(),
        resp2.status_code()
    ))
}

#[worker::send]
pub async fn handle_fetch_json(
    _req: Request,
    _env: Env,
    _data: SomeSharedData,
) -> Result<Response> {
    let data: ApiData = Fetch::Url(
        "https://jsonplaceholder.typicode.com/todos/1"
            .parse()
            .unwrap(),
    )
    .send()
    .await?
    .json()
    .await?;
    Response::ok(format!(
        "API Returned user: {} with title: {} and completed: {}",
        data.user_id, data.title, data.completed
    ))
}

#[worker::send]
pub async fn handle_proxy_request(
    req: Request,
    _env: Env,
    _data: SomeSharedData,
) -> Result<Response> {
    let uri = req.url()?;
    let url = uri
        .path_segments()
        .unwrap()
        .skip(1)
        .collect::<Vec<_>>()
        .join("/");
    crate::console_log!("{}", url);
    Fetch::Url(url.parse()?).send().await
}

#[worker::send]
pub async fn handle_request_init_fetch(
    _req: Request,
    _env: Env,
    _data: SomeSharedData,
) -> Result<Response> {
    let init = RequestInit::new();
    Fetch::Request(Request::new_with_init("https://cloudflare.com", &init)?)
        .send()
        .await
}

#[worker::send]
pub async fn handle_request_init_fetch_post(
    _req: Request,
    _env: Env,
    _data: SomeSharedData,
) -> Result<Response> {
    let mut init = RequestInit::new();
    init.method = Method::Post;
    Fetch::Request(Request::new_with_init("https://httpbin.org/post", &init)?)
        .send()
        .await
}

#[worker::send]
pub async fn handle_cancelled_fetch(
    _req: Request,
    _env: Env,
    _data: SomeSharedData,
) -> Result<Response> {
    let controller = AbortController::default();
    let signal = controller.signal();

    let (tx, rx) = futures_channel::oneshot::channel();

    // Spawns a future that'll make our fetch request and not block this function.
    wasm_bindgen_futures::spawn_local({
        async move {
            let fetch = Fetch::Url("https://cloudflare.com".parse().unwrap());
            let res = fetch.send_with_signal(&signal).await;
            tx.send(res).unwrap();
        }
    });

    // And then we try to abort that fetch as soon as we start it, hopefully before
    // cloudflare.com responds.
    controller.abort();

    let res = rx.await.unwrap();
    let res = res.unwrap_or_else(|err| {
        let text = err.to_string();
        Response::ok(text).unwrap()
    });

    Ok(res)
}

#[worker::send]
pub async fn handle_fetch_timeout(
    _req: Request,
    _env: Env,
    _data: SomeSharedData,
) -> Result<Response> {
    let controller = AbortController::default();
    let signal = controller.signal();

    let fetch_fut = async {
        let fetch = Fetch::Url("https://miniflare.mocks/delay".parse().unwrap());
        let mut res = fetch.send_with_signal(&signal).await?;
        let text = res.text().await?;
        Ok::<String, worker::Error>(text)
    };
    let delay_fut = async {
        Delay::from(Duration::from_millis(100)).await;
        controller.abort();
        Response::ok("Cancelled")
    };

    futures_util::pin_mut!(fetch_fut);
    futures_util::pin_mut!(delay_fut);

    match futures_util::future::select(delay_fut, fetch_fut).await {
        Either::Left((res, cancelled_fut)) => {
            // Ensure that the cancelled future returns an AbortError.
            match cancelled_fut.await {
                Err(e) if e.to_string().contains("AbortError") => { /* Yay! It worked, let's do nothing to celebrate */
                }
                Err(e) => panic!(
                    "Fetch errored with a different error than expected: {:#?}",
                    e
                ),
                Ok(text) => panic!("Fetch unexpectedly succeeded: {}", text),
            }

            res
        }
        Either::Right(_) => panic!("Delay future should have resolved first"),
    }
}

#[worker::send]
pub async fn handle_cloned_fetch(
    _req: Request,
    _env: Env,
    _data: SomeSharedData,
) -> Result<Response> {
    let mut resp = Fetch::Url(
        "https://jsonplaceholder.typicode.com/todos/1"
            .parse()
            .unwrap(),
    )
    .send()
    .await?;
    let mut resp1 = resp.cloned()?;

    let left = resp.text().await?;
    let right = resp1.text().await?;

    Response::ok((left == right).to_string())
}

#[worker::send]
pub async fn handle_cloned_response_attributes(
    _req: Request,
    _env: Env,
    _data: SomeSharedData,
) -> Result<Response> {
    #[derive(Serialize, Deserialize, PartialEq, Debug)]
    struct TestCf {
        foo: String,
    }
    let mut resp = Response::builder()
        .with_status(200)
        .with_encode_body(EncodeBody::Manual)
        .with_cf(TestCf {
            foo: "bar".to_owned(),
        })?
        .empty();

    let resp1 = resp.cloned()?;

    assert!(matches!(resp.encode_body(), EncodeBody::Manual));
    assert!(matches!(resp1.encode_body(), EncodeBody::Manual));

    let cf: TestCf = resp.cf()?.unwrap();
    let cf1: TestCf = resp1.cf()?.unwrap();

    assert_eq!(cf, cf1);

    Response::ok("true")
}