Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
120 commits
Select commit Hold shift + click to select a range
a30e7c9
New parser. Nowhere near functional. Just prototyping.
tyler Dec 12, 2024
6eaca34
starting to wonder if this is a good idea
tyler Dec 13, 2024
2b59e00
Getting there. Correctly-ish identify HTML (and ignore it) and pull o…
tyler Dec 19, 2024
214080d
That's the easy tags out of the way
tyler Dec 19, 2024
5e9b3f7
All of the rest of the esi tags we currently support.
tyler Dec 19, 2024
b23d0e3
Integrate nom expression parser from @vagetman (thank you!) and do a …
tyler Dec 20, 2024
f216023
Convert new parser to use types understood by interpreter and impleme…
tyler Dec 21, 2024
fc2ff13
Merge nom-based parser from tyler/compiler_and_vm branch
vagetman Oct 30, 2025
c833efb
Replace XML and ESI parsers with unified nom-based parser implementation
vagetman Oct 31, 2025
f543dc4
Refactor function signatures to use lifetimes to suppress errors
vagetman Oct 31, 2025
9063da3
Remove quick-xml dependency and legacy XML parsing code in favor of n…
vagetman Oct 31, 2025
f8984df
Refactor ESI parser and related tests to improve handling of variable…
vagetman Oct 31, 2025
0f1145b
Refactor ESI parser to change `esi:otherwise` to a unit variant and u…
vagetman Oct 31, 2025
e50d2f7
Refactor ESI parser to change attempt_events from a single vector to …
vagetman Oct 31, 2025
be56b6d
Simplify `esi:assign` tags by removing alternative tag options
vagetman Oct 31, 2025
364a0c9
simplified parsers by only using `complete` combinators
vagetman Oct 31, 2025
48abd1b
adjust formatting with `rustfmt`
vagetman Oct 31, 2025
35f7314
Renamed and ported parse tests.
vagetman Oct 31, 2025
919f95c
Restore three-phase (concurrent) fragment fetching
vagetman Oct 31, 2025
2e3d5d6
Fix: Pass `is_escaped_content` parameter to fragment request functions
vagetman Nov 1, 2025
c38d6a0
Add support for process_fragment_response callback and enhance fragme…
vagetman Nov 1, 2025
56edfcc
Refactor clippy warnings and improve variable parsing in the parser m…
vagetman Nov 1, 2025
bc71664
Implement ESI expression enhancements and parser improvements
vagetman Nov 1, 2025
440af4d
Enhance fragment request handling and improve error management in pro…
vagetman Nov 1, 2025
f926da6
got rid of unnecessary `if` block
vagetman Nov 1, 2025
811bf70
Refactor ESI parser types and enhance tests for interpolation
vagetman Nov 1, 2025
cab4193
Refactor parser functions to improve clarity and consistency; rename …
vagetman Nov 1, 2025
b28ac0b
Refactor fetch_elements to `NomTag` -> `Tag` enum;
vagetman Nov 1, 2025
39499dc
Refactor ESI fragment handling: Re-Introduce Fragment struct and proc…
vagetman Nov 2, 2025
7f4a15b
Refactor ESI processing: improve handling of runtime elements
vagetman Nov 2, 2025
d69fa61
The `Processor` now owns the `EvalContext` (aka `ctx`)
vagetman Nov 2, 2025
6ff1f5c
Convert to streaming processing: dispatch→execute one at a time inste…
vagetman Nov 3, 2025
89c3930
Implement ESI Processor with streaming support for fragment requests …
vagetman Nov 3, 2025
94406c1
Refactor try/except handling in ESI processing: implement parallel di…
vagetman Nov 3, 2025
42b00c0
Refactor ESI include dispatching: streamline logic by consolidating r…
vagetman Nov 3, 2025
c4de44e
Enhance ESI document processing with streaming architecture
vagetman Nov 3, 2025
943436c
Refactor streaming parser: implement iteration limit and enhance EOF …
vagetman Nov 4, 2025
4cf4cd8
Add comprehensive parser tests for ESI tags
vagetman Nov 4, 2025
f10ef49
Add comprehensive tests for streaming parser behavior and incomplete …
vagetman Nov 5, 2025
8b770eb
Refactor parser to implement a loop strategy for handling incomplete …
vagetman Nov 6, 2025
c52fadd
Refactor parser tests to use Bytes for zero-copy parsing
vagetman Nov 6, 2025
e04c388
Refactor parser: Remove parser_str_backup.rs and update streaming_beh…
vagetman Nov 6, 2025
60979a4
Refactor parser: Update parse_interpolated_string to accept Bytes for…
vagetman Nov 6, 2025
bd67ac0
Refactor parser and functions to use Bytes for zero-copy handling, im…
vagetman Nov 6, 2025
abac73d
Refactor functions: Clean up formatting and improve readability in ht…
vagetman Nov 6, 2025
55f71a4
Refactor parser: Simplify content parsing in esi_assign_long, esi_exc…
vagetman Nov 6, 2025
a9052b4
Update previously disabled tests
vagetman Nov 11, 2025
c333b26
Restored more disabled tests
vagetman Nov 11, 2025
ccb5de2
Refactor parser: Replace custom parsers with nom's built-in functions…
vagetman Nov 11, 2025
e91575f
Refactor parser: Consolidate ESI tag handling into a unified dispatch…
vagetman Nov 13, 2025
2309321
Refactor parser: Enhance esi_vars_content and htmlstring functions fo…
vagetman Nov 13, 2025
e944b2c
Refactor parser: Rename functions for clarity and improve tag handlin…
vagetman Nov 13, 2025
aec65e7
Add benchmarks for ESI parser performance and integrate Criterion for…
vagetman Nov 29, 2025
d0df910
Refactor Value display logic: Simplify to_string method and enhance D…
vagetman Dec 14, 2025
0e719ad
Refactor parser: Optimize attribute handling by switching from Vec to…
vagetman Dec 14, 2025
87ad082
Refactor parser: Replace Vec with ParseResult to optimize memory allo…
vagetman Dec 16, 2025
fdab9ad
Refactor parser: Enhance expression parsing with complete combinator …
vagetman Dec 16, 2025
9fc0c3b
Refactor parser: Update parser functions to use complete parsers for …
vagetman Dec 16, 2025
ad181cb
Refactor parser: Replace complete parsers with streaming parsers for …
vagetman Dec 17, 2025
6ced6cd
Parser functions rearranged
vagetman Dec 17, 2025
5a02523
Refactor esi:assign and esi:remove: streaming capture + complete parsing
vagetman Jan 30, 2026
0fb82a5
Refactor parser: Enhance `esi:include` handling to support `esi:param…
vagetman Jan 30, 2026
22755ea
feat: Implement ESI param support with optimized expression parsing
vagetman Jan 30, 2026
eb0ea9d
Refactor Processor: Simplify include dispatching by consolidating URL…
vagetman Jan 31, 2026
993557e
perf: optimize expression evaluation and apply clippy recommendations
vagetman Jan 31, 2026
20cb989
Add dict/list literals, foreach loops, subscript assignment, has/has_…
vagetman Feb 5, 2026
853be23
fix: correct argument order in strftime function and update tests
vagetman Feb 16, 2026
742802c
feat: add caching support for ESI fragments with TTL tracking and Cac…
vagetman Feb 18, 2026
0c23bd7
refactor: some clippy inspired simplifications and improvements
vagetman Feb 18, 2026
b46048b
refactor: streamline element processing with dedicated handlers for c…
vagetman Feb 18, 2026
5275277
feat: add tests for default values in ESI variables, some cleanup
vagetman Feb 24, 2026
9c8b4e5
feat: Add HTTP request customization attributes to esi:include
vagetman Feb 25, 2026
a30fb76
feat: Add missing base64 decoding and MD5 digest functions with upda…
vagetman Feb 25, 2026
19fa109
feat: Enhance expression parsing with left-to-right evaluation and un…
vagetman Feb 25, 2026
6f12fc8
feat: Implement arithmetic operators and left-to-right evaluation in …
vagetman Feb 25, 2026
3457f02
refactor(expression): extract comparison logic and clean up imports
vagetman Feb 25, 2026
4767e1a
refactor(parser): simplify parse_loop function parameters
vagetman Feb 25, 2026
b57b849
refactor(literals) Introduce a new module with centralized constants …
vagetman Feb 26, 2026
c81c3d7
refactor(include): streamline handling of include parameters and attr…
vagetman Feb 26, 2026
80e4927
feat(cache): add uncacheable flag to EvalContext and update cache con…
vagetman Feb 26, 2026
8bd36de
feat(range): implement range operator for list creation and add corre…
vagetman Feb 26, 2026
24cf69d
feat(eval): introduce <esi:eval> tag with dynamic content assembly su…
vagetman Feb 27, 2026
72847d1
feat(eval): add HTTP headers caching and parsing support in EvalContext
vagetman Feb 27, 2026
fa1c390
feat(eval): enhance HTTP header parsing for cookies and add string in…
vagetman Feb 27, 2026
59ad8bd
feat(fragment): refactor `FragmentMetadata` and enhance request param…
vagetman Feb 28, 2026
ed11f3a
feat(functions): implement user-defined functions with recursion support
vagetman Feb 28, 2026
378da1b
Refactor ESI element processing by introducing ElementHandler trait
vagetman Mar 1, 2026
a13aef3
fix(try): includes inside attempt blocks now see correct variable state
vagetman Mar 2, 2026
6747f57
refactor(try): parallel try-block dispatch with flat-buf slot tracking
vagetman Mar 3, 2026
b936717
refactor(config): remove namespace field and related methods from Con…
vagetman Mar 3, 2026
0e55188
perf: use Bytes in Expr::String, optimize tag dispatch, rename Elemen…
vagetman Mar 3, 2026
f515661
refactor(parser): optimize attribute extraction and reduce Vec alloca…
vagetman Mar 3, 2026
afbe7aa
perf: use first-byte dispatch to eliminate unnecessary parser attempts
vagetman Mar 3, 2026
e3c7cac
Refactor streaming parser with nom 8 with `Parser` trait
vagetman Mar 3, 2026
50e315f
refactor(parser): optimize HashMap usage and reduce allocations
vagetman Mar 3, 2026
4d559f8
refactor(parser): improve performance of byte conversion and attribut…
vagetman Mar 3, 2026
e165e10
refactor: performance and clippy recommendations. i64 removed per spec
vagetman Mar 3, 2026
2e72049
perf refactor: removed over-engineered focus on Unicode
vagetman Mar 4, 2026
5d09318
refactor(config): add chunk_size configuration for streaming input an…
vagetman Mar 4, 2026
2e11c3c
refactor: update Value type to use Rc<RefCell> for lists and dictiona…
vagetman Mar 4, 2026
a701641
refactor: refactor: capacity allocation in various data structures, r…
vagetman Mar 4, 2026
f83cfe9
refactor(parser): simplify parse_content_complete by removing duplica…
vagetman Mar 4, 2026
880b285
Enhance ESI parsing and configuration
vagetman Mar 5, 2026
59f10bc
refactor(functions): simplify argument validation with macros for con…
vagetman Mar 5, 2026
8401ab0
feat(functions): simplify argument parsing for Value type, crates ver…
vagetman Mar 5, 2026
1a50f80
perf refactor: replace `fold_many0` with loops for improved performan…
vagetman Mar 5, 2026
10c51c0
refactor(functions): use Value::as_i32() for integer coercion
vagetman Mar 5, 2026
0445b30
refactor(parser): improve error handling and parsing logic for tag at…
vagetman Mar 5, 2026
5616b20
refactor(parser): replace streaming parsers with complete parsers for…
vagetman Mar 5, 2026
b4000f1
chore: bump version to 0.7.0-beta.2
vagetman Mar 5, 2026
0f9402e
refactor: update module visibility and simplify imports in config and…
vagetman Mar 10, 2026
efef139
Code review improvements: single-char logical operators, backslash es…
vagetman Mar 10, 2026
317bec8
refactor: add expose-internals feature and update dependencies for im…
vagetman Mar 11, 2026
212e0c4
Refactor error handling in ESI crate
vagetman Mar 12, 2026
8517b9e
refactor: add parse_eof mode and naming cleanup
vagetman Mar 12, 2026
c8bbd13
refactor: enhance header attribute handling to support duplicates and…
vagetman Mar 13, 2026
efceb3c
refactor: enhance expression evaluation to support list concatenation…
vagetman Mar 13, 2026
e3a510a
refactor: remove unused ping function and related test
vagetman Mar 16, 2026
9d304f8
refactor: improve error handling in EvalContext and remove obsolete t…
vagetman Mar 25, 2026
3ade5ef
Refactor code structure for improved readability and maintainability
vagetman Mar 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
877 changes: 773 additions & 104 deletions Cargo.lock

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ members = [
"examples/esi_vars_example",
"examples/esi_example_variants",
]
resolver = "2"

[workspace.package]
version = "0.6.2"
version = "0.7.0-beta.3"
authors = [
"Kailan Blanks <kblanks@fastly.com>",
"Vadim Getmanshchuk <vadim@fastly.com>",
"Tyler McMullen <tyler@fastly.com>",
]
license = "MIT"
edition = "2018"
edition = "2021"
290 changes: 281 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,265 @@

This crate provides a streaming Edge Side Includes parser and executor designed for Fastly Compute.

The implementation is a subset of the [ESI Language Specification 1.0](https://www.w3.org/TR/esi-lang/) supporting the following tags:
The implementation is a subset of Akamai ESI 5.0 supporting the following tags:

- `<esi:include>` (+ `alt`, `onerror="continue"`)
- `<esi:include>`
- `<esi:eval>` - evaluates included content as ESI
- `<esi:try>` | `<esi:attempt>` | `<esi:except>`
- `<esi:vars>` | `<esi:assign>`
- `<esi:vars>` | `<esi:assign>` (with subscript support for dict/list assignment)
- `<esi:choose>` | `<esi:when>` | `<esi:otherwise>`
- `<esi:foreach>` | `<esi:break>` (loop over lists and dicts)
- `<esi:function>` | `<esi:return>` (user-defined functions)
- `<esi:comment>`
- `<esi:remove>`
- `<esi:text>` (raw passthrough — content is emitted verbatim, no ESI processing)

**Note:** The following tags support nested ESI tags: `<esi:try>`, `<esi:attempt>`, `<esi:except>`, `<esi:choose>`, `<esi:when>`, `<esi:otherwise>`, `<esi:foreach>`, `<esi:function>`, and `<esi:assign>` (long form only).

**Dynamic Content Assembly (DCA)**: Both `<esi:include>` and `<esi:eval>` support the `dca` attribute:

- `dca="none"` (default): For `include`, inserts raw content without ESI processing. For `eval`, fragment executes in parent's context (variables shared).
- `dca="esi"`: Two-phase processing: fragment is first processed in an isolated context, then the output is processed in parent's context (variables from phase 1 don't leak, but output can contain ESI tags).

**Include vs Eval**:

- `<esi:include>`: Fetches content from origin
- `dca="none"`: Inserts content verbatim (no ESI processing)
- `dca="esi"`: Parses and evaluates content as ESI before insertion
- `<esi:eval>`: Fetches content and **always** parses it as ESI (blocking operation)
- `dca="none"`: Evaluates in parent's namespace (variables from fragment affect parent)
- `dca="esi"`: **Two-phase**: Phase 1 processes fragment in isolated context (variables set here stay isolated), then Phase 2 processes the output in parent's context (output can contain ESI that accesses parent variables)

### Include/Eval Attributes

Both `<esi:include>` and `<esi:eval>` support the following attributes:

**Required:**

- `src="url"` - Source URL to fetch (supports ESI expressions)

**Fallback & Error Handling:**

- `alt="url"` - Fallback URL if primary request fails (include only, eval uses try/except)
- `onerror="continue"` - On error, delete the tag with no output (continue processing without failing)

**Content Processing:**

- `dca="none|esi"` - Dynamic Content Assembly mode (default: `none`)
- `none`: For include, insert content as-is. For eval, process in parent's context (single-phase).
- `esi`: For include, parse and evaluate as ESI. For eval, two-phase processing: first in isolated context, then output processed in parent context.

**Caching:**

- `ttl="duration"` - Cache time-to-live (e.g., `"120m"`, `"1h"`, `"2d"`, `"0s"` to disable)
- `no-store="on|off"` - Enable/disable cache bypass (`on` bypasses cache, `off` leaves caching enabled)

**Request Configuration:**

- `maxwait="milliseconds"` - Request timeout in milliseconds
- `method="GET|POST"` - HTTP method (default: `GET`)
- `entity="body"` - Request body for POST requests

**Headers:**

- `appendheaders="header:value"` - Append headers to the request
- `removeheaders="header1,header2"` - Remove headers from the request
- `setheaders="header:value"` - Set/replace headers on the request

**Parameters:**

- Nested `<esi:param name="key" value="val"/>` elements append query parameters to the URL

**Example:**

```html
<esi:include src="http://api.example.com/user" alt="http://cache.example.com/user" dca="esi" ttl="5m" maxwait="1000" onerror="continue">
<esi:param name="id" value="$(user_id)" />
<esi:param name="format" value="'json'" />
</esi:include>
```

Other tags will be ignored and served to the client as-is.

This implementation also includes an expression interpreter and library of functions that can be used. Current functions include:
### Expression Features

- **Integer literals**: `42`, `-10`, `0`
- **String literals**: `'single quoted'`, `"double quoted"`, `'''triple quoted'''`
- **Dict literals**: `{'key1': 'value1', 'key2': 'value2'}`
- **List literals**: `['item1', 'item2', 'item3']`
- **Nested structures**: Lists can be nested: `['one', ['a', 'b', 'c'], 'three']`
- **Subscript assignment**: `<esi:assign name="dict{'key'}" value="val"/>` or `<esi:assign name="list{0}" value="val"/>`
- **Subscript access**: `$(dict{'key'})` or `$(list{0})`
- **Foreach loops**: Iterate over lists or dicts with `<esi:foreach>` and use `<esi:break>` to exit early
- **Comparison operators**: `==`, `!=`, `<`, `>`, `<=`, `>=`, `has`, `has_i`, `matches`, `matches_i`
- `has` - Case-sensitive substring containment: `$(str) has 'substring'`
- `has_i` - Case-insensitive substring containment: `$(str) has_i 'substring'`
- `matches` - Case-sensitive regex matching: `$(str) matches 'pattern'`
- `matches_i` - Case-insensitive regex matching: `$(str) matches_i 'pattern'`
- **Logical operators**: `&&` (and), `||` (or), `!` (not)

### Function Library

This implementation includes a comprehensive library of ESI functions:

**String Manipulation:**

- `$lower(string)` - Convert to lowercase
- `$upper(string)` - Convert to uppercase
- `$lstrip(string)`, `$rstrip(string)`, `$strip(string)` - Remove whitespace
- `$substr(string, start [, length])` - Extract substring
- `$replace(haystack, needle, replacement [, count])` - Replace occurrences
- `$str(value)` - Convert to string
- `$join(list, separator)` - Join list elements
- `$string_split(string, delimiter [, maxsplit])` - Split string into list

**Encoding/Decoding:**

- `$html_encode(string)`, `$html_decode(string)` - HTML entity encoding
- `$url_encode(string)`, `$url_decode(string)` - URL encoding
- `$base64_encode(string)`, `$base64_decode(string)` - Base64 encoding/decoding
- `$convert_to_unicode(string)`, `$convert_from_unicode(string)` - Unicode conversion

**Quote Helpers:**

- `$dollar()` - Returns `$`
- `$dquote()` - Returns `"`
- `$squote()` - Returns `'`

