Skip to content

Commit

Permalink
Add encodeBody and cf to ResponseInit. Adopt builder pattern. (#568)
Browse files Browse the repository at this point in the history
* Add `encodeBody` and `cf` to ResponseInit. Adopt builder pattern.

Closes #567

* Refine builder pattern

* address comments

* Response.clone, test, http extensions

* Do not check content-type in Response.json

Closes #577
  • Loading branch information
kflansburg authored May 19, 2024
1 parent 6bf6cf7 commit bfe6d56
Show file tree
Hide file tree
Showing 12 changed files with 471 additions and 222 deletions.
25 changes: 11 additions & 14 deletions worker-sandbox/src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use super::SomeSharedData;
use futures_util::stream::StreamExt;
use rand::Rng;
use std::time::Duration;
use worker::{console_log, Cache, Date, Delay, Env, Request, Response, Result};
use worker::{console_log, Cache, Date, Delay, Env, Request, Response, ResponseBuilder, Result};

fn key(req: &Request) -> Result<Option<String>> {
let uri = req.url()?;
Expand All @@ -24,12 +24,11 @@ pub async fn handle_cache_example(
Ok(resp)
} else {
console_log!("Cache MISS!");
let mut resp =
Response::from_json(&serde_json::json!({ "timestamp": Date::now().as_millis() }))?;

// Cache API respects Cache-Control headers. Setting s-max-age to 10
// will limit the response to be in cache for 10 seconds max
resp.headers_mut().set("cache-control", "s-maxage=10")?;
let mut resp = ResponseBuilder::new()
.with_header("cache-control", "s-maxage=10")?
.from_json(&serde_json::json!({ "timestamp": Date::now().as_millis() }))?;
cache.put(key, resp.cloned()?).await?;
Ok(resp)
}
Expand Down Expand Up @@ -60,13 +59,11 @@ pub async fn handle_cache_api_put(
) -> Result<Response> {
if let Some(key) = key(&req)? {
let cache = Cache::default();

let mut resp =
Response::from_json(&serde_json::json!({ "timestamp": Date::now().as_millis() }))?;

// Cache API respects Cache-Control headers. Setting s-max-age to 10
// will limit the response to be in cache for 10 seconds max
resp.headers_mut().set("cache-control", "s-maxage=10")?;
let mut resp = ResponseBuilder::new()
.with_header("cache-control", "s-maxage=10")?
.from_json(&serde_json::json!({ "timestamp": Date::now().as_millis() }))?;
cache.put(format!("https://{key}"), resp.cloned()?).await?;
return Ok(resp);
}
Expand Down Expand Up @@ -111,12 +108,12 @@ pub async fn handle_cache_stream(
Result::Ok(text.as_bytes().to_vec())
});

let mut resp = Response::from_stream(stream)?;
console_log!("resp = {:?}", resp);
// Cache API respects Cache-Control headers. Setting s-max-age to 10
// will limit the response to be in cache for 10 seconds max
resp.headers_mut().set("cache-control", "s-maxage=10")?;

let mut resp = ResponseBuilder::new()
.with_header("cache-control", "s-maxage=10")?
.from_stream(stream)?;
console_log!("resp = {:?}", resp);
cache.put(key, resp.cloned()?).await?;
Ok(resp)
}
Expand Down
6 changes: 3 additions & 3 deletions worker-sandbox/src/counter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ impl DurableObject for Counter {
.serialize_attachment("hello")
.expect("failed to serialize attachment");

return Ok(Response::empty()
.unwrap()
return Ok(ResponseBuilder::new()
.with_status(101)
.with_websocket(Some(pair.client)));
.with_websocket(pair.client)
.empty());
}

self.count += 10;
Expand Down
36 changes: 34 additions & 2 deletions worker-sandbox/src/fetch.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use super::{ApiData, SomeSharedData};
use futures_util::future::Either;
use serde::{Deserialize, Serialize};
use std::time::Duration;
use worker::{
wasm_bindgen_futures, AbortController, Delay, Env, Fetch, Method, Request, RequestInit,
Response, Result,
wasm_bindgen_futures, AbortController, Delay, EncodeBody, Env, Fetch, Method, Request,
RequestInit, Response, Result,
};

#[worker::send]
Expand Down Expand Up @@ -177,3 +178,34 @@ pub async fn handle_cloned_fetch(

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")
}
12 changes: 7 additions & 5 deletions worker-sandbox/src/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use futures_util::StreamExt;
use futures_util::TryStreamExt;
use std::time::Duration;
use worker::Env;
use worker::{console_log, Date, Delay, Request, Response, ResponseBody, Result};
use worker::{console_log, Date, Delay, Request, Response, ResponseBody, ResponseBuilder, Result};
pub fn handle_a_request(req: Request, _env: Env, _data: SomeSharedData) -> Result<Response> {
Response::ok(format!(
"req at: {}, located at: {:?}, within: {}",
Expand Down Expand Up @@ -60,7 +60,9 @@ pub async fn handle_headers(req: Request, _env: Env, _data: SomeSharedData) -> R
let mut headers: http::HeaderMap = req.headers().into();
headers.append("Hello", "World!".parse().unwrap());

Response::ok("returned your headers to you.").map(|res| res.with_headers(headers.into()))
ResponseBuilder::new()
.with_headers(headers.into())
.ok("returned your headers to you.")
}

#[worker::send]
Expand Down Expand Up @@ -133,9 +135,9 @@ pub async fn handle_status(req: Request, _env: Env, _data: SomeSharedData) -> Re
let code = segments.nth(1);
if let Some(code) = code {
return match code.parse::<u16>() {
Ok(status) => {
Response::ok("You set the status code!").map(|resp| resp.with_status(status))
}
Ok(status) => ResponseBuilder::new()
.with_status(status)
.ok("You set the status code!"),
Err(_e) => Response::error("Failed to parse your status code.", 400),
};
}
Expand Down
34 changes: 20 additions & 14 deletions worker-sandbox/src/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::{
use std::convert::TryInto;
use std::sync::atomic::Ordering;

use worker::{console_log, Env, Fetch, Headers, Request, Response, Result};
use worker::{console_log, Env, Fetch, Request, Response, ResponseBuilder, Result};

#[cfg(not(feature = "http"))]
use worker::{RouteContext, Router};
Expand Down Expand Up @@ -152,6 +152,10 @@ pub fn make_router(data: SomeSharedData, env: Env) -> axum::Router {
get(handler!(request::handle_cloned_stream)),
)
.route("/cloned-fetch", get(handler!(fetch::handle_cloned_fetch)))
.route(
"/cloned-response",
get(handler!(fetch::handle_cloned_response_attributes)),
)
.route("/wait/:delay", get(handler!(request::handle_wait_delay)))
.route(
"/custom-response-body",
Expand Down Expand Up @@ -300,6 +304,10 @@ pub fn make_router<'a>(data: SomeSharedData) -> Router<'a, SomeSharedData> {
.get_async("/cloned", handler!(request::handle_cloned))
.get_async("/cloned-stream", handler!(request::handle_cloned_stream))
.get_async("/cloned-fetch", handler!(fetch::handle_cloned_fetch))
.get_async(
"/cloned-response",
handler!(fetch::handle_cloned_response_attributes),
)
.get_async("/wait/:delay", handler!(request::handle_wait_delay))
.get_async(
"/custom-response-body",
Expand Down Expand Up @@ -348,19 +356,15 @@ pub fn make_router<'a>(data: SomeSharedData) -> Router<'a, SomeSharedData> {
}

fn respond(req: Request, _env: Env, _data: SomeSharedData) -> Result<Response> {
Response::ok(format!("Ok: {}", String::from(req.method()))).map(|resp| {
let mut headers = Headers::new();
headers.set("x-testing", "123").unwrap();
resp.with_headers(headers)
})
ResponseBuilder::new()
.with_header("x-testing", "123")?
.ok(format!("Ok: {}", String::from(req.method())))
}

async fn respond_async(req: Request, _env: Env, _data: SomeSharedData) -> Result<Response> {
Response::ok(format!("Ok (async): {}", String::from(req.method()))).map(|resp| {
let mut headers = Headers::new();
headers.set("x-testing", "123").unwrap();
resp.with_headers(headers)
})
ResponseBuilder::new()
.with_header("x-testing", "123")?
.ok(format!("Ok (async): {}", String::from(req.method())))
}

#[worker::send]
Expand All @@ -382,10 +386,12 @@ async fn catchall(req: Request, _env: Env, _data: SomeSharedData) -> Result<Resp
let path = uri.path();
console_log!("[or_else_any_method_async] caught: {}", path);

Fetch::Url("https://github.com/404".parse().unwrap())
let (builder, body) = Fetch::Url("https://github.com/404".parse().unwrap())
.send()
.await
.map(|resp| resp.with_status(404))
.await?
.into_parts();

Ok(builder.with_status(404).body(body))
}

async fn handle_options_catchall(
Expand Down
5 changes: 5 additions & 0 deletions worker-sandbox/tests/clone.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,9 @@ describe("cache", () => {
const resp = await mf.dispatchFetch("https://fake.host/cloned-fetch");
expect(await resp.text()).toBe("true");
});

test("cloned response", async () => {
const resp = await mf.dispatchFetch("https://fake.host/cloned-response");
expect(await resp.text()).toBe("true");
});
});
17 changes: 16 additions & 1 deletion worker-sys/src/ext/response.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use wasm_bindgen::prelude::*;

mod glue {

use super::*;

#[wasm_bindgen]
Expand All @@ -10,16 +11,30 @@ mod glue {

#[wasm_bindgen(method, catch, getter)]
pub fn webSocket(this: &Response) -> Result<Option<web_sys::WebSocket>, JsValue>;

#[wasm_bindgen(method, catch, getter)]
pub fn cf(this: &Response) -> Result<Option<js_sys::Object>, JsValue>;
}
}

pub trait ResponseExt {
/// Getter for the `webSocket` field of this object.
fn websocket(&self) -> Option<web_sys::WebSocket>;

/// Getter for the `cf` field of this object.
fn cf(&self) -> Option<js_sys::Object>;
}

impl ResponseExt for web_sys::Response {
fn websocket(&self) -> Option<web_sys::WebSocket> {
self.unchecked_ref::<glue::Response>().webSocket().unwrap()
self.unchecked_ref::<glue::Response>()
.webSocket()
.expect("read response.webSocket")
}

fn cf(&self) -> Option<js_sys::Object> {
self.unchecked_ref::<glue::Response>()
.cf()
.expect("read response.cf")
}
}
33 changes: 24 additions & 9 deletions worker-sys/src/ext/response_init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,32 @@ use wasm_bindgen::prelude::*;

pub trait ResponseInitExt {
/// Change the `webSocket` field of this object.
fn websocket(&mut self, val: &web_sys::WebSocket) -> &mut Self;
fn websocket(&mut self, val: &web_sys::WebSocket) -> Result<&mut Self, JsValue>;

/// Change the `encodeBody` field of this object.
fn encode_body(&mut self, val: &str) -> Result<&mut Self, JsValue>;

/// Change the `cf` field of this object.
fn cf(&mut self, val: &JsValue) -> Result<&mut Self, JsValue>;
}

impl ResponseInitExt for web_sys::ResponseInit {
fn websocket(&mut self, val: &web_sys::WebSocket) -> &mut Self {
let r = js_sys::Reflect::set(self.as_ref(), &JsValue::from("webSocket"), val.as_ref());
debug_assert!(
r.is_ok(),
"setting properties should never fail on our dictionary objects"
);
let _ = r;
self
fn websocket(&mut self, val: &web_sys::WebSocket) -> Result<&mut Self, JsValue> {
js_sys::Reflect::set(self.as_ref(), &JsValue::from("webSocket"), val.as_ref())?;
Ok(self)
}

fn encode_body(&mut self, val: &str) -> Result<&mut Self, JsValue> {
js_sys::Reflect::set(
self.as_ref(),
&JsValue::from("encodeBody"),
&JsValue::from(val),
)?;
Ok(self)
}

fn cf(&mut self, val: &JsValue) -> Result<&mut Self, JsValue> {
js_sys::Reflect::set(self.as_ref(), &JsValue::from("cf"), val)?;
Ok(self)
}
}
19 changes: 19 additions & 0 deletions worker/src/cf.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#[cfg(feature = "timezone")]
use crate::Result;

use serde::de::DeserializeOwned;
use wasm_bindgen::JsCast;

/// In addition to the methods on the `Request` struct, the `Cf` struct on an inbound Request contains information about the request provided by Cloudflare’s edge.
///
/// [Details](https://developers.cloudflare.com/workers/runtime-apis/request#incomingrequestcfproperties)
Expand Down Expand Up @@ -254,3 +257,19 @@ impl From<worker_sys::TlsClientAuth> for TlsClientAuth {
Self { inner }
}
}

#[derive(Clone)]
pub struct CfResponseProperties(pub(crate) js_sys::Object);

impl CfResponseProperties {
pub fn into_raw(self) -> js_sys::Object {
self.0
}

pub fn try_into<T: DeserializeOwned>(self) -> crate::Result<T> {
Ok(serde_wasm_bindgen::from_value(self.0.unchecked_into())?)
}
}

unsafe impl Send for CfResponseProperties {}
unsafe impl Sync for CfResponseProperties {}
Loading

0 comments on commit bfe6d56

Please sign in to comment.