workers-rs icon indicating copy to clipboard operation
workers-rs copied to clipboard

How to set CfProperties in a cloned Request?

Open Angelin01 opened this issue 8 months ago • 4 comments
trafficstars

I'm performing a simple PoC using Cloudflare workers to redirect to different origins based on path. Please don't mind the overly simplistic code, it's just a PoC.

This typescript snippet words:

export default {
    async fetch(request, env, ctx): Promise<Response> {
        const TARGET_1 = "target1.example.com";
        const TARGET_2 = "target2.example.com";

        const ORIGINS: Record<string, string> = {
            "/page/foo": TARGET_1,
            "/page/bar": TARGET_2,
        };

        const url = new URL(request.url);
        const targetOrigin = ORIGINS[url.pathname];

        if (targetOrigin) {
            return fetch(request, { cf: { resolveOverride: targetOrigin } });
        }

        return new Response("Default response at " + url.pathname);
    },
} satisfies ExportedHandler<Env>;

But I can't seem to be able to get my CfProperties struct into the new request in Rust:

#[event(fetch)]
async fn fetch(req: Request, _env: Env, _ctx: Context) -> Result<Response> {
    console_error_panic_hook::set_once();
    let target_1 = "target1.example.com";
    let target_2 = "target2.example.com";

    let origins = HashMap::from([
        ("/page/foo".to_string(), target_1),
        ("/page/bar".to_string(), target_2),
    ]);

    let url = req.url()?;
    let path = url.path();

    if let Some(target) = origins.get(path) {
        let cf_properties = CfProperties {
            apps: None,
            cache_everything: None,
            cache_key: None,
            cache_ttl: None,
            cache_ttl_by_status: None,
            minify: None,
            mirage: None,
            polish: None,
            resolve_override: Some(target.to_string()),
            scrape_shield: None,
        };
        let request_init = RequestInit::new().
            with_cf_properties(cf_properties);

        let mut request = req.clone_mut()?;
        // How do I set the cf properties in the cloned request? Do I even need to clone the request?
        return Fetch::Request(request).send().await;
    }

    Fetch::Request(req).send().await
}

I've figured I can do a Request::new_with_init, but that doesn't let me copy the headers, body, etc.

Any help is appreciated. Again, this is a simple PoC, the simplest solution will work.

Angelin01 avatar Mar 11 '25 21:03 Angelin01

I may have found a way by using web_sys::Request directly and performing a bunch of conversions:

        let cf_properties = CfProperties {
            apps: None,
            cache_everything: None,
            cache_key: None,
            cache_ttl: None,
            cache_ttl_by_status: None,
            minify: None,
            mirage: None,
            polish: None,
            resolve_override: Some(target.to_string()),
            scrape_shield: None,
        };
        let mut init = RequestInit::new();
        init.with_cf_properties(cf_properties);
        let request = web_sys::Request::new_with_request_and_init(&req.inner(), &(&init).into())?;

        return Fetch::Request(request.into()).send().await;

Seems quite inefficient, however. Is there a better way?

Angelin01 avatar Mar 12 '25 12:03 Angelin01

To be honest I don't know, but I'm in the process of converting all my code to use web_sys natively...

I mean, I handle web_sys::Request directly and return web_sys::Response.

use web_sys::{Request, Response};
use worker::{event, Context, Env, Result};

#[event(fetch, respond_with_errors)]
async fn fetch(req: Request, env: Env, _ctx: Context) -> Result<Response> {

And yes, I also do redirects, but a web_sys::Request to DurableObjects...

use web_sys::{Request, Response};
use worker::{Env, Result};

pub async fn redirect_to_anonymous_session_router(req: Request, env: &Env, session_token: &str) -> Result<Response> {
  let namespace = env.durable_object("DO")?;
  let stub = namespace.id_from_name(session_token)?.get_stub()?;
  stub.fetch_with_request_raw(req).await
}

I had to add a fetch_with_request_raw to the Stub to allow it, as internally its what's done:

 pub async fn fetch_with_request_raw(&self, req: web_sys::Request) -> Result<web_sys::Response> {
        let promise = self.inner.fetch_with_request(&req)?;
        let response = JsFuture::from(promise).await?;
        Ok(response.dyn_into::<web_sys::Response>()?)
    }

Its not perfect and there are some things missing, but I started following some hints on discord and I'm very happy with it.

So the better way, that I use, is to receive a web_sys::Request directly and add the missing bits.

spigaz avatar Mar 12 '25 15:03 spigaz

To be honest I don't know, but I'm in the process of converting all my code to use web_sys natively...

I mean, I handle web_sys::Request directly and return web_sys::Response.

use web_sys::{Request, Response}; use worker::{event, Context, Env, Result};

#[event(fetch, respond_with_errors)] async fn fetch(req: Request, env: Env, _ctx: Context) -> Result<Response> { And yes, I also do redirects, but a web_sys::Request to DurableObjects...

use web_sys::{Request, Response}; use worker::{Env, Result};

pub async fn redirect_to_anonymous_session_router(req: Request, env: &Env, session_token: &str) -> Result<Response> { let namespace = env.durable_object("DO")?; let stub = namespace.id_from_name(session_token)?.get_stub()?; stub.fetch_with_request_raw(req).await } I had to add a fetch_with_request_raw to the Stub to allow it, as internally its what's done:

pub async fn fetch_with_request_raw(&self, req: web_sys::Request) -> Result<web_sys::Response> { let promise = self.inner.fetch_with_request(&req)?; let response = JsFuture::from(promise).await?; Ok(response.dyn_into::<web_sys::Response>()?) } Its not perfect and there are some things missing, but I started following some hints on discord and I'm very happy with it.

So the better way, that I use, is to receive a web_sys::Request directly and add the missing bits.

I'm concerned this is what I'm going to have to do as well, as the feature parity is not being addressed. Thanks for the examples I might just have to start rolling my own as well.

PeterMHammond avatar Mar 13 '25 21:03 PeterMHammond