diff --git a/Cargo.toml b/Cargo.toml index f389029..2c5b31e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "examples/esi_example_minimal", "examples/esi_example_advanced_error_handling", "examples/esi_try_example", + "examples/esi_vars_example", ] [workspace.package] diff --git a/esi/src/lib.rs b/esi/src/lib.rs index 09d1271..604982d 100644 --- a/esi/src/lib.rs +++ b/esi/src/lib.rs @@ -10,7 +10,7 @@ use fastly::http::request::PendingRequest; use fastly::http::{header, Method, StatusCode, Url}; use fastly::{mime, Body, Request, Response}; use log::{debug, error, trace}; -use std::collections::VecDeque; +use std::collections::{HashMap, VecDeque}; use std::io::{BufRead, Write}; pub use crate::document::{Element, Fragment}; @@ -142,6 +142,9 @@ impl Processor { // `root_task` is the root task that will be used to fetch tags in recursive manner let root_task = &mut Task::new(); + // variables + let mut variables = HashMap::new(); + let is_escaped = self.configuration.is_escaped_content; // Call the library to parse fn `parse_tags` which will call the callback function // on each tag / event it finds in the document. @@ -156,6 +159,7 @@ impl Processor { is_escaped, &original_request_metadata, dispatch_fragment_request, + &mut variables, ) }, )?; @@ -393,6 +397,7 @@ fn event_receiver( is_escaped: bool, original_request_metadata: &Request, dispatch_fragment_request: &FragmentRequestDispatcher, + variables: &mut HashMap, ) -> Result<()> { debug!("got {:?}", event); @@ -430,12 +435,14 @@ fn event_receiver( is_escaped, original_request_metadata, dispatch_fragment_request, + variables, )?; let except_task = task_handler( except_events, is_escaped, original_request_metadata, dispatch_fragment_request, + variables, )?; trace!( @@ -450,10 +457,17 @@ fn event_receiver( }); } Event::ESI(Tag::Assign { name, value }) => { - // process assignment + variables.insert(name, value); } Event::ESI(Tag::Vars { name }) => { - // process vars + if let Some(name) = name { + if let Some(value) = variables.get(&name) { + let value = value.to_owned(); + queue.push_back(Element::Raw(value.into_bytes())); + } + } else { + // TODO: long form + } } Event::XML(event) => { debug!("pushing content to buffer, len: {}", queue.len()); @@ -473,6 +487,7 @@ fn task_handler( is_escaped: bool, original_request_metadata: &Request, dispatch_fragment_request: &FragmentRequestDispatcher, + variables: &mut HashMap, ) -> Result { let mut task = Task::new(); for event in events { @@ -482,6 +497,7 @@ fn task_handler( is_escaped, original_request_metadata, dispatch_fragment_request, + variables, )?; } Ok(task) diff --git a/examples/esi_vars_example/.cargo/config.toml b/examples/esi_vars_example/.cargo/config.toml new file mode 100644 index 0000000..0787801 --- /dev/null +++ b/examples/esi_vars_example/.cargo/config.toml @@ -0,0 +1,6 @@ +[target.wasm32-wasi] +rustflags = ["-C", "debuginfo=2"] +runner = "viceroy run -C fastly.toml -- " + +[build] +target = "wasm32-wasi" diff --git a/examples/esi_vars_example/Cargo.toml b/examples/esi_vars_example/Cargo.toml new file mode 100644 index 0000000..b3d99b1 --- /dev/null +++ b/examples/esi_vars_example/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "esi_vars_example" +version.workspace = true +authors.workspace = true +license.workspace = true +edition.workspace = true +publish = false + +[dependencies] +fastly = "^0.11" +esi = { path = "../../esi" } +log = "^0.4" +env_logger = "^0.11" diff --git a/examples/esi_vars_example/fastly.toml b/examples/esi_vars_example/fastly.toml new file mode 100644 index 0000000..01d2f07 --- /dev/null +++ b/examples/esi_vars_example/fastly.toml @@ -0,0 +1,28 @@ +# This file describes a Fastly Compute package. To learn more visit: +# https://developer.fastly.com/reference/fastly-toml/ + +authors = ["kailan@enviark.com"] +description = "" +language = "rust" +manifest_version = 2 +name = "esi_example_minimal" +service_id = "" + +[local_server] + + [local_server.backends] + + [local_server.backends.mock-s3] + url = "https://mock-s3.edgecompute.app" + override_host = "mock-s3.edgecompute.app" + +[scripts] + build = "cargo build --bin esi_example_minimal --release --target wasm32-wasi --color always" + +[setup] + + [setup.backends] + + [setup.backends.mock-s3] + address = "mock-s3.edgecompute.app" + port = 443 diff --git a/examples/esi_vars_example/src/index.html b/examples/esi_vars_example/src/index.html new file mode 100644 index 0000000..86b459c --- /dev/null +++ b/examples/esi_vars_example/src/index.html @@ -0,0 +1,22 @@ + + + + My Variable Website + + +
+

My Variable Website

+
+
+ Assigning "YES" to variable...
+ + Check (short form): YES=
+ Check (long form): YES=$(test)
+ + Updating variable to "PERHAPS"...
+ + Check (short form): PERHAPS=
+ Check (long form): PERHAPS=$(test)
+
+ + diff --git a/examples/esi_vars_example/src/main.rs b/examples/esi_vars_example/src/main.rs new file mode 100644 index 0000000..a6f29be --- /dev/null +++ b/examples/esi_vars_example/src/main.rs @@ -0,0 +1,62 @@ +use fastly::{http::StatusCode, mime, Error, Request, Response}; +use log::info; + +fn main() { + env_logger::builder() + .filter(None, log::LevelFilter::Trace) + .init(); + + if let Err(err) = handle_request(Request::from_client()) { + println!("returning error response"); + + Response::from_status(StatusCode::INTERNAL_SERVER_ERROR) + .with_body(err.to_string()) + .send_to_client(); + } +} + +fn handle_request(req: Request) -> Result<(), Error> { + if req.get_path() != "/" { + Response::from_status(StatusCode::NOT_FOUND).send_to_client(); + return Ok(()); + } + + // Generate synthetic test response from "index.html" file. + // You probably want replace this with a backend call, e.g. `req.clone_without_body().send("origin_0")` + let mut beresp = + Response::from_body(include_str!("index.html")).with_content_type(mime::TEXT_HTML); + + // If the response is HTML, we can parse it for ESI tags. + if beresp + .get_content_type() + .is_some_and(|c| c.subtype() == mime::HTML) + { + let processor = esi::Processor::new(Some(req), esi::Configuration::default()); + + processor.process_response( + &mut beresp, + None, + Some(&|req| { + info!("Sending request {} {}", req.get_method(), req.get_path()); + Ok(req.with_ttl(120).send_async("mock-s3")?.into()) + }), + Some(&|req, mut resp| { + info!( + "Received response for {} {}", + req.get_method(), + req.get_path() + ); + if !resp.get_status().is_success() { + // Override status so we still insert errors. + resp.set_status(StatusCode::OK); + } + Ok(resp) + }), + )?; + } else { + // Otherwise, we can just return the response. + beresp.send_to_client(); + } + + Ok(()) +}