From 47da89dd23f0f37599f97c047130dbfda3444eff Mon Sep 17 00:00:00 2001 From: Katsuyuki Omuro Date: Tue, 14 Jan 2025 12:13:35 +0900 Subject: [PATCH] Update into a simple "fanout forward" starter kit --- Cargo.toml | 2 +- README.md | 39 +++++++++++++++++++++++++++++++++++---- fastly.toml | 7 +++++-- src/main.rs | 37 ++++++++++++++++++++++++++++++++++++- 4 files changed, 77 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5e08217..f2433c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,4 +10,4 @@ publish = false debug = 1 [dependencies] -fastly = "0.10.0" +fastly = "0.11.0" diff --git a/README.md b/README.md index 91494a4..055bbc8 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Deploy to Fastly](https://deploy.edgecompute.app/button)](https://deploy.edgecompute.app/deploy) -Learn about Fastly Compute with Fanout using a basic starter that sends connections through the Fanout GRIP proxy to a backend. +Learn about [Fastly Compute with Fanout](https://www.fastly.com/documentation/guides/concepts/real-time-messaging/fanout/) using a basic starter that sends connections through the Fanout GRIP proxy to a backend. **For more details about this and other starter kits for Compute, see the [Fastly Documentation Hub](https://www.fastly.com/documentation/solutions/starters/)**. @@ -10,11 +10,42 @@ Learn about Fastly Compute with Fanout using a basic starter that sends connecti The app expects a configured backend named "origin" that points to an origin server. For example, if the server is available at domain `example.com`, then you'll need to create a backend on your Compute service named "origin" with the destination host set to `example.com` and port `443`. Also set `Override Host` to the same host value. -After deploying the app and setting up the backend configuration, all connections received by the service will be passed through the Fanout proxy to the origin. If WebSocket-over-HTTP mode is enabled on your service, then client WebSocket activity will be converted into HTTP when sending to the origin. +You'll also need to [enable Fanout](https://www.fastly.com/documentation/guides/concepts/real-time-messaging/fanout/#enable-fanout) on your Fastly service to run this application. To enable Fanout on your service, type: -## Note +```shell +fastly products --enable=fanout +``` -This app is not currently supported in Fastly's [local development server](https://www.fastly.com/documentation/guides/compute/testing/#running-a-local-testing-server), as the development server does not support Fanout features. To experiment with Fanout, you will need to publish this project to your Fastly Compute service. using the `fastly compute publish` command. +> [!NOTE] +> This app is not currently supported in Fastly's [local development server](https://www.fastly.com/documentation/guides/compute/testing/#running-a-local-testing-server), as the development server does not support Fanout features. To experiment with Fanout, you will need to publish this project to your Fastly Compute service. using the `fastly compute publish` command. + +## Running the application + +After deploying the app and setting up the backend configuration, incoming HTTP and WebSocket requests that arrive at the service will be processed by the fetch handler: + +1. WebSocket connections will be handed off to Fanout to reach the backend server. Fanout maintains a long-lived connection with the client, and uses the [WebSocket-over-HTTP protocol](https://pushpin.org/docs/protocols/websocket-over-http/) to transform the messages to and from the backend server. + +2. HTTP GET and HEAD requests will be handed off to Fanout to reach the backend server. The backend can include [GRIP control messages](https://pushpin.org/docs/protocols/grip/) in its response, instructing Fanout to maintain a long-lived connection with the client. + +## Next Steps + +The starter kit is written to send all WebSocket and HTTP GET (and HEAD) traffic to Fanout. In an actual app we would be selective about which requests are handed off to Fanout, because requests that are handed off to Fanout do not pass through the Fastly cache. + +For details, see [What to hand off to Fanout](https://www.fastly.com/documentation/guides/concepts/real-time-messaging/fanout/#what-to-hand-off-to-fanout) in the Developer Documentation. + +The starter kit code contains a TODO section where you may insert additional conditions to check before setting the `use_fanout` variable to `true`. + +For example, to check the request for the existence of a certain header: + +```rust + if let Some(_) = req.get_header("fanout") { + use_fanout = true; + } +``` + +## Notes + +The code in this starter kit cannot be used with the [`fastly::main` attribute](https://docs.rs/fastly/0.11.2/fastly/attr.main.html) on the `main()` entry point. This is because a function decorated with `fastly::main` is expected to return a response, but handing off to Fanout is an action that does not create a response. Use an undecorated `main()` function instead, and use `Request::from_client()` and `Response::send_to_client()` as needed. ## Security issues diff --git a/fastly.toml b/fastly.toml index 63e24b8..b1fbd7f 100644 --- a/fastly.toml +++ b/fastly.toml @@ -1,8 +1,11 @@ +# This file describes a Fastly Compute package. To learn more visit: +# https://www.fastly.com/documentation/reference/compute/fastly-toml + authors = [""] description = "Enables Fanout on a service, forwarding to a backend." language = "rust" -manifest_version = 2 -name = "Fanout forwarding" +manifest_version = 3 +name = "Fanout forwarding starter kit for Rust" [scripts] build = "cargo build --bin fastly-compute-project --release --target wasm32-wasi --color always" diff --git a/src/main.rs b/src/main.rs index 1758366..e022352 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ use fastly::{Error, Request}; +use fastly::http::{HeaderValue, Method}; fn main() -> Result<(), Error> { // Log service version. @@ -9,5 +10,39 @@ fn main() -> Result<(), Error> { let req = Request::from_client(); - Ok(req.handoff_fanout("origin")?) + let mut use_fanout = false; + + if req.get_method() == &Method::GET && + req.get_header_all("upgrade") + .collect() + .contains(&&HeaderValue::from_static("websocket")) + { + // If a GET request contains "Upgrade: websocket" in its headers, then hand off to Fanout + // to handle as WebSocket-over-HTTP. + // For details on WebSocket-over-HTTP, see https://pushpin.org/docs/protocols/websocket-over-http/ + use_fanout = true; + } else if req.get_method() == &Method::GET || req.get_method() == &Method::HEAD { + // If it's a GET or HEAD request, then hand off to Fanout. + // The backend response can include GRIP control messages to specify connection behavior. + // For details on GRIP, see https://pushpin.org/docs/protocols/grip/. + + // NOTE: In an actual app we would be selective about which requests are handed off to Fanout, + // because requests that are handed off to Fanout do not pass through the Fastly cache. + // For example, we may examine the request path or the existence of certain headers. + // See https://www.fastly.com/documentation/guides/concepts/real-time-messaging/fanout/#what-to-hand-off-to-fanout + + // TODO: add any additional conditions before setting use_fanout to true + + use_fanout = true; + } + + if use_fanout { + // Hand off the request through Fanout to the specified backend. + req.handoff_fanout("origin")? + } else { + // Send the request to the specified backend normally. + req.send("origin")?.send_to_client() + } + + Ok(()) }