diff --git a/worker-sandbox/src/counter.rs b/worker-sandbox/src/counter.rs index acfc8374..d421efba 100644 --- a/worker-sandbox/src/counter.rs +++ b/worker-sandbox/src/counter.rs @@ -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; diff --git a/worker/src/lib.rs b/worker/src/lib.rs index 57d5808b..183bee63 100644 --- a/worker/src/lib.rs +++ b/worker/src/lib.rs @@ -184,7 +184,7 @@ pub use crate::queue::*; pub use crate::r2::*; pub use crate::request::{FromRequest, Request}; pub use crate::request_init::*; -pub use crate::response::{IntoResponse, Response, ResponseBody}; +pub use crate::response::{IntoResponse, Response, ResponseBody, ResponseBuilder}; pub use crate::router::{RouteContext, RouteParams, Router}; pub use crate::schedule::*; pub use crate::socket::*; diff --git a/worker/src/response.rs b/worker/src/response.rs index 7d8079d1..6c3b9943 100644 --- a/worker/src/response.rs +++ b/worker/src/response.rs @@ -31,7 +31,7 @@ const CONTENT_TYPE: &str = "content-type"; #[derive(Debug)] pub struct Response { body: ResponseBody, - init: ResponseInitBuilder, + init: ResponseBuilder, } #[cfg(feature = "http")] @@ -53,6 +53,11 @@ impl TryFrom for crate::HttpResponse { } impl Response { + /// Construct a builder for a new `Response`. + pub fn builder() -> ResponseBuilder { + ResponseBuilder::new() + } + /// Create a `Response` using `B` as the body encoded as JSON. Sets the associated /// `Content-Type` header for the `Response` as `application/json`. pub fn from_json(value: &B) -> Result { @@ -60,13 +65,12 @@ impl Response { let mut headers = Headers::new(); headers.set(CONTENT_TYPE, "application/json")?; - return Ok(Self { - body: ResponseBody::Body(data.into_bytes()), - init: ResponseInit::builder().with_headers(headers), - }); + Ok(ResponseBuilder::new() + .with_headers(headers) + .fixed(data.into_bytes())) + } else { + Err(Error::Json(("Failed to encode data to json".into(), 500))) } - - Err(Error::Json(("Failed to encode data to json".into(), 500))) } /// Create a `Response` using the body encoded as HTML. Sets the associated `Content-Type` @@ -76,10 +80,8 @@ impl Response { headers.set(CONTENT_TYPE, "text/html; charset=utf-8")?; let data = html.as_ref().as_bytes().to_vec(); - Ok(Self { - body: ResponseBody::Body(data), - init: ResponseInit::builder().with_headers(headers), - }) + + Ok(ResponseBuilder::new().with_headers(headers).fixed(data)) } /// Create a `Response` using unprocessed bytes provided. Sets the associated `Content-Type` @@ -88,30 +90,22 @@ impl Response { let mut headers = Headers::new(); headers.set(CONTENT_TYPE, "application/octet-stream")?; - Ok(Self { - body: ResponseBody::Body(bytes), - init: ResponseInit::builder().with_headers(headers), - }) + Ok(ResponseBuilder::new().with_headers(headers).fixed(bytes)) } /// Create a `Response` using a `ResponseBody` variant. Sets a status code of 200 and an empty /// set of Headers. Modify the Response with methods such as `with_status` and `with_headers`. pub fn from_body(body: ResponseBody) -> Result { - Ok(Self { - body, - init: ResponseInit::builder(), - }) + Ok(ResponseBuilder::new().body(body)) } /// Create a `Response` using a `WebSocket` client. Configures the browser to switch protocols /// (using status code 101) and returns the websocket. pub fn from_websocket(websocket: WebSocket) -> Result { - Ok(Self { - body: ResponseBody::Empty, - init: ResponseInit::builder() - .with_websocket(websocket) - .with_status(101), - }) + Ok(ResponseBuilder::new() + .with_websocket(websocket) + .with_status(101) + .empty()) } /// Create a `Response` using a [`Stream`](futures::stream::Stream) for the body. Sets a status @@ -147,18 +141,14 @@ impl Response { let mut headers = Headers::new(); headers.set(CONTENT_TYPE, "text/plain; charset=utf-8")?; - Ok(Self { - body: ResponseBody::Body(body.into().into_bytes()), - init: ResponseInit::builder().with_headers(headers), - }) + Ok(ResponseBuilder::new() + .with_headers(headers) + .fixed(body.into().into_bytes())) } /// Create an empty `Response` with a 200 status code. pub fn empty() -> Result { - Ok(Self { - body: ResponseBody::Empty, - init: ResponseInit::builder(), - }) + Ok(ResponseBuilder::new().empty()) } /// A helper method to send an error message to a client. Will return `Err` if the status code @@ -170,10 +160,7 @@ impl Response { )); } - Ok(Self { - body: ResponseBody::Body(msg.into().into_bytes()), - init: ResponseInit::builder(), - }) + Ok(ResponseBuilder::new().fixed(msg.into().into_bytes())) } /// Create a `Response` which redirects to the specified URL with default status_code of 302 @@ -267,7 +254,7 @@ impl Response { /// Set this response's `Headers`. pub fn with_headers(mut self, headers: Headers) -> Self { - self.init.headers = headers; + self.init = self.init.with_headers(headers); self } @@ -275,7 +262,7 @@ impl Response { /// The Workers platform will reject HTTP status codes outside the range of 200..599 inclusive, /// and will throw a JavaScript `RangeError`, returning a response with an HTTP 500 status code. pub fn with_status(mut self, status_code: u16) -> Self { - self.init.status_code = status_code; + self.init = self.init.with_status(status_code); self } @@ -289,10 +276,9 @@ impl Response { /// .with_cors(&cors) /// } /// ``` - pub fn with_cors(self, cors: &Cors) -> Result { - let mut headers = self.init.headers.clone(); - cors.apply_headers(&mut headers)?; - Ok(self.with_headers(headers)) + pub fn with_cors(mut self, cors: &Cors) -> Result { + self.init = self.init.with_cors(cors)?; + Ok(self) } /// Sets this response's `webSocket` option. @@ -302,6 +288,23 @@ impl Response { self } + /// Set this response's `encodeBody` option. + /// In most cases this is not needed, but it can be set to "manual" to + /// return already compressed data to the user without re-compression. + pub fn with_encode_body(mut self, encode_body: Option) -> Self { + self.init.encode_body = encode_body; + self + } + + /// Set this response's `cf` options. + pub fn with_cf(mut self, cf: Option) -> Result { + match cf { + Some(cf) => self.init = self.init.with_cf(cf)?, + None => self.init.cf = None, + } + Ok(self) + } + /// Read the `Headers` on this response. pub fn headers(&self) -> &Headers { &self.init.headers @@ -346,31 +349,17 @@ fn no_using_invalid_error_status_code() { assert!(Response::error("399", 399).is_err()); } -pub struct ResponseInit { - status: u16, - headers: Headers, - websocket: Option, - encode_body: Option, - cf: Option, -} - -impl ResponseInit { - pub fn builder() -> ResponseInitBuilder { - ResponseInitBuilder::new() - } -} - #[derive(Debug, Clone)] -pub struct ResponseInitBuilder { +pub struct ResponseBuilder { status_code: u16, headers: Headers, websocket: Option, encode_body: Option, - cf: Option, + cf: Option, } -impl ResponseInitBuilder { - fn new() -> Self { +impl ResponseBuilder { + pub fn new() -> Self { Self { status_code: 200, headers: Headers::new(), @@ -380,46 +369,97 @@ impl ResponseInitBuilder { } } + /// Set this response's status code. + /// The Workers platform will reject HTTP status codes outside the range of 200..599 inclusive, + /// and will throw a JavaScript `RangeError`, returning a response with an HTTP 500 status code. pub fn with_status(mut self, status: u16) -> Self { self.status_code = status; self } + /// Set this response's `Headers`. pub fn with_headers(mut self, headers: Headers) -> Self { self.headers = headers; self } + /// Sets this response's cors headers from the `Cors` struct. + /// Example usage: + /// ``` + /// use worker::*; + /// fn fetch() -> worker::Result { + /// let cors = Cors::default(); + /// Response::empty()? + /// .with_cors(&cors) + /// } + /// ``` + pub fn with_cors(self, cors: &Cors) -> Result { + let mut headers = self.headers.clone(); + cors.apply_headers(&mut headers)?; + Ok(self.with_headers(headers)) + } + + /// Sets this response's `webSocket` option. + /// This will require a status code 101 to work. pub fn with_websocket(mut self, websocket: WebSocket) -> Self { self.websocket = Some(websocket); self } + /// Set this response's `encodeBody` option. + /// In most cases this is not needed, but it can be set to "manual" to + /// return already compressed data to the user without re-compression. pub fn with_encode_body(mut self, encode_body: String) -> Self { self.encode_body = Some(encode_body); self } + /// Set this response's `cf` options. pub fn with_cf(mut self, cf: T) -> Result { - self.cf = Some(serde_json::to_value(&cf)?); - Ok(self) + let value = serde_wasm_bindgen::to_value(&cf)?; + if value.is_object() { + let obj = value.unchecked_into::(); + self.cf = Some(obj); + Ok(self) + } else { + Err(Error::from("cf must be an object")) + } } - pub fn build(self) -> ResponseInit { - ResponseInit { - status: self.status_code, - headers: self.headers, - websocket: self.websocket, - encode_body: self.encode_body, - cf: self.cf, + /// Build a response with a fixed-length body. + pub fn fixed(self, body: Vec) -> Response { + Response { + body: ResponseBody::Body(body), + init: self, + } + } + + /// Build a response with a stream body. + pub fn stream(self, stream: ReadableStream) -> Response { + Response { + body: ResponseBody::Stream(stream), + init: self, + } + } + + /// Build a response from a [`ResponseBody`]. + pub fn body(self, body: ResponseBody) -> Response { + Response { body, init: self } + } + + /// Build a response with an empty body. + pub fn empty(self) -> Response { + Response { + body: ResponseBody::Empty, + init: self, } } } -impl From for web_sys::ResponseInit { - fn from(init: ResponseInit) -> Self { +impl From for web_sys::ResponseInit { + fn from(init: ResponseBuilder) -> Self { let mut edge_init = web_sys::ResponseInit::new(); - edge_init.status(init.status); + edge_init.status(init.status_code); edge_init.headers(&init.headers.0); if let Some(websocket) = &init.websocket { edge_init @@ -432,9 +472,7 @@ impl From for web_sys::ResponseInit { .expect("failed to set encode_body"); } if let Some(cf) = init.cf { - edge_init - .cf(&serde_wasm_bindgen::to_value(&cf).expect("failed to serialize cf")) - .expect("failed to set cf"); + edge_init.cf(&cf).expect("failed to set cf"); } edge_init } @@ -442,23 +480,25 @@ impl From for web_sys::ResponseInit { impl From for web_sys::Response { fn from(res: Response) -> Self { - let init = res.init.build(); match res.body { ResponseBody::Body(bytes) => { let array = Uint8Array::new_with_length(bytes.len() as u32); array.copy_from(&bytes); - web_sys::Response::new_with_opt_buffer_source_and_init(Some(&array), &init.into()) - .unwrap() + web_sys::Response::new_with_opt_buffer_source_and_init( + Some(&array), + &res.init.into(), + ) + .unwrap() } ResponseBody::Stream(stream) => { web_sys::Response::new_with_opt_readable_stream_and_init( Some(&stream), - &init.into(), + &res.init.into(), ) .unwrap() } ResponseBody::Empty => { - web_sys::Response::new_with_opt_str_and_init(None, &init.into()).unwrap() + web_sys::Response::new_with_opt_str_and_init(None, &res.init.into()).unwrap() } } } @@ -466,7 +506,7 @@ impl From for web_sys::Response { impl From<&Response> for web_sys::Response { fn from(res: &Response) -> Self { - let init = res.init.clone().build(); + let init = res.init.clone(); match &res.body { ResponseBody::Body(bytes) => { let array = Uint8Array::new_with_length(bytes.len() as u32); @@ -487,19 +527,16 @@ impl From<&Response> for web_sys::Response { impl From for Response { fn from(res: web_sys::Response) -> Self { - let init = ResponseInitBuilder { + let builder = ResponseBuilder { headers: Headers(res.headers()), status_code: res.status(), websocket: res.websocket().map(|ws| ws.into()), encode_body: None, cf: None, }; - Self { - init, - body: match res.body() { - Some(stream) => ResponseBody::Stream(stream), - None => ResponseBody::Empty, - }, + match res.body() { + Some(stream) => builder.stream(stream), + None => builder.empty(), } } }