Rust插件库esi的使用:高效实现ESI(Edge Side Includes)功能的Rust库
ESI for Fastly
这个crate提供了一个流式Edge Side Includes解析器和执行器,专为Fastly Compute设计。
实现是ESI语言规范1.0的一个子集,支持以下标签:
<esi:include>
(+alt
,onerror="continue"
)<esi:try>
|<esi:attempt>
|<esi:except>
<esi:vars>
|<esi:assign>
<esi:choose>
|<esi:when>
|<esi:otherwise>
<esi:comment>
<esi:remove>
其他标签将被忽略并按原样提供给客户端。
此实现还包括一个表达式解释器和可用函数库。当前函数包括:
$lower(string)
$html_encode(string)
$replace(haystack, needle, replacement [, count])
示例用法
use fastly::{http::StatusCode, mime, Error, Request, Response};
fn main() {
if let Err(err) = handle_request(Request::from_client()) {
println!(“returning error response”);
Response::<span class="hljs-title function_ invoke__">from_status</span>(StatusCode::INTERNAL_SERVER_ERROR)
.<span class="hljs-title function_ invoke__">with_body</span>(err.<span class="hljs-title function_ invoke__">to_string</span>())
.<span class="hljs-title function_ invoke__">send_to_client</span>();
}
}
fn handle_request(req: Request) -> Result<(), Error> {
// 从后端获取ESI文档。
let mut beresp = req.clone_without_body().send(“origin_0”)?;
<span class="hljs-comment">// 如果响应是HTML,我们可以解析其中的ESI标签。</span>
<span class="hljs-keyword">if</span> beresp
.<span class="hljs-title function_ invoke__">get_content_type</span>()
.<span class="hljs-title function_ invoke__">map</span>(|c| c.<span class="hljs-title function_ invoke__">subtype</span>() == mime::HTML)
.<span class="hljs-title function_ invoke__">unwrap_or</span>(<span class="hljs-literal">false</span>)
{
<span class="hljs-keyword">let</span> <span class="hljs-variable">processor</span> = esi::Processor::<span class="hljs-title function_ invoke__">new</span>(
<span class="hljs-comment">// 原始客户端请求。</span>
<span class="hljs-title function_ invoke__">Some</span>(req),
<span class="hljs-comment">// 使用默认ESI配置。</span>
esi::Configuration::<span class="hljs-title function_ invoke__">default</span>()
);
processor.<span class="hljs-title function_ invoke__">process_response</span>(
<span class="hljs-comment">// ESI源文档。注意,body将被消耗。</span>
&<span class="hljs-keyword">mut</span> beresp,
<span class="hljs-comment">// 可选地提供客户端响应的模板。</span>
<span class="hljs-title function_ invoke__">Some</span>(Response::<span class="hljs-title function_ invoke__">from_status</span>(StatusCode::OK).<span class="hljs-title function_ invoke__">with_content_type</span>(mime::TEXT_HTML)),
<span class="hljs-comment">// 提供发送片段请求的逻辑,否则请求URL的主机名将被用作后端名称。</span>
<span class="hljs-title function_ invoke__">Some</span>(&|req| {
<span class="hljs-built_in">println!</span>(<span class="hljs-string">"Sending request {} {}"</span>, req.<span class="hljs-title function_ invoke__">get_method</span>(), req.<span class="hljs-title function_ invoke__">get_path</span>());
<span class="hljs-title function_ invoke__">Ok</span>(req.<span class="hljs-title function_ invoke__">with_ttl</span>(<span class="hljs-number">120</span>).<span class="hljs-title function_ invoke__">send_async</span>(<span class="hljs-string">"mock-s3"</span>)?.<span class="hljs-title function_ invoke__">into</span>())
}),
<span class="hljs-comment">// 可选地提供一个方法,在片段响应流式传输到客户端之前处理它们。</span>
<span class="hljs-title function_ invoke__">Some</span>(&|req, resp| {
<span class="hljs-built_in">println!</span>(
<span class="hljs-string">"Received response for {} {}"</span>,
req.<span class="hljs-title function_ invoke__">get_method</span>(),
req.<span class="hljs-title function_ invoke__">get_path</span>()
);
<span class="hljs-title function_ invoke__">Ok</span>(resp)
}),
)?;
} <span class="hljs-keyword">else</span> {
<span class="hljs-comment">// 否则,我们可以直接返回响应。</span>
beresp.<span class="hljs-title function_ invoke__">send_to_client</span>();
}
<span class="hljs-title function_ invoke__">Ok</span>(())
}
由于此处理器在片段可用时立即将其流式传输到客户端,一旦我们开始将响应流式传输到客户端,就无法为后续错误返回相关状态码。因此,建议您参考esi_example_advanced_error_handling
应用程序,它允许您通过保持输出流的所有权来优雅地处理错误。
测试
要运行此存储库中包的测试套件,必须在您的PATH中可用viceroy
。您可以通过运行以下命令安装最新版本的viceroy
:
cargo install viceroy
许可证
此项目的源代码和文档根据MIT许可证发布。
完整示例代码
use fastly::{http::StatusCode, mime, Error, Request, Response};
fn main() {
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> {
// 从后端获取ESI文档
let mut beresp = req.clone_without_body().send(“origin_0”)?;
// 检查响应是否为HTML类型
if beresp
.get_content_type()
.map(|c| c.subtype() == mime::HTML)
.unwrap_or(false)
{
// 创建ESI处理器
let processor = esi::Processor::new(
// 原始客户端请求
Some(req),
// 使用默认ESI配置
esi::Configuration::default()
);
// 处理ESI响应
processor.process_response(
// ESI源文档
&mut beresp,
// 客户端响应模板
Some(Response::from_status(StatusCode::OK).with_content_type(mime::TEXT_HTML)),
// 片段请求发送逻辑
Some(&|req| {
println!("Sending request {} {}", req.get_method(), req.get_path());
Ok(req.with_ttl(120).send_async("mock-s3")?.into())
}),
// 片段响应处理逻辑
Some(&|req, resp| {
println!(
"Received response for {} {}",
req.get_method(),
req.get_path()
);
Ok(resp)
}),
)?;
} else {
// 非HTML响应直接返回
beresp.send_to_client();
}
Ok(())
}
esi是一个用于高效实现ESI(Edge Side Includes)功能的Rust库。该库提供了在边缘服务器上处理ESI标签的能力,帮助开发者实现内容片段的动态组装和缓存优化。
主要功能
- 解析和处理ESI标签
- 支持条件包含、变量替换等标准ESI功能
- 提供异步HTTP请求处理
- 可自定义解析器和处理器
安装方法
在Cargo.toml中添加依赖:
[dependencies]
esi = "0.3"
基本使用方法
use esi::EsiParser;
use std::collections::HashMap;
#[tokio::main]
async fn main() {
// 示例ESI内容
let content = r#"
<html>
<esi:include src="/header.html"/>
<body>
<esi:include src="/widget.html?user=$(USER_ID)"/>
</body>
</html>
"#;
// 创建解析器
let mut parser = EsiParser::new();
// 设置变量
let mut variables = HashMap::new();
variables.insert("USER_ID".to_string(), "12345".to_string());
// 处理ESI标签
let result = parser.parse(content, &variables).await.unwrap();
println!("Processed content: {}", result);
}
高级配置示例
use esi::{EsiParser, EsiConfig};
use reqwest::Client;
#[tokio::main]
async fn main() {
let config = EsiConfig::default()
.with_http_client(Client::new())
.with_timeout(std::time::Duration::from_secs(5));
let parser = EsiParser::with_config(config);
// 自定义处理器示例
let content = r#"<esi:vars>Hello, $(USER_NAME)!</esi:vars>"#;
let mut variables = HashMap::new();
variables.insert("USER_NAME".to_string(), "Alice".to_string());
let result = parser.parse(content, &variables).await.unwrap();
println!("{}", result); // 输出: Hello, Alice!
}
错误处理
use esi::{EsiParser, EsiError};
async fn process_content(content: &str) -> Result<String, EsiError> {
let parser = EsiParser::new();
let variables = HashMap::new();
parser.parse(content, &variables).await
}
该库支持所有标准ESI标签,包括include、vars、choose等,并提供了灵活的扩展接口用于自定义处理逻辑。
完整示例demo
use esi::{EsiParser, EsiConfig, EsiError};
use reqwest::Client;
use std::collections::HashMap;
#[tokio::main]
async fn main() -> Result<(), EsiError> {
// 示例1: 基本使用
println!("=== 基本使用示例 ===");
let basic_content = r#"
<html>
<head>
<title>ESI示例</title>
</head>
<body>
<esi:include src="/header.html"/>
<main>
<h1>欢迎使用ESI</h1>
<esi:include src="/widget.html?user=$(USER_ID)&lang=$(LANGUAGE)"/>
</main>
<esi:include src="/footer.html"/>
</body>
</html>
"#;
let mut parser = EsiParser::new();
let mut variables = HashMap::new();
variables.insert("USER_ID".to_string(), "1001".to_string());
variables.insert("LANGUAGE".to_string(), "zh-CN".to_string());
let basic_result = parser.parse(basic_content, &variables).await?;
println!("基本示例处理结果:\n{}", basic_result);
// 示例2: 高级配置
println!("\n=== 高级配置示例 ===");
let config = EsiConfig::default()
.with_http_client(Client::new())
.with_timeout(std::time::Duration::from_secs(3))
.with_max_redirects(5);
let advanced_parser = EsiParser::with_config(config);
let advanced_content = r#"
<esi:choose>
<esi:when test="$(LEVEL) == 'VIP'">
<div class="vip-welcome">尊贵的VIP用户,欢迎回来!</div>
</esi:when>
<esi:when test="$(LEVEL) == 'NORMAL'">
<div class="normal-welcome">欢迎回来!</div>
</esi:when>
<esi:otherwise>
<div class="guest-welcome">您好,请登录</div>
</esi:otherwise>
</esi:choose>
<esi:vars>
<p>当前时间: $(CURRENT_TIME)</p>
<p>用户积分: $(USER_POINTS)</p>
</esi:vars>
"#;
let mut advanced_vars = HashMap::new();
advanced_vars.insert("LEVEL".to_string(), "VIP".to_string());
advanced_vars.insert("CURRENT_TIME".to_string(), "2024-01-15 10:30:00".to_string());
advanced_vars.insert("USER_POINTS".to_string(), "1500".to_string());
let advanced_result = advanced_parser.parse(advanced_content, &advanced_vars).await?;
println!("高级配置处理结果:\n{}", advanced_result);
// 示例3: 错误处理
println!("\n=== 错误处理示例 ===");
let error_content = r#"<esi:include src="/non-existent-page.html"/>"#;
match parser.parse(error_content, &HashMap::new()).await {
Ok(result) => println!("成功: {}", result),
Err(e) => println!("错误处理示例 - 预期错误: {:?}", e),
}
Ok(())
}
// 自定义错误处理函数示例
async fn safe_parse_content(parser: &EsiParser, content: &str, variables: &HashMap<String, String>) -> String {
match parser.parse(content, variables).await {
Ok(result) => result,
Err(e) => {
eprintln!("解析失败: {:?}", e);
"解析失败,使用默认内容".to_string()
}
}
}
// 单元测试示例
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_basic_esi_parsing() {
let parser = EsiParser::new();
let mut variables = HashMap::new();
variables.insert("NAME".to_string(), "TestUser".to_string());
let content = r#"<esi:vars>Hello, $(NAME)!</esi:vars>"#;
let result = parser.parse(content, &variables).await.unwrap();
assert_eq!(result, "Hello, TestUser!");
}
}