**Type Conversion & Checks:**

- `$int(value)` - Convert to integer
- `$exists(value)` - Check if value exists
- `$is_empty(value)` - Check if value is empty
- `$len(value)` - Get length of string or list

**List Operations:**

- `$list_delitem(list, index)` - Remove item from list
- `$index(string, substring)`, `$rindex(string, substring)` - Find substring position

**Cryptographic:**

- `$digest_md5(string)` - Generate MD5 hash (binary)
- `$digest_md5_hex(string)` - Generate MD5 hash (hex string)

**Time/Date:**

- `$time()` - Current Unix timestamp
- `$http_time(timestamp)` - Format timestamp as HTTP date
- `$strftime(timestamp, format)` - Format timestamp with custom format
- `$bin_int(binary_string)` - Convert binary string to integer

**Random & Response:**

- `$lower(string)`
- `$html_encode(string)`
- `$replace(haystack, needle, replacement [, count])`
- `$rand()` - Generate random number
- `$last_rand()` - Get last generated random number

**Response Manipulation:**

These functions modify the HTTP response sent to the client:

- `$add_header(name, value)` - Add a custom response header
```html
<esi:vars>$add_header('X-Custom-Header', 'my-value')</esi:vars>
```
- `$set_response_code(code [, body])` - Set HTTP status code and optionally override response body
```html
<esi:vars>$set_response_code(404, 'Page not found')</esi:vars>
```
- `$set_redirect(url)` - Set HTTP redirect (302 Moved Temporarily)
```html
<esi:vars>$set_redirect('https://example.com/new-location')</esi:vars> <esi:vars>$set_redirect('https://example.com/moved'</esi:vars>
```

