当前位置: 首页 > news >正文

潍坊企业模板建站江门网站建设定制

潍坊企业模板建站,江门网站建设定制,济南seo网站排名关键词优化,企业网站托管服务公司系列文章目录 【跟小嘉学 Rust 编程】一、Rust 编程基础 【跟小嘉学 Rust 编程】二、Rust 包管理工具使用 【跟小嘉学 Rust 编程】三、Rust 的基本程序概念 【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念 【跟小嘉学 Rust 编程】五、使用结构体关联结构化数据 【跟小嘉学…系列文章目录 【跟小嘉学 Rust 编程】一、Rust 编程基础 【跟小嘉学 Rust 编程】二、Rust 包管理工具使用 【跟小嘉学 Rust 编程】三、Rust 的基本程序概念 【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念 【跟小嘉学 Rust 编程】五、使用结构体关联结构化数据 【跟小嘉学 Rust 编程】六、枚举和模式匹配 【跟小嘉学 Rust 编程】七、使用包(Packages)、单元包(Crates)和模块(Module)来管理项目 【跟小嘉学 Rust 编程】八、常见的集合 【跟小嘉学 Rust 编程】九、错误处理(Error Handling) 【跟小嘉学 Rust 编程】十一、编写自动化测试 【跟小嘉学 Rust 编程】十二、构建一个命令行程序 【跟小嘉学 Rust 编程】十三、函数式语言特性迭代器和闭包 【跟小嘉学 Rust 编程】十四、关于 Cargo 和 Crates.io 【跟小嘉学 Rust 编程】十五、智能指针(Smart Point) 【跟小嘉学 Rust 编程】十六、无畏并发(Fearless Concurrency) 【跟小嘉学 Rust 编程】十七、面向对象语言特性 【跟小嘉学 Rust 编程】十八、模式匹配(Patterns and Matching) 【跟小嘉学 Rust 编程】十九、高级特性 【跟小嘉学 Rust 编程】二十、进阶扩展 【跟小嘉学 Rust 编程】二十一、网络编程 【跟小嘉学 Rust 编程】二十三、Cargo 使用指南 【跟小嘉学 Rust 编程】二十四、内联汇编(inline assembly) 【跟小嘉学 Rust 编程】二十五、Rust命令行参数解析库(clap) 【跟小嘉学 Rust 编程】二十六、Rust的序列化解决方案(Serde) 【跟小嘉学 Rust 编程】二十七、Rust 异步编程(Asynchronous Programming) 【跟小嘉学 Rust 编程】二十八、Rust中的日期与时间 【跟小嘉学 Rust 编程】二十九、Rust 中的零拷贝序列化解决方案(rkyv) 【跟小嘉学 Rust 编程】三十、Rust 使用 Slint UI 【跟小嘉学 Rust 编程】三十一、Rust的日志与追踪 【跟小嘉学 Rust 编程】三十二、Rust的设计模式(Design Patterns) 【跟小嘉学 Rust 编程】三十三、Rust的Web开发框架之一: Actix-Web的基础 【跟小嘉学 Rust 编程】三十四、Rust的Web开发框架之一: Actix-Web的进阶 文章目录 系列文章目录[TOC](文章目录) 前言一、Actix-Web 进阶1.1、错误1.1.1、错误介绍1.1.2、自定义错误响应1.1.3、错误辅助1.1.4、错误日志1.1.5、错误处理的推荐方式1.1.6、日志记录 1.2、URL分发1.2.1、URL分发介绍1.2.2、资源配置1.2.2.1、配置路由 1.2.3、路由匹配1.2.4、资源匹配语法1.2.5、路由范围1.2.6、匹配信息1.2.6.1、路径信息提取器 1.2.7、生成资源URL1.2.8、外部资源1.2.9、路径规范化和重定向到斜杠附加到路由1.2.9.1、使用应用程序前缀组合应用程序 1.2.10、自定义路由守卫1.2.10.1、修改守卫的值 1.2.11、修改默认 NOT Found 响应 1.3、请求1.3.1、JSON 请求1.3.1.1、JSONT 提取器1.3.1.2、Payload 提取 1.3.2、Content 编码1.3.3、分块传输编码(Chunked transfer encoding)1.3.4、文件上传1.3.5、URL编码的内容1.3.6、流式请求 1.4、响应1.4.1、JSON 响应1.4.2、Content 编码 1.5、测试1.5.1、集成测试1.5.2、流响应测试1.5.3、单元测试 1.6、中间件(Middleware)1.6.1、中间件介绍1.6.2、日志中间件1.6.2.1、使用1.6.2.2、格式 1.6.3、Default headers1.6.4、用户会话1.6.5、错误处理 1.7、静态文件1.7.1、单个文件1.7.2、目录1.7.3、配置 二、WebSocket三、热加载(Auto-Reloading)四、Shuttle总结 前言 上一章我们讲解了 Actix Web的基础现在讲解 Actix web的进阶包含了错误处理、URL 分发原理、请求响应处理、中间件、静态文件、Websocket集成、热加载以及 shuttle 部署。 主要教材参考 《The Rust Programming Language》 主要教材参考 《Rust For Rustaceans》 主要教材参考 《The Rustonomicon》 主要教材参考 《Rust 高级编程》 主要教材参考 《Cargo 指南》 主要教材参考 《Rust 异步编程》 主要教材参考 《Rust 设计模式》 一、Actix-Web 进阶 1.1、错误 1.1.1、错误介绍 Actix Web 使用自己的 actix_web::error::Error 类型 和actix_web::error::ResponseError 特征来进行错误处理。如果一个处理器返回了一个 Error同样实现了 ResponseError 的ReulstActix Web 会把该错误呈现为带有 actix_web:: HTTP::StatusCode 的HTTP响应 pub trait ResponseError {fn error_response(self) - ResponseBody;fn status_code(self) - StatusCode; }1.1.2、自定义错误响应 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() - Resultstatic 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() - Resultstatic str, MyError {Err(MyError::BadClientData) }1.1.3、错误辅助 Actix Web 提供了一组错误辅助函数用于从其他错误生成特定的HTTP 错误代码。这里我们使用 map_err 将 MyError 转换为 400(错误请求)。 use actix_web::{error, get, App, HttpServer};#[derive(Debug)] struct MyError {name: static str, }#[get(/)] async fn index() - actix_web::ResultString {let result Err(MyError { name: test error });result.map_err(|err| error::ErrorBadRequest(err.name)) }1.1.4、错误日志 Actix 日志所有错误都在 WARN 日志级别。如果应用日志级别设置为 DEBUG并且开启 RUST_BACKTRACE错误的堆栈信息都会记录。 RUST_BACKTRACE1 RUST_LOGactix_webdebug cargo run1.1.5、错误处理的推荐方式 考虑将应用程序产生的错误分为两大类面向用户的错误和非面向用户的错误。 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,}} }并不是所有的错误都需要返回错误信息例如服务器发生了故障我们需要对用户隐藏细节例如数据库宕机等等。 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() - Resultstatic str, UserError {do_thing_that_fails().map_err(|_e| UserError::InternalError)?;Ok(success!) }1.1.6、日志记录 我们可以使用 middleware::Loagger 默认依赖env_logger 和 log 来记录日志。 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() - Resultstatic 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 }1.2、URL分发 1.2.1、URL分发介绍 URL分发提供了一个使用简单模式匹配语言将URL映射到处理程序代码的简单方法。如果其中一个个模式匹配与请求关联的路径信息则调用特定的处理程序对象。 请求处理程序是一个函数接收一个可以从 FromRequest 中提取零个或多个参数并返回一个可以转换为 HttpResponse 的类型。 1.2.2、资源配置 资源配置像应用程序添加新资源的行为。资源有一个名称他充当用于生成URL的标识符。该名称还允许开发人员向现有资源添加路由。资源也有一个模式用来匹配URL的PATH部分。 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 提供了注册路由的简单方法但要访问完整的资源配置必须使用另一种方法。service() 方法将单个资源添加到应用程序路由表中。 如果资源没有包含任何路由或匹配路由它将返回 NOT FOUND HTTP响应。 1.2.2.1、配置路由 资源包含一组路由每个路由一次有一组守卫和一个处理程序。可以使用 Resource::route 方法创建新路由该方法返回一个路由实例的引用。默认情况下路由不包含任何守卫你因此匹配所有请求默认处理程序是 HttpNotFound。 应用程序根据在资源注册和路由注册期间定义的路由标准路由传入请求。 Resource 按照通过 Resource:: route 注册的路由顺序匹配所包含的所有的路由。 例子一个路由可以包含任意数量的守卫但是只能包含一个处理程序。 App::new().service(web::resource(/path).route(web::route().guard(guard::Get()).guard(guard::Header(content-type, text/plain)).to(HttpResponse::Ok),), )在上述例子中如果请求包含 content-type 报头且报头的值为 ext/plain 并且 path 等于 /path则返回HttpResponse::Ok 如果资源不能匹配任何路由则返回 NOT FOUND 响应。 ResourceHandler::route 返回一个 route 对象。路由可以用类似于构建器的模式来配置。 Route::guard()注册一个新的保护。每条路线可以注册任意数量的警卫。Route::method()注册一个方法保护。每条路线可以注册任意数量的警卫。Route::to()为该路由注册了一个异步处理函数。只能注册一个处理程序。通常处理程序注册是最后一个配置操作。 1.2.3、路由匹配 路由配置的主要目的是根据 URL 路径模式匹配请求的路径Path 表示被请求URL的路径部分。Actix Web要做到这一点的方法非常简单当请求进入系统时候对于系统中存在的每个资源配置声明根据声明的模式检查请求的路径。这种检查按照通过 App::service 方法声明路由的顺序进行的如果找不到资源则使用默认资源作为匹配的资源。 当路由配置声明时可能包含路由守卫参数所有路由声明相关联的路由保护必须为true以便在检查期间将路由配置用于给定的请求如果提供给路由配置的路由保护参数集合中的任何保护在检查期间返回false则跳过该路由并通过有序的路由集继续进行路由匹配。 如果有任何路由匹配路由匹配过程停止并调用与该路由关联的处理程序如果在用尽所有路由模式后没有路由匹配则返回NOT FOUND 响应。 1.2.4、资源匹配语法 Actix Web在模式参数重使用的模式匹配语言的语法很简单。路由配置中使用的模式可以以斜杆字符开头如果模式不以斜杆字符开水则匹配时将在其前面加上隐式斜杆。 {foo}/bar/baz /{foo}/bar/baz上述两种写法是等价的。 变量部分以标识符的形式制定这意味着接受除下一个斜杆字符以外的任何字符并且将其作用 HttpRequest.match_info 对象中的名称。 模式中的替换标记匹配正则表达式 :[^{}/] match_info 是 Params 对象表示基于路由模式从 URL中提取的动态部分它可以通过 request.match_info 获取例如下面模式定义了一个文字段和两个替换标志。 foo/{baz}/{bar}上述模式匹配的URL将生成一下信息 foo/1/2 - Params {baz: 1, bar: 2} foo/abc/def - Params {baz: abc, bar: def}下面URL将不匹配 foo/1/2/ - No match (trailing slash) bar/abc/def - First segment literal mismatch段中的段替换标记的匹配将只在模式中的段中的第一个非字母、数字、字符之前完成。因此例如如果使用了一下路由模式 1.2.5、路由范围 路由范围可以帮助我们组织公共根路径的路由可以在作用域内嵌套作用域。例如下面的Path /usrs /users/show /users/show/{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 }scope的路径可以包含可变路径段作为你资源与未使用作用域路径一致。可以从 HttpRequest:: match_info 中获取可变路径。路径提取器也能够提取范围级变量段。 1.2.6、匹配信息 所有表示匹配路径段段值都可以在 HttpRequest::match_info 中找到。可以使用 Path::get 检索特定的值。 use actix_web::{get, App, HttpRequest, HttpServer, Result};#[get(/a/{v1}/{v2}/)] async fn index(req: HttpRequest) - ResultString {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 将会解析为v11和v22 1.2.6.1、路径信息提取器 Actix Web 提供了类型安全的路径信息提取器的功能路径信息提取目的类型可以用不同的形式定义最简单的方法就是使用元组类型元组中的每个元素必须对应路径模式中的一个元素你可以匹配路径模式 /{id}/{username} 与 Path(u32, String) 类型但是如果匹配Path(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)) - ResultString {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 }我们也可以抽取路径模式信息到结构体结构体必需实现 Deserialize trait。 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::PathInfo) - ResultString {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 也同样提供了类似的功能。 1.2.7、生成资源URL 使用 HttpRequest.url_for 方法根据资源模式生成URLHttpRequest.url_for 只能被命名资源调用否则将会返回错误。 use actix_web::{get, guard, http::header, HttpRequest, HttpResponse, Result};#[get(/test/)] async fn index(req: HttpRequest) - ResultHttpResponse {let url req.url_for(foo, [1, 2, 3])?; // - generate url for foo resourceOk(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://localhost:8080/test/ 地址重定向http://localhost:8080/test/1/2/3地址。 1.2.8、外部资源 有效的URL资源可以注册为外部资源它们仅对URL生成有用在请求时从不考虑进行匹配。 use actix_web::{get, App, HttpRequest, HttpServer, Responder};#[get(/)] async fn index(req: HttpRequest) - impl Responder {let url req.url_for(baidu, [test]).unwrap();url.to_string() }#[actix_web::main] async fn main() - std::io::Result() {HttpServer::new(|| {App::new().service(index).external_resource(baidu, https://www.baidu.com/s?wd{wd})}).bind((127.0.0.1, 8080))?.run().await }1.2.9、路径规范化和重定向到斜杠附加到路由 路径规范化意味着 1、在路径中添加尾斜杆2、用一个斜杆代替多个斜杆 处理程序一旦找到正确解析的路径就返沪i如果所有条件都启用则规范化条件的顺序是 合并合并和追加追加 如果路径至少满足其中一个条件它将会重定向到新路径。 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数据。 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 }1.2.9.1、使用应用程序前缀组合应用程序 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。 1.2.10、自定义路由守卫 路由守卫可以看作是一个简单的函数它接受请求对象引用并返回true或false形式上guard 是任何实现 guard 特性的对象。 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 标头时才会调用索引处理程序。 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 }1.2.10.1、修改守卫的值 你可以通过将任何动词值包装在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 }我们可以也使用 Any 守卫接受一个列表来匹配任何允许的守卫也可以使用 ALL 守卫接受任何匹配的值。 1.2.11、修改默认 NOT Found 响应 如果在路由表中找不到的路径模式或者某个资源找不到匹配的路由则使用默认资源默认响应是 NOT FOUND可以使用 App::default_service() 覆盖NOT FOUND响应此方法接受一个与 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 }1.3、请求 1.3.1、JSON 请求 JSON 请求有几种实现方法 1.3.1.1、JSONT 提取器 我们可以定义一个接受 JSONT 作为参数的处理程序函数然后注册这个处理程序也可以通过使用 serde_json::Value 作为类型 T 来接受任意有效的 json 对象。 [dependencies] actix-web {version 4.4.0} futures 0.3.29 serde {version 1.0.190, features [derive]} serde_json 1.0.108例子如果想添加默认值 可以参考文档https://serde.rs/attr-default.html 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::JsonInfo) - ResultString {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 }1.3.1.2、Payload 提取 您也手动将 payload 加载到内存中然后反序列化 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) - ResultHttpResponse, Error {// payload is a stream of Bytes objectslet mut body web::BytesMut::new();while let Some(chunk) payload.next().await {let chunk chunk?;// limit max size of in-memory payloadif (body.len() chunk.len()) MAX_SIZE {return Err(error::ErrorBadRequest(overflow));}body.extend_from_slice(chunk);}// body is loaded, now we can deserialize serde-jsonlet obj serde_json::from_slice::MyObj(body)?;Ok(HttpResponse::Ok().json(obj)) // - send response }1.3.2、Content 编码 Actix Web 支持下面几种 payload 编解码。 Brotli开源的一种新型的压缩算法比Gzip压缩性能更好支持的类型有text/xml、text/plain、text/css、application/javascript、application/x-javascript、application/rssxml、text/javascript、image/tiff、image/svgxml、application/json、application/xmlGzipDeflateZstd 如果你的请求头包含 Content-Encoding 报头信息则根据报头对请求负载进行解压缩。不支持多个值。 1.3.3、分块传输编码(Chunked transfer encoding) Actix 会自动解码分块编码web::Payloa 提取器已经包含了解码的字节流。如果请求负载使用了支持的压缩编解码器之一进行压缩则字节流将被解压缩。 1.3.4、文件上传 Actix Web 支持 multipart 流我们需要引入 actix-multipart crate。 1、添加依赖 cargo add actix-multipart 2、添加代码 use actix_multipart::{form::{tempfile::{TempFile, TempFileConfig},MultipartForm,}, }; use actix_web::{ web, App, Error, HttpResponse, HttpServer, Responder}; #[derive(Debug, MultipartForm)] struct UploadForm {#[multipart(rename file)]files: VecTempFile, }async fn save_files(MultipartForm(form): MultipartFormUploadForm) - Resultimpl Responder, Error {for f in form.files {let path format!(./tmp/{}, f.file_name.unwrap());f.file.persist(path).unwrap();}Ok(HttpResponse::Ok()) }#[actix_web::main] async fn main() - std::io::Result() {std::fs::create_dir_all(./tmp)?;println!(starting HTTP server at http://localhost:8080);HttpServer::new(|| {App::new().app_data(TempFileConfig::default().directory(./tmp)).service(web::resource(/).route(web::post().to(save_files)),)}).bind((127.0.0.1, 8080))?.workers(2).run().await }1.3.5、URL编码的内容 Actix Web 提供了支持 application/x-www-form-urlencoded 编码的请求体可以使用 Web::Form 提取器来接受反序列化实例。 下面几种情况可能会导致 UrlEncoded 特性出错 context-type 不是 application/x-www-form-urlencodedtransfer 编码是 chunkedcontent-length 大于 256kpayload 终止错误 use actix_web::{post, web, HttpResponse}; use serde::Deserialize;#[derive(Deserialize)] struct FormData {username: String, }#[post(/)] async fn index(form: web::FormFormData) - HttpResponse {HttpResponse::Ok().body(format!(username: {}, form.username)) }1.3.6、流式请求 HttpRequest 是一个 Bytes 流所以他可以使用 Payload来读取。 use actix_web::{get, web, Error, HttpResponse}; use futures::StreamExt;#[get(/)] async fn index(mut body: web::Payload) - ResultHttpResponse, 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()) }1.4、响应 一个类似建造器的模式用于构造 HttpResponse 实例HttpResponse 提供了几个返回 HttpResponseBuilder 实例的方法该实例实现了用于构建响应的各种方便方法 use actix_web::{http::header::ContentType, HttpResponse};async fn index() - HttpResponse {HttpResponse::Ok().content_type(ContentType::plaintext()).insert_header((X-Hdr, sample)).body(data) }1.4.1、JSON 响应 JSON 类型允许使用格式良好的 JSON 数据进行响应只需要返回 JSONT 的值。其中T就是序列为JSON 的结构的类型类型T必须实习 serde 的 Serialize 特征。 1、添加依赖 serde {version 1.0.190, features [derive]}2、编写代码 use actix_web::{get, web, Responder, Result}; use serde::Serialize;#[derive(Serialize)] struct MyObj {name: String, }#[get(/a/{name})] async fn index(name: web::PathString) - Resultimpl 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 }1.4.2、Content 编码 Actix web 支持自动压缩我们可以使用 压缩中间件 middleware::Compress。支持下列编码 BrotliGzipDeflateIdentity 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 }响应头默认是ContentEncoding::Auto它是基于请求的Accept-Encoding头执行自动内容压缩协商的。可以通过将 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 }我们也可以手动设置响应头来绕过中间件 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) }1.5、测试 每个应用程序都应该经过良好的测试Actix Web 提供了针对应用程序执行集成测试的工具以及针对自定义提取器和中间件的担忧测试工具。 Actix Web 提供了请求构建器类型TestRequest 实现了类型构建器的模式。您可以使用 to_http_request 生成一个 HttpRequest 实例并用它调用处理程序或提取器。 1.5.1、集成测试 有几种方法可以测试应用测试Actix Web 可用于在真实的HTTP服务器中运行带有特定处理程序的应用程序。 TestRequest::get 、TestRequest::post 和其他方法向测试服务器发送请求要创建一个用于测试的服务请使用 test::init_service 方法该方法接受一个常规的应用构建器。 #[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());} }如果你需要更富啊咋的应用程序配置测试应该与创建普通应用程序非常相似。例如您可能需要初始化应用程序状态。创建一个带有 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);} }1.5.2、流响应测试 如果你需要测试流生成我们可以调用 into_parts 并将结果体转换为 future 并执行它就足够了例如当我们测试SSE。 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| - PollOptionResultweb::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 chunklet bytes future::poll_fn(|cx| body.as_mut().poll_next(cx)).await;assert_eq!(bytes.unwrap().unwrap(),web::Bytes::from_static(bdata: 5\n\n));// second chunklet bytes future::poll_fn(|cx| body.as_mut().poll_next(cx)).await;assert_eq!(bytes.unwrap().unwrap(),web::Bytes::from_static(bdata: 4\n\n));// remaining partfor 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(bdata: 5\n\ndata: 4\n\ndata: 3\n\ndata: 2\n\ndata: 1\n\n));} }1.5.3、单元测试 单元测试对应用程序的价值相当有限但在开发提取器、中间件和响应器时非常有用。考虑到这一点如果您想在自定义响应上进行断言可以追杰调用独立定义的处理程序函数而不使用路由宏。 #[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);} }1.6、中间件(Middleware) 1.6.1、中间件介绍 Actix Web 的中间件系统允许我们在请求/响应处理中添加额外的行为。中间件可以与传入的请求进程挂钩使我们能够秀姑请求以及暂停请求处理以提前返回响应。 中间件可以hook到响应处理通常中间件涉及一下操作 预处理请求后处理请求修改应用状态访问外部服务(redis、日志、会话) 中间件为每个应用程序、范围或资源注册并以与注册相反的顺序执行。一般来说中间件是一种实现 Service 特征和 Transform 特征的类型。每个方法都有一个默认实现每个方法都可以立即返回结果或返回未来的对象。 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. Middlewares call method gets called with normal request. pub struct SayHi;// Middleware factory is Transform trait // S - type of the next service // B - type of responses body implS, B TransformS, ServiceRequest for SayHi whereS: ServiceServiceRequest, Response ServiceResponseB, Error Error,S::Future: static,B: static, {type Response ServiceResponseB;type Error Error;type InitError ();type Transform SayHiMiddlewareS;type Future ReadyResultSelf::Transform, Self::InitError;fn new_transform(self, service: S) - Self::Future {ready(Ok(SayHiMiddleware { service }))} }pub struct SayHiMiddlewareS {service: S, }implS, B ServiceServiceRequest for SayHiMiddlewareS whereS: ServiceServiceRequest, Response ServiceResponseB, Error Error,S::Future: static,B: static, {type Response ServiceResponseB;type Error Error;type Future LocalBoxFuturestatic, ResultSelf::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! }),); }1.6.2、日志中间件 日志是作为中间件实现的通常将日志中间件注册为应用程序的第一个中间件必须为每个应用程序注册日志中间件。 Logger 中间件使用标准日志箱记录信息您应该为actix web 包启用记录器以表查看访问日志(env_logger或类似)。 1.6.2.1、使用 使用指定的格式创建 Logger 中间件。默认记录器可以用默认方法创建使用默认的格式。 %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.0006461.6.2.2、格式 %% 百分号%a 远程IP地址(使用反向代理时为代理IP地址)%t 开始处理请求的时间%P 服务请求的子进程的进程ID%r 请求的第一行%s 响应状态码%b 响应大小(以字节为单位)包括 HTTP 头%T 处理请求所需要的时间以秒为单位带有 0.06f 格式的浮动分数%D 服务请求所需的时间以毫米为单位%{FOO}i request.headers[‘FOO’]%{FOO}o response.headers[‘FOO’]%{FOO}e os.environ[‘FOO’] 1.6.3、Default headers 要设置默认的响应头我们可以使用 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 }1.6.4、用户会话 Actix Web 提供了会话管理的通用解决方案使用 actix-session 中间件可以使用多种后段类型来存储会话数据。默认实现cookie会话后段也可以添加其他后段实现。 CookieSession 使用 cookie 作为会话存储CookieSessionBackend 创建的会话呗限制为存储少于4000字节的数据因为 payload 必须适合单个 cookie如果会话长度超过 4000 字节会产生服务器内部错误。 可能具有已签名或私有的安全策略每个都有一个各自的 CookieSession 构造器。 签名的 cookie 可以被查看但不能被客户端修改客户端既不能查看也不能修改私有 cookie。构造函数接受一个键作为参数这是 cookie 会话的私钥——当这个值被改变所有会话数据都会消失。 通常您可以创建 SessionStorage 中间件并使用特定的后段实现(如 cookiesession)对其进行初始化要访问会话数据必须使用会话提取器这个方法返回一个Session 对象它允许我们获取或设置会话数据。 1、添加依赖 cargo add actix-session2、例子代码 use actix_session::{Session, SessionMiddleware, storage::CookieSessionStore}; use actix_web::{web, App, Error, HttpResponse, HttpServer, cookie::Key};async fn index(session: Session) - ResultHttpResponse, Error {// access session dataif 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 middlewareSessionMiddleware::builder(CookieSessionStore::default(), Key::from([0; 64])).cookie_secure(false).build()).service(web::resource(/).to(index))}).bind((127.0.0.1, 8080))?.run().await }1.6.5、错误处理 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_headerB(mut res: dev::ServiceResponseB) - ResultErrorHandlerResponseB {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 }1.7、静态文件 1.7.1、单个文件 可以使用自定义路径模式和 NamedFile 来提供静态文件。为了匹配路径尾部我们可以使用 [.*] 正则表达式。 use actix_files::NamedFile; use actix_web::{HttpRequest, Result}; use std::path::PathBuf;async fn index(req: HttpRequest) - ResultNamedFile {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 有安全隐患它为攻击者提供者插入并访问主机上运行服务器的用户有权访问的每个文件。 1.7.2、目录 为了从特定目录和子目录中提供文件可以使用文件。文件时必须用 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 响应。要启用文件列表使用 fles::show_files_liisting() 方法。 可以重定向到特定的索引文件而不是显示目录的文件列表。使用 Files::index_file 方法配置此重定向。 1.7.3、配置 NamedFiles 可以为服务文件指定各种选项 set_content_disposition 函数用于将文件的 MIME 映射到相应的内容处理类型use_etag 指定是否计算 ETag 并将其包含在 header 中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) - Resultfs::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 }二、WebSocket Actix-Web 同样也支持 WebSocket使用 actix-web-actors 库即可。它可以将 Payload 转换为带有 web::Payload 到 ws::Message 流然后使用流组合子来处理实际的消息但是使用 http actor 处理 websocket 通信更简单。 1、查看依赖 cargo add actix-web-actors2、代码 use actix::{Actor, StreamHandler}; use actix_web::{web, App, Error, HttpRequest, HttpResponse, HttpServer}; use actix_web_actors::ws;/// Define HTTP actor struct MyWs;impl Actor for MyWs {type Context ws::WebsocketContextSelf; }/// Handler for ws::Message message impl StreamHandlerResultws::Message, ws::ProtocolError for MyWs {fn handle(mut self, msg: Resultws::Message, ws::ProtocolError, ctx: mut Self::Context) {match msg {Ok(ws::Message::Ping(msg)) ctx.pong(msg),Ok(ws::Message::Text(text)) ctx.text(text),Ok(ws::Message::Binary(bin)) ctx.binary(bin),_ (),}} }async fn index(req: HttpRequest, stream: web::Payload) - ResultHttpResponse, Error {let resp ws::start(MyWs {}, req, stream);println!({:?}, resp);resp }#[actix_web::main] async fn main() - std::io::Result() {HttpServer::new(|| App::new().route(/ws/, web::get().to(index))).bind((127.0.0.1, 8080))?.run().await }三、热加载(Auto-Reloading) 在开发过程中让 cargo 在发生更改时自动重新编译代码是非常方便的。可以使用 cargo-watch 完成。 cargo watch -x run四、Shuttle Shuttle 是一个 Rust 云原生开发平台可以让我们免费部署 Rust 应用程序。Shuttle 对 Actix Web 具有开箱即用的支持按照一下步骤在 Shuttle 上托管 Web 服务。 1、添加依赖 cargo add shuttle-actix-web shuttle-runtime2、修改 main 函数 use actix_web::{get, web::ServiceConfig}; use shuttle_actix_web::ShuttleActixWeb;#[shuttle_runtime::main] async fn main() - ShuttleActixWebimpl FnOnce(mut ServiceConfig) Send Clone static {let config move |cfg: mut ServiceConfig| {// set up your service here, e.g.:cfg.service(hello_world);};Ok(config.into()) }3、安装 cargo-shuttle cargo install cargo-shuttle4、在 shuttle 平台上创建项目 cargo shuttle project start5、部署 cargo shuttle deploy总结 本章节讲解 Actix-Web 的进阶使用。
http://www.tj-hxxt.cn/news/131448.html

