错误
Actix Web 使用自己的actix_web::error::Error
类型和actix_web::error::ResponseError
trait 来处理来自 Web handler的错误。如果处理程序在也实现了ResponseError
trait则返回一个Error(指一般 Rust 特征std::error::Error),Actix Web 会将该错误呈现为 HTTP 响应及其相应的actix_web::http::StatusCode
. 默认情况下会生成内部服务器错误:ResultResponseError
和actix_web::http::StatusCode
。
pub trait ResponseError {
fn error_response(&self) -> Response<Body>;
fn status_code(&self) -> StatusCode;
}
一个Responder将兼容的Results 强制转换为 HTTP 响应:
impl<T: Responder, E: Into<Error>> Responder for Result<T, E>
Error上面的代码中是actix-web的错误定义,任何实现的错误都ResponseError
可以自动转换为错误。Actix Web 提供了ResponseError一些常见非 Actix 错误的实现。例如,如果处理程序响应io::Error,则该错误将转换为HttpInternalServerError
:
use std::io;
use actix_files::NamedFile;
fn index(\_req: HttpRequest) -> io::Result<NamedFile> {
Ok(NamedFile::open("static/index.html")?)
}
自定义错误
下面示例实现ResponseError,使用derive_more板条箱进行声明性错误枚举。
use actix_web::{error, Result};
use derive_more::{Display, Error};
# [derive(Debug, Display, Error)] #[display(fmt = "my error: {}", name)]
struct MyError {
name: &'static str,
}
// Use default implementation for `error_response()` method
impl error::ResponseError for MyError {}
async fn index() -> Result<&'static str, MyError> {
Err(MyError { name: "test" })
}
ResponseError有一个默认实现,error_response()
将呈现500(内部服务器错误),这就是index上面执行处理程序时会发生的情况。覆盖error_response()以产生更有用的结果:
use actix_web::{
error, get,
http::{header::ContentType, StatusCode},
App, HttpResponse,
};
use derive_more::{Display, Error};
# [derive(Debug, Display, Error)]
enum MyError { #[display(fmt = "internal error")]
InternalError,
#[display(fmt = "bad request")]
BadClientData,
#[display(fmt = "timeout")]
Timeout,
}
impl error::ResponseError for MyError {
fn error_response(&self) -> HttpResponse {
HttpResponse::build(self.status_code())
.insert_header(ContentType::html())
.body(self.to_string())
}
fn status_code(&self) -> StatusCode {
match *self {
MyError::InternalError => StatusCode::INTERNAL_SERVER_ERROR,
MyError::BadClientData => StatusCode::BAD_REQUEST,
MyError::Timeout => StatusCode::GATEWAY_TIMEOUT,
}
}
}
# [get("/")]
async fn index() -> Result<&'static str, MyError> {
Err(MyError::BadClientData)
}
错误
Actix Web 提供了一组错误帮助函数,可用于从其他错误生成特定的 HTTP 错误代码。在这里,我们使用MyError以下方法将未实现该ResponseError
trait的 转换为400(错误请求)map_err:
use actix_web::{error, get, App, HttpServer};
# [derive(Debug)]
struct MyError {
name: &'static str,
}
# [get("/")]
async fn index() -> actix_web::Result<String> {
let result = Err(MyError { name: "test error" });
result.map_err(|err| error::ErrorBadRequest(err.name))
}
有关可用错误帮助程序的完整列表,请参阅actix-weberror模块的 API 文档。
错误
Actix 在日志级别记录所有错误WARN。如果应用程序的日志级别设置为DEBUG并RUST_BACKTRACE启用,则还会记录回溯。这些可以通过环境变量进行配置:
RUST_BACKTRACE=1 RUST_LOG=actix_web=debug cargo run
该Error类型使用原因的错误回溯(如果可用)。如果底层故障不提供回溯,则会构造一个新的回溯,指向发生转换的点(而不是错误的起源)。
错误
考虑将应用程序产生的错误分为两大类可能会很有用:那些旨在面向用户的错误和那些不面向用户的错误。
前者的一个例子是,我可能会使用 failure 来指定一个UserError枚举,该枚举封装了 aValidationError以在用户发送错误输入时返回:
use actix_web::{
error, get,
http::{header::ContentType, StatusCode},
App, HttpResponse, HttpServer,
};
use derive_more::{Display, Error};
# [derive(Debug, Display, Error)]
enum UserError { #[display(fmt = "Validation error on field: {}", field)]
ValidationError { field: String },
}
impl error::ResponseError for UserError {
fn error_response(&self) -> HttpResponse {
HttpResponse::build(self.status_code())
.insert_header(ContentType::html())
.body(self.to_string())
}
fn status_code(&self) -> StatusCode {
match \*self {
UserError::ValidationError { .. } => StatusCode::BAD_REQUEST,
}
}
}
这将完全按照预期运行,因为用 定义的错误消息display是按照明确的意图写入的,以供用户读取。然而,发回错误消息并不适合所有错误——在服务器环境中会发生许多故障,我们可能希望对用户隐藏具体信息。例如,如果数据库出现故障并且客户端库开始产生连接超时错误,或者 HTML 模板格式不正确且呈现时出现错误。在这些情况下,最好将错误映射到适合用户使用的通用错误。InternalError下面是一个使用自定义消息将内部错误映射到面向用户的示例:
use actix_web::{
error, get,
http::{header::ContentType, StatusCode},
App, HttpResponse, HttpServer,
};
use derive_more::{Display, Error};
# [derive(Debug, Display, Error)]
enum UserError { #[display(fmt = "An internal error occurred. Please try again later.")]
InternalError,
}
impl error::ResponseError for UserError {
fn error_response(&self) -> HttpResponse {
HttpResponse::build(self.status_code())
.insert_header(ContentType::html())
.body(self.to_string())
}
fn status_code(&self) -> StatusCode {
match *self {
UserError::InternalError => StatusCode::INTERNAL_SERVER_ERROR,
}
}
}
# [get("/")]
async fn index() -> Result<&'static str, UserError> {
do_thing_that_fails().map_err(|\_e| UserError::InternalError)?;
Ok("success!")
}
通过将错误分为面向用户的错误和不面向用户的错误,我们可以确保不会意外地将用户暴露于应用程序内部抛出的他们不希望看到的错误。
错误
这是一个基本示例,使用middleware::Logger
它取决于env_logger和log:
[dependencies]
env_logger = "0.8"
log = "0.4"
use actix_web::{error, get, middleware::Logger, App, HttpServer, Result};
use derive_more::{Display, Error};
use log::info;
# [derive(Debug, Display, Error)] #[display(fmt = "my error: {}", name)]
pub struct MyError {
name: &'static str,
}
// Use default implementation for `error_response()` method
impl error::ResponseError for MyError {}
# [get("/")]
async fn index() -> Result<&'static str, MyError> {
let err = MyError { name: "test error" };
info!("{}", err);
Err(err)
}
# [rustfmt::skip] #[actix_web::main]
async fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "info");
std::env::set_var("RUST_BACKTRACE", "1");
env_logger::init();
HttpServer::new(|| {
let logger = Logger::default();
App::new()
.wrap(logger)
.service(index)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
URL调度
URL 调度提供了一种通过简单模式匹配语言将 URL 映射到对应处理程序代码的简单方法。如果模式之一与与请求关联的路径信息匹配,则调用特定的处理程序对象。请求处理程序是一个函数,它接受零个或多个可从请求中提取的参数(即impl FromRequest)并返回可转换为 HttpResponse 的类型(即impl Responder)。处理程序部分提供了更多信息。
资源
资源配置是将新资源添加到应用程序的行为。资源有一个名称,它充当 URL 生成的标识符。该名称还允许开发人员向现有资源添加路由。资源还有一个模式,旨在与URL的PATH部分进行匹配(方案和端口后面的部分,例如URL中的/foo/bar http://localhost:8080/foo/bar?q=value) 。它与QUERY部分( ?之后的部分,例如http://localhost:8080/foo/bar?q=value中的q=value)不匹配。
App ::route()方法提供了注册路由的简单方法。此方法将单个路由添加到应用程序路由表中。该方法接受路径模式、HTTP 方法和处理函数。route()可以对同一路径多次调用方法,在这种情况下,多个路由会注册同一资源路径。
use actix_web::{web, App, HttpResponse, HttpServer};
async fn index() -> HttpResponse {
HttpResponse::Ok().body("Hello")
}
# [actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.route("/", web::get().to(index))
.route("/user", web::post().to(index))
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
虽然App::route()提供了注册路由的简单方法,但要访问完整的资源配置,必须使用不同的方法。App ::service()方法将单个资源添加到应用程序路由表中。此方法接受路径模式、防护和一条或多条路线。
use actix_web::{guard, web, App, HttpResponse};
async fn index() -> HttpResponse {
HttpResponse::Ok().body("Hello")
}
pub fn main() {
App::new()
.service(web::resource("/prefix").to(index))
.service(
web::resource("/user/{name}")
.name("user_detail")
.guard(guard::Header("content-type", "application/json"))
.route(web::get().to(HttpResponse::Ok))
.route(web::put().to(HttpResponse::Ok)),
);
}
如果资源不包含任何路由或没有任何匹配的路由,则返回NOT FOUND HTTP 响应。
配置
资源包含一组路由。每个路由依次有一组guards和一个处理程序。可以使用返回新RouteResource::route()
实例引用创建新路由。默认情况下,路由不包含任何guards,因此匹配所有请求,默认处理程序为:HttpNotFound
应用程序根据资源注册和路由注册期间定义的路由标准路由传入请求。资源按照路由注册的顺序匹配它包含的所有路由Resource::route()。一条路线可以包含任意数量的guards,但只能包含一名处理程序。
App::new().service(
web::resource("/path").route(
web::route()
.guard(guard::Get())
.guard(guard::Header("content-type", "text/plain"))
.to(HttpResponse::Ok),
),
)
在此示例中,如果GET的请求包含Content-Type
、该head的值为text/plain
、路径等于/path
,则GET 返回HttpResponse::Ok()
。Content-Type/path 如果资源无法匹配任何路由,则返回“NOT FOUND”响应。ResourceHandler::route()返回一个Route对象。可以使用类似构建器的模式来配置路由。可以使用以下配置方法:
- Route::guard()注册一个新的
guards
。每条路线可以注册任意数量的警卫。 - Route::method()注册一个方法保护。每条路线可以注册任意数量的警卫。
- Route::to()为此路由注册一个异步处理函数。只能注册一个处理程序。通常处理程序注册是最后一个配置操作。
路由
path路由配置的主要目的是根据 URL 路径模式匹配(或不匹配)请求。path表示所请求的 URL 的路径部分。
actix-web执行此操作的方式非常简单。当请求进入系统时,对于系统中存在的每个资源配置声明,actix 都会根据声明的模式检查请求的路径。此检查按照通过App::service()
方法声明路由的顺序进行。如果找不到资源,则使用默认资源作为匹配的资源。当声明路由配置时,它可能包含路由保护参数。与路由声明关联的所有路由防护都必须用于true在检查期间用于给定请求的路由配置。如果提供给路由配置的路由保护参数集中的任何保护false在检查期间返回,则该路由将被跳过,并且路由匹配将继续通过有序的路由集。
如果任何路由匹配,则路由匹配过程停止,并调用与该路由关联的处理程序。如果在用尽所有路由模式后没有路由匹配,则返回NOT FOUND响应。
资源模式
actix 在模式参数中使用的模式匹配语言的语法很简单。路由配置中使用的模式可以以/
开头。如果模式不以/
字符开头,则在匹配时将在其前面添加/
。例如,以下模式是等效的:
{foo}/bar/baz
和:
/{foo}/bar/baz
变量部分(替换标记)以{identifier}形式指定,这意味着“接受下一个/
字符之前的任何字符,并将其用作对象中的名称HttpRequest.match_info()
”。模式中的替换标记与正则表达式匹配[^{}/]+
。Amatch_info
是表示基于路由模式从URLParams中提取的动态部分的对象。它可以作为request.match_info
使用。例如,以下模式定义了一个文字段 (foo) 和两个替换标记(baz 和 bar):
foo/{baz}/{bar}
上面的模式将匹配这些 URL,生成以下匹配信息:
foo/1/2 -> Params {'baz': '1', 'bar': '2'}
foo/abc/def -> Params {'baz': 'abc', 'bar': 'def'}
但是,它不会匹配以下模式:
foo/1/2/ -> No match (trailing slash)
bar/abc/def -> First segment literal mismatch
段中段替换标记的匹配将仅进行到模式中段中的第一个非字母数字字符。因此,例如,如果使用此路由模式:
foo/{name}.html
文字路径/foo/biz.html将与上述路由模式匹配,匹配结果将为Params {'name': 'biz'}
。但是,文字路径将不匹配,因为它在由 表示的段末尾/foo/biz
不包含文字(它只包含 biz
,而不包含 biz.html
)。.html{name}.html
要捕获这两个片段,可以使用两个替换标记:
foo/{name}.{ext}
文字路径/foo/biz.html
将与上述路由模式匹配,匹配结果将为Params {'name': 'biz', 'ext': 'html'}
。.发生这种情况是因为两个替换标记{name}和之间存在 (句点) 的字面部分{ext}。
替换标记可以选择指定一个正则表达式,该表达式将用于决定路径段是否应与标记匹配。要指定替换标记应仅匹配正则表达式定义的一组特定字符,您必须使用稍微扩展形式的替换标记语法。在大括号内,替换标记名称后面必须跟一个冒号,然后是正则表达式。与替换标记关联的默认正则表达式[^/]+匹配一个或多个不是斜杠的字符。例如,在底层,替换标记{foo}可以更详细地拼写为{foo:[^/]+}。您可以将其更改为任意正则表达式以匹配任意字符序列,例如{foo:\d+}仅匹配数字。
段必须至少包含一个字符才能匹配段替换标记。例如,对于 URL `/abc/``:
/abc/{foo}将不匹配。
/{foo}/会匹配。
注意:在匹配模式之前,路径将被 URL 不加引号并解码为有效的 unicode 字符串,并且表示匹配路径段的值也将被 URL 不加引号。
例如,以下模式:
foo/{bar}
当匹配以下 URL 时:
http://example.com/foo/La%20Pe%C3%B1a
匹配字典将如下所示(该值经过 URL 解码):
Params {'bar': 'La Pe\xf1a'}
路径段中的文字字符串应表示提供给 actix 的路径的解码值。您不想在模式中使用 URL 编码值。例如,而不是这个:
/Foo%20Bar/{baz}
你会想要使用这样的东西:
/Foo Bar/{baz}
可以获得“尾部匹配”。为此,必须使用自定义正则表达式。
foo/{bar}/{tail:.\*}
上面的模式将匹配这些 URL,生成以下匹配信息:
foo/1/2/ -> Params {'bar': '1', 'tail': '2/'}
foo/abc/def/a/b/c -> Params {'bar': 'abc', 'tail': 'def/a/b/c'}
范围界定
范围界定可帮助您组织共享公共根路径的路由。您可以在范围内嵌套范围。假设您想要组织用于查看“用户”的端点的路径。此类路径可能包括:
/用户
/用户/显示
/用户/显示/{id}
这些路径的范围布局如下所示:
# [get("/show")]
async fn show_users() -> HttpResponse {
HttpResponse::Ok().body("Show users")
}
# [get("/show/{id}")]
async fn user_detail(path: web::Path<(u32,)>) -> HttpResponse {
HttpResponse::Ok().body(format!("User detail: {}", path.into_inner().0))
}
# [actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new().service(
web::scope("/users")
.service(show_users)
.service(user_detail),
)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
作用域路径可以包含可变路径段作为资源。与未限定范围的路径一致。您可以从 获取可变路径段HttpRequest::match_info()。Path提取器还能够提取范围级别的变量段。表示匹配路径段的所有值都可以在 中找到HttpRequest::match_info。可以使用 检索具体值Path::get()。
use actix_web::{get, App, HttpRequest, HttpServer, Result};
# [get("/a/{v1}/{v2}/")]
async fn index(req: HttpRequest) -> Result<String> {
let v1: u8 = req.match_info().get("v1").unwrap().parse().unwrap();
let v2: u8 = req.match_info().query("v2").parse().unwrap();
let (v3, v4): (u8, u8) = req.match_info().load().unwrap();
Ok(format!("Values {} {} {} {}", v1, v2, v3, v4))
}
# [actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| App::new().service(index))
.bind(("127.0.0.1", 8080))?
.run()
.await
}
对于路径“/a/1/2/”的此示例,值 v1 和 v2 将解析为“1”和“2”。
路径信息
Actix 提供类型安全路径信息提取功能。路径提取信息,目的地类型可以以几种不同的形式定义。最简单的方法是使用tuple类型。元组中的每个元素必须对应于路径模式中的一个元素。即您可以将路径模式/{id}/{username}/与Path<(u32, String)>类型进行匹配,但Path<(String, String, String)>类型总是会失败。
use actix_web::{get, web, App, HttpServer, Result};
# [get("/{username}/{id}/index.html")] // <- define path parameters
async fn index(info: web::Path<(String, u32)>) -> Result<String> {
let info = info.into_inner();
Ok(format!("Welcome {}! id: {}", info.0, info.1))
}
# [actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| App::new().service(index))
.bind(("127.0.0.1", 8080))?
.run()
.await
}
还可以将路径模式信息提取到结构中。在这种情况下,该结构必须实现serde 的 Deserialize特征。
use actix_web::{get, web, App, HttpServer, Result};
use serde::Deserialize;
# [derive(Deserialize)]
struct Info {
username: String,
}
// extract path info using serde #[get("/{username}/index.html")] // <- define path parameters
async fn index(info: web::Path<Info>) -> Result<String> {
Ok(format!("Welcome {}!", info.username))
}
# [actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| App::new().service(index))
.bind(("127.0.0.1", 8080))?
.run()
.await
}
Query为请求查询参数提供了类似的功能。
生成资源
使用HttpRequest.url_for()方法根据资源模式生成 URL。例如,如果您配置了名称为“foo”且模式为“{a}/{b}/{c}”的资源,则可以执行以下操作:
use actix_web::{get, guard, http::header, HttpRequest, HttpResponse, Result};
# [get("/test/")]
async fn index(req: HttpRequest) -> Result<HttpResponse> {
let url = req.url_for("foo", ["1", "2", "3"])?; // <- generate url for "foo" resource
Ok(HttpResponse::Found()
.insert_header((header::LOCATION, url.as_str()))
.finish())
}
# [actix_web::main]
async fn main() -> std::io::Result<()> {
use actix_web::{web, App, HttpServer};
HttpServer::new(|| {
App::new()
.service(
web::resource("/test/{a}/{b}/{c}")
.name("foo") // <- set resource name, then it could be used in `url_for`
.guard(guard::Get())
.to(HttpResponse::Ok),
)
.service(index)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
这将返回类似字符串的内容http://example.com/test/1/2/3(至少如果当前协议和主机名隐含http://example.com)。url_for()方法返回Url 对象,以便您可以修改此 url(添加查询参数、锚点等)。url_for()只能为命名资源调用,否则将返回错误。
外部
有效 URL 的资源可以注册为外部资源。它们仅用于 URL 生成目的,并且在请求时从不考虑进行匹配。
use actix_web::{get, App, HttpRequest, HttpServer, Responder};
# [get("/")]
async fn index(req: HttpRequest) -> impl Responder {
let url = req.url_for("youtube", ["oHg5SJYRHA0"]).unwrap();
assert_eq!(url.as_str(), "<https://youtube.com/watch/oHg5SJYRHA0>");
url.to_string()
}
# [actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(index)
.external_resource("youtube", "<https://youtube.com/watch/{video_id}>")
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
路径规范化和重定向到/
之后,标准化意味着:
- 向路径添加尾部斜杠。
- 将多个斜杠替换为一个。
- 一旦找到正确解析的路径,处理程序就会返回。如果全部启用,规范化条件的顺序为 1) 合并,2) 合并和附加,以及 3) 附加。如果路径至少满足其中一个条件,它将重定向到新路径。
use actix_web::{middleware, HttpResponse};
async fn index() -> HttpResponse {
HttpResponse::Ok().body("Hello")
}
# [actix_web::main]
async fn main() -> std::io::Result<()> {
use actix_web::{web, App, HttpServer};
HttpServer::new(|| {
App::new()
.wrap(middleware::NormalizePath::default())
.route("/resource/", web::to(index))
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
在本例//resource///中将被重定向到/resource/。在此示例中,为所有方法注册了路径规范化处理程序,但您不应依赖此机制来重定向POST请求。附加斜杠Not Found的重定向会将POST请求转换为 GET,从而丢失原始请求中的所有POST数据。可以仅为GET请求注册路径规范化:
use actix_web::{get, http::Method, middleware, web, App, HttpServer};
# [actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.wrap(middleware::NormalizePath::default())
.service(index)
.default_service(web::route().method(Method::GET))
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
使用应用程序前缀来编写
该web::scope()方法允许设置特定的应用范围。此范围表示一个资源前缀,该前缀将添加到资源配置添加的所有资源模式之前。这可用于帮助在与所包含的可调用对象作者预期不同的位置安装一组路由,同时仍保持相同的资源名称。
例如:
# [get("/show")]
async fn show_users() -> HttpResponse {
HttpResponse::Ok().body("Show users")
}
# [get("/show/{id}")]
async fn user_detail(path: web::Path<(u32,)>) -> HttpResponse {
HttpResponse::Ok().body(format!("User detail: {}", path.into_inner().0))
}
# [actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new().service(
web::scope("/users")
.service(show_users)
.service(user_detail),
)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
在上面的示例中,show_users路由将具有/users/show而不是/show的有效路由模式,因为应用程序的范围将被添加到该模式之前。仅当 URL 路径为/users/show时,该路由才会匹配,并且当HttpRequest.url_for()使用路由名称 show_users 调用该函数时,它将生成具有相同路径的 URL。
自定义路由
您可以将guards视为一个简单的函数,它接受请求对象引用并返回true或false。从形式上来说,guards
是任何实现该Guard特征的对象。Actix提供了几个谓词,您可以查看API文档的函数部分。这是一个简单的防护,用于检查请求是否包含特定标头:
use actix_web::{guard::{Guard, GuardContext},http, HttpResponse,};
struct ContentTypeHeader;
impl Guard for ContentTypeHeader {
fn check(&self, req: &GuardContext) -> bool {
req.head()
.headers()
.contains_key(http::header::CONTENT_TYPE)
}
}
# [actix_web::main]
async fn main() -> std::io::Result<()> {
use actix_web::{web, App, HttpServer};
HttpServer::new(|| {
App::new().route(
"/",
web::route().guard(ContentTypeHeader).to(HttpResponse::Ok),
)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
在此示例中,仅当请求包含CONTENT-TYPE标头时才会调用索引处理程序。
guards
无法访问或修改请求对象,但可以在请求扩展中存储额外信息。
修改保护
您可以通过将任何谓词值包装在谓词中来反转其含义Not。例如,如果您想为除“GET”之外的所有方法返回“METHOD NOT ALLOWED”响应:
use actix_web::{guard, web, App, HttpResponse, HttpServer};
# [actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new().route(
"/",
web::route()
.guard(guard::Not(guard::Get()))
.to(HttpResponse::MethodNotAllowed),
)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
该Anyguards
接受guards
列表,并且如果提供的任何guards
匹配,则匹配。IE:
guard::Any(guard::Get()).or(guard::Post())
该Allguards
接受guards
列表,并且如果所有提供的guards
都匹配,则匹配。IE:
guard::All(guard::Get()).and(guard::Header("content-type", "plain/text"))
更改默认的“未找到”
如果在路由表中找不到路径模式或者资源找不到匹配的路由,则使用默认资源。默认响应是NOT FOUND。可以使用 覆盖NOT FOUND响应App::default_service()。该方法接受与普通资源配置相同的配置功能App::service()。
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(web::resource("/").route(web::get().to(index)))
.default_service(
web::route()
.guard(guard::Not(guard::Get()))
.to(HttpResponse::MethodNotAllowed),
)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
Json请求
json body 反序列化有多种选项。第一个选项是使用Json提取器。首先,定义一个接受Json
[dependencies]
serde = { version = "1.0", features = ["derive"] }
的第二个例子JSON Request取决于serdeandserde_json和futures:
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1"
futures = "0.3"
如果要为字段添加默认值,请参阅serde的文档。
use actix_web::{web, App, HttpServer, Result};
use serde::Deserialize;
#[derive(Deserialize)]
struct Info {
username: String,
}
/// extract `Info` using serde
async fn index(info: web::Json<Info>) -> Result<String> {
Ok(format!("Welcome {}!", info.username))
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| App::new().route("/", web::post().to(index)))
.bind(("127.0.0.1", 8080))?
.run()
.await
}
您还可以手动将有效负载加载到内存中,然后将其反序列化。在下面的示例中,我们将反序列化MyObj结构。我们需要先加载请求体,然后将json反序列化为对象。
use actix_web::{error, post, web, App, Error, HttpResponse};
use futures::StreamExt;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct MyObj {
name: String,
number: i32,
}
const MAX_SIZE: usize = 262_144; // max payload size is 256k
#[post("/")]
async fn index_manual(mut payload: web::Payload) -> Result<HttpResponse, Error> {
// payload is a stream of Bytes objects
let mut body = web::BytesMut::new();
while let Some(chunk) = payload.next().await {
let chunk = chunk?;
// limit max size of in-memory payload
if (body.len() + chunk.len()) > MAX_SIZE {
return Err(error::ErrorBadRequest("overflow"));
}
body.extend_from_slice(&chunk);
}
// body is loaded, now we can deserialize serde-json
let obj = serde_json::from_slice::<MyObj>(&body)?;
Ok(HttpResponse::Ok().json(obj)) // <- send response
}
示例目录中提供了这两个选项的完整示例。
Actix Web 自动解压缩有效负载。支持以下编解码器:
- Brotli
- gzip
- Deflate
- Zstd
如果请求标头包含Content-Encoding标头,则根据标头值解压缩请求负载。不支持多个编解码器,即:Content-Encoding: br, gzip.
分块传输
Actix 自动解码分块编码。提取web::Payload器已经包含解码的字节流。如果请求有效负载使用受支持的压缩编解码器之一(br、gzip、deflate)进行压缩,则字节流将被解压缩。
多部分请求体
Actix Web 通过外部 crate 提供多部分流支持actix-multipart。完整例子。
Urlencoded
Actix Web 通过解析为反序列化实例的提取器提供对application/x-www-form-urlencoded编码体的支持。web::Form实例的类型必须实现serdeDeserialize的特征。UrlEncoded future 在多种情况下可能会解析为错误:
- 内容类型不是application/x-www-form-urlencoded
- 传输编码是chunked.
- 内容长度大于 256k
- 有效负载因错误而终止。
use actix_web::{post, web, HttpResponse};
use serde::Deserialize;
#[derive(Deserialize)]
struct FormData {
username: String,
}
#[post("/")]
async fn index(form: web::Form<FormData>) -> HttpResponse {
HttpResponse::Ok().body(format!("username: {}", form.username))
}
流请求
HttpRequest是一个对象流Bytes。它可用于读取请求正文有效负载。在下面的示例中,我们逐块读取并打印请求负载:
use actix_web::{get, web, Error, HttpResponse};
use futures::StreamExt;
#[get("/")]
async fn index(mut body: web::Payload) -> Result<HttpResponse, Error> {
let mut bytes = web::BytesMut::new();
while let Some(item) = body.next().await {
let item = item?;
println!("Chunk: {:?}", &item);
bytes.extend_from_slice(&item);
}
Ok(HttpResponse::Ok().finish())
}
响应
类似构建器的模式用于构造的HttpResponse实例。HttpResponse提供了几种返回HttpResponseBuilder实例的方法,该实例实现了用于构建响应的各种便捷方法。方法.body、.finish和.json完成响应创建并返回构造的HttpResponse实例。如果在同一个构建器实例上多次调用此方法,构建器将出现panic。
use actix_web::{http::header::ContentType, HttpResponse};
async fn index() -> HttpResponse {
HttpResponse::Ok()
.content_type(ContentType::plaintext())
.insert_header(("X-Hdr", "sample"))
.body("data")
}
JSON
该Json类型允许使用格式良好的 JSON 数据进行响应:只需返回 type 的值,Json
[dependencies]
serde = { version = "1.0", features = ["derive"] }
use actix_web::{get, web, Responder, Result};
use serde::Serialize;
#[derive(Serialize)]
struct MyObj {
name: String,
}
#[get("/a/{name}")]
async fn index(name: web::Path<String>) -> Result<impl Responder> {
let obj = MyObj {
name: name.to_string(),
};
Ok(web::Json(obj))
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
use actix_web::{App, HttpServer};
HttpServer::new(|| App::new().service(index))
.bind(("127.0.0.1", 8080))?
.run()
.await
}
以这种方式使用Json类型而不是调用.json 上的方法HttpResponse可以立即清楚地表明该函数返回 JSON 而不是任何其他类型的响应。
内容
Actix Web 可以使用Compress 中间件自动压缩有效负载。支持以下编解码器:
- Brotli
- Gzip
- Deflate
- Identity
响应的Content-Encoding标头默认为ContentEncoding::Auto,它根据请求的Accept-Encoding标头执行自动内容压缩协商。
use actix_web::{get, middleware, App, HttpResponse, HttpServer};
#[get("/")]
async fn index() -> HttpResponse {
HttpResponse::Ok().body("data")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.wrap(middleware::Compress::default())
.service(index)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
Content-Encoding通过设置一个值来显式禁用处理程序上的内容压缩Identity:
use actix_web::{
get, http::header::ContentEncoding, middleware, App, HttpResponse, HttpServer,
};
#[get("/")]
async fn index() -> HttpResponse {
HttpResponse::Ok()
// v- disable compression
.insert_header(ContentEncoding::Identity)
.body("data")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.wrap(middleware::Compress::default())
.service(index)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
当处理已经压缩的主体时(例如,当提供预压缩的资产时),Content-Encoding手动设置响应的标头以绕过中间件:
use actix_web::{
get, http::header::ContentEncoding, middleware, App, HttpResponse, HttpServer,
};
static HELLO_WORLD: &[u8] = &[
0x1f, 0x8b, 0x08, 0x00, 0xa2, 0x30, 0x10, 0x5c, 0x00, 0x03, 0xcb, 0x48, 0xcd, 0xc9, 0xc9,
0x57, 0x28, 0xcf, 0x2f, 0xca, 0x49, 0xe1, 0x02, 0x00, 0x2d, 0x3b, 0x08, 0xaf, 0x0c, 0x00,
0x00, 0x00,
];
#[get("/")]
async fn index() -> HttpResponse {
HttpResponse::Ok()
.insert_header(ContentEncoding::Gzip)
.body(HELLO_WORLD)
}
测试
每个应用程序都应该经过充分测试。Actix Web 提供了针对您的应用程序执行集成测试的工具以及用于自定义提取器和中间件的单元测试工具。Actix Web 提供了请求构建器类型。TestRequest实现了类似构建器的模式。您可以使用它生成一个HttpRequest实例to_http_request()并调用您的处理程序或提取器。另请参阅
集成测试
有几种方法可以测试您的应用程序。Actix Web 可用于在真实 HTTP 服务器中运行具有特定处理程序的应用程序。TestRequest::get(),TestRequest::post()也可以使用其他方法向测试服务器发送请求。要创建Service测试用,请使用test::init_service接受常规App构建器的方法。查看API 文档以获取更多信息。
#[cfg(test)]
mod tests {
use actix_web::{http::header::ContentType, test, App};
use super::*;
#[actix_web::test]
async fn test_index_get() {
let app = test::init_service(App::new().service(index)).await;
let req = test::TestRequest::default()
.insert_header(ContentType::plaintext())
.to_request();
let resp = test::call_service(&app, req).await;
assert!(resp.status().is_success());
}
#[actix_web::test]
async fn test_index_post() {
let app = test::init_service(App::new().service(index)).await;
let req = test::TestRequest::post().uri("/").to_request();
let resp = test::call_service(&app, req).await;
assert!(resp.status().is_client_error());
}
}
如果您需要更复杂的应用程序配置,测试应该与创建普通应用程序非常相似。例如,您可能需要初始化应用程序状态。创建一个App方法data并附加状态,就像在普通应用程序中一样。
#[cfg(test)]
mod tests {
use super::*;
use actix_web::{test, web, App};
#[actix_web::test]
async fn test_index_get() {
let app = test::init_service(
App::new()
.app_data(web::Data::new(AppState { count: 4 }))
.service(index),
)
.await;
let req = test::TestRequest::get().uri("/").to_request();
let resp: AppState = test::call_and_read_body_json(&app, req).await;
assert_eq!(resp.count, 4);
}
}
流响应
如果您需要测试流生成,则只需调用并将into_parts()结果主体转换为 future 并执行它就足够了,例如在测试Server Sent Events时。
use std::task::Poll;
use actix_web::{
http::{self, header::ContentEncoding, StatusCode},
web, App, Error, HttpRequest, HttpResponse,
};
use futures::stream;
async fn sse(_req: HttpRequest) -> HttpResponse {
let mut counter: usize = 5;
// yields `data: N` where N in [5; 1]
let server_events =
stream::poll_fn(move |_cx| -> Poll<Option<Result<web::Bytes, Error>>> {
if counter == 0 {
return Poll::Ready(None);
}
let payload = format!("data: {}\n\n", counter);
counter -= 1;
Poll::Ready(Some(Ok(web::Bytes::from(payload))))
});
HttpResponse::build(StatusCode::OK)
.insert_header((http::header::CONTENT_TYPE, "text/event-stream"))
.insert_header(ContentEncoding::Identity)
.streaming(server_events)
}
pub fn main() {
App::new().route("/", web::get().to(sse));
}
#[cfg(test)]
mod tests {
use super::*;
use actix_web::{body, body::MessageBody as _, rt::pin, test, web, App};
use futures::future;
#[actix_web::test]
async fn test_stream_chunk() {
let app = test::init_service(App::new().route("/", web::get().to(sse))).await;
let req = test::TestRequest::get().to_request();
let resp = test::call_service(&app, req).await;
assert!(resp.status().is_success());
let body = resp.into_body();
pin!(body);
// first chunk
let bytes = future::poll_fn(|cx| body.as_mut().poll_next(cx)).await;
assert_eq!(
bytes.unwrap().unwrap(),
web::Bytes::from_static(b"data: 5\n\n")
);
// second chunk
let bytes = future::poll_fn(|cx| body.as_mut().poll_next(cx)).await;
assert_eq!(
bytes.unwrap().unwrap(),
web::Bytes::from_static(b"data: 4\n\n")
);
// remaining part
for i in 0..3 {
let expected_data = format!("data: {}\n\n", 3 - i);
let bytes = future::poll_fn(|cx| body.as_mut().poll_next(cx)).await;
assert_eq!(bytes.unwrap().unwrap(), web::Bytes::from(expected_data));
}
}
#[actix_web::test]
async fn test_stream_full_payload() {
let app = test::init_service(App::new().route("/", web::get().to(sse))).await;
let req = test::TestRequest::get().to_request();
let resp = test::call_service(&app, req).await;
assert!(resp.status().is_success());
let body = resp.into_body();
let bytes = body::to_bytes(body).await;
assert_eq!(
bytes.unwrap(),
web::Bytes::from_static(b"data: 5\n\ndata: 4\n\ndata: 3\n\ndata: 2\n\ndata: 1\n\n")
);
}
}
单元测试
单元测试对于应用程序的价值相当有限,但在开发提取器、中间件和响应器时可能很有用。鉴于此,如果您想对自定义进行断言,则可以直接调用独立定义的处理程序函数,而不使用路由宏(如) 。#[get(“/“)]Responder
#[cfg(test)]
mod tests {
use super::*;
use actix_web::{
http::{self, header::ContentType},
test,
};
#[actix_web::test]
async fn test_index_ok() {
let req = test::TestRequest::default()
.insert_header(ContentType::plaintext())
.to_http_request();
let resp = index(req).await;
assert_eq!(resp.status(), http::StatusCode::OK);
}
#[actix_web::test]
async fn test_index_not_ok() {
let req = test::TestRequest::default().to_http_request();
let resp = index(req).await;
assert_eq!(resp.status(), http::StatusCode::BAD_REQUEST);
}
}
中间件
Actix Web 的中间件系统允许我们向请求/响应处理添加额外的行为。中间件可以挂钩传入的请求进程,使我们能够修改请求以及停止请求处理以尽早返回响应。中间件还可以连接到响应处理。通常,中间件涉及以下操作:
- 预处理请求
- 后处理响应
- 修改应用程序状态
- 访问外部服务(redis、日志记录、会话)
中间件为每个App、scope、 或注册Resource,并以与注册相反的顺序执行。一般来说,中间件是一种实现Service Trait和Transform Trait 的类型。特征中的每个方法都有一个默认实现。每个方法都可以立即返回结果或未来对象。
下面演示创建一个简单的中间件:
use std::future::{ready, Ready};
use actix_web::{
dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
Error,
};
use futures_util::future::LocalBoxFuture;
// There are two steps in middleware processing.
// 1. Middleware initialization, middleware factory gets called with
// next service in chain as parameter.
// 2. Middleware's call method gets called with normal request.
pub struct SayHi;
// Middleware factory is `Transform` trait
// `S` - type of the next service
// `B` - type of response's body
impl<S, B> Transform<S, ServiceRequest> for SayHi
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type InitError = ();
type Transform = SayHiMiddleware<S>;
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ready(Ok(SayHiMiddleware { service }))
}
}
pub struct SayHiMiddleware<S> {
service: S,
}
impl<S, B> Service<ServiceRequest> for SayHiMiddleware<S>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
forward_ready!(service);
fn call(&self, req: ServiceRequest) -> Self::Future {
println!("Hi from start. You requested: {}", req.path());
let fut = self.service.call(req);
Box::pin(async move {
let res = fut.await?;
println!("Hi from response");
Ok(res)
})
}
}
或者,对于简单的用例,您可以使用wrap_fn来创建小型的临时中间件:
use actix_web::{dev::Service as _, web, App};
use futures_util::future::FutureExt;
#[actix_web::main]
async fn main() {
let app = App::new()
.wrap_fn(|req, srv| {
println!("Hi from start. You requested: {}", req.path());
srv.call(req).map(|res| {
println!("Hi from response");
res
})
})
.route(
"/index.html",
web::get().to(|| async { "Hello, middleware!" }),
);
}
Actix Web 提供了一些有用的中间件,例如日志记录、用户会话、压缩等。
警告:如果多次使用wrap()、wrap_fn()多次,最后一次出现的将首先执行。
日志
日志记录作为中间件实现。通常将日志记录中间件注册为应用程序的第一个中间件。必须为每个应用程序注册日志记录中间件。中间件Logger使用标准日志箱来记录信息。您应该为actix_web包启用记录器以查看访问日志(env_logger或类似)。使用Logger指定的format. Logger可以使用方法创建默认值default,它使用默认格式:
%a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T
use actix_web::middleware::Logger;
use env_logger::Env;
#[actix_web::main]
async fn main() -> std::io::Result<()> {
use actix_web::{App, HttpServer};
env_logger::init_from_env(Env::default().default_filter_or("info"));
HttpServer::new(|| {
App::new()
.wrap(Logger::default())
.wrap(Logger::new("%a %{User-Agent}i"))
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
以下是默认日志记录格式的示例:
INFO:actix_web::middleware::logger: 127.0.0.1:59934 [02/Dec/2017:00:21:43 -0800] "GET / HTTP/1.1" 302 0 "-" "curl/7.54.0" 0.000397
INFO:actix_web::middleware::logger: 127.0.0.1:59947 [02/Dec/2017:00:22:40 -0800] "GET /index.html HTTP/1.1" 200 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:57.0) Gecko/20100101 Firefox/57.0" 0.000646
%%
百分号%a
远程 IP 地址(如果使用反向代理,则为代理的 IP 地址)%t
开始处理请求的时间%P
为请求提供服务的子进程 ID%r
请求的第一行%s
响应状态码%b
响应大小(以字节为单位),包括 HTTP 标头%T
处理请求所花费的时间(以秒为单位,采用 .06f 格式的浮动分数)%D
处理请求所花费的时间(以毫秒为单位)%{FOO}
irequest.headers [‘FOO’]%{FOO}
o响应.headers [‘FOO’]%{FOO}
eos.environ [‘FOO’]
默认
要设置默认响应头,DefaultHeaders可以使用中间件。如果响应标头已包含指定标头,则 DefaultHeaders中间件不会设置标头。
use actix_web::{http::Method, middleware, web, App, HttpResponse, HttpServer};
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.wrap(middleware::DefaultHeaders::new().add(("X-Version", "0.2")))
.service(
web::resource("/test")
.route(web::get().to(HttpResponse::Ok))
.route(web::method(Method::HEAD).to(HttpResponse::MethodNotAllowed)),
)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
用户
Actix Web 提供了会话管理的通用解决方案。actix-session中间件可以使用多种后端类型来存储会话数据。默认情况下,仅实现 cookie 会话后端。可以添加其他后端实现。CookieSession使用cookie 作为会话存储。CookieSessionBackend创建的会话仅限于存储少于 4000 字节的数据,因为有效负载必须适合单个 cookie。如果会话包含超过 4000 字节,则会生成内部服务器错误。cookie 可能具有签名或私有的安全策略。每个都有一个各自的CookieSession构造函数。客户端可以查看但不能修改签名的cookie 。客户端既不能查看也不能修改私有cookie 。构造函数将键作为参数。这是 cookie 会话的私钥 - 当该值更改时,所有会话数据都会丢失。一般来说,您创建一个SessionStorage中间件并使用特定的后端实现(例如CookieSession. 要访问会话数据,Session必须使用提取器。该方法返回一个Session对象,它允许我们获取或设置会话数据。
actix_session::storage::CookieSessionStore在板条箱功能“cookie-session”上可用。
use actix_session::{Session, SessionMiddleware, storage::CookieSessionStore};
use actix_web::{web, App, Error, HttpResponse, HttpServer, cookie::Key};
async fn index(session: Session) -> Result<HttpResponse, Error> {
// access session data
if let Some(count) = session.get::<i32>("counter")? {
session.insert("counter", count + 1)?;
} else {
session.insert("counter", 1)?;
}
Ok(HttpResponse::Ok().body(format!(
"Count is {:?}!",
session.get::<i32>("counter")?.unwrap()
)))
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.wrap(
// create cookie based session middleware
SessionMiddleware::builder(CookieSessionStore::default(), Key::from(&[0; 64]))
.cookie_secure(false)
.build()
)
.service(web::resource("/").to(index))
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
错误
ErrorHandlers中间件允许我们为响应提供自定义处理程序。您可以使用该ErrorHandlers::handler()方法为特定状态代码注册自定义错误处理程序。您可以修改现有响应或创建全新的响应。错误处理程序可以立即返回响应,也可以返回解析为响应的 future。
use actix_web::middleware::{ErrorHandlerResponse, ErrorHandlers};
use actix_web::{
dev,
http::{header, StatusCode},
web, App, HttpResponse, HttpServer, Result,
};
fn add_error_header<B>(mut res: dev::ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> {
res.response_mut().headers_mut().insert(
header::CONTENT_TYPE,
header::HeaderValue::from_static("Error"),
);
Ok(ErrorHandlerResponse::Response(res.map_into_left_body()))
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.wrap(
ErrorHandlers::new()
.handler(StatusCode::INTERNAL_SERVER_ERROR, add_error_header),
)
.service(web::resource("/").route(web::get().to(HttpResponse::InternalServerError)))
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
单个文件
可以使用自定义路径模式和NamedFile. 为了匹配路径尾部,我们可以使用[.*]正则表达式。
use actix_files::NamedFile;
use actix_web::{HttpRequest, Result};
use std::path::PathBuf;
async fn index(req: HttpRequest) -> Result<NamedFile> {
let path: PathBuf = req.match_info().query("filename").parse().unwrap();
Ok(NamedFile::open(path)?)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
use actix_web::{web, App, HttpServer};
HttpServer::new(|| App::new().route("/{filename:.*}", web::get().to(index)))
.bind(("127.0.0.1", 8080))?
.run()
.await
}
危险:将路径尾部与[.*]正则表达式匹配并使用它返回一个NamedFile会产生严重的安全隐患。../它为攻击者提供了插入 URL 并访问运行服务器的用户有权访问的主机上的每个文件的可能性。要从特定目录和子目录提供文件,Files可以使用。Files必须用方法注册App::service(),否则将无法服务子路径。
use actix_files as fs;
use actix_web::{App, HttpServer};
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| App::new().service(fs::Files::new("/static", ".").show_files_listing()))
.bind(("127.0.0.1", 8080))?
.run()
.await
}
默认情况下,子目录的文件列表被禁用。尝试加载目录列表将返回404 Not Found响应。要启用文件列表,请使用Files::show_files_listing()方法。可以重定向到特定的索引文件,而不是显示目录的文件列表。使用该Files::index_file()方法来配置此重定向。NamedFiles可以指定服务文件的各种选项:
- set_content_disposition:用于将文件的 mime 映射到相应Content-Disposition类型的函数
- use_etag:指定是否ETag应计算并包含在标题中。
- use_last_modified:指定是否应使用文件修改时间戳并将其添加到Last-Modified标头。
上述所有方法都是可选的,并提供了最佳默认值,但可以自定义其中任何一个。
use actix_files as fs;
use actix_web::http::header::{ContentDisposition, DispositionType};
use actix_web::{get, App, Error, HttpRequest, HttpServer};
#[get("/{filename:.*}")]
async fn index(req: HttpRequest) -> Result<fs::NamedFile, Error> {
let path: std::path::PathBuf = req.match_info().query("filename").parse().unwrap();
let file = fs::NamedFile::open(path)?;
Ok(file
.use_last_modified(true)
.set_content_disposition(ContentDisposition {
disposition: DispositionType::Attachment,
parameters: vec![],
}))
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| App::new().service(index))
.bind(("127.0.0.1", 8080))?
.run()
.await
}
该配置也可以应用于目录服务:
use actix_files as fs;
use actix_web::{App, HttpServer};
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new().service(
fs::Files::new("/static", ".")
.show_files_listing()
.use_last_modified(true),
)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}