**Diagnostic:**

- `$ping()` - Returns the string `"pong"` (useful for testing)

**Note:** Response manipulation functions are buffered during ESI processing and applied when `process_response()` sends the final response to the client.

### User-Defined Functions

You can define reusable functions with `<esi:function>` and return values with `<esi:return>`:

```html
<esi:function name="greet">
<esi:assign name="greeting" value="'Hello, ' + $(ARGS{0}) + '!'" />
<esi:return value="$(greeting)" />
</esi:function>

<esi:vars>$greet('World')</esi:vars>
```

- `<esi:function name="...">` defines a function; the body can contain any ESI tags.
- `<esi:return value="..."/>` returns a value from the function.
- Inside a function body, `$(ARGS)` is a list of the positional arguments passed to the call, and individual arguments can be accessed with `$(ARGS{0})`, `$(ARGS{1})`, etc.
- Functions support recursion up to the configured depth (default: 5, see [Configuration](#configuration)).
- User-defined functions take priority over built-in functions of the same name.

### Built-in Variables

The following variables are available in ESI expressions:

**Request metadata:**

- `$(REQUEST_METHOD)` - HTTP method of the original client request (e.g. `GET`)
- `$(REQUEST_PATH)` - Path component of the request URL
- `$(QUERY_STRING)` - Raw query string from the request URL
- `$(REMOTE_ADDR)` - Client IP address

**HTTP headers:**

- `$(HTTP_<HEADER>)` - Value of the named request header (e.g. `$(HTTP_HOST)`, `$(HTTP_ACCEPT)`)
- `$(HTTP_COOKIE{'name'})` - Value of a specific cookie from the `Cookie` header

**Regex captures:**

- `$(MATCHES{0})`, `$(MATCHES{1})`, … - Capture groups from the last `matches` / `matches_i` operator or `<esi:when matchname="...">` test

### Configuration

`Configuration` controls the processor's runtime behaviour. All fields have sensible defaults and can be customised with builder methods:

```rust,no_run
let config = esi::Configuration::default()
.with_escaped(true) // unescape HTML entities in URLs (default: true)
.with_chunk_size(32768) // streaming read buffer, in bytes (default: 16384)
.with_function_recursion_depth(10) // max depth for user-defined function calls (default: 10)
.with_caching(esi::CacheConfig {
is_rendered_cacheable: true,
rendered_cache_control: true,
rendered_ttl: Some(600),
is_includes_cacheable: true,
includes_default_ttl: Some(300),
includes_force_ttl: None,
});
```

| Field | Builder method | Default | Description |
| -------------------------- | ------------------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `is_escaped_content` | `with_escaped(bool)` | `true` | Unescape HTML entities in URLs. Set to `false` for non-HTML templates (e.g. JSON). |
| `chunk_size` | `with_chunk_size(usize)` | `16384` | Size (bytes) of the read buffer used when streaming ESI input. Larger values may improve throughput; smaller values reduce memory. |
| `function_recursion_depth` | `max_function_recursion_depth(usize)` | `5` | Maximum call-stack depth for user-defined ESI functions. |
| `cache` | `with_caching(CacheConfig)` | see below | Cache settings for rendered output and included fragments. |

**`CacheConfig` fields:**

| Field | Default | Description |
| ------------------------ | ------- | ---------------------------------------------------------------- |
| `is_rendered_cacheable` | `false` | Whether the final rendered output is cacheable. |
| `rendered_cache_control` | `false` | Emit a `Cache-Control` header on the rendered response. |
| `rendered_ttl` | `None` | TTL (seconds) for the rendered response. |
| `is_includes_cacheable` | `false` | Whether individual include responses should be cached. |
| `includes_default_ttl` | `None` | Default TTL (seconds) for cached includes. |
| `includes_force_ttl` | `None` | Force a specific TTL on all includes, overriding origin headers. |

## Example Usage

### Streaming Processing (Recommended)

The recommended approach uses streaming to process the document as it arrives, minimizing memory usage and latency:

```rust,no_run
use fastly::{http::StatusCode, mime, Error, Request, Response};

Expand Down Expand Up @@ -51,14 +291,15 @@ fn handle_request(req: Request) -> Result<(), Error> {
esi::Configuration::default()
);

// Stream the ESI response directly to the client
processor.process_response(
// The ESI source document. Note that the body will be consumed.
// The ESI source document. Body will be consumed and streamed.
&mut beresp,
// Optionally provide a template for the client response.
Some(Response::from_status(StatusCode::OK).with_content_type(mime::TEXT_HTML)),
// Provide logic for sending fragment requests, otherwise the hostname
// of the request URL will be used as the backend name.
Some(&|req| {
Some(&|req, _maxwait| {
println!("Sending request {} {}", req.get_method(), req.get_path());
Ok(req.with_ttl(120).send_async("mock-s3")?.into())
}),
Expand All @@ -82,6 +323,37 @@ fn handle_request(req: Request) -> Result<(), Error> {
}
```

### Custom Stream Processing

For advanced use cases, you can process any `BufRead` source and write to any `Write` destination:

```rust,no_run
use std::io::{BufReader, Write};
use esi::{Processor, Configuration};

fn process_custom_stream(
input: impl std::io::Read,
output: &mut impl Write,
) -> Result<(), esi::ESIError> {
let mut processor = Processor::new(None, Configuration::default());

// Process from any readable source
let reader = BufReader::new(input);

processor.process_stream(
reader,
output,
Some(&|req, _maxwait| {
// Custom fragment dispatcher
Ok(req.send_async("backend")?.into())
}),
None,
)?;

Ok(())
}
```

See example applications in the [`examples`](./examples) subdirectory or read the hosted documentation at [docs.rs/esi](https://docs.rs/esi). Due to the fact that this processor streams fragments to the client as soon as they are available, it is not possible to return a relevant status code for later errors once we have started streaming the response to the client. For this reason, it is recommended that you refer to the [`esi_example_advanced_error_handling`](./examples/esi_example_advanced_error_handling) application, which allows you to handle errors gracefully by maintaining ownership of the output stream.

## Testing
Expand Down
27 changes: 26 additions & 1 deletion esi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,38 @@ description = "A streaming parser and executor for Edge Side Includes"
repository = "https://github.com/fastly/esi"
readme = "./README.md"

[features]
expose-internals = []

[dependencies]
quick-xml = "0.38.0"
thiserror = "2.0.6"
fastly = "^0.11"
log = "^0.4"
regex = "1.11.1"
html-escape = "0.2.13"
nom = "8"
bytes = "1.5"
atoi = "2"
base64 = "0.22"
percent-encoding = "2.3"
md5 = "0.8.0"
chrono = { version = "0.4", default-features = false, features = [
"clock",
"std",
] }
rand = "0.10.0"

[dev-dependencies]
esi = { path = ".", features = ["expose-internals"] }
env_logger = "^0.11"
criterion = { version = "0.5", default-features = false }

[[bench]]
name = "parser_benchmarks"
harness = false
required-features = ["expose-internals"]

[[bench]]
name = "interpolated_text_bench"
harness = false
required-features = ["expose-internals"]
Loading
Loading