相关文章:

  • 国内设计欣赏网站昆明网站建设猫咪科技
  • 公司官方网站制作上海网络推广报价
  • 湛江网站建设方案优化wordpress关于我们插件
  • 安全的小网站小程序页面设计用什么软件
  • 妇科医院网站建设怎么做网站建设品
  • 网站的logo在百度怎么显示不出来阿里巴巴注册网站首页
  • 深圳网站建设 利科技湖北民族建设集团网站
  • 英文网站建设平台青海项目信息网官网
  • 手册设计网站app推广多少钱一单
  • 智联招聘网站怎么做两份简历模板wordpress 插件评论区
  • 漂亮的博客网站模板莱芜吧重汽怎么样
  • 微信企业网站html5模板网站后台上传图片无法显示
  • 公司做网站推广需要多少钱网站开发综合实训报告
  • wordpress样板seo怎么做新手入门
  • 秦皇岛公司做网站公司网站建设制度
  • 聊城网站建设设计营销网站的例子
  • 苏州做网站的公司有哪些沈阳建站经验
  • 做业帮网站英文网站建设szjijie
  • 柳州做网站哪家好查询网站开发的端口
  • 如何查看一个网站做的外链无锡那家网络公司做网站好
  • 网站模板网东莞网站建设哪家最好
  • 做设计必看十大网站wordpress主页显示图片
  • 公司网站规划案例怎么做网站营销
  • 商务网站建设实验记录东莞市住建局官网
  • 网站tdk优化文档廊坊seo软件
  • 焦作公司做网站网站页面设计图片素材
  • 用商标做网站名字启信宝企业查询官网
  • 怎么在百度搜索自己的网站中国风网站怎么配色
  • 怎么给自己的网站做扫描码中国住房和城乡建设部网站
  • 企业网站建设存在的问题高平企业网站