Fastly Compute Rust
This module enables the use of DataDome Bot Protection on the Fastly Compute platform, for services written in Rust. It is distributed as the datadome-fastly-compute crate.
Looking for the JavaScript version? See Fastly Compute.
Installation
Prerequisites
- The server-side key available in your DataDome dashboard
-
Fastly CLI installed and configured.
-
A Rust toolchain 1.85+ with the
wasm32-wasip1target:rustup target add wasm32-wasip1
Protect your traffic
Update an existing Rust Compute service to start protecting your traffic with DataDome Bot Protection:
-
Add the crate to your
Cargo.toml:[dependencies] datadome-fastly-compute = "1.0.0" -
In your entry file (
src/main.rs), hand every request to the module before your own logic:use datadome_fastly_compute::DataDome; use fastly::{Error, Request, Response}; #[fastly::main] fn main(req: Request) -> Result<Response, Error> { DataDome::new() .on_allowed_request(|req| { // Current processing of your service. // This code is executed when the request is allowed, or when DataDome // could not analyse it: it receives the request (carrying the DataDome // enriched headers when DataDome returned them) and must send it to // your origin backend. req.send("origin") }) .on_challenged_request(|_req, response| { // This code is executed when a request is challenged: it receives the // client request (with the DataDome enriched headers, for logging) and // the challenge response, and returns the response to send to the client. Ok::<_, Error>(response) }) .handle_request(req) }on_allowed_requestis required - it's expected to call theoriginserver.handle_requestonly exists once it has been called, so forgetting it is a compile error.
The module never calls your origin itself, the call must be defined hereon_challenged_requestis optional; without it the challenge response is returned untouched. -
Declare the backends and stores in your
fastly.toml, so thatfastly compute publishcreates them on first deployment:[setup.backends.datadome] address = "api-fastly.datadome.co" description = "DataDome Protection API" port = 443 [setup.backends.origin] address = "www.example.com" # the service to protect description = "Origin backend" port = 443 [setup.secret_stores.datadome] description = "Secrets for DataDome Bot protection" [setup.secret_stores.datadome.entries.datadome_server_side_key] description = "Server side key available in the DataDome dashboard" [setup.config_stores.datadome] description = "Optional configuration for DataDome Bot protection"On an existing service, create and link the resources yourself instead:
- a
datadomebackend towardsapi-fastly.datadome.co:443 - a
datadomeSecret Store holding your server-side key underdatadome_server_side_key - optionally a
datadomeConfig Store.
- a
-
Deploy:
fastly compute publish -
Lower the timeouts of the
datadomebackend: they are the timeout of the call to the DataDome API, and a new Fastly backend waits up to 1 s to connect and 15 s for the first byte. See How do I change the timeout value?
Congrats! 🎉 Your traffic is now protected by DataDome Bot Protection.
Configuration
The module reads its configuration from two Fastly stores linked to the service. Both are named datadome by default, and every key starts with the datadome_ prefix.
- The Secret Store holds the server-side key.
- The Config Store holds every other setting. It may also hold the server-side key, which is used when the Secret Store isn't linked or has no entry.
Each store is optional on its own: without a Config Store every setting keeps its default, without a Secret Store the key is read from the Config Store. If the key is found in neither, the module logs an error and lets the traffic through unprotected.
Settings
| Setting | Store | Description | Required | Default |
|---|---|---|---|---|
datadome_server_side_key | Secret Store (or Config Store) | DataDome server-side key | Yes | — |
datadome_url_pattern_exclusion | Config Store | Regular expression to ignore all matching URLs (see URL matching) | Optional | — |
datadome_static_asset_exclusion | Config Store | Regular expression of static-asset extensions to ignore. An empty value disables the list. | Optional | Default static-asset pattern |
datadome_encoded_char_inclusion | Config Store | Regular expression of encoded characters; a static-asset URL whose path contains a match is inspected anyway. An empty value disables it. | Optional | %23|%25|%2f|%3b|%3f |
datadome_remove_matrix_params | Config Store | Strip matrix parameters (;name=value segment suffixes, RFC 3986) from the path before matching | Optional | true |
datadome_url_pattern_inclusion | Config Store | Regular expression to process only matching URLs, applied after the exclusions | Optional | — |
datadome_enable_graphql_support | Config Store | Extract the GraphQL operation type, name, and count from requests to the GraphQL endpoint (see Enable GraphQL support) | Optional | false |
datadome_graphql_endpoint | Config Store | Comma-separated list of GraphQL endpoint paths; a request matches when its path contains one of them (case-insensitive) | Optional | /graphql |
datadome_datadome_backend_name | Config Store | Name of the DataDome API backend in the Fastly service | Optional | datadome |
Changes to a Secret Store or Config Store are live within seconds and don't require a new service version.
There is no timeout setting: the timeout of the call to the DataDome API is the one of the datadome backend (see How do I change the timeout value?).
URL matching
Patterns are matched against host + path (for example www.example.com/products/1.html), without the query string. The path is used exactly as received, percent-encoded.
The default static-asset pattern is:
(?i)\.(avi|avif|bmp|css|eot|flac|flv|gif|gz|ico|jpeg|jpg|js|json|less|map|mka|mkv|mov|mp3|mp4|mpeg|mpg|ogg|ogm|opus|otf|pdf|png|svg|svgz|swf|tif|tiff|ttf|wav|webm|webp|woff|woff2|xml|zip)$
Regex supportRegular expressions use the Rust
regexsyntax. Look-around and backreferences are not supported; an invalid pattern is logged (see How do I test or debug the service?) and ignored, and the default is kept.
Advanced Configuration
Using your own stores
To read the settings from stores you already own, pick their names and a key prefix through the builder. The prefix replaces datadome_ on every key:
DataDome::new()
.secret_store_name("my_secrets") // holds `s1_server_side_key`
.config_store_name("my_config") // holds `s1_url_pattern_exclusion`, ...
.key_prefix("s1_")
.on_allowed_request(|req| req.send("origin"))
.handle_request(req)Providing the server-side key from your code
If you don't want a store for the key, pass it to the builder. A key given this way always takes precedence: the Secret Store is never read, a datadome_server_side_key entry in the Config Store is ignored, and the Config Store is still read, optionally, for the other settings:
//Minimal default configuration
DataDome::new()
.server_side_key("YOUR_SERVER_SIDE_KEY") // e.g. env!("DATADOME_SERVER_SIDE_KEY")
.on_allowed_request(|req| req.send("origin"))
.handle_request(req)Other configuration must be provided using a Configuration Store.
Prefer a Secret Store whenever possible: a key compiled into the package is visible to anyone able to download it.
Enable GraphQL support
Set datadome_enable_graphql_support to true in the Config Store.
Once enabled, POST requests targeting a GraphQL endpoints with content-type: application/json will be analyzed to extract GraphQL operation name and type.
Multiple endpoints can be configured using defining the datadome_graphql_endpoint setting.
Send custom fields
Use request_metadata to send your own values to the DataDome API, such as a user or product ID. It takes a list of payload key and value pairs. A value is one of:
- a fixed string:
"premium".into() - a header of the client request:
DataDome::header("x-user-id") - a closure receiving the client request and returning an
Option<String>:DataDome::from_fn(|req| ...)
use datadome_fastly_compute::{DataDome, MetadataField};
DataDome::new()
.request_metadata([
("UserID", DataDome::header("x-user-id")),
("ProductID", DataDome::header("x-product-id")),
("CustomFieldString1", "premium".into()),
(
"CustomFieldInteger1",
DataDome::from_fn(|req| {
let value = req.get_header("x-cart-size")?.to_str().ok()?;
value.parse::<i64>().ok().map(|n| n.to_string())
}),
),
])
.on_allowed_request(|req| req.send("origin"))
.handle_request(req)- Your values have priority: they override the value the module collects for the same field.
Values are sent as-is: only use headers set by your own infrastructure, never ones the client can forge. Don't send personal data in
UserID.
Logging
The request handed to both hooks already carries the x-datadome-* headers returned by the DataDome API (the x-datadome-request-headers list), whether the request ends up allowed or challenged. The challenge response handed to on_challenged_request carries the x-datadome-headers list, and so does the response of an allowed request once your on_allowed_request returns it.
No header is added when DataDome could not analyse the request (excluded URL, configuration error, timeout or error of the API call), nor when the API answer carries no valid verdict: the request is then forwarded to on_allowed_request untouched.
fn log_headers(prefix: &str, req: &Request) {
for name in req.get_header_names().filter(|name| name.as_str().starts_with("x-datadome")) {
println!("{prefix} {}: {}", name, req.get_header_str(name).unwrap_or(""));
}
}
DataDome::new()
.on_allowed_request(|req| {
log_headers("allowed", &req);
req.send("origin")
})
.on_challenged_request(|req, response| {
log_headers("challenged", &req);
Ok::<_, Error>(response)
})
.handle_request(req)FAQ
How do I change the timeout value?
The timeout of the call to the DataDome API is the one of the datadome backend, configured in the Fastly service (Advanced options of the host): the Fastly Rust SDK has no per-request timeout. A new backend waits up to 1 s to connect and 15 s for the first byte, so lower both, for example to 300 ms:
fastly backend update --service-id <SERVICE_ID> --version <VERSION> \
--name datadome --connect-timeout 300 --first-byte-timeout 300Then activate the new service version. When the timeout expires, the module logs a DataDome:Timeout: message and forwards the request to the origin.
How do I test or debug the service?
Check the logs of your Compute service in the Fastly dashboard, or tail them with fastly log-tail --service-id <id>. The module reports configuration problems with a DataDome:Configuration: prefix, timeouts of the DataDome API call with a DataDome:Timeout: prefix, and any other failure of that call with a DataDome:Error: prefix. In every case the request is forwarded to the origin unprotected, except for an invalid regular expression, where only that setting falls back to its default.
-
Neither store is linked to the service:
DataDome:Configuration:Secret Store 'datadome' or Config Store 'datadome' must be linked to this service -
The server-side key is missing from both stores:
DataDome:Configuration:'datadome_server_side_key' is missing from Secret Store and Config Store -
A regular expression setting is invalid (here the custom exclusion; the static-asset and encoded-character settings keep their default instead):
DataDome:Configuration:'datadome_url_pattern_exclusion' is not a valid regex, ignored: <regex error> -
The DataDome API did not answer within the timeouts of the
datadomebackend (connection, first byte or DNS resolution): the error is logged with theDataDome:Timeout:prefix. -
The
datadomebackend does not exist in the service, or its name is invalid: the error is logged with theDataDome:Error:prefix. Check the backend name, or setdatadome_datadome_backend_name. -
The
originbackend does not exist in the service: the request fails with Fastly'sRequested backend named 'origin' does not existerror. Check the backend name used in youron_allowed_requesthook: the module never calls your origin itself.
Updated about 1 hour ago

