├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── doc ├── Application.md ├── AutoReloading.md ├── ConnectionLifecycle.md ├── Databases.md ├── Errors.md ├── Extractors.md ├── GettingStarted.md ├── HTTP2.md ├── HTTPServerInitialization.md ├── Handlers.md ├── Middleware.md ├── Requests.md ├── Responses.md ├── Server.md ├── StaticFiles.md ├── Testing.md ├── URLDispatch.md ├── Webscokets.md ├── WelcomeToActix.md └── WhatIsActix.md ├── images ├── connection_accept.svg ├── connection_overview.svg ├── connection_request.svg ├── connection_worker.svg └── http_server.svg └── src ├── bin ├── application.rs ├── errors_custom_error_response.rs ├── extractors_application_state_arc.rs ├── extractors_application_state_cell.rs ├── extractors_json.rs ├── extractors_type_safe_path.rs ├── handlers_different_return_types.rs ├── handlers_request_handlers.rs ├── handlers_response_with_custom_type.rs ├── handlers_streaming_response_body.rs ├── hello_world.rs ├── middleware.rs ├── middleware_error_handler.rs ├── middleware_logging.rs ├── middleware_session.rs ├── requests.rs ├── responses.rs ├── server.rs ├── server_graceful_shutdown.rs ├── server_keepalive.rs ├── static_file.rs ├── url_dispatch_scoping.rs └── websocket_echo.rs └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | *.iml 3 | .idea 4 | 5 | 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "actix-web3-CN-doc" 3 | version = "0.1.0" 4 | authors = ["dslchd "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | # 添加actix-web 3.0版本依赖 11 | #actix-web = { version = "3", features = ["openssl"] } 12 | #openssl = { version = "0.10" } 13 | actix-web = "3" 14 | # 异步请求与响应操作的组合器 15 | actix-service = "1.0.6" 16 | actix-session = "0.4.0" 17 | actix-files = "0.3.0" 18 | actix-web-actors = "3.0.0" 19 | actix = "0.10.0" 20 | # 序列化 21 | serde = "1.0.116" 22 | serde_json = "1.0.57" 23 | futures = "0.3.5" 24 | env_logger = "0.7" 25 | bytes = "0.5" 26 | rand = "0.7.3" 27 | derive_more = "0.99.10" 28 | log = "0.4.11" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 dslchd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # actix-web 3.0 中文文档 2 | 3 | ## 1.说明 4 | 基本上算是翻译了官文档,但是示例并不一定和官方的相同. 所有的示例代码都源自官方文档中的示例,但又不完全与之相同. 5 | 6 | 算是一边学习一边理解写出来的demo代码且全部都能正常运行. 7 | 8 | 可以使用如下命令 + 指定文件名执行并查看结果: 9 | 10 | ```shell script 11 | cargo run --bin hello_world 12 | ``` 13 | 14 | **另外:** `Actix-Web` 的网络部分是基于[Tokio](https://tokio.rs/tokio/tutorial) 来实现的. 因此要想更加深入的了解`Actix-web`的实现细节, `Tokio`是你 15 | 必须要学习和了解的框架. `Tokio` 的中文文档指南请参考: [这里](https://github.com/dslchd/tokio-cn-doc). 16 | 17 | ## 2.文档索引 18 | ### 介绍(Introduction) 19 | [欢迎(Welcome)](doc/WelcomeToActix.md) 20 | 21 | [什么是Actix(What is Actix)](doc/WhatIsActix.md) 22 | ### 基础(Basics) 23 | [起步(Getting Started)](doc/GettingStarted.md) 24 | 25 | [应用(Application)](doc/Application.md) 26 | 27 | [服务器(Server)](doc/Server.md) 28 | 29 | [处理器(Handlers)](doc/Handlers.md) 30 | 31 | [提取器(Extractors)](doc/Extractors.md) 32 | 33 | ### 高级(Advanced) 34 | [错误(Errors)](doc/Errors.md) 35 | 36 | [URL分发(URL Dispatch)](doc/URLDispatch.md) 37 | 38 | [请求(Requests)](doc/Requests.md) 39 | 40 | [响应(Responses)](doc/Responses.md) 41 | 42 | [测试(Testing)](doc/Testing.md) 43 | 44 | [中间件(Middleware)](doc/Middleware.md) 45 | 46 | [静态文件(Static Files)](doc/StaticFiles.md) 47 | 48 | ### 协议(Protocols) 49 | [Websockets](doc/Webscokets.md) 50 | 51 | [HTTP/2](doc/HTTP2.md) 52 | 53 | ## 模式(Patterns) 54 | [自动重载(Auto-Reloading)](doc/AutoReloading.md) 55 | 56 | [数据库(Databases)](doc/Databases.md) 57 | 58 | ## 图解(Diagrams) 59 | [HTTP服务初始化(HTTP Server Initialization)](doc/HTTPServerInitialization.md) 60 | 61 | [链接生命周期(Connection Lifecycle)](doc/ConnectionLifecycle.md) 62 | 63 | ## API文档 64 | [actix](https://docs.rs/actix) 65 | 66 | [actix-web](https://docs.rs/actix-web/) 67 | 68 | ## 3.其它 69 | 由于水平有限,在翻译过程中过程中难免有错误或遗漏,可以发现后及时向我提出(提 issue). 70 | 71 | 希望此文档能给不想看英文原文或英文不太好的朋友, 在使用或学习 `Actix-web` 与 `Rust` 来开发Web应用时带来帮助, 72 | 大家共同提高, 为Rust的流行作出丁点贡献. 73 | 74 | **如果觉得给你的学习带来了帮助, 可以帮忙点个star, 这将是我一直同步更新下去的动力.** -------------------------------------------------------------------------------- /doc/Application.md: -------------------------------------------------------------------------------- 1 | ## 编写一个应用程序(Writing an Application) 2 | `actix-web` 里面提供了一系列可以使用rust来构建web server的原语。它提供了路由,中间件,request预处理,response的后置处理等。 3 | 4 | 所有的 `actix-web` 服务都围绕App实例来构建. 它被用来注册路由资源和中间件. 它也存储同一个scope内所有处理程序之间共享的应用程序状态. 5 | 6 | 应用的 `scope` 扮演所有路由命名空间的角色, 比如, 为所有的路由指定一个应用级范围(scope), 那么就会有一个相同前缀的url路径. 7 | 应用前缀总是包含一个 "/" 开头,如果提供的前缀没有包含斜杠,那么会默认自动的插入一个斜杠. 8 | 9 | 比如应用使用 `/app` 来限定, 那么任何使用了路径为 `/app`, `/app/` 或者 `/app/test` 的请求都将被匹配,但是` /application` 这种path不会被匹配. 10 | 11 | ```rust 12 | use actix_web::{web, App, HttpServer, Responder}; 13 | 14 | async fn index() -> impl Responder { 15 | "Hello world!" 16 | } 17 | 18 | #[actix_web::main] 19 | async fn main() -> std::io::Result<()> { 20 | HttpServer::new(|| { 21 | App::new().service( 22 | // 所有资源与路由加上前缀... 23 | web::scope("/app") 24 | // ...因此handler的请求是对应 `GET /app/index.html` 25 | .route("/index.html", web::get().to(index)), 26 | ) 27 | }) 28 | .bind("127.0.0.1:8080")? 29 | .run() 30 | .await 31 | } 32 | ``` 33 | 34 | 在此示例中,将创建具有 `/app` 前缀和 `index.html` 资源的应用程序.因此完整的资源路径url就是 `/app/index.html`. 35 | 36 | 更多的信息,将会在 `URL Dispatch` 章节讨论. 37 | 38 | ## 状态(State) 39 | 应用程序状态(State)被同一作用域(Scope)内的所有路由和资源共享.State 能被 `web::Data` 来访问,其中 `T` 是 state的类型. State也能被中间件访问. 40 | 41 | 让我们编写一个简单的应用程序并将应用程序名称存储在状态中,你可以在应用程序中注册多个State. 42 | 43 | ```rust 44 | use actix_web::{get, web, App, HttpServer}; 45 | 46 | // 这个结构体代表一个State 47 | struct AppState { 48 | app_name: String, 49 | } 50 | 51 | #[get("/")] 52 | async fn index(data: web::Data) -> String { 53 | let app_name = &data.app_name; // <- 得到app名 54 | 55 | format!("Hello {}!", app_name) // <- 响应app名 56 | } 57 | ``` 58 | 59 | 并且在初始化 app 时传递状态(state),然后再启动应用程序: 60 | 61 | ```rust 62 | #[actix_web::main] 63 | async fn main() -> std::io::Result<()> { 64 | HttpServer::new(|| { 65 | App::new() 66 | .data(AppState { 67 | app_name: String::from("Actix-web"), 68 | }) 69 | .service(index) 70 | }) 71 | .bind("127.0.0.1:8080")? 72 | .run() 73 | .await 74 | } 75 | ``` 76 | 你可以在应用中注册任意数量的状态类型. 77 | 78 | ## 共享可变状态(Shared Mutable State) 79 | `HttpServer` 接收一个应用程序工厂而不是一个应用程序实例, 一个 `HttpServer` 为每一个线程构造一个应用程序实例. 80 | 因此必须多次构造应用程序数据,如果你想在两个不同的线程之间共享数据,一个可以共享的对象可以使用比如: `Sync + Send`. 81 | 82 | 内部 `web::Data` 使用 `Arc`. 因此为了避免创建两个 `Arc`, 我们应该在在使用 `App::app_data()` 之前创建好我们的数据. 83 | 84 | 下面的例子中展示了应用中使用可变共享状态, 首先我们定义state并创建处理器(handler). 85 | 86 | ```rust 87 | use actix_web::{web, App, HttpServer}; 88 | use std::sync::Mutex; 89 | 90 | struct AppStateWithCounter { 91 | counter: Mutex, // <- Mutex 必须安全的在线程之间可变 92 | } 93 | 94 | async fn index(data: web::Data) -> String { 95 | let mut counter = data.counter.lock().unwrap(); // <- 得到 counter's MutexGuard 96 | *counter += 1; // <- 在 MutexGuard 内访问 counter 97 | 98 | format!("Request number: {}", counter) // <- 响应counter值 99 | } 100 | ``` 101 | 102 | 并在 `App` 中注册数据: 103 | 104 | ```rust 105 | #[actix_web::main] 106 | async fn main() -> std::io::Result<()> { 107 | let counter = web::Data::new(AppStateWithCounter { 108 | counter: Mutex::new(0), 109 | }); 110 | 111 | HttpServer::new(move || { 112 | // 移动 counter 进闭包中 113 | App::new() 114 | // 注意这里使用 .app_data() 代替 data 115 | .app_data(counter.clone()) // <- 注册创建的data 116 | .route("/", web::get().to(index)) 117 | }) 118 | .bind("127.0.0.1:8080")? 119 | .run() 120 | .await 121 | } 122 | ``` 123 | 124 | ## 使用一个应用级Scope去组合应用(Using an Application Scope to Compose Applications) 125 | `web::scope()` 方法允许你设置一个资源组前缀. 它表示所有资源类型(或者说是一组资源定位符)前缀配置. 126 | 127 | 比如说: 128 | 129 | ```rust 130 | #[actix_web::main] 131 | async fn main() { 132 | let scope = web::scope("/users").service(show_users); 133 | App::new().service(scope); 134 | } 135 | ``` 136 | 137 | 在上面的示例中, `show_users` 路由将是具有 `/users/show` 而不是 `/show` 路径的有效路由模式, 因为应用程序的scope参数被添加到该模式之前. 138 | 所以只有在URL路径为 `/users/show` 时, 路由才会匹配, 当使用路由名称 `show_users` 调用 `HttpRequest.url_for()` 函数时, 它也会生成同样的 139 | URL路径. 140 | 141 | ## 应用防护和虚拟主机(Application guards and virtual hosting) 142 | 其实"防护"(guards)可以是说是actix-web为handler函数提供的一种安全配置. 143 | 144 | 你可以将防护看成一个接收请求对象引用并返回ture或者false的简单函数. 可以说guard是实现了Guard trait的任何对象. `actix-web` 提供了几种开箱即用的 `guards`. 145 | 你可以查看 [functions section](https://docs.rs/actix-web/3/actix_web/guard/index.html#functions) API文档. 146 | 147 | 其中一个guards就是 `Header`. 它可以被用在请求头信息的过滤. 148 | ```rust 149 | #[actix_web::main] 150 | async fn main() -> std::io::Result<()> { 151 | HttpServer::new(|| { 152 | App::new() 153 | .service( 154 | web::scope("/") 155 | .guard(guard::Header("Host", "www.rust-lang.org")) 156 | .route("", web::to(|| HttpResponse::Ok().body("www"))), 157 | ) 158 | .service( 159 | web::scope("/") 160 | .guard(guard::Header("Host", "users.rust-lang.org")) 161 | .route("", web::to(|| HttpResponse::Ok().body("user"))), 162 | ) 163 | .route("/", web::to(|| HttpResponse::Ok())) 164 | }) 165 | .bind("127.0.0.1:8080")? 166 | .run() 167 | .await 168 | } 169 | ``` 170 | 171 | ## 可配置(Configure) 172 | 为了简单与可重用, `App` 与 `web::Scope` 两者都提供了 `configure` 方法. 此功能让配置的各个部分在不同的模块甚至不同的库(library) 173 | 中移动时非常有用. 比如说, 一些资源的配置可以被移动到不同的模块中. 174 | 175 | (译者注: 其实这是一种拆分管理,一般来说可以提高代码重用,减少修改某个Scope组时可能带来的影响其它模块的错误.) 176 | 177 | ```rust 178 | use actix_web::{web, App, HttpResponse, HttpServer}; 179 | 180 | // 此功能可以位于其他模块中 181 | fn scoped_config(cfg: &mut web::ServiceConfig) { 182 | cfg.service( 183 | web::resource("/test") 184 | .route(web::get().to(|| HttpResponse::Ok().body("test"))) 185 | .route(web::head().to(|| HttpResponse::MethodNotAllowed())), 186 | ); 187 | } 188 | 189 | // 此功能可以位于其他模块中 190 | fn config(cfg: &mut web::ServiceConfig) { 191 | cfg.service( 192 | web::resource("/app") 193 | .route(web::get().to(|| HttpResponse::Ok().body("app"))) 194 | .route(web::head().to(|| HttpResponse::MethodNotAllowed())), 195 | ); 196 | } 197 | 198 | #[actix_web::main] 199 | async fn main() -> std::io::Result<()> { 200 | HttpServer::new(|| { 201 | App::new() 202 | .configure(config) 203 | .service(web::scope("/api").configure(scoped_config)) 204 | .route("/", web::get().to(|| HttpResponse::Ok().body("/"))) 205 | }) 206 | .bind("127.0.0.1:8080")? 207 | .run() 208 | .await 209 | } 210 | ``` 211 | 上面例子的结果是: 212 | ```text 213 | / -> "/" 214 | /app -> "app" 215 | /api/test -> "test" 216 | ``` 217 | 218 | 每一个 `ServiceConfig` 都有它自己的 `data`, `routers`, 和 `services`. -------------------------------------------------------------------------------- /doc/AutoReloading.md: -------------------------------------------------------------------------------- 1 | ## 自动重载开发服务(Auto-Reloading Development Server) 2 | 3 | 在开发的过程中,让**Cargo**在代码发生变化时自动编译是非常方便的. 可以使用 [cargo-watch](https://github.com/passcod/cargo-watch) 非常 4 | 容易的来完成这件事. 5 | 6 | ```shell 7 | cargo watch -x 'run --bin app' 8 | ``` 9 | 10 | ## 值得注意点(Historical Note) 11 | 12 | 此页面的旧版本建议使用`systemfd`和`listenfd`的组合,但这也有些缺陷,很难正确的整合,尤其是在更广泛的开发工作流中. 我们考虑使用 `cargo-watch` 13 | 可以非常方便的达到自动重载(auto-reloading)的目的. 14 | 15 | -------------------------------------------------------------------------------- /doc/ConnectionLifecycle.md: -------------------------------------------------------------------------------- 1 | # 链接生命周期(Connection Lifecycle) 2 | 3 | ## 架构总览(Architecture overview) 4 | 在服务启动并监听所有socket链接后, `Accept` 和 `Worker` 两个主要的轮循是来处理从客户端传入的链接的. 5 | 6 | 一旦接受链接, 应用程序级协议处理就会从 `Worker` 派生(spawn)的指定 `Dispatcher` 循环中发生. 7 | 8 | ![](../images/connection_overview.svg) 9 | 10 | ## Accept循环的更多细节 (Accept loop in more detail) 11 | 12 | ![](../images/connection_accept.svg) 13 | 14 | 关于更多 [Accept](https://github.com/actix/actix-net/blob/master/actix-server/src/accept.rs) 结构体 15 | 代码的实现, 请参考 [actix-server](https://crates.io/crates/actix-server) 包. 16 | 17 | ## Worker循环的更多细节(Worker loop in more detail) 18 | 19 | ![](../images/connection_worker.svg) 20 | 21 | 关于更多 [Worker](https://github.com/actix/actix-net/blob/master/actix-server/src/worker.rs) 结构体 22 | 代码的实现, 请参考 [actix-server](https://crates.io/crates/actix-server) 包. 23 | 24 | ## 请求循环大体过程(Request loop roughly) 25 | 26 | ![](../images/connection_request.svg) 27 | 28 | 更多请求循环的代码实现请参考 [actix-web](https://crates.io/crates/actix-web) 与 [actix-http](https://crates.io/crates/actix-http) 29 | 这两个包. -------------------------------------------------------------------------------- /doc/Databases.md: -------------------------------------------------------------------------------- 1 | ## 异步选项(Async Options) 2 | 我们(actix-web)提供了几个使用异步数据库适配器的示例工程: 3 | * SQLx: https://github.com/actix/examples/tree/master/sqlx_todo 4 | * Postgres: https://github.com/actix/examples/tree/master/async_pg 5 | * SQLite: https://github.com/actix/examples/tree/master/async_db 6 | 7 | ## Diesel 8 | Diesel的当前版本(v1)还不支持异步操作, 因此使用 `web::block` 函数将数据库操作装载到 _Actix_ 运行时线程池中是非常重要的. 9 | 10 | 你可以创建与应用程序在数据库上执行的所有操作相对应的动作功能. 11 | ```rust 12 | fn insert_new_user(db: &SqliteConnection, user: CreateUser) -> Result { 13 | use self::schema::users::dsl::*; 14 | 15 | // 创建插入模型 16 | let uuid = format!("{}", uuid::Uuid::new_v4()); 17 | let new_user = models::NewUser { 18 | id: &uuid, 19 | name: &user.name, 20 | }; 21 | 22 | // 正常 diesel 操作 23 | diesel::insert_into(users) 24 | .values(&new_user) 25 | .execute(&self.0) 26 | .expect("Error inserting person"); 27 | 28 | let mut items = users 29 | .filter(id.eq(&uuid)) 30 | .load::(&self.0) 31 | .expect("Error loading person"); 32 | 33 | Ok(items.pop().unwrap()) 34 | } 35 | ``` 36 | 37 | 现在你应该使用例如像 `r2d2` 这样的包来设置数据库链接池, 这将使你的应用程序有许多数据库链接可用. 这也意味着多个处理函数可以同时操作数据库, 38 | 并且还能够接收新的链接. 简单的来说链接池是应用程序状态. 在这种情况下最好不要使用状态包装结构体(struct), 因为在池中会共享访问. 39 | 40 | ```rust 41 | type DbPool = r2d2::Pool>; 42 | 43 | #[actix_web::main] 44 | async fn main() -> io::Result<()> { 45 | // 创建链接池 46 | let pool = r2d2::Pool::builder() 47 | .build(manager) 48 | .expect("Failed to create pool."); 49 | 50 | // 启动HTTP服务 51 | HttpServer::new(move || { 52 | App::new::data(pool.clone()) 53 | .resource("/{name}", web::get().to(index)) 54 | }) 55 | .bind("127.0.0.1:8080")? 56 | .run() 57 | .await 58 | } 59 | ``` 60 | 61 | 现在, 在请求处理器中使用 `Data` 提取器来从应用程序State中得到pool并进而得到链接. 这提供了一个可以传入到 `web::block` 闭包中的数据库链接. 62 | 然后只需要使用必要的参数调用 action 函数, `.await` 结果即可. 63 | 64 | 如果你返回了一个实现了 `ResponseError` 的错误类型, 示例中在使用 `?` 号操作符前将一个错误映射成一个 `HttpResponse` 中时, 这个操作并不是必须的. 65 | 66 | ```rust 67 | async fn index(req: web::Data, name: web::Path<(String)>) -> impl Responder { 68 | let name = name.into_inner(); 69 | 70 | let conn = pool.get().expect("couldn't get db connection from pool"); 71 | 72 | let user = web::block(move || actions::insert_new_user(&conn, &user)) 73 | .await 74 | .map_err(|e| { 75 | eprintln!("{}", e); 76 | HttpResponse::InternalServerError().finish() 77 | })?; 78 | 79 | Ok(HttpResponse::Ok().json(user)) 80 | } 81 | ``` 82 | 83 | 完整的示例参考这里: https://github.com/actix/examples/tree/master/diesel -------------------------------------------------------------------------------- /doc/Errors.md: -------------------------------------------------------------------------------- 1 | ## 错误(Errors) 2 | Actix-web使用它自己的`actix_web::error::Error`类型和`actix_web::error:ResponseError` trait 来处理web处理函数中的错误. 3 | 4 | 如果一个处理函数在一个`Result`中返回`Error`(指普通的Rust trait `std::error:Error`),此`Result`也实现了`ResponseError` trait的话, 5 | actix-web将使用相应的`actix_web::http::StatusCode` 响应码作为一个Http response响应渲染.默认情况下会生成内部服务错误: 6 | ```rust 7 | pub trait ResponseError { 8 | fn error_response(&self) -> Response; 9 | fn status_code(&self) -> StatusCode; 10 | } 11 | ``` 12 | `Response` 将兼容的 `Result` 强制转换到Http响应中: 13 | ```rust 14 | impl> Responder for Result{} 15 | ``` 16 | 上面代码中的`Error`是actix-web中的error定义, 并且任何实现了`ResponseError`的错误都会被自动转换. 17 | 18 | Actix-web提供了一些常见的非actix error的 `ResponseError` 实现. 例如一个处理函数返回一个 `io::Error`,那么这个错误将会被自动的转换成一个 19 | `HttpInternalServerError`: 20 | ```rust 21 | use std::io; 22 | use actix_files::NamedFile; 23 | 24 | fn index(_req: HttpRequest) -> io::Result { 25 | Ok(NamedFile::open("static/index.html")) 26 | } 27 | ``` 28 | 参见完整的 `ResponseError` 外部实现[the actix-web API documentation](https://docs.rs/actix-web/3/actix_web/error/trait.ResponseError.html#foreign-impls) 29 | 30 | ## 自定义错误响应示例(An example of a custom error response) 31 | 这里有一个实现了`ResponseError`的示例, 使用`derive_more`声明错误枚举. 32 | 33 | ```rust 34 | use actix_web::{error, Result}; 35 | use derive_more::{Display, Error}; 36 | 37 | #[derive(Debug, Display, Error)] 38 | #[display(fmt = "my error: {}", name)] 39 | struct MyError { 40 | name: &'static str, 41 | } 42 | 43 | // Use default implementation for `error_response()` method 44 | // 为 `error_response()` 方法使用默认实现 45 | impl error::ResponseError for MyError {} 46 | 47 | async fn index() -> Result<&'static str, MyError> { 48 | Err(MyError { name: "test" }) 49 | } 50 | ``` 51 | 当上面的 `index` 处理函数执行时, `ResponseError` 的一个默认实现 `error_response()` 会渲染500(服务器内部错误)返回. 52 | 53 | 重写 `error_response()` 方法来产生更多有用的结果: 54 | 55 | ```rust 56 | use actix_web::{ 57 | dev::HttpResponseBuilder, error, get, http::header, http::StatusCode, App, HttpResponse, 58 | }; 59 | use derive_more::{Display, Error}; 60 | 61 | #[derive(Debug, Display, Error)] 62 | enum MyError { 63 | #[display(fmt = "internal error")] 64 | InternalError, 65 | 66 | #[display(fmt = "bad request")] 67 | BadClientData, 68 | 69 | #[display(fmt = "timeout")] 70 | Timeout, 71 | } 72 | 73 | impl error::ResponseError for MyError { 74 | fn error_response(&self) -> HttpResponse { 75 | HttpResponseBuilder::new(self.status_code()) 76 | .set_header(header::CONTENT_TYPE, "text/html; charset=utf-8") 77 | .body(self.to_string()) 78 | } 79 | 80 | fn status_code(&self) -> StatusCode { 81 | match *self { 82 | MyError::InternalError => StatusCode::INTERNAL_SERVER_ERROR, 83 | MyError::BadClientData => StatusCode::BAD_REQUEST, 84 | MyError::Timeout => StatusCode::GATEWAY_TIMEOUT, 85 | } 86 | } 87 | } 88 | 89 | #[get("/")] 90 | async fn index() -> Result<&'static str, MyError> { 91 | Err(MyError::BadClientData) 92 | } 93 | ``` 94 | 95 | ## 错误帮助(Error helpers) 96 | Actix-web提供了一个错误帮助功能的集合, 这个集合在从其它错误生成指定的HTTP错误码时非常有用.下面我们转换 `MyError`, 它没有实现 97 | `ResponseError` trait, 使用`map_err`来返回400(bad request): 98 | 99 | ```rust 100 | use actix_web::{error, get, App, HttpServer, Result}; 101 | 102 | #[derive(Debug)] 103 | struct MyError { 104 | name: &'static str, 105 | } 106 | 107 | #[get("/")] 108 | async fn index() -> Result<&'static str> { 109 | let result: Result<&'static str, MyError> = Err(MyError { name: "test error" }); 110 | 111 | Ok(result.map_err(|e| error::ErrorBadRequest(e.name))?) 112 | } 113 | ``` 114 | 查看[The documentation for actix-web's `error` `module` ](https://docs.rs/actix-web/3/actix_web/error/struct.Error.html) 115 | 了解完整的错误帮助清单. 116 | 117 | ## 错误日志(Error logging) 118 | Actix 所有错误日志都是 `WARN` 级别的. 如果应用日志级别启用 `DEBUG` 和 `RUST_BACKTRACE`, 回溯日志也会被启用.这些可以使用环境变量进行配置: 119 | ```text 120 | RUST_BACKTRACE=1 RUST_LOG=actix_web=debug cargo run 121 | ``` 122 | 使用 error backtrace 的`Error`类型如果可用. 如果底层的异常(failure)(译者注: 这里翻译成异常好点) 不提供回溯(backtrace),则构造一个新的回溯,指向发生转换的点(而不是错误的根源). 123 | 124 | ## 错误处理的最佳实践(Recommended practices in error handling). 125 | 考虑将应用产生的错误分成两个大类是非常有用的: 这意味着一部分面向用户,另外一部分则不是. 126 | 127 | 前者的一个示例是, 我们可以使用失败来批定个 `UserError` 的枚举, 该枚举封装了一个 `ValidationError` , 以便在用户输入错误时返回: 128 | 129 | ```rust 130 | use actix_web::{ 131 | dev::HttpResponseBuilder, error, get, http::header, http::StatusCode, App, HttpResponse, 132 | HttpServer, 133 | }; 134 | use derive_more::{Display, Error}; 135 | 136 | #[derive(Debug, Display, Error)] 137 | enum UserError { 138 | #[display(fmt = "Validation error on field: {}", field)] 139 | ValidationError { field: String }, 140 | } 141 | 142 | impl error::ResponseError for UserError { 143 | fn error_response(&self) -> HttpResponse { 144 | HttpResponseBuilder::new(self.status_code()) 145 | .set_header(header::CONTENT_TYPE, "text/html; charset=utf-8") 146 | .body(self.to_string()) 147 | } 148 | fn status_code(&self) -> StatusCode { 149 | match *self { 150 | UserError::ValidationError { .. } => StatusCode::BAD_REQUEST, 151 | } 152 | } 153 | } 154 | ``` 155 | 这将完全按预期的方式运行, 因为编写使用 `display` 定义的错误消,其目的是为了用户要明确读取. 156 | 157 | 然而, 并不是所有的错误都要返回 - 在服务端环境捕获的许多异常我们都想让它对用户是隐藏的(不展示给用户看). 例如, 数据库关闭了导致的客户端 158 | 连接超时, 或 HTML 模板渲染时的格式错误. 在这些情况下最好将错误映射为适合用户使用的一般错误. 159 | 160 | 下面的示例,将内部错误映射为一个面向用户的 `InternalError`. 161 | 162 | ```rust 163 | use actix_web::{ 164 | dev::HttpResponseBuilder, error, get, http::header, http::StatusCode, App, HttpResponse, 165 | HttpServer, 166 | }; 167 | use derive_more::{Display, Error}; 168 | 169 | #[derive(Debug, Display, Error)] 170 | enum UserError { 171 | #[display(fmt = "An internal error occurred. Please try again later.")] 172 | InternalError, 173 | } 174 | 175 | impl error::ResponseError for UserError { 176 | fn error_response(&self) -> HttpResponse { 177 | HttpResponseBuilder::new(self.status_code()) 178 | .set_header(header::CONTENT_TYPE, "text/html; charset=utf-8") 179 | .body(self.to_string()) 180 | } 181 | fn status_code(&self) -> StatusCode { 182 | match *self { 183 | UserError::InternalError => StatusCode::INTERNAL_SERVER_ERROR, 184 | } 185 | } 186 | } 187 | 188 | #[get("/")] 189 | async fn index() -> Result<&'static str, UserError> { 190 | do_thing_that_fails().map_err(|_e| UserError::InternalError)?; 191 | Ok("success!") 192 | } 193 | ``` 194 | 通过将错误划分为面向用户的与非面向用户的部分, 我们可以确保我们不会意外的将应用程序内部错误暴露给用户,因为这部分错误并不是用户想看到的. 195 | 196 | ## 错误日志(Error Logging) 197 | 使用 `middleware::Logger` 示例: 198 | ```rust 199 | use actix_web::{error, get, middleware::Logger, App, HttpServer, Result}; 200 | use log::debug; 201 | use derive_more::{Display, Error}; 202 | 203 | #[derive(Debug, Display, Error)] 204 | #[display(fmt = "my error: {}", name)] 205 | pub struct MyError { 206 | name: &'static str, 207 | } 208 | 209 | // Use default implementation for `error_response()` method 210 | impl error::ResponseError for MyError {} 211 | 212 | #[get("/")] 213 | async fn index() -> Result<&'static str, MyError> { 214 | let err = MyError { name: "test error" }; 215 | debug!("{}", err); 216 | Err(err) 217 | } 218 | 219 | #[actix_web::main] 220 | async fn main() -> std::io::Result<()> { 221 | std::env::set_var("RUST_LOG", "my_errors=debug,actix_web=info"); 222 | std::env::set_var("RUST_BACKTRACE", "1"); 223 | env_logger::init(); 224 | 225 | HttpServer::new(|| App::new().wrap(Logger::default()).service(index)) 226 | .bind("127.0.0.1:8080")? 227 | .run() 228 | .await 229 | } 230 | ``` 231 | 232 | -------------------------------------------------------------------------------- /doc/Extractors.md: -------------------------------------------------------------------------------- 1 | ## 类型安全的信息提取器(Type-safe information extractor) 2 | `actix-web` 提供了一个灵活的类型安全的请求信息访问者,它被称为提取器(extractors)(实现了 `impl FromRequest`). 3 | 默认下actix-web提供了几种extractors的实现. 4 | 5 | 提取器可以被作为处理函数的参数访问. `actix-web` 每个处理函数(handler function)最多支持10个提取器. 它们作为参数的位置没有影响. 6 | ```rust 7 | async fn index(path: web::Path<(String, String)>, json: web::Json) -> impl Responder { 8 | let path = path.into_inner(); 9 | format!("{} {} {} {}", path.0, path.1, json.id, json.username) 10 | } 11 | ``` 12 | 13 | # 路径(Path) 14 | Path提供了能够从请求路径中提取信息的能力. 你可以从path中反序列化成任何变量. 15 | 16 | 因此,注册一个/users/{user_id}/{friend}的路径, 你可以反序列化两个字段, user_id和 friend. 17 | 这些字段可以被提取到一个 `tuple` (元组)中去, 比如: Path 或者是任何实现了 `serde trait`包中的 `Deserialize` 18 | 的结构体(structure) 19 | 20 | 请参见下面的示例: 21 | ```rust 22 | use actix_web::{get, web, Result}; 23 | 24 | /// 从 path url "/users/{user_id}/{friend}" 中提取参数 25 | /// {user_id} - 反序列化为一个 u32 26 | /// {friend} - 反序列化为一个 String 27 | #[get("/users/{user_id}/{friend}")] // <- 定义路径参数 28 | async fn index(web::Path((user_id, friend)): web::Path<(u32, String)>) -> Result { 29 | Ok(format!("Welcome {}, user_id {}!", friend, user_id)) 30 | } 31 | 32 | #[actix_web::main] 33 | async fn main() -> std::io::Result<()> { 34 | use actix_web::{App, HttpServer}; 35 | 36 | HttpServer::new(|| App::new().service(index)) 37 | .bind("127.0.0.1:8080")? 38 | .run() 39 | .await 40 | } 41 | ``` 42 | 43 | 44 | 也可以提取信息到一个指定的实现了`serde trait`反序列化的类型中去. 这种serde的使用方式与使用元组等效. 45 | 46 | 47 | 另外你也可以使用 `get` 或者 `query` 方法从请求path中通过名称提取参数值: 48 | ```rust 49 | #[get("/users/{userid}/{friend}")] // <- 定义路径参数 50 | async fn index(req: HttpRequest) -> Result { 51 | let name: String = req.match_info().get("friend").unwrap().parse().unwrap(); 52 | let userid: i32 = req.match_info().query("userid").parse().unwrap(); 53 | 54 | Ok(format!("Welcome {}, userid {}!", name, userid)) 55 | } 56 | 57 | #[actix_web::main] 58 | async fn main() -> std::io::Result<()> { 59 | use actix_web::{App, HttpServer}; 60 | 61 | HttpServer::new(|| App::new().service(index)) 62 | .bind("127.0.0.1:8080")? 63 | .run() 64 | .await 65 | } 66 | ``` 67 | 68 | ## 查询(Query) 69 | 查询 `Query` 类型为请求参数提供了提取功能. 底层是使用了 `serde_urlencoded` 包功能. 70 | 71 | ```rust 72 | use actix_web::{get, web, App, HttpServer}; 73 | use serde::Deserialize; 74 | 75 | #[derive(Deserialize)] 76 | struct Info { 77 | username: String, 78 | } 79 | 80 | // 仅仅在请求查询参数中有 'username' 字段时才会被调用 81 | #[get("/")] 82 | async fn index(info: web::Query) -> String { 83 | format!("Welcome {}!", info.username) 84 | } 85 | ``` 86 | 87 | ## Json 88 | `Json`提取器允许你将请求body中的信息反序列化到一个结构体中. 为了从请求body中提取类型的信息,类型 T 必须要实现 `serde` 的 `Deserialize trait`. 89 | 90 | 参考如下示例: 91 | ```rust 92 | use actix_web::{get, web, App, HttpServer, Result}; 93 | use serde::Deserialize; 94 | 95 | #[derive(Deserialize)] 96 | struct Info { 97 | username: String, 98 | } 99 | 100 | /// 将request body中的信息反序列化到 Info 结构体中去 101 | #[get("/")] 102 | async fn index(info: web::Json) -> Result { 103 | Ok(format!("Welcome {}!", info.username)) 104 | } 105 | ``` 106 | 一些提取器提供了一种可以配置提取过程的方式. `Json` 提取器就使用 `JsonConfig` 类来配置. 为了配置提取器,可以将配置对象通过`resource`的`.data()` 107 | 方法传进去.如果是Json提取器,它将返回一个 `JsonConfig`. 另外你也可以配置json的最大有效负载(playload)和自定义错误处理功能. 108 | 109 | 下面的示例限制了`playload`的最大大小为4kb,且使用一个自定义的`error`处理. 110 | ```rust 111 | use actix_web::{error, web, App, FromRequest, HttpResponse, HttpServer, Responder}; 112 | use serde::Deserialize; 113 | 114 | #[derive(Deserialize)] 115 | struct Info { 116 | username: String, 117 | } 118 | 119 | /// 反序列化request body中的信息到 Info结构体中,且设置 playload大小为 4kb 120 | async fn index(info: web::Json) -> impl Responder { 121 | format!("Welcome {}!", info.username) 122 | } 123 | 124 | #[actix_web::main] 125 | async fn main() -> std::io::Result<()> { 126 | HttpServer::new(|| { 127 | // 配置json提取器 128 | let json_config = web::JsonConfig::default() 129 | .limit(4096) 130 | .error_handler(|err, _req| { 131 | // 创建一个自定义的错误类型 132 | error::InternalError::from_response(err, HttpResponse::Conflict().finish()).into() 133 | }); 134 | 135 | App::new().service( 136 | web::resource("/") 137 | // 通过.app_data()改变json 提取器配置 138 | .app_data(json_config) 139 | .route(web::post().to(index)), 140 | ) 141 | }) 142 | .bind("127.0.0.1:8080")? 143 | .run() 144 | .await 145 | } 146 | ``` 147 | 148 | ## 表单(Form) 149 | 当前仅支持url编码的形式. 使用`url-encoded`的body能被提取成一个指定的类型.这个类型必须实现`serde`包的`Deserialize trait`. 150 | 151 | `FormConfig` 允许你配置提取的过程. 152 | 153 | 参考如下示例: 154 | ```rust 155 | use actix_web::{post, web, App, HttpServer, Result}; 156 | use serde::Deserialize; 157 | 158 | #[derive(Deserialize)] 159 | struct FormData { 160 | username: String, 161 | } 162 | 163 | /// 使用serde提取表单数据 164 | /// 仅当 content type 类型是 *x-www-form-urlencoded* 是 handler处理函数才会被调用 165 | /// 且请求中的内容能够被反序列化到一个 "FormData" 结构体中去. 166 | #[post("/")] 167 | async fn index(form: web::Form) -> Result { 168 | Ok(format!("Welcome {}!", form.username)) 169 | } 170 | ``` 171 | 172 | ## 其它(Other) 173 | `Actix-web` 也提供了一些其它的提取器(extractors): 174 | * [Data](https://docs.rs/actix-web/3/actix_web/web/struct.Data.html) - 如果你需要访问应用程序状态的话. 175 | * HttpRequest - 如果你需要访问请求, `HttpRequest`本身就是一个提取器,它返回Self. 176 | * String - 你可以转换一个请求的`playload`成一个`String`. 可以参考文档中的[example](https://docs.rs/actix-web/3/actix_web/trait.FromRequest.html#example-2) 177 | * bytes::`Bytes` - 你可以转换一个请求的`playload`到`Bytes`中. 可以参考文档中的[example](https://docs.rs/actix-web/3/actix_web/trait.FromRequest.html#example-4) 178 | * Playload - 可以访问请求中的playload. [example](https://docs.rs/actix-web/3/actix_web/web/struct.Payload.html) 179 | 180 | ## 应用程序状态提取器(Application state extractor) 181 | 可以使用 `web::Data` 提取器在handler函数中访问应用程序状态(state); 然而state仅能作为一个只读引用来访问.如果你需要以可变(mutable)的方式 182 | 来访问state, 则它必须被实现. 183 | 184 | **Beware**, actix为应用程序状态和处理函数创建多个副本. 它为每一个线程创建一个副本. 185 | 186 | 下面是存储了处理的请求数的处理程序示例: 187 | 188 | ```rust 189 | use actix_web::{web, Responder}; 190 | use std::cell::Cell; 191 | 192 | #[derive(Clone)] 193 | struct AppState { 194 | count: Cell, 195 | } 196 | 197 | async fn show_count(data: web::Data) -> impl Responder { 198 | format!("count: {}", data.count.get()) 199 | } 200 | 201 | async fn add_one(data: web::Data) -> impl Responder { 202 | let count = data.count.get(); 203 | data.count.set(count + 1); 204 | 205 | format!("count: {}", data.count.get()) 206 | } 207 | 208 | #[actix_web::main] 209 | async fn main() -> std::io::Result<()> { 210 | use actix_web::{App, HttpServer}; 211 | 212 | let data = AppState { 213 | count: Cell::new(0), 214 | }; 215 | 216 | HttpServer::new(move || { 217 | App::new() 218 | .data(data.clone()) 219 | .route("/", web::to(show_count)) 220 | .route("/add", web::to(add_one)) 221 | }) 222 | .bind("127.0.0.1:8080")? 223 | .run() 224 | .await 225 | } 226 | ``` 227 | 虽然处理函数可以工作,`self.0` 的值会有所不同,具体取决于线程数量和每个线程处理的请求数.正解的实现是使用`Arc` 和 `ActomicUsize` . 228 | (因为它们是线程安全的). 229 | 230 | ```rust 231 | use actix_web::{get, web, App, HttpServer, Responder}; 232 | use std::sync::atomic::{AtomicUsize, Ordering}; 233 | use std::sync::Arc; 234 | 235 | #[derive(Clone)] 236 | struct AppState { 237 | count: Arc, 238 | } 239 | 240 | #[get("/")] 241 | async fn show_count(data: web::Data) -> impl Responder { 242 | format!("count: {}", data.count.load(Ordering::Relaxed)) 243 | } 244 | 245 | #[get("/add")] 246 | async fn add_one(data: web::Data) -> impl Responder { 247 | data.count.fetch_add(1, Ordering::Relaxed); 248 | 249 | format!("count: {}", data.count.load(Ordering::Relaxed)) 250 | } 251 | 252 | #[actix_web::main] 253 | async fn main() -> std::io::Result<()> { 254 | let data = AppState { 255 | count: Arc::new(AtomicUsize::new(0)), 256 | }; 257 | 258 | HttpServer::new(move || { 259 | App::new() 260 | .data(data.clone()) 261 | .service(show_count) 262 | .service(add_one) 263 | }) 264 | .bind("127.0.0.1:8080")? 265 | .run() 266 | .await 267 | } 268 | ``` 269 | 小心使用这些同步原语像`Mutex` 或 `Rwlock` . `actix-web` 框架是异步处理请求的. 如果阻塞了执行线程,那么所有的并发请求都将在处理函数 270 | 阻塞. 如果你需要在多线程中共享或更新状态, 考虑使用`tokio`的同步原语. 271 | 272 | -------------------------------------------------------------------------------- /doc/GettingStarted.md: -------------------------------------------------------------------------------- 1 | ## 安装Rust(Installing Rust) 2 | 如果你还没有安装Rust, 我们建议你使用 `rustup` 来管理你的Rust安装. [official rust guide](https://doc.rust-lang.org/book/ch01-01-installation.html) 3 | 提供了精彩的安装起步指导. 4 | 5 | Actix Web 当前支持的Rust最低版本(MSRV) 是1.42. 执行 `rustup update` 命令Rust可用的最佳版本. 因此本指南假设你在 Rust 1.42或更高的版本上运行. 6 | 7 | ## Hello, world! 8 | 基于Cargo 命令创建一个新的字节码工程, 并转换到新的目录: 9 | 10 | ```shell script 11 | cargo new hello-world 12 | cd hello-world 13 | ``` 14 | 在你的工程下的 `Cargo.toml` 文件内添加 `Actix-web` 的依赖. 15 | ```toml 16 | [depandencies] 17 | actix-web = "3" 18 | ``` 19 | 20 | 使用异步函数的请求处理器接受零个或多个参数. 这些参数能从请求中提取(查看 `FromRequest` trait) 且返回一个能被转换成一个 `HttpResponse` 21 | (查看 `Responder` trait) 的类型. 22 | 23 | ```rust 24 | use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder}; 25 | 26 | #[get("/")] 27 | async fn hello() -> impl Responder { 28 | HttpResponse::Ok().body("Hello world!") 29 | } 30 | 31 | #[post("/echo")] 32 | async fn echo(req_body: String) -> impl Responder { 33 | HttpResponse::Ok().body(req_body) 34 | } 35 | 36 | async fn manual_hello() -> impl Responder { 37 | HttpResponse::Ok().body("Hey there!") 38 | } 39 | ``` 40 | 41 | 注意,其中某些处理器使用内置宏直接附加了路由信息. 这就允许你指定处理器程序响应的方法与路径. 你也将在下面的示例中看到如何不使用路由宏来注册 42 | 一个其它的路由. 43 | 44 | 下一步,创建一个 `App` 实例并注册请求处理器. 为使用了路由宏的处理器来使用 `App::service` 方法设置(路由), 并且 `App::route` 可以手动的 45 | 注册路由处理器,并声明一个路径(path)和方法(译者注: 比如post或者get). 最后应用程序使用 `HttpServer` 来启动, 它将使你的 `App` 作为一个 46 | "application factory" 来处理传入的请求. 47 | 48 | ```rust 49 | #[actix_web::main] 50 | async fn main() -> std::io::Result<()> { 51 | HttpServer::new(|| { 52 | App::new() 53 | .service(hello) 54 | .service(echo) 55 | .route("/hey", web::get().to(manual_hello)) 56 | }) 57 | .bind("127.0.0.1:8080")? 58 | .run() 59 | .await 60 | } 61 | ``` 62 | 63 | 使用 `Cargo run` 来编译并运行程序. 使用actix运行时的 `#[actix_web::main]` 宏用来执行异步main函数. 现在你可以访问 `http://localhost:8080/` 64 | 或者任何一个你定义的路由来查看结果. 65 | -------------------------------------------------------------------------------- /doc/HTTP2.md: -------------------------------------------------------------------------------- 1 | ## HTTP2.0 2 | 如果有可能 `actix-web` 会自动的将链接升级成 _HTTP/2_ . 3 | 4 | ## Negotiation 5 | _HTTP/2_ 协议是基于TLS的且需要 [TLS ALPN](https://tools.ietf.org/html/rfc7301). 6 | 7 | 当前仅有 `rust-openssl` 有支持. 8 | 9 | `alpn` 需要协商启用该功能, 当启用时, `HttpServer` 提供了一个 [bind_openssl](https://docs.rs/actix-web/3/actix_web/struct.HttpServer.html#method.bind_openssl) 10 | 的方法来操作. 11 | 12 | toml文件中加入如下依赖: 13 | 14 | ```toml 15 | actix-web = { version = "3", features = ["openssl"] } 16 | openssl = { version = "0.10", features = ["v110"] } 17 | ``` 18 | 19 | ```rust 20 | use actix_web::{web, App, HttpRequest, HttpServer, Responder}; 21 | use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; 22 | 23 | async fn index(_req: HttpRequest) -> impl Responder { 24 | "Hello." 25 | } 26 | 27 | #[actix_web::main] 28 | async fn main() -> std::io::Result<()> { 29 | // 载入 ssl keys 30 | // 为测试创建自签名临时证书: 31 | // `openssl req -x509 -newkey rsa:4096 -nodes -keyout key.pem -out cert.pem -days 365 -subj '/CN=localhost'` 32 | let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); 33 | builder 34 | .set_private_key_file("key.pem", SslFiletype::PEM) 35 | .unwrap(); 36 | builder.set_certificate_chain_file("cert.pem").unwrap(); 37 | 38 | HttpServer::new(|| App::new().route("/", web::get().to(index))) 39 | .bind_openssl("127.0.0.1:8080", builder)? 40 | .run() 41 | .await 42 | } 43 | ``` 44 | 不支持升级到RFC3.2节中描述的HTTP/2.0模式. _HTTP/2_ 明文链接与TLS链接都支持. 45 | 46 | 具体示例参考 [examples/tls](https://github.com/actix/examples/tree/master/rustls). -------------------------------------------------------------------------------- /doc/HTTPServerInitialization.md: -------------------------------------------------------------------------------- 1 | ## 架构总览(Architecture overview) 2 | 下面的图表是 HttpServer 初始化过程, 它发生在如下代码处理过程中: 3 | 4 | ```rust 5 | #[actix_web::main] 6 | async fn main() -> std::io::Result<()> { 7 | HttpServer::new(|| { 8 | App::new() 9 | .route("/", web::to(|| HttpResponse::Ok())) 10 | }) 11 | .bind("127.0.0.1:8080")? 12 | .run() 13 | .await 14 | } 15 | ``` 16 | 17 | ![](../images/http_server.svg) -------------------------------------------------------------------------------- /doc/Handlers.md: -------------------------------------------------------------------------------- 1 | ## 请求处理器(Request Handlers) 2 | 一个请求处理器,它是一个异步函数,可以接收零个或多个参数,而这些参数被实现了(比如, [impl FromRequest](https://docs.rs/actix-web/3/actix_web/trait.FromRequest.html) )的请求所提取, 3 | 并且返回一个被转换成HttpResponse或者其实现(比如, [impl Responder](https://docs.rs/actix-web/3/actix_web/trait.Responder.html) )的类型. 4 | 5 | 请求处理发生在两个阶段.首先处理对象被调用,并返回一个实现了 `Responder` trait的任何对象.然后在返回的对象上调用 `respond_to()` 方法,将其 6 | 自身转换成一个 `HttpResponse` 或者 `Error` . 7 | 8 | 默认情况下 _actix-web_ 为 `&‘static str` , `String` 等提供了 `Responder` 的标准实现. 9 | 10 | 完整的实现清单可以参考 [Responder documentation](https://docs.rs/actix-web/3/actix_web/trait.Responder.html#foreign-impls) . 11 | 12 | 有效的 handler示例: 13 | 14 | ```rust 15 | async fn index(_req: HttpRequest) -> &'static str { 16 | "Hello World" 17 | } 18 | async fn index_two(_req: HttpRequest) -> String { 19 | "Hello world".to_string() 20 | } 21 | ``` 22 | 23 | 你也可以改变返回的签名为 `impl Responder` 它在要返回复杂类型时比较好用. 24 | 25 | ```rust 26 | async fn index(_req: HttpRequest) -> impl Responder { 27 | Bytes::from_static(b"Hello world") 28 | } 29 | ``` 30 | 31 | ```rust 32 | async fn index(_req: HttpRequest) -> impl Responder { 33 | // ... 34 | } 35 | ``` 36 | 37 | ## 返回自定义类型(Response with custom Type) 38 | 为了想直接从处理函数返回自定义类型的话, 需要这个类型实现 `Responder` trait. 39 | 40 | 让我们创建一个自定义响应类型,它可以序列化为一个 `application/json` 响应. 41 | 42 | ```rust 43 | use actix_web::{Error, HttpRequest, HttpResponse, Responder}; 44 | use serde::Serialize; 45 | use futures::future::{ready, Ready}; 46 | 47 | #[derive(Serialize)] 48 | struct MyObj { 49 | name: &'static str, 50 | } 51 | 52 | // Responder 53 | impl Responder for MyObj { 54 | type Error = Error; 55 | type Future = Ready>; 56 | 57 | fn respond_to(self, _req: &HttpRequest) -> Self::Future { 58 | let body = serde_json::to_string(&self).unwrap(); 59 | 60 | // 创建响应并设置Content-Type 61 | ready(Ok(HttpResponse::Ok() 62 | .content_type("application/json") 63 | .body(body))) 64 | } 65 | } 66 | 67 | async fn index() -> impl Responder { 68 | MyObj { name: "user" } 69 | } 70 | ``` 71 | 72 | ## 流式响应body(Streaming response body) 73 | 响应主体也可以异步生成. 在下面的案例中, body 必须实现 Steam trait `Stream` , 比如像下面这样: 74 | 75 | ```rust 76 | use actix_web::{get, App, Error, HttpResponse, HttpServer}; 77 | use bytes::Bytes; 78 | use futures::future::ok; 79 | use futures::stream::once; 80 | 81 | #[get("/stream")] 82 | async fn stream() -> HttpResponse { 83 | let body = once(ok::<_, Error>(Bytes::from_static(b"test"))); 84 | 85 | HttpResponse::Ok() 86 | .content_type("application/json") 87 | .streaming(body) 88 | } 89 | 90 | #[actix_web::main] 91 | async fn main() -> std::io::Result<()> { 92 | HttpServer::new(|| App::new().service(stream)) 93 | .bind("127.0.0.1:8080")? 94 | .run() 95 | .await 96 | } 97 | ``` 98 | 99 | ## 不同的返回类型(两种) (Different Return Types(Either)) 100 | 有时候你需要在响应中返回两种不同的类型, 例如,你可以进行错误检查并返回错误,返回异步响应或需要两种不同类型的任何结果. 101 | 102 | 对于这种情况, 你可以使用 `Either` 类型. `Either` 允许你组合两个不同类型的responder到一个单个类型中去. 103 | 104 | 请看如下示例: 105 | 106 | ```rust 107 | use actix_web::{Either, Error, HttpResponse}; 108 | 109 | type RegisterResult = Either>; 110 | 111 | async fn index() -> RegisterResult { 112 | if is_a_variant() { 113 | // <- 变体 A 114 | Either::A(HttpResponse::BadRequest().body("Bad data")) 115 | } else { 116 | // <- 变体 B 117 | Either::B(Ok("Hello!")) 118 | } 119 | } 120 | ``` 121 | (译者注: 相当于将不同分支的不同类型包装到某一个类型中再返回). -------------------------------------------------------------------------------- /doc/Middleware.md: -------------------------------------------------------------------------------- 1 | ## 中间件(Middleware) 2 | Actix-web的中间件系统允许我们在请求/响应处理时添加一些其它的行为. 中间件可以(hook into)挂接到进入的请求处理过程中. 使我们能修改请求以及 3 | 暂停请求处理来及早的返回响应. 4 | 5 | 中间件也可以挂接(hook into)响应处理过程中. 6 | 7 | 一般来说, 中间件参与以下操作: 8 | * 请求预处理. 9 | * 响应后置处理. 10 | * 修改应用程序状态. 11 | * 访问扩展服务(redis, logging, sessions). 12 | 13 | 每个 `App` , `scope`, `Resource` 都能注册中件间, 并且它以注册顺序相反的顺序来执行. 通常, 中间件是一种实现了 [Service trait](https://docs.rs/actix-web/3/actix_web/dev/trait.Service.html) 14 | 和 [Transform trait](https://docs.rs/actix-web/3/actix_web/dev/trait.Transform.html) 的类型. 在trait中的每一个方法都有其默认实现. 15 | 每一个方法能立即返回一个结果或者一个 _future_ 对象. 16 | 17 | 下面演示创建一个简单的中件间: 18 | ```rust 19 | use std::pin::Pin; 20 | use std::task::{Context, Poll}; 21 | 22 | use actix_service::{Service, Transform}; 23 | use actix_web::{dev::ServiceRequest, dev::ServiceResponse, Error}; 24 | use futures::future::{ok, Ready}; 25 | use futures::Future; 26 | 27 | /// 在中间件处理过程器有两步. 28 | /// 1. 中间件初始化, 下一个服务链中作为一个参数中间件工厂被调用. 29 | /// 2. 中间件的调用方法被正常的请求调用. 30 | pub struct SayHi; 31 | 32 | ///中间件工厂是来自 actix_service 包下的一个 `Transform` trait. 33 | /// `S` - 下一个服务类型 34 | /// `B` - 响应body类型 35 | impl Transform for SayHi 36 | where 37 | S: Service, Error = Error>, 38 | S::Future: 'static, 39 | B: 'static, 40 | { 41 | type Request = ServiceRequest; 42 | type Response = ServiceResponse; 43 | type Error = Error; 44 | type InitError = (); 45 | type Transform = SayHiMiddleware; 46 | type Future = Ready>; 47 | 48 | fn new_transform(&self, service: S) -> Self::Future { 49 | ok(SayHiMiddleware { service }) 50 | } 51 | } 52 | 53 | pub struct SayHiMiddleware { 54 | service: S, 55 | } 56 | 57 | impl Service for SayHiMiddleware 58 | where 59 | S: Service, Error = Error>, 60 | S::Future: 'static, 61 | B: 'static, 62 | { 63 | type Request = ServiceRequest; 64 | type Response = ServiceResponse; 65 | type Error = Error; 66 | type Future = Pin>>>; 67 | 68 | fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { 69 | self.service.poll_ready(cx) 70 | } 71 | 72 | fn call(&mut self, req: ServiceRequest) -> Self::Future { 73 | println!("Hi from start. You requested: {}", req.path()); 74 | 75 | let fut = self.service.call(req); 76 | 77 | Box::pin(async move { 78 | let res = fut.await?; 79 | 80 | println!("Hi from response"); 81 | Ok(res) 82 | }) 83 | } 84 | } 85 | ``` 86 | 87 | 另外对于简单的使用示例, 你可以使用 [wrap_fn](https://docs.rs/actix-web/3/actix_web/struct.App.html#method.wrap_fn) 来创建一个小的临时的中间件: 88 | 89 | ```rust 90 | use actix_service::Service; 91 | use actix_web::{web, App}; 92 | use futures::future::FutureExt; 93 | 94 | #[actix_web::main] 95 | async fn main() { 96 | let app = App::new() 97 | .wrap_fn(|req, srv| { 98 | println!("Hi from start. You requested: {}", req.path()); 99 | srv.call(req).map(|res| { 100 | println!("Hi from response"); 101 | res 102 | }) 103 | }) 104 | .route( 105 | "/index.html", 106 | web::get().to(|| async { 107 | "Hello, middleware!" 108 | }), 109 | ); 110 | } 111 | ``` 112 | 113 | Actix-web提供了一些有用的中间件, 比如, `logging`, `user sessions`, `Compress` 等等. 114 | 115 | ## 日志(Logging) 116 | 日志被作为一种中间件来实现. 常见的做法是将日志中间件注册为应用程序第一个中间件. 必须为每个应用程序注册日志中间件. 117 | 118 | `Logger` 中间件使用标准的日志包去记录日志信息. 为了查看日志你必须为 _actix_web_ 包启用日志功能. ([env_logger](https://docs.rs/env_logger/*/env_logger/) 或者其它的). 119 | 120 | ## 用法(Usage) 121 | 创建指定格式的 `Logger` 中件间. 可以使用默认 `default` 方法创建默认的 `Logger`, 它使用默认的格式: 122 | 123 | ```text 124 | %a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T 125 | ``` 126 | 127 | ```rust 128 | use actix_web::middleware::Logger; 129 | use env_logger::Env; 130 | 131 | #[actix_web::main] 132 | async fn main() -> std::io::Result<()> { 133 | use actix_web::{App, HttpServer}; 134 | 135 | env_logger::from_env(Env::default().default_filter_or("info")).init(); 136 | 137 | HttpServer::new(|| { 138 | App::new() 139 | .wrap(Logger::default()) 140 | .wrap(Logger::new("%a %{User-Agent}i")) 141 | }) 142 | .bind("127.0.0.1:8080")? 143 | .run() 144 | .await 145 | } 146 | ``` 147 | 148 | 下面是默认情况下日志的记录格式: 149 | ```text 150 | 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 151 | 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 152 | ``` 153 | 154 | ## 格式(Format) 155 | * %% - %号标识 156 | * %a - 远程IP地址(如果使用了反向代码,则为代理的IP) 157 | * %t - 当请求开始处理的时间 158 | * %P - 请求子服务进程ID号 159 | * %r - 请求的第一行 160 | * %s - 响应状态码 161 | * %b - 包含http响应头的响应字节大小 162 | * %T - 服务请求所用的时间, 以秒为单位, 以.06f为浮点数格式 163 | * %D - 服务请求所有时间, 以毫秒为单位 164 | * %{FOO}i - 请求header中的["FOO"] (译者注: 这相当于可以在日志中取到自定义请求头中的内容) 165 | * %{FOO}o - 响应header中的["FOO"] (译者注: 这相当于可以在日志中取到自定义响应头中的内容) 166 | * %{FOO} - 系统环境中["FOO"] 167 | 168 | ## 默认头(Default headers) 169 | 为了设置默认响应头, `DefaultHeaders` 中间件可以被使用. 如果响应中已经包含了指定的响应头,则 _DefaultHeaders_ 中间件不会再次设置头. 170 | 171 | ```rust 172 | use actix_web::{http, middleware, HttpResponse}; 173 | 174 | #[actix_web::main] 175 | async fn main() -> std::io::Result<()> { 176 | use actix_web::{web, App, HttpServer}; 177 | 178 | HttpServer::new(|| { 179 | App::new() 180 | .wrap(middleware::DefaultHeaders::new().header("X-Version", "0.2")) 181 | .service( 182 | web::resource("/test") 183 | .route(web::get().to(|| HttpResponse::Ok())) 184 | .route( 185 | web::method(http::Method::HEAD) 186 | .to(|| HttpResponse::MethodNotAllowed()), 187 | ), 188 | ) 189 | }) 190 | .bind("127.0.0.1:8080")? 191 | .run() 192 | .await 193 | } 194 | ``` 195 | 196 | ## 用户会话(User sessions) 197 | Actix-web提供了会话管理的通用解决方案. [actix-session](https://docs.rs/actix-session/0.3.0/actix_session/) 中间件可以使用多种 198 | 后端类型来存储 session 数据. 199 | 200 | 默认情况下, 仅cookie会话后端被实现. 可以添加其它后端的实现. 201 | 202 | [CookieSession](https://docs.rs/actix-session/0.3.0/actix_session/struct.CookieSession.html) 使用cookie作为会话存储. 203 | `CookieSessionBackend` 创建的 session 仅限制用来存少于4000字节的数据, 因为payload必须适合单个cookie. 如果session包含了超过4000 204 | 字节的数据, 就会产生一个内部服务错误. 205 | 206 | cookie可能要有签名的或者私有的安全策略. 每种都由各自的 `CookieSession` 构建函数来创建. 207 | 208 | 一个签名的cookie可以被客户端查看,但不能被修改.一个私有的cookie客户端既不能修改也不可查看. 209 | 210 | 构造函数以key来作为参数. 这是Cookie会话的私钥,如果更改此值,所有的会话都会丢失. 211 | 212 | 一般来说你创建一个 `SessionStorage` 中间件并使用指定后端实现来初始化它, 比如一个 `CookieSession`. 为了访问session的数据必须使用 213 | `Session` 提取器. 该方法返回一个 [Session](https://docs.rs/actix-session/0.3.0/actix_session/struct.Session.html) 对象, 214 | 并允许我们访问或设置session data. 215 | 216 | ```rust 217 | use actix_session::{CookieSession, Session}; 218 | use actix_web::{web, App, Error, HttpResponse, HttpServer}; 219 | 220 | async fn index(session: Session) -> Result { 221 | // 访问 Session数据 222 | if let Some(count) = session.get::("counter")? { 223 | session.set("counter", count + 1)?; 224 | } else { 225 | session.set("counter", 1)?; 226 | } 227 | 228 | Ok(HttpResponse::Ok().body(format!( 229 | "Count is {:?}!", 230 | session.get::("counter")?.unwrap() 231 | ))) 232 | } 233 | 234 | #[actix_web::main] 235 | async fn main() -> std::io::Result<()> { 236 | HttpServer::new(|| { 237 | App::new() 238 | .wrap( 239 | CookieSession::signed(&[0; 32]) // <- 基于Session中间件创建 cookie 240 | .secure(false), 241 | ) 242 | .service(web::resource("/").to(index)) 243 | }) 244 | .bind("127.0.0.1:8080")? 245 | .run() 246 | .await 247 | } 248 | ``` 249 | 250 | ## 错误处理 251 | `ErrorHandlers` 中间件可以让我们为响应定制处理. 252 | 253 | 为指定的状态码你可以使用 `ErrorHandlers::handler()` 方法去注册一个错误处理函数. 你可以修改现在有响应,或者创建一个全新的. 错误处理函数 254 | 可以立即返回一个响应或者返回可以解析为响应的 future. 255 | 256 | ```rust 257 | use actix_web::middleware::errhandlers::{ErrorHandlerResponse, ErrorHandlers}; 258 | use actix_web::{dev, http, HttpResponse, Result}; 259 | 260 | fn render_500(mut res: dev::ServiceResponse) -> Result> { 261 | res.response_mut().headers_mut().insert( 262 | http::header::CONTENT_TYPE, 263 | http::HeaderValue::from_static("Error"), 264 | ); 265 | Ok(ErrorHandlerResponse::Response(res)) 266 | } 267 | 268 | #[actix_web::main] 269 | async fn main() -> std::io::Result<()> { 270 | use actix_web::{web, App, HttpServer}; 271 | 272 | HttpServer::new(|| { 273 | App::new() 274 | .wrap( 275 | ErrorHandlers::new() 276 | .handler(http::StatusCode::INTERNAL_SERVER_ERROR, render_500), 277 | ) 278 | .service( 279 | web::resource("/test") 280 | .route(web::get().to(|| HttpResponse::Ok())) 281 | .route(web::head().to(|| HttpResponse::MethodNotAllowed())), 282 | ) 283 | }) 284 | .bind("127.0.0.1:8080")? 285 | .run() 286 | .await 287 | } 288 | ``` 289 | -------------------------------------------------------------------------------- /doc/Requests.md: -------------------------------------------------------------------------------- 1 | ## 内容编码(Content Encoding) 2 | Actix-web 能自动的解压缩 payloads (载荷). 支持如下几个解码器: 3 | * Brotli 4 | * Chunked 5 | * Compress 6 | * Gzip 7 | * Deflate 8 | * Identity 9 | * Trailers 10 | * EncodingExt 11 | 12 | 如果请求头中包含一个 `Content-Encoding` 头, 请求的payload会根据header中的值来解压缩. 不支持多重解码器. 比如: `Content-Encoding:br, gzip` . 13 | 14 | ## Json请求(Json Request) 15 | 对于Json body 的反序列化有一些可选项. 16 | 17 | 第一种选择是使用 _Json extractor_ (Json 提取器). 首先, 你要定义一个处理器函数将 `Json` 作为一个参数来接收, 然后, 在处理器函数上 18 | 使用 `.to()` 方法来注册. 使用 `serde_json::Value` 作为类型 `T` , 可以接受任意有效的 json对象. 19 | 20 | ```rust 21 | use actix_web::{web, App, HttpServer, Result}; 22 | use serde::Deserialize; 23 | 24 | #[derive(Deserialize)] 25 | struct Info { 26 | username: String, 27 | } 28 | 29 | /// 使用 serde 来提取Info 30 | async fn index(info: web::Json) -> Result { 31 | Ok(format!("Welcome {}!", info.username)) 32 | } 33 | 34 | #[actix_web::main] 35 | async fn main() -> std::io::Result<()> { 36 | HttpServer::new(|| App::new().route("/", web::post().to(index))) 37 | .bind("127.0.0.1:8080")? 38 | .run() 39 | .await 40 | } 41 | ``` 42 | 你也可以手动的将payload导入内存中然后反序列化它. 43 | 44 | 在下面的示例中, 我们将反序列化一个 _MyObj_ struct. 我们必须首先导入request body 然后再反序列化json到一个Object对象中去. 45 | 46 | ```rust 47 | use actix_web::{error, post, web, App, Error, HttpResponse}; 48 | use futures::StreamExt; 49 | use serde::{Deserialize, Serialize}; 50 | use serde_json; 51 | 52 | #[derive(Serialize, Deserialize)] 53 | struct MyObj { 54 | name: String, 55 | number: i32, 56 | } 57 | 58 | const MAX_SIZE: usize = 262_144; // max payload size is 256k 59 | 60 | #[post("/")] 61 | async fn index_manual(mut payload: web::Payload) -> Result { 62 | // payload是一个流式字节对象 63 | let mut body = web::BytesMut::new(); 64 | while let Some(chunk) = payload.next().await { 65 | let chunk = chunk?; 66 | // 限制payload在内存中的最大 大小 67 | if (body.len() + chunk.len()) > MAX_SIZE { 68 | return Err(error::ErrorBadRequest("overflow")); 69 | } 70 | body.extend_from_slice(&chunk); 71 | } 72 | 73 | // body导入后现在我们使用 serde-json 来反序列化它 74 | let obj = serde_json::from_slice::(&body)?; 75 | Ok(HttpResponse::Ok().json(obj)) // <- 发送响应 76 | } 77 | ``` 78 | 两种选择的完整使用示例参考 [examples directory](https://github.com/actix/examples/tree/master/json/) 79 | 80 | ## 分块传输编码(Chunked transfer encoding) 81 | Actix自动的解码 _chunked_ 编码. `web::Payload` 已经包含解码成字节流的方式. 如果请求负载(payload)是使用支持的编解码器(br, gzip, deflate) 82 | 其中之一来压缩, 那么会被作为字节流来解码. 83 | 84 | ## 多重body(Multipart body) 85 | Actix-web 使用扩展包 `actix-multipart` 来支持多重流(stream). 86 | 87 | 完整的使用示例可以查看[examples directory](https://github.com/actix/examples/tree/master/multipart/) 88 | 89 | # Urlencoded body 90 | Actix-web 通过 `web::Form` 提取器为应用程序 _application/x-www-form-urlencoded_ 编码提供支持, 该提取器解析为反序列化实例. 这些实例必须从 serde 实现 91 | `Deserialize` trait. 92 | 93 | 以下几种情况, _Urlencoded_ 未来可能解决错误. 94 | * content type 不是 `applicaton/x-www-form-urlencoded`. 95 | * transfer encoding 是 `chunked`. 96 | * content-length 超过 256k. 97 | * payload 终止错误. 98 | 99 | ```rust 100 | use actix_web::{post, web, HttpResponse}; 101 | use serde::Deserialize; 102 | 103 | #[derive(Deserialize)] 104 | struct FormData { 105 | username: String, 106 | } 107 | 108 | #[post("/")] 109 | async fn index(form: web::Form) -> HttpResponse { 110 | HttpResponse::Ok().body(format!("username: {}", form.username)) 111 | } 112 | ``` 113 | 114 | ## 流式请求(Streaming request) 115 | _HttpRequest_ 是一个字节流对象. 它能用来读取请求 body的负载(payload). 116 | 117 | 在下面的示例中, 我们读取并分块打印 request payload. 118 | 119 | ```rust 120 | use actix_web::{get, web, Error, HttpResponse}; 121 | use futures::StreamExt; 122 | 123 | #[get("/")] 124 | async fn index(mut body: web::Payload) -> Result { 125 | let mut bytes = web::BytesMut::new(); 126 | while let Some(item) = body.next().await { 127 | let item = item?; 128 | println!("Chunk: {:?}", &item); 129 | bytes.extend_from_slice(&item); 130 | } 131 | 132 | Ok(HttpResponse::Ok().finish()) 133 | } 134 | ``` 135 | 136 | -------------------------------------------------------------------------------- /doc/Responses.md: -------------------------------------------------------------------------------- 1 | ## 响应(Response) 2 | 一种类 builder 模式被使用来构造一个 `HttpResponse` 实例. `HttpResponse` 提供了几个返回 `HttpResponseBuilder` 实例的方法, 3 | 此实例实现了一系列方便的方法用来构建响应. 4 | 5 | 查看文档 [documentation](https://docs.rs/actix-web/3/actix_web/dev/struct.HttpResponseBuilder.html) 获取类型描述. 6 | 7 | 方法 `.body`, `.finish`, 和 `.json` 完成响应的创建并返回构建好的 _HttpResponse_ 实例. 如果在同一构建器实例上多次调用这些方法, 8 | 构建器将会发生 panic(恐慌). 9 | 10 | ```rust 11 | use actix_web::HttpResponse; 12 | 13 | async fn index() -> HttpResponse { 14 | HttpResponse::Ok() 15 | .content_type("plain/text") 16 | .header("X-Hdr", "sample") 17 | .body("data") 18 | } 19 | ``` 20 | 21 | ## 内容编码(Content encoding) 22 | Actix-web 能使用 [Compress middleware](https://docs.rs/actix-web/3/actix_web/middleware/struct.Compress.html) 自动的压缩payloads. 23 | 支持下面的编解码器: 24 | * Brotli 25 | * gzip 26 | * Deflate 27 | * Identity 28 | 29 | ```rust 30 | use actix_web::{get, middleware, App, HttpResponse, HttpServer}; 31 | 32 | #[get("/")] 33 | async fn index_br() -> HttpResponse { 34 | HttpResponse::Ok().body("data") 35 | } 36 | 37 | #[actix_web::main] 38 | async fn main() -> std::io::Result<()> { 39 | HttpServer::new(|| { 40 | App::new() 41 | .wrap(middleware::Compress::default()) 42 | .service(index_br) 43 | }) 44 | .bind("127.0.0.1:8080")? 45 | .run() 46 | .await 47 | } 48 | ``` 49 | 根据中间件 `middleware::BodyEncoding` trait 的编码参数来压缩响应的payloads. 默认情况下 `ContentEncoding::Auto` 被使用. 如果使用 50 | `ContentEncoding` 那么就会根据请求头中的 `Accept-Encoding` 来决定压缩. 51 | 52 | `ContentEncoding::Identity` 可用来禁用压缩. 如果选择了其它方式的压缩, 那么该编解码器就会强制执行压缩. 53 | 54 | 例如对单个处理函数启用 `Brotli` 可以使用 `ContentEncoding::Br`. 55 | 56 | ```rust 57 | use actix_web::{ 58 | dev::BodyEncoding, get, http::ContentEncoding, middleware, App, HttpResponse, HttpServer, 59 | }; 60 | 61 | #[get("/")] 62 | async fn index_br() -> HttpResponse { 63 | HttpResponse::Ok() 64 | .encoding(ContentEncoding::Br) 65 | .body("data") 66 | } 67 | 68 | #[actix_web::main] 69 | async fn main() -> std::io::Result<()> { 70 | HttpServer::new(|| { 71 | App::new() 72 | .wrap(middleware::Compress::default()) 73 | .service(index_br) 74 | }) 75 | .bind("127.0.0.1:8080")? 76 | .run() 77 | .await 78 | } 79 | ``` 80 | 81 | 或者在整个应用中配置: 82 | ```rust 83 | use actix_web::{http::ContentEncoding, dev::BodyEncoding, HttpResponse}; 84 | 85 | async fn index_br() -> HttpResponse { 86 | HttpResponse::Ok().body("data") 87 | } 88 | 89 | #[actix_web::main] 90 | async fn main() -> std::io::Result<()> { 91 | use actix_web::{middleware, web, App, HttpServer}; 92 | 93 | HttpServer::new(|| { 94 | App::new() 95 | .wrap(middleware::Compress::new(ContentEncoding::Br)) 96 | .route("/", web::get().to(index_br)) 97 | }) 98 | .bind("127.0.0.1:8080")? 99 | .run() 100 | .await 101 | } 102 | ``` 103 | 104 | 我们通过设置content encoding 的值为 `Identity` 方式来禁用内容压缩. 105 | 106 | ```rust 107 | use actix_web::{ 108 | dev::BodyEncoding, get, http::ContentEncoding, middleware, App, HttpResponse, HttpServer, 109 | }; 110 | 111 | #[get("/")] 112 | async fn index() -> HttpResponse { 113 | HttpResponse::Ok() 114 | // 禁用压缩 115 | .encoding(ContentEncoding::Identity) 116 | .body("data") 117 | } 118 | 119 | #[actix_web::main] 120 | async fn main() -> std::io::Result<()> { 121 | HttpServer::new(|| { 122 | App::new() 123 | .wrap(middleware::Compress::default()) 124 | .service(index) 125 | }) 126 | .bind("127.0.0.1:8080")? 127 | .run() 128 | .await 129 | } 130 | ``` 131 | 132 | 当我们处理已经存在压缩的body数据时(比如,当处理资产时), 通过手动设置 _header_ 中 `content-encoding` 的值为 `Identity` 的方式来避免压缩已经压缩过的数据. 133 | ```rust 134 | use actix_web::{ 135 | dev::BodyEncoding, get, http::ContentEncoding, middleware, App, HttpResponse, HttpServer, 136 | }; 137 | 138 | static HELLO_WORLD: &[u8] = &[ 139 | 0x1f, 0x8b, 0x08, 0x00, 0xa2, 0x30, 0x10, 0x5c, 0x00, 0x03, 0xcb, 0x48, 0xcd, 0xc9, 0xc9, 0x57, 140 | 0x28, 0xcf, 0x2f, 0xca, 0x49, 0xe1, 0x02, 0x00, 0x2d, 0x3b, 0x08, 0xaf, 0x0c, 0x00, 0x00, 0x00, 141 | ]; 142 | 143 | #[get("/")] 144 | async fn index() -> HttpResponse { 145 | HttpResponse::Ok() 146 | .encoding(ContentEncoding::Identity) 147 | .header("content-encoding", "gzip") 148 | .body(HELLO_WORLD) 149 | } 150 | ``` 151 | 152 | 也可以在应用级别设置默认的内容编码, 默认情况下 `ContentEncoding::Auto` 被使用. 这意味着内容压缩是可以交涉的 153 | (译者注: 换句话说,默认情况下是根据请求中的 Accept-encoding 来指定压缩方式的). 154 | 155 | ```rust 156 | use actix_web::{get, http::ContentEncoding, middleware, App, HttpResponse, HttpServer}; 157 | 158 | #[get("/")] 159 | async fn index() -> HttpResponse { 160 | HttpResponse::Ok().body("data") 161 | } 162 | 163 | #[actix_web::main] 164 | async fn main() -> std::io::Result<()> { 165 | HttpServer::new(|| { 166 | App::new() 167 | .wrap(middleware::Compress::new(ContentEncoding::Br)) // 这里就是设置应用级别的编码压缩方式. 168 | .service(index) 169 | }) 170 | .bind("127.0.0.1:8080")? 171 | .run() 172 | .await 173 | } 174 | ``` 175 | 176 | ## Json响应(Json Response) 177 | `Json` 类型允许使用正确的Json格式数据进行响应. 只需要返回 `Json` 类型, 其中`T`是序列化为Json格式的类型. 178 | ```rust 179 | use actix_web::{get, web, HttpResponse, Result}; 180 | use serde::{Deserialize, Serialize}; 181 | 182 | #[derive(Serialize, Deserialize)] 183 | struct MyObj { 184 | name: String, 185 | } 186 | 187 | #[get("/a/{name}")] 188 | async fn index(obj: web::Path) -> Result { 189 | Ok(HttpResponse::Ok().json(MyObj { 190 | name: obj.name.to_string(), 191 | })) 192 | } 193 | 194 | #[actix_web::main] 195 | async fn main() -> std::io::Result<()> { 196 | use actix_web::{App, HttpServer}; 197 | 198 | HttpServer::new(|| App::new().service(index)) 199 | .bind("127.0.0.1:8080")? 200 | .run() 201 | .await 202 | } 203 | ``` 204 | 205 | 206 | 207 | -------------------------------------------------------------------------------- /doc/Server.md: -------------------------------------------------------------------------------- 1 | ## Http服务器(The HTTP Server) 2 | [HttpServer](https://docs.rs/actix-web/3/actix_web/struct.HttpServer.html) 负责HTTP请求的处理. 3 | 4 | `HttpServer` 接收一个应用程序工厂作为一个参数,且应用程序工厂必须有 `Sync` + `Send` 边界. 会在多线程章节解释这一点. 5 | 6 | 使用 `bind()` 方法来绑定一个指定的Socket地址,它可以被多次调用. 使用 `bind_openssl()` 或者 `bind_rustls()` 方法绑定 7 | ssl Socket地址. 使用 `HttpServer::run()` 方法来运行一个 Http 服务. 8 | 9 | ```rust 10 | use actix_web::{web, App, HttpResponse, HttpServer}; 11 | 12 | #[actix_web::main] 13 | async fn main() -> std::io::Result<()> { 14 | HttpServer::new(|| { 15 | App::new().route("/", web::get().to(|| HttpResponse::Ok())) 16 | }) 17 | .bind("127.0.0.1:8080")? 18 | .run() 19 | .await 20 | } 21 | ``` 22 | 23 | `run()` 方法返回一个 `server` 类型的实例, server中的方法可以被用来管理HTTP服务器. 24 | * pause() - 暂停接收进来的链接. 25 | * resume() - 继续接收进来的链接. 26 | * stop() - 停止接收进来的链接,且停止所有worker线程后退出. 27 | 28 | 下面的例子展示了如何在单独的线程中启动HTTP服务. 29 | 30 | ```rust 31 | use actix_web::{web, App, HttpResponse, HttpServer, rt::System}; 32 | use std::sync::mpsc; 33 | use std::thread; 34 | 35 | #[actix_web::main] 36 | async fn main() { 37 | let (tx, rx) = mpsc::channel(); 38 | 39 | thread::spawn(move || { 40 | let sys = System::new("http-server"); 41 | 42 | let srv = HttpServer::new(|| { 43 | App::new().route("/", web::get().to(|| HttpResponse::Ok())) 44 | }) 45 | .bind("127.0.0.1:8080")? 46 | .shutdown_timeout(60) // <- 设置关机超时时间为60s. 47 | .run(); 48 | 49 | let _ = tx.send(srv); 50 | sys.run() 51 | }); 52 | 53 | let srv = rx.recv().unwrap(); 54 | 55 | // 暂停接收新的链接 56 | srv.pause().await; 57 | // 继续接收新的链接 58 | srv.resume().await; 59 | // 停止服务 60 | srv.stop(true).await; 61 | } 62 | ``` 63 | 64 | ## 多线程(Multi-threading) 65 | `HttpServer` 自动启动一些 Http Workers(工作线程), 它的默认值是系统cpu的逻辑个数. 这个值你可以通过 `HttpServer::workers()` 方法来自定义并覆盖. 66 | 67 | ```rust 68 | use actix_web::{web, App, HttpResponse, HttpServer}; 69 | 70 | #[actix_web::main] 71 | async fn main() { 72 | HttpServer::new(|| { 73 | App::new().route("/", web::get().to(|| HttpResponse::Ok())) 74 | }) 75 | .workers(4); // <- 启动4个worker线程 76 | } 77 | ``` 78 | 79 | 一旦workers被创建,它们每个都接收一个单独的应用程序实例来处理请求.应用程序State不能在这些workers线程之间共享,且处理程序可以自由操作状态副本,而无需担心并发问题. 80 | 81 | 应用程序状态不需要 `Send` 或者 `Sync` , 但是应用程序工厂必须要是 `Send` + `Sync` (因为它需要在不同的线程中共享与传递). 82 | 83 | 为了在worker线程之间共享State, 可以使用 `Arc`. 引入共享与同步后,应该格外的小心,在许多情况下由于锁定共享状态而无意中造成了"性能成本". 84 | 85 | 在某些情况下,可以使用更加有效的锁策略来减少这种 "性能成本", 举个例子,可以使用读写锁 [read/write locks](https://doc.rust-lang.org/std/sync/struct.RwLock.html) 86 | 来代替排它锁 [mutex](https://doc.rust-lang.org/std/sync/struct.Mutex.html) 来实现互斥性,但是性能最高的情况下,还是不要使用任何锁. 87 | 88 | 因为每一个worker线程是按顺序处理请求的,所以当处理程序阻塞当前线程时,会停止处理新的请求. 89 | 90 | ```rust 91 | fn my_handler() -> impl Responder { 92 | std::thread::sleep(Duration::from_secs(5)); // <-- 糟糕的实践方式,这样会导致当前worker线程停止处理新的请求.并挂起当前线程 93 | "response" 94 | } 95 | ``` 96 | 97 | 因此,任何长时间的或者非cpu绑定操作(比如:I/O,数据库操作等),都应该使用future或异步方法来处理. 异步处理程序由工作线程(worker)并发执行, 98 | 因此不会阻塞当前线程的执行. 99 | 100 | 例如下面的使用示例: 101 | ```rust 102 | fn my_handler() -> impl Responder { 103 | tokio::time::delay_for(Duration::from_secs(5)).await; // 这种没问题,工作线程将继续处理其它请求. 104 | "response" 105 | } 106 | ``` 107 | 上面说的这种限制同样也存在于提取器(extractor)中. 当一个handler函数在接收一个实现了 `FromRequest` 的参数时, 并且这个实现 108 | 如果阻塞了当前线程,那么worker线程也会在运行时阻塞. 因此,在实现提取器时必须特别注意,在需要的时候要异步实现它们. 109 | 110 | ## SSL 111 | 有两种方式来实现ssl的server. 一个是 `rustls` 一个是 `openssl` . 在Cargo.toml文件中加入如下依赖: 112 | ```toml 113 | [dependencies] 114 | actix-web = { version = "3", features = ["openssl"] } 115 | openssl = { version="0.10" } 116 | ``` 117 | 118 | ```rust 119 | use actix_web::{get, App, HttpRequest, HttpServer, Responder}; 120 | use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; 121 | 122 | #[get("/")] 123 | async fn index(_req: HttpRequest) -> impl Responder { 124 | "Welcome!" 125 | } 126 | 127 | #[actix_web::main] 128 | async fn main() -> std::io::Result<()> { 129 | // 导入 ssl keys 130 | // 为了测试创建自签名临时证书: 131 | // `openssl req -x509 -newkey rsa:4096 -nodes -keyout key.pem -out cert.pem -days 365 -subj '/CN=localhost'` 132 | let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); 133 | builder 134 | .set_private_key_file("key.pem", SslFiletype::PEM) 135 | .unwrap(); 136 | builder.set_certificate_chain_file("cert.pem").unwrap(); 137 | 138 | HttpServer::new(|| App::new().service(index)) 139 | .bind_openssl("127.0.0.1:8080", builder)? 140 | .run() 141 | .await 142 | } 143 | ``` 144 | 145 | **注意:** HTTP2.0需要 [tls alpn](https://tools.ietf.org/html/rfc7301) 来支持, 目前仅仅只有 `openssl` 支持 `alpn`. 146 | 更多的示例可以参考 [examples/openssl](https://github.com/actix/examples/blob/master/openssl) . 147 | 148 | 为了创建生成key.pem与cert.pem,可以使用如下示例命令. **其它需要修改的地方请填写自己的主题** 149 | ```shell 150 | openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -sha256 -subj "/C=CN/ST=Fujian/L=Xiamen/O=TVlinux/OU=Org/CN=muro.lxd" 151 | ``` 152 | 要删除密码,然后复制 nopass.pem到 key.pem 153 | ```shell 154 | openssl rsa -in key.pem -out nopass.pem 155 | ``` 156 | 157 | ## 链接保持(Keep-Alive) 158 | Actix 可以在keep-alive 链接上等待请求. 159 | 160 | _keep alive_ 链接行为被server设置定义. 161 | * 75, Some(75), KeepAlive::Timeout(75) - 开启keep alive 保活时间. 162 | * None or KeepAlive::Disable - 关闭keep alive设置. 163 | * KeepAlive::Tcp(75) - 使用 tcp socket SO_KEEPALIVE 设置选项. 164 | 165 | ```rust 166 | use actix_web::{web, App, HttpResponse, HttpServer}; 167 | 168 | #[actix_web::main] 169 | async fn main() -> std::io::Result<()> { 170 | let one = HttpServer::new(|| { 171 | App::new().route("/", web::get().to(|| HttpResponse::Ok())) 172 | }) 173 | .keep_alive(75); // <- 设置keep alive时间为75秒. 174 | 175 | // let _two = HttpServer::new(|| { 176 | // App::new().route("/", web::get().to(|| HttpResponse::Ok())) 177 | // }) 178 | // .keep_alive(); // <- Use `SO_KEEPALIVE` socket option. 179 | 180 | let _three = HttpServer::new(|| { 181 | App::new().route("/", web::get().to(|| HttpResponse::Ok())) 182 | }) 183 | .keep_alive(None); // <- 禁用 keep alive 184 | 185 | one.bind("127.0.0.1:8080")?.run().await 186 | } 187 | ``` 188 | 189 | 如果上面的第一个选项被选择,那么 _keep alive_ 状态将会根据响应的 _connection-type_ 类型来计算. 默认的 `HttpResponse::connection_type` 没有被定义 190 | 这种情况下会根据http的版本来默认是否开启keep alive. 191 | 192 | _keep alive_ 在 HTTP/1.0是默认 **关闭** 的, 在 _HTTP/1.1_ 和 _HTTP/2.0_ 是默认**开启**的. 193 | 194 | 链接类型可以使用 `HttpResponseBuilder::connection_type()` 方法来改变. 195 | 196 | ```rust 197 | use actix_web::{http, HttpRequest, HttpResponse}; 198 | async fn index(req: HttpRequest) -> HttpResponse { 199 | HttpResponse::Ok().connection_type(http::ConnectionType::Close) // 关闭链接 200 | .force_close() // 这种写法与上面那种写法二选一 201 | .finish() 202 | } 203 | ``` 204 | 205 | ## 优雅关机(Graceful shutdown) 206 | `HttpServer` 支持优雅关机. 在接收到停机信号后,worker线程有一定的时间来完成请求. 超过时间后的所有worker都会被强制drop掉. 207 | 默认的shutdown 超时时间设置为30秒. 你也可以使用 `HttpServer::shutdown_timeout()` 方法来改变这个时间. 208 | 209 | 你可以使用服务器地址向服务器发送停止消息,并指定是否要正常关机, server的 `start()` 方法可以返回一个地址. 210 | 211 | `HttpServer` 处理几种OS信号. _ctrl-c_ 在所有操作系统上都适用(表示优雅关机), 也可以在其它类unix系统上使用如下命令: 212 | * SIGINT - 强制关闭worker 213 | * SIGTERM - 优雅关闭worker 214 | * SIGQUIT - 强制关闭worker 215 | 216 | 另外也可以使用 `HttpServer::disable_signals()` 方法来禁用信号处理. -------------------------------------------------------------------------------- /doc/StaticFiles.md: -------------------------------------------------------------------------------- 1 | ## 个体文件(Individual file) 2 | 可以使用自定义路径模式和 `NameFile`来提供静态文件. 为了匹配路径尾端, 我们可以使用 `[.*]` 正则表达式. 3 | 4 | ```rust 5 | use actix_files::NameFile; 6 | use actix_web::{HttpRequest, Result}; 7 | use std::path::PathBuf; 8 | 9 | async fn index(req: HttpRequest) -> Result { 10 | let path: PathBuf = req.match_info().query("filename").parse().unwrap(); 11 | Ok(NamedFile::open(path)?) 12 | } 13 | 14 | #[actix_web::main] 15 | async fn main() -> std::io::Result<()> { 16 | use actix_web::{web, App, HttpServer}; 17 | 18 | HttpServer::new(|| App::new().route("/{filename:.*}", web::get().to(index))) 19 | .bind("127.0.0.1:8080")? 20 | .run() 21 | .await 22 | } 23 | ``` 24 | 25 | ## 目录(Directory) 26 | 提供来自指定目录或子目录的文件, 可以使用 `Files`. `Files` 必须使用 `App::service()` 方法来注册, 否而它不能被用在子目录处理上. 27 | 28 | ```rust 29 | use actix_files as fs; 30 | use actix_web::{App, HttpServer}; 31 | 32 | #[actix_web::main] 33 | async fn main() -> std::io::Result<()> { 34 | HttpServer::new(|| { 35 | App::new().service(fs::Files::new("/static", ".").show_files_listing()) 36 | }) 37 | .bind("127.0.0.1:8080")? 38 | .run() 39 | .await 40 | } 41 | ``` 42 | 43 | 默认情况下子目录的文件清单是被禁用的. 尝试去加载目录列表将返回一个 _404 NOT FOUND_ 的响应. 为了启用文件清单功能, 要使用 44 | [Files::show_files_listing()](https://docs.rs/actix-files/0.2/actix_files/struct.Files.html) 方法. 45 | 46 | 除了显示某个目录中文件的列表外, 还可以重定向到特定的index文件. 可以使用 [Files::index_file()](https://docs.rs/actix-files/0.2/actix_files/struct.Files.html#method.index_file) 来配置这个重定向. 47 | 48 | ## 配置(Configuration) 49 | `NameFiles` 提供一系列的可选项: 50 | * `set_content_disposition` - 函数用来将文件mime映射为 `Content-Disposition` 类型. 51 | * `use_etag` - 指定是否计算ETag值并将其包含在headers中. 52 | * `use_last_modified` - 指定是否将文件修改的时间戳添加在header的 `Last-Modified`中. 53 | 54 | 上面所有的方法都是可选的,并且提供了最佳的默认值,同时你也可以定制它们中的任何一个. 55 | ```rust 56 | use actix_files as fs; 57 | use actix_web::http::header::{ContentDisposition, DispositionType}; 58 | use actix_web::{get, App, Error, HttpRequest, HttpServer}; 59 | 60 | #[get("/{filename:.*}")] 61 | async fn index(req: HttpRequest) -> Result { 62 | let path: std::path::PathBuf = req.match_info().query("filename").parse().unwrap(); 63 | let file = fs::NamedFile::open(path)?; 64 | Ok(file 65 | .use_last_modified(true) 66 | .set_content_disposition(ContentDisposition { 67 | disposition: DispositionType::Attachment, 68 | parameters: vec![], 69 | })) 70 | } 71 | 72 | #[actix_web::main] 73 | async fn main() -> std::io::Result<()> { 74 | HttpServer::new(|| App::new().service(index)) 75 | .bind("127.0.0.1:8080")? 76 | .run() 77 | .await 78 | } 79 | ``` 80 | 81 | 该配置也可以用于目录: 82 | ```rust 83 | use actix_files as fs; 84 | use actix_web::{App, HttpServer}; 85 | 86 | #[actix_web::main] 87 | async fn main() -> std::io::Result<()> { 88 | HttpServer::new(|| { 89 | App::new().service( 90 | fs::Files::new("/static", ".") 91 | .show_files_listing() 92 | .use_last_modified(true), 93 | ) 94 | }) 95 | .bind("127.0.0.1:8080")? 96 | .run() 97 | .await 98 | } 99 | ``` -------------------------------------------------------------------------------- /doc/Testing.md: -------------------------------------------------------------------------------- 1 | ## 测试(Testing) 2 | 每一个应用程序都应该经过良好的测试. Actix-web 提供了执行单元与集成测试的工具. 3 | 4 | ## 单元测试(Unit Testing) 5 | 为了单元测试, actix_web提供了一个请求的builder类型. `TestRequest` 实现了类 builders 模式. 你可以使用 `to_http_requests()` 方法来生成 6 | 一个 `HttpRequest` 实例, 并且你可以用它来调用处理函数. 7 | 8 | ```rust 9 | #[cfg(test)] 10 | mod tests { 11 | use super::*; 12 | use actix_web::test; 13 | 14 | #[actix_rt::test] 15 | async fn test_index_ok() { 16 | let req = test::TestRequest::with_header("content-type", "text/plain").to_http_request(); 17 | let resp = index(req).await; 18 | assert_eq!(resp.status(), http::StatusCode::OK); 19 | } 20 | 21 | #[actix_rt::test] 22 | async fn test_index_not_ok() { 23 | let req = test::TestRequest::default().to_http_request(); 24 | let resp = index(req).await; 25 | assert_eq!(resp.status(), http::StatusCode::BAD_REQUEST); 26 | } 27 | } 28 | ``` 29 | 30 | ## 集成测试(Integration tests) 31 | 有几种测试应用程序的方法. Actix-web可用于在真实的HTTP服务器中使用特定的处理程序来运行程序. `TestRequest::get()`, `TestRequest::post()`, 32 | 可以被用作发送请求到测试服务器. 33 | 34 | 为了创建一个测试的 `Service`, 可以使用 `test::init_service` 方法来接受一个常规的 `App` 构建器. 35 | 36 | 更多信息查看 [API Documentation] (https://docs.rs/actix-web/3/actix_web/test/index.html). 37 | 38 | ```rust 39 | #[cfg(test)] 40 | mod tests { 41 | use super::*; 42 | use actix_web::{test, web, App}; 43 | 44 | #[actix_rt::test] 45 | async fn test_index_get() { 46 | let mut app = test::init_service(App::new().route("/", web::get().to(index))).await; 47 | let req = test::TestRequest::with_header("content-type", "text/plain").to_request(); 48 | let resp = test::call_service(&mut app, req).await; 49 | assert!(resp.status().is_success()); 50 | } 51 | 52 | #[actix_rt::test] 53 | async fn test_index_post() { 54 | let mut app = test::init_service(App::new().route("/", web::get().to(index))).await; 55 | let req = test::TestRequest::post().uri("/").to_request(); 56 | let resp = test::call_service(&mut app, req).await; 57 | assert!(resp.status().is_client_error()); 58 | } 59 | } 60 | ``` 61 | 62 | 如果你需要更复杂的应用程序配置, 则测试应该与普通的应用程序创建非常类似. 比如说, 你可能需要初始化应用程序状态(State). 使用 `data` 方法 63 | 创建一个 `App` 并且附加状态, 这就像使用普通应用程序一样. 64 | 65 | ```rust 66 | #[cfg(test)] 67 | mod tests { 68 | use super::*; 69 | use actix_web::{test, web, App}; 70 | 71 | #[actix_rt::test] 72 | async fn test_index_get() { 73 | let mut app = test::init_service( 74 | App::new() 75 | .data(AppState { count: 4 }) 76 | .route("/", web::get().to(index)), 77 | ).await; 78 | let req = test::TestRequest::get().uri("/").to_request(); 79 | let resp: AppState = test::read_response_json(&mut app, req).await; 80 | 81 | assert_eq!(resp.count, 4); 82 | } 83 | } 84 | ``` 85 | 86 | ## 流式响应测试(Stream response tests) 87 | 如果你需要测试流(Stream)的生成, 只要调用 `take_body()` 方法并转换结果 `ResponseBody` 到一个 _future_ 中并执行它, 比如当你在测试 88 | [Server Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) 时. 89 | 90 | ```rust 91 | use std::task::Poll; 92 | use bytes::Bytes; 93 | use futures::stream::poll_fn; 94 | 95 | use actix_web::http::{ContentEncoding, StatusCode}; 96 | use actix_web::{web, http, App, Error, HttpRequest, HttpResponse}; 97 | 98 | async fn sse(_req: HttpRequest) -> HttpResponse { 99 | let mut counter: usize = 5; 100 | 101 | // yields `data: N` where N in [5; 1] 102 | let server_events = poll_fn(move |_cx| -> Poll>> { 103 | if counter == 0 { 104 | return Poll::Ready(None); 105 | } 106 | let payload = format!("data: {}\n\n", counter); 107 | counter -= 1; 108 | Poll::Ready(Some(Ok(Bytes::from(payload)))) 109 | }); 110 | 111 | HttpResponse::build(StatusCode::OK) 112 | .set_header(http::header::CONTENT_TYPE, "text/event-stream") 113 | .set_header( 114 | http::header::CONTENT_ENCODING, 115 | ContentEncoding::Identity.as_str(), 116 | ) 117 | .streaming(server_events) 118 | } 119 | 120 | pub fn main() { 121 | App::new().route("/", web::get().to(sse)); 122 | } 123 | 124 | #[cfg(test)] 125 | mod tests { 126 | use super::*; 127 | 128 | use futures_util::stream::StreamExt; 129 | use futures_util::stream::TryStreamExt; 130 | 131 | use actix_web::{test, web, App}; 132 | 133 | #[actix_rt::test] 134 | async fn test_stream() { 135 | let mut app = test::init_service(App::new().route("/", web::get().to(sse))).await; 136 | let req = test::TestRequest::get().to_request(); 137 | 138 | let mut resp = test::call_service(&mut app, req).await; 139 | assert!(resp.status().is_success()); 140 | 141 | // first chunk 142 | let (bytes, mut resp) = resp.take_body().into_future().await; 143 | assert_eq!(bytes.unwrap().unwrap(), Bytes::from_static(b"data: 5\n\n")); 144 | 145 | // second chunk 146 | let (bytes, mut resp) = resp.take_body().into_future().await; 147 | assert_eq!(bytes.unwrap().unwrap(), Bytes::from_static(b"data: 4\n\n")); 148 | 149 | // remaining part 150 | let bytes = test::load_stream(resp.take_body().into_stream()).await; 151 | assert_eq!(bytes.unwrap(), Bytes::from_static(b"data: 3\n\ndata: 2\n\ndata: 1\n\n")); 152 | } 153 | } 154 | ``` -------------------------------------------------------------------------------- /doc/URLDispatch.md: -------------------------------------------------------------------------------- 1 | ## URL分发(URL Dispatch) 2 | URL分发提供了一种使用简单模式匹配的方式去将URL映射到处理器函数代码上. 如果请求关联的路径信息被一个模式匹配, 那么一个特定的handler处理 3 | 函数对象会被激活. 4 | 5 | 一个请求处理器的功能是接收零个或多个能在请求中被提取的参数(ie, `impl FromRequest`) 且返回一个能被转换成HttpResponse的类型(ie, `impl HttpResponse`). 6 | 更多的信息参见[handler section](https://actix.rs/docs/handlers/). 7 | 8 | ## 资源配置(Resource configuration) 9 | 资源配置它是向应用中添加一个新资源的行为. 资源具有名称,该名称用于URL生成的标识符. 该名称也允许开发者向现有资源添加路由. 资源也有一种 10 | 模式, 这意味着可以匹配 _URL_ _PATH_ 中的一部分(比如格式与端口号后的一部分: /foo/bar in the URL http://localhost:8080/foo/bar?q=value). 11 | 它不匹配查询 _Query_ 的部分(?号后面的部分,比如: q=value in http://localhost:8080/foo/bar?q=value) 12 | 13 | 使用 `App::route()` 方法提供了一个简单的方式注册路由. 这个方法添加单个路由到应用的路由表中去. 这个方法接收一个路径模式, HTTP方法和一个处理函数. 14 | 同一路径下`route()`方法可以被多次调用, 因此相同的资源路径可以注册多个路由. 15 | 16 | ```rust 17 | use actix_web::{web, App, HttpResponse, HttpServer}; 18 | 19 | async fn index() -> HttpResponse { 20 | HttpResponse::Ok().body("Hello") 21 | } 22 | 23 | #[actix_web::main] 24 | async fn main() -> std::io::Result<()> { 25 | HttpServer::new(|| { 26 | App::new() 27 | .route("/", web::get().to(index)) 28 | .route("/user", web::post().to(index)) 29 | }) 30 | .bind("127.0.0.1:8080")? 31 | .run() 32 | .await 33 | } 34 | ``` 35 | 虽然 _App::route()_ 提供了一个简单的方式来注册路由, 为了访问完整的资源配置,一个不同的方法可以使用. 它就是 `App::service()` 方法:添加 36 | 单个资源到应用路由表中去. 这个方法接收一个路径, guards, 和一个或多个routers. 37 | 38 | ```rust 39 | use actix_web::{guard, web, App, HttpResponse}; 40 | 41 | fn index() -> HttpResponse { 42 | HttpResponse::Ok().body("Hello") 43 | } 44 | 45 | pub fn main() { 46 | App::new() 47 | .service(web::resource("/prefix").to(index)) 48 | .service( 49 | web::resource("/user/{name}") 50 | .name("user_detail") 51 | .guard(guard::Header("content-type", "application/json")) 52 | .route(web::get().to(|| HttpResponse::Ok())) 53 | .route(web::put().to(|| HttpResponse::Ok())), 54 | ); 55 | } 56 | ``` 57 | 如果一个资源不包含任何的路由或者没有任何能匹配上的路由,它将返回 _NOT FOUND_ HTTP响应(也就是404). 58 | 59 | ## 配置一个路由(Configuration a Route) 60 | 资源包含一组路由的集合. 每一个路由都依次有一组 `guards` 和 一个处理函数. 使用 `Resource::route()`方法创建一个新的路由,它返回一个新路由 61 | 实例的引用. 默认的情况下路由不包含任何的 guards, 因此所有的请求都可以被默认的处理器(HttpNotFound)处理. 62 | 63 | 应用程序传入请求是基于资源注册与路由注册期间定义的路由标准. 资源按 `Resource::route()` 注册的路由顺序来匹配所有的请求. 64 | 65 | 一个路由可以包含任意数量 _guards_ 但仅仅只能有一个处理器函数. 66 | 67 | ```rust 68 | #[actix_web::main] 69 | fn main() -> std::io::Result<()> { 70 | App::new().service( 71 | web::resource("/path").route( 72 | web::route() 73 | .guard(guard::Get()) 74 | .guard(guard::Header("content-type", "text/plain")) 75 | .to(|| HttpResponse::Ok()), 76 | ), 77 | ) 78 | } 79 | ``` 80 | 在上面这个示例中, 如果GET请求中的header包含指定的 _Content-type_ 为 _text/plain_ 且路径为 `/path` `HttpResponse::Ok()`才会被返回. 81 | 82 | 如果资源没有任何匹配的路由,那么会返回 _NOT FOUNT_ 响应. 83 | 84 | `ResourceHanlder::route()` 返回一个 `Route` 对象. Route使用类似builder模式来配置. 提供如下的配置方法: 85 | * `Route::guard()` - 注册一个新的守卫, 每个路由都能注册多个guards. 86 | * `Route::method()` - 注册一个方法级守卫, 每个路由都能注册多个guards. 87 | * `Route::to()` - 为某个路由注册一个处理函数. 仅能注册一个处理器函数. 通常处理器函数在最后的配置操作时注册. 88 | * `Route::to_async()` - 注册一个异步处理函数. 仅能注册一个处理器函数. 通常处理器函数在最后的配置操作时注册. 89 | 90 | ## 路由匹配(Route matching) 91 | 路由配置的主要目的是针对一个URL路径模式去匹配(或者匹配)请求中的`path`. `path`代表被请求URL中的一部分. 92 | 93 | _actix-web_ 做到这一点是非常简单的. 当一个请求进入到系统时, 对系统中存在的每一个资源配置声明,actix会根据声明的模式去检查请求中的路径. 94 | 这种检查按照 `App::service()` 方法声明的路径顺序进行. 如果找不到资源, 默认的资源就会作为匹配资源来使用. 95 | 96 | 当一个路由配置被声明时, 它可能包含路由的保护参数. 与路由声明关联的所有路由保护对于在检查期间用于给定请求路由的配置,都必须为ture 97 | (译者注:换句话说, 路由上配置的所有guard,进来的请求都必须满足才能通过). 如果在检查期间注册在路由上的任意一个保护(guard)参数配置返回了`false` 98 | 那么当前路由就会被跳过,然后继续通过有序的路由集合进行匹配. 99 | 100 | 如果有任意一个路由被匹配上了, 那么路由匹配的过程就会停止与此路由相关联的处理函数就会被激活.如果没有一个路由被匹配上,就会得到一个 _NOT FOUND_ 的响应返回. 101 | 102 | ## 资源模式语法(Resource pattern syntax) 103 | actix使用的模式匹配语言在匹配模式参数上是很简单的事. 104 | 105 | 在路由中使用模式可以使用斜杠开头. 如果模式不是以斜杠开头, 在匹配的时候会在前面加一个隐式的斜杠. 例如下面的模式是等效的: 106 | ```text 107 | {foo}/bar/baz 108 | ``` 109 | 110 | 和: 111 | 112 | ```text 113 | /{foo}/bar/baz 114 | ``` 115 | 上面变量替换的部分,{}标识符的形式指定, 这意味着 “直到下一个斜杠符前,可以接受任何的字符”, 并将其用作 `HttpRequest.match_info()`对象中的名称. 116 | 117 | 模式中的替换标记与正则表达式 `[^{}/]+` 相匹配. 118 | 119 | 匹配的信息是一个 `Params` 对象, 代表基于路由模式从URL中动态提取出来的部分. 它可以作为 `request.match_info` 信息来使用. 120 | 例如, 以下模式定义一个字段(foo)和两个标记(baz,and bar): 121 | 122 | ```text 123 | foo/{baz}/{bar} 124 | ``` 125 | 上面的模式将匹配这些URL, 生成以下的匹配信息: 126 | 127 | ```text 128 | foo/1/2 -> Params {'baz': '1', 'bar':'2'} 129 | foo/abc/def -> Params {'baz': 'abc', 'bar':'def'} 130 | ``` 131 | 然而下面的模式不会被匹配: 132 | ```text 133 | foo/1/2/ -> Not match(trailing slash) (不匹配最后面一个斜杠) 134 | bar/abc/def -> First segment literal mismatch (第一个字段就不匹配) 135 | ``` 136 | 字面路径 _/foo/biz.html_ 将和上面的路由模式匹配,且匹配的结果 `Params{'name': 'biz' }`. 但是字面路径 _/foo/biz_ 不会被匹配, 137 | 是因为它由{name}.html表示的字段末尾不包含文字 _.html_ (因为它仅仅包含biz. 而不是biz.html). 138 | 139 | 为了要捕获两个分段, 可以使用两个替换标志符: 140 | ```text 141 | foo/{name}.{ext} 142 | ``` 143 | 字面路径 _/foo/biz.html_ 将会被上面的路由模式匹配, 并且匹配的结果将是 _Params {'name':'biz', 'ext':'html'}_ . 这种情况被匹配是因为 144 | 字面部分 _.(period)_ 在两个替换标记 _{name}_ 与 _{ext}_ 之间. 145 | 146 | 替换标记也可以使用正则表达式, 该正则表达式被用于确定路径段是否与标记匹配. 为了指定替换标记仅仅只匹配正则表达式定义的一组特定字符,你必须使用 147 | 稍微有扩展的替换标记语法. 在大括号内,替换标记名后必须跟一个冒号, 然后才是正则表达式. 与替换标记 `[^/]+` 相关联的是默认正则表达式匹配一个或 148 | 多个非斜杠的字符. 比如, 替换标记 _{foo}_ 可以更详细的写为 _{foo: [^/]+}_ 这种. 你可以将其更改为任意的正则表达式来匹配任意字符序列.比如: 149 | _{foo: \d+}_ 可以匹配 foo 后面的任意数字. 150 | 151 | 分段必须至少包含一个字符,这样才能匹配段替换标记. 例如,对于URL _/abc/_ : 152 | * /abc/{foo} 不会匹配. 153 | * /{foo} 将会匹配. 154 | 155 | **注意**: 在匹配前, 将对路径(path)进行URL取消引号并将其解码为有效的unicode字符串, 且表示匹配路径段的值也将被URL取消引号. 156 | 157 | 因此,对于下面的模式: 158 | ```text 159 | foo/{bar} 160 | ``` 161 | 当它匹配下面这种URL时: 162 | ```text 163 | http://example.com/foo/La%20Pe%C3%B1a 164 | ``` 165 | 匹配字典看起来像下面这样(值是URL解码过的): 166 | ```text 167 | Params {'bar': 'La Pe\xf1a'} 168 | ``` 169 | 路径段中的文件字字符串会被代表解码后的值提供给actix. 如果你不想在模式中使用URL解码的值, 例如, 而不是这样: 170 | ```text 171 | /Foo%20Bar/{baz} 172 | ``` 173 | 你将使用以下内容: 174 | ```text 175 | /Foo Bar/{baz} 176 | ``` 177 | 有可能得到一种“尾部匹配”. 因此你必须使用正则表达式. 178 | ```text 179 | foo/{bar}/{tail:.*} 180 | ``` 181 | 上面的模式将和这些URL匹配, 生成如下匹配的信息: 182 | ```text 183 | foo/1/2/ -> Params: { 'bar': '1', 'tail':'2' } 184 | foo/abc/def/a/b/c -> Params: { 'bar':u'abc', 'tail':'def/a/b/c' } 185 | ``` 186 | 187 | ## 范围路由(Scoping Routes) 188 | 范围的界定可以帮助你组织共享路由的根路径. 你也可以嵌套范围(scopes). 189 | 190 | 假设你要组织 "Users" 的端点路径. 这样的路径可能包含: 191 | * /users 192 | * /users/show 193 | * /users/show/{id} 194 | 195 | 这些路径作用域的布局如下: 196 | ```rust 197 | #[get("/show")] 198 | async fn show_users() -> HttpResponse { 199 | HttpResponse::Ok.body("Show Users") 200 | } 201 | 202 | #[get("/show/{id}")] 203 | async fn user_detail(path: web::Path<(u32,)>) -> HttpResponse { 204 | HttpResponse::Ok.body(format!("User Detail: {}", path.into_inner().0)) 205 | } 206 | 207 | #[actix_web::main] 208 | async fn main() -> std::io::Result<()> { 209 | HttpServer::new(||{ 210 | App::new().service( 211 | web::scope("/users") 212 | .service(show_users) 213 | .service(user_detail) 214 | ) 215 | }).bind("127.0.0.1:8080")? 216 | .run().await 217 | } 218 | ``` 219 | 范围路径可以包含路径变量段来作为资源(译者注: 像上面示例的{id}). 与未限制范围的路径一致. 220 | 221 | 你可以从 `HttpRequest::match_info()` 方法中得到路径变量段值. `Path extractor` 还能提取范围级别的变量段. 222 | 223 | ## 匹配信息(Match information) 224 | 所有表示路径匹配段的值都可以在 `HttpRequest::match_info()` 中获取. 可以使用 `Path::get()` 方法来获取特定的值. 225 | 226 | ```rust 227 | use actix_web::{get, App, HttpRequest, HttpServer, Result}; 228 | 229 | #[get("/a/{v1}/{v2}/")] 230 | async fn index(req: HttpRequest) -> Result { 231 | let v1: u8 = req.match_info().get("v1").unwrap().parse().unwrap(); 232 | let v2: u8 = req.match_info().query("v2").parse().unwrap(); 233 | let (v3, v4): (u8, u8) = req.match_info().load().unwrap(); 234 | Ok(format!("Values {} {} {} {}", v1, v2, v3, v4)) 235 | } 236 | 237 | #[actix_web::main] 238 | async fn main() -> std::io::Result<()> { 239 | HttpServer::new(|| App::new().service(index)) 240 | .bind("127.0.0.1:8080")? 241 | .run() 242 | .await 243 | } 244 | ``` 245 | 对于上面这个例子来说, 路径 '/a/1/2',分别对应v1和v2并被解析成"1"和"2". 246 | 247 | 可以从尾部路径参数创建一个 `PathBuf` . 返回的 `PathBuf` 按百分比解码. 如果一个分段等于 ".." 则跳过前一个段. 248 | 249 | 出于安全的目的, 如果一个分段满足下面任何一个条件, 则返回一个 `Err` , 表明已满足条件: 250 | * 解码的分段以 `. (除了 ..), *` 任意一个开头的. 251 | * 解码的分段以 `:`, `>`, `<` 任意一个结尾的. 252 | * 解码的分段包含了任意的 `/` 253 | * 在Windows上, 解码段包含任意的: ‘' 254 | * 百分比编码导致无效的UTF-8 255 | 256 | 由于这些条件的存在, 根据请求路径参数解析出来的一个 `PathBuf` 可以安全的插入其中, 或用作没有其它检查的路径后缀. 257 | 258 | ```rust 259 | use actix_web::{get, App, HttpRequest, HttpServer, Result}; 260 | use std::path::PathBuf; 261 | 262 | #[get("/a/{tail:.*}")] 263 | async fn index(req: HttpRequest) -> Result { 264 | let path: PathBuf = req.match_info().query("tail").parse().unwrap(); 265 | Ok(format!("Path {:?}", path)) 266 | } 267 | 268 | #[actix_web::main] 269 | async fn main() -> std::io::Result<()> { 270 | HttpServer::new(|| App::new().service(index)) 271 | .bind("127.0.0.1:8080")? 272 | .run() 273 | .await 274 | } 275 | ``` 276 | 277 | ## 路径信息提取器(Path information extractor) 278 | Actix支持类型安全的路径信息提取功能. `Path` 提取信息, 目的地(转换后)的类型可以以几种不同的形式来定义. 一种简单的方式是一个 `tuple` 类型. 279 | 元组中的每一个元素必须对应模式路径中的一个元素. 比如: 你可以匹配一个路径模式 `/{id}/{username}` 为一个 `Path<(u32, String, String)>` 280 | 但是 `Path<(u32,String, String)>` 这种类型就会失败. 281 | 282 | ```rust 283 | use actix_web::{get, web, App, HttpServer, Result}; 284 | 285 | #[get("/{username}/{id}/index.html")] // <- 定义路径参数 286 | async fn index(info: web::Path<(String, u32)>) -> Result { 287 | let info = info.into_inner(); 288 | Ok(format!("Welcome {}! id: {}", info.0, info.1)) 289 | } 290 | 291 | #[actix_web::main] 292 | async fn main() -> std::io::Result<()> { 293 | HttpServer::new(|| App::new().service(index)) 294 | .bind("127.0.0.1:8080")? 295 | .run() 296 | .await 297 | } 298 | ``` 299 | 300 | 你也可以提取路径模式信息到一个结构体中. 在这种情况下结构体必须实现 **serde's** `Deserialize` trait. 301 | ```rust 302 | use actix_web::{get, web, App, HttpServer, Result}; 303 | use serde::Deserialize; 304 | 305 | #[derive(Deserialize)] 306 | struct Info { 307 | username: String, 308 | } 309 | 310 | // 使用serde提取路径信息 311 | #[get("/{username}/index.html")] // <- 定义路径参数 312 | async fn index(info: web::Path) -> Result { 313 | Ok(format!("Welcome {}!", info.username)) 314 | } 315 | 316 | #[actix_web::main] 317 | async fn main() -> std::io::Result<()> { 318 | HttpServer::new(|| App::new().service(index)) 319 | .bind("127.0.0.1:8080")? 320 | .run() 321 | .await 322 | } 323 | ``` 324 | 325 | 为请求查询参数 [Query](https://docs.rs/actix-web/3/actix_web/web/struct.Query.html) 提供了类似的功能. 326 | 327 | ## 生成资源URL(Generating resource URLs) 328 | 329 | 使用 [HttpRequest.url_for()](https://docs.rs/actix-web/3/actix_web/struct.HttpRequest.html#method.url_for) 方法去生成基于 330 | 资源模式的URLs. 比如, 你配置了一个以"foo" 为名称的资源,并且模式是 “{a}/{b}/{c}”, 你可能会这样做: 331 | ```rust 332 | use actix_web::{get, guard, http::header, HttpRequest, HttpResponse, Result}; 333 | 334 | #[get("/test/")] 335 | async fn index(req: HttpRequest) -> Result { 336 | let url = req.url_for("foo", &["1", "2", "3"])?; // <- 为"foo"资源生成url 337 | 338 | Ok(HttpResponse::Found() 339 | .header(header::LOCATION, url.as_str()) 340 | .finish()) 341 | } 342 | 343 | #[actix_web::main] 344 | async fn main() -> std::io::Result<()> { 345 | use actix_web::{web, App, HttpServer}; 346 | 347 | HttpServer::new(|| { 348 | App::new() 349 | .service( 350 | web::resource("/test/{a}/{b}/{c}") 351 | .name("foo") // <- 设置资源名, 然后它可以被 'url_for'使用 352 | .guard(guard::Get()) 353 | .to(|| HttpResponse::Ok()), 354 | ) 355 | .service(index) 356 | }) 357 | .bind("127.0.0.1:8080")? 358 | .run() 359 | .await 360 | } 361 | ``` 362 | 363 | 这将返回类似 ` http://example.com/test/1/2/3` 的字符串(至少在当前协议与主机名下包含 http://example.com). `url_for()` 方法返回 `Url object` 364 | 对象以变你可以修改这个url(添加查询参数,锚定等). `url_for()` 方法仅能在已经命名的资源时调用, 否则将返回错误. 365 | 366 | ## 外部资源(External resources) 367 | 有效的URL资源可以注册成外部资源.它们仅用于生成URL,而在请求时不考虑匹配. 368 | 369 | ```rust 370 | use actix_web::{get, App, HttpRequest, HttpServer, Responder}; 371 | 372 | #[get("/")] 373 | async fn index(req: HttpRequest) -> impl Responder { 374 | let url = req.url_for("youtube", &["oHg5SJYRHA0"]).unwrap(); 375 | assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0"); 376 | 377 | url.into_string() 378 | } 379 | 380 | #[actix_web::main] 381 | async fn main() -> std::io::Result<()> { 382 | HttpServer::new(|| { 383 | App::new() 384 | .service(index) 385 | .external_resource("youtube", "https://youtube.com/watch/{video_id}") 386 | }) 387 | .bind("127.0.0.1:8080")? 388 | .run() 389 | .await 390 | } 391 | ``` 392 | 393 | 394 | ## 路径正规化并重定向(Path normalization and redirection to slash-appended routes) 395 | 396 | 规范化意味着: 397 | * 在路径上添加斜杠 398 | * 用一个替换多个斜杠 399 | 400 | 这样的好处是处理器能够正确的解析路径(path)并返回. 如果全部启用, 规范化条件的顺序为 1) 合并, 2) 合并且追加 3). 如果路径至少满足这些条件中 401 | 的一个, 则它将重定向到新路径. 402 | ```rust 403 | use actix_web::{middleware, HttpResponse}; 404 | 405 | async fn index() -> HttpResponse { 406 | HttpResponse::Ok().body("Hello") 407 | } 408 | 409 | #[actix_web::main] 410 | async fn main() -> std::io::Result<()> { 411 | use actix_web::{web, App, HttpServer}; 412 | 413 | HttpServer::new(|| { 414 | App::new() 415 | .wrap(middleware::NormalizePath::default()) 416 | .route("/resource/", web::to(index)) 417 | }) 418 | .bind("127.0.0.1:8080")? 419 | .run() 420 | .await 421 | } 422 | ``` 423 | 上面示例中的 `//resource///` 将会被重定向为 `/resource/` . 424 | 425 | 在上面的示例中, 为所有方法(译者注: 指GET/POST/DELETE/PUT/HEAD等)注册了路径规范化处理函数,但是不能依赖此机制来重定向 _POST_ 请求. 426 | 427 | 带有斜杠的 _NOT FOUND_ 的重定向会将原POST请求转换成GET请求, 从而丢失原始请求POST中的所有数据. 428 | 429 | 可以仅仅针对GET请求注册规范化的路径: 430 | 431 | ```rust 432 | use actix_web::{get, http::Method, middleware, web, App, HttpServer}; 433 | 434 | #[actix_web::main] 435 | async fn main() -> std::io::Result<()> { 436 | HttpServer::new(|| { 437 | App::new() 438 | .wrap(middleware::NormalizePath::default()) 439 | .service(index) 440 | .default_service(web::route().method(Method::GET)) 441 | }) 442 | .bind("127.0.0.1:8080")? 443 | .run() 444 | .await 445 | } 446 | ``` 447 | 448 | ## 使用应用程序前缀来编写应用(Using an Application Prefix to Compose Applications) 449 | `web::scope()` 方法允许你设置一个指定的应用程序范围. 此范围表示资源的前缀, 前缀能附加到资源配置添加的所有资源模式中. 它可以用在将一组 450 | 路由安装在与它包含的可被调用位置的不同地方, 同时还可以保持相同的资源名称(译者注: 很难理解, 你就当是 scope是一组路由资源的前缀就行了, 这样 451 | 做是好管理,资源路径清晰). 452 | 453 | 示例如下: 454 | ```rust 455 | #[get("/show")] 456 | async fn show_users() -> HttpResponse { 457 | HttpResponse::Ok().body("Show users") 458 | } 459 | 460 | 461 | #[get("/show/{id}")] 462 | async fn user_detail(path: web::Path<(u32,)>) -> HttpResponse { 463 | HttpResponse::Ok().body(format!("User detail: {}", path.into_inner().0)) 464 | } 465 | 466 | #[actix_web::main] 467 | async fn main() -> std::io::Result<()> { 468 | HttpServer::new(|| { 469 | App::new().service( 470 | web::scope("/users") 471 | .service(show_users) 472 | .service(user_detail), 473 | ) 474 | }) 475 | .bind("127.0.0.1:8080")? 476 | .run() 477 | .await 478 | } 479 | ``` 480 | 在上面的示例中, _show_users_ 路由的有效模式为 _/users/show_ 而不是 _/show_ 那是因为应用程序作用域(scope)会在该模式之前. 然后, 仅仅 481 | 当URL路径为 _/users/show_ 时路由才会被匹配, 并组当 `HttpRequest.url_for()` 函数被调用时, 它也会生成相同路径的URL. 482 | 483 | ## 自定义路由防护(Custom route guard) 484 | 你可以将路由防护(guard)看作是一个接收请求对象引用并返回ture或者false的简单函数. 一般来说一个防护它是实现了 `guard` trait的任何对象. 485 | Actix 提供了多个谓词, 你可以查看 [functions section](https://docs.rs/actix-web/3/actix_web/guard/index.html#functions) API 文档. 486 | 487 | 下面是一个简单的检查一个请求中是否包含指定 header 的防护示例: 488 | ```rust 489 | use actix_web::{dev::RequestHead, guard::Guard, http, HttpResponse}; 490 | 491 | struct ContentTypeHeader; 492 | 493 | // 实现Guard 并重写了check函数 494 | impl Guard for ContentTypeHeader { 495 | fn check(&self, req: &RequestHead) -> bool { 496 | req.headers().contains_key(http::header::CONTENT_TYPE) 497 | } 498 | } 499 | 500 | #[actix_web::main] 501 | async fn main() -> std::io::Result<()> { 502 | use actix_web::{web, App, HttpServer}; 503 | 504 | HttpServer::new(|| { 505 | App::new().route( 506 | "/", 507 | web::route() 508 | .guard(ContentTypeHeader) // 添加一个防护 509 | .to(|| HttpResponse::Ok()), 510 | ) 511 | }) 512 | .bind("127.0.0.1:8080")? 513 | .run() 514 | .await 515 | } 516 | ``` 517 | 在上面的示例中, 仅当请求头中包含 _CONTENT-TYPE_ 时才会调用index 处理器. 518 | 519 | 防护(guard)不能够访问和修改请求对象, 但是它可以存一些额外的信息, 参考[request extensions](https://docs.rs/actix-web/3/actix_web/struct.HttpRequest.html#method.extensions) 520 | 521 | ## 修改防护(guard)值(Modifying guard values) 522 | 你可以通过将任意谓词的值包装在 `Not` 谓词中来反转其含义. 比如, 如果你想为除了 "GET" 以外的所有方法返回 "METHOD NOT ALLOWED" 响应, 523 | 可以使用如下示例方式操作: 524 | ```rust 525 | use actix_web::{guard, web, App, HttpResponse, HttpServer}; 526 | 527 | #[actix_web::main] 528 | async fn main() -> std::io::Result<()> { 529 | HttpServer::new(|| { 530 | App::new().route( 531 | "/", 532 | web::route() 533 | .guard(guard::Not(guard::Get())) 534 | .to(|| HttpResponse::MethodNotAllowed()), 535 | ) 536 | }) 537 | .bind("127.0.0.1:8080")? 538 | .run() 539 | .await 540 | } 541 | ``` 542 | 如果提供的guards能匹配 `Any` guard 表示接收一个防护清单 543 | ``` 544 | guard::Any(guard::Get()).or(guard::Post()) // 任意的Get Post都被接受 545 | ``` 546 | 如果要使所有提供的 guards 能匹配, 可以使用 `All` guard. 547 | ```text 548 | guard::All(guard::Get()).and(guard::Header("Content-Type","plain/text")) 549 | ``` 550 | (译者注: 上面的表示所有的get且header中ContentType为 "plain/text" 的请求才能接受) 551 | 552 | ## 改变默认 **NOT FOUND** 响应(Changing the default NOT FOUND response) 553 | 如果路径模式不能在路由表中发现或者资源没有匹配的路由, 那么默认的资源就会被使用. 默认的响应是 _NOT FOUND_ . 可以使用 `App::default_service()` 554 | 方法来重写 _NOT FOUND_ 响应. 这个方法具有接受与 `App::service()` 方法的常规资源配置相同配置的功能. 555 | ```rust 556 | #[actix_web::main] 557 | async fn main() -> std::io::Result<()> { 558 | HttpServer::new(|| { 559 | App::new() 560 | .service(web::resource("/").route(web::get().to(index))) 561 | .default_service( 562 | web::route() 563 | .guard(guard::Not(guard::Get())) 564 | .to(|| HttpResponse::MethodNotAllowed()), 565 | ) 566 | }) 567 | .bind("127.0.0.1:8080")? 568 | .run() 569 | .await 570 | } 571 | ``` 572 | 573 | 574 | -------------------------------------------------------------------------------- /doc/Webscokets.md: -------------------------------------------------------------------------------- 1 | ## Websockets 2 | Actix-web使用 `actix-web-actors` 包来对WebSockets提供支持. 可以通过 [web::Payload](https://docs.rs/actix-web/3/actix_web/web/struct.Payload.html) 将请求的有效payload转换成 3 | [ws::Message](https://docs.rs/actix-web-actors/2/actix_web_actors/ws/enum.Message.html) 流, 然后使用组合器处理实时消息. 4 | 但是使用 _http actor_ 处理WebSocket通信更加简单. 5 | 6 | 下面是一个简单的 WebSocket 回声(echo) 示例: 7 | ```rust 8 | use actix::{Actor, StreamHandler}; 9 | use actix_web::{web, App, Error, HttpRequest, HttpResponse, HttpServer}; 10 | use actix_web_actors::ws; 11 | 12 | /// 定义 HTTP actor 13 | struct MyWs; 14 | 15 | impl Actor for MyWs { 16 | type Context = ws::WebsocketContext; 17 | } 18 | 19 | /// ws 消息处理器 20 | impl StreamHandler> for MyWs { 21 | fn handle( 22 | &mut self, 23 | msg: Result, 24 | ctx: &mut Self::Context, 25 | ) { 26 | match msg { 27 | Ok(ws::Message::Ping(msg)) => ctx.pong(&msg), 28 | Ok(ws::Message::Text(text)) => ctx.text(text), 29 | Ok(ws::Message::Binary(bin)) => ctx.binary(bin), 30 | _ => (), 31 | } 32 | } 33 | } 34 | 35 | async fn index(req: HttpRequest, stream: web::Payload) -> Result { 36 | let resp = ws::start(MyWs {}, &req, stream); 37 | println!("{:?}", resp); 38 | resp 39 | } 40 | 41 | #[actix_web::main] 42 | async fn main() -> std::io::Result<()> { 43 | HttpServer::new(|| App::new().route("/ws/", web::get().to(index))) 44 | .bind("127.0.0.1:8080")? 45 | .run() 46 | .await 47 | } 48 | ``` 49 | 50 | 完整可用的 WebSocket echo 服务示例请查看 [examples directory](https://github.com/actix/examples/tree/master/websocket/) 51 | 52 | [websocket-chat directory](https://github.com/actix/examples/tree/master/websocket-chat/) 这里面提供了一个聊天服务器示例, 53 | 可以通过websocket或TCP链接来实现聊天功能. -------------------------------------------------------------------------------- /doc/WelcomeToActix.md: -------------------------------------------------------------------------------- 1 | ## 欢迎来到Actix(Welcome to Actix) 2 | Actix Web 可以使你快速且自信的在Rust中开发Web服务, 而本指南将指导你如何使用它. 3 | 4 | 网站上的文档主要关注Actix Web 框架. 有关Actix的actor框架相关的信息, 请查看 [Actix book](https://actix.rs/book/actix) 5 | (或者低级别的 [actix API docs](https://docs.rs/actix) ). 否则,请转入 [起步与入门](GettingStarted.md) . 如果你了解自己的学习方式, 6 | 并且只需要特定的信息, 则可能你需要阅读 [actix-web API docs](https://docs.rs/actix-web) 文档. -------------------------------------------------------------------------------- /doc/WhatIsActix.md: -------------------------------------------------------------------------------- 1 | ## Actix是相关包的生态系统(Actix is an ecosystem of crates) 2 | 3 |  Actix 表示几种事务. 它基于Rust中功能强大的actor系统, 最开始是基于 `actix-web` 系统来构建的.这是你最可能使用的一点. `actix-web` 为你提供了功能强大且能快速开发的Web开发框架. 4 | 5 |  我们一般称 `actix-web` 是一个小巧且实用的框架. 出于这些目的, 这是一个微框架. 如果你已经是一个Rust程序员, 6 | 那么你很快就能在这里找到家(译者注:有家的感觉?), 但即使你是由其它编程语言过来的,你也会发现 `actix-web` 上手非常容易. 7 | 8 |  使用 `actix-web` 开发的应用将暴露一个本机可执行文件中包含的HTTP服务器. 同样,你也可以将它放在其它的HTTP服务 9 | 之后, 比如像nginx. 即使完全没有其它的HTTP服务器, `actix-web` 也足以提供 _HTTP/1_ 和 _HTTP/2_ 以及支持 10 | TLS(HTTPS)服务. 这用来构建分发小型服务很有用. 11 | 12 |  最重要的是: `actix-web` 是运行在Rust 1.42 及更高的stable release 版本之上. -------------------------------------------------------------------------------- /images/connection_accept.svg: -------------------------------------------------------------------------------- 1 | ServerBuildermioAcceptWorkerClientstart(socks, workers)mio::Poll::poll()process_*backpressureaccept_one(Conn)send(Conn)break loopworker_faulted(idx)remove worker, get next workeralt[ send(Conn) -> Ok(_) ][ send(Conn) -> Err(_) ]loop[ while exist WorkerClient ]alt[ poll() -> TIMER | CMD ][ poll() -> NOTIFY ][ poll() -> OTHER(token) ]loop[ Continuous: poll ]ServerBuildermioAcceptWorkerClient -------------------------------------------------------------------------------- /images/connection_overview.svg: -------------------------------------------------------------------------------- 1 | ServerBuilderAcceptWorkerClientWorkerDispatcherstart(socks, workers)accept Connectionspoll() --> Connbackpressure logicsend(Conn)loop[ poll connections on sockets ]process Connectionsrx.poll_next()Service factoriesnew(stream)loop[ Worker as Future::poll ]process RequestsDispatch requestsloop[ Dispatcher::poll ]ServerBuilderAcceptWorkerClientWorkerDispatcher -------------------------------------------------------------------------------- /images/connection_request.svg: -------------------------------------------------------------------------------- 1 | HttpServerHttpServiceHandlerHSHRStateDispatcherTokioHttpServiceHandlerResponseeventually build...H1::Dispatcher::new()State::H1(Dispatcher)State::H2Handshakealt[ Protocol::HTTP1 ][ Protocol::HTTP2 ]HttpServiceHandlerResponse::new(State)StreamService->Tokio::spawn(HttpServiceHandlerResponse as Future)poll()H2::Dispatcher::new(stream,services)poll()poll()alt[ State::H2Handshake ]requests loopHttpServerHttpServiceHandlerHSHRStateDispatcherTokio -------------------------------------------------------------------------------- /images/connection_worker.svg: -------------------------------------------------------------------------------- 1 | WorkerClientWorkerStreamServiceHttpServiceHandlerTokio(token, web::App)poll()rx.poll_next()WorkerCommand(Conn)check_readiness()Worker::services[Conn.token]call(ServerMessage::Connect(stream))call(stream)HttpServiceHandlerResponse as Futurespawn(HttpServiceHandlerResponse)WorkerState::UnavailableWorkerState::Restartingalt[ check_readiness() -> Ok(true) ][ check_readiness() -> Ok(false) ][ check_readiness() -> Err(token,idx) ]loop[ ]alt[ WorkerState::Available ]process connectionWorkerClientWorkerStreamServiceHttpServiceHandlerTokio -------------------------------------------------------------------------------- /images/http_server.svg: -------------------------------------------------------------------------------- 1 | HttpServerServerBuilderWorkerStreamNewServiceHttpServiceHttpServiceResponseTokiobuild...listen(addr,Fn->HttpService)create(addr,Fn->HttpService)start()start(StreamNewService)InternalServiceFactory::create()new_service()HttpServiceResponse::new()HttpServiceResponse as FutureStreamService(HttpServiceResponse) as Futurespawn(StreamService(HttpServiceResponse)).map(Worker))poll()Ready(Worker(HttpServiceHandler))poll->Pending...pull messagesloop[ Worker process messages ]HttpServerServerBuilderWorkerStreamNewServiceHttpServiceHttpServiceResponseTokio -------------------------------------------------------------------------------- /src/bin/application.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{web, get, App, HttpServer, Responder, guard, HttpResponse}; 2 | use std::sync::Mutex; 3 | 4 | /// ## 写一个应用 5 | /// * actix-web 里面提供了一系列可以使用rust来构建web server的原语。它提供了路由,中间件,request预处理,response的后置处理等。 6 | /// * 所有的actix-web servers都围绕App实例来构建. 它被用来注册路由资源来中间件. 它也存储同一个scope内所有处理程序之间共享的应用程序状态. 7 | /// * 应用前缀总是包含一个 "/" 开头,如果提供的前缀没有包含斜杠,那么会默认自动的插入一个斜杠. 8 | /// * 比如应用使用 /app 来限定,那么任何使用了路径为 /app, /app/ 或者 /app/test的请求都将被匹配,但是 /application这种path不会被匹配. 9 | /// * 下面使用async main 函数来创建一个app 实例并注册请求处理器. 10 | /// * 使用App::service 来处理使用路由宏,或者你也可以使用App::route来手功注册路由处理函数,声明一个path与方法. 11 | /// * 最后使用HttpServer来启动服务,并处理incoming请求. 12 | /// * 使用cargo run 运行,然后访问http://localhost:8080/ 或其它路由path 就可以看到结果. 13 | /// * 下面这个例子使用 /app 前缀开头且以一个 index.html作用资源路径,因此完整的资源路径url就是 /app/index.html. 14 | /// * 更多的信息,将会在URL Dispatch章节。 15 | /// 16 | /// ## State 17 | /// * 应用程序状态(State)被同一作用域(Scope)内的所有路由和资源共享。 18 | /// * State 能被web::Data 来访问,其中 T是 state的类型. State也能被中间件访问. 19 | /// 20 | /// 让我们编写一个简单的应用程序并将应用程序名称存储在状态中,你可以在应用程序中注册多个State 21 | /// 22 | /// ## 共享可变State 23 | /// HttpServer接收一个应用程序工厂而不是一个应用程序实例,一个HttpServer 为每一个线程构造一个应用程序实例. 24 | /// 25 | /// 因此必须多次构造应用程序数据,如果你想在两个不同的线程之间共享数据,一个可以共享的对象应用使用比如: Sync + Send 26 | /// 27 | /// 内部 web::Data 使用 Arc. 因此为了避免创建两个 Arc, 我们应该在在使用App::app_data() 之前创建 好我们的数据。 28 | /// 下面的例子中展示了应用中使用可变共享状态,首先我们定义state并创建处理器(handler). 29 | /// 30 | /// ## 使用一个应用级Scope去组合应用 31 | /// web::scope()方法允许你设置一个资源组前缀. 它表示所有资源类型(或者说是一组资源定位符)前缀配置。 32 | /// 下面的 /app 就是这种使用方式,可以方便管理一组资源. 33 | /// 34 | /// ## 应用防护和虚拟主机 35 | /// 其实"防护"(guards)可以是说是actix-web为handler函数提供的一种安全配置. 36 | /// 你可以将防护看成一个接收请求对象引用并返回ture或者false的简单函数. 可以说guard可以是实现了Guard trait的任何对象. 37 | /// 38 | /// actix-web 提供了几种开箱即用的guards. 你可以在api文档中查找. 39 | /// 其中一个guards就是 Header. 它可以被用在请求头信息的过滤. 40 | /// 41 | /// ## 可配置 42 | /// 为了简单与可重用,App与web::Scope两者都提供了configure方法. 此功能让配置的各个部分在不同的模块甚至不同的库(library) 43 | /// 中移动时非常有用. 44 | /// 45 | /// 其实这是一种拆分管理,一般来说可以提高代码重用,减少修改某个Scope组时可能带来的影响其它模块的错误. 46 | /// 每一个ServiceConfig 都有它自己的 data, routers, 和 services 47 | 48 | #[actix_web::main] 49 | async fn main() -> std::io::Result<()> { 50 | // 外部声明一个counter 51 | let counter = web::Data::new(AppStateWithCounter{counter:Mutex::new(0)}); 52 | HttpServer::new(move ||{ 53 | // 移动所有权 54 | App::new() 55 | // 在初始化的时候添加一个状态,并启动应用, 也就是说,这里设置的data,可以被同一Scope中的所有route共享到 56 | .data(AppState{app_name: String::from("Actix-web 3.0 demo")}) 57 | // 设置一个可变的State 在多个线程中共享, 适合在多个线程中需要修改的场景 58 | .app_data(counter.clone())// 注册counter,为什么要用clone? 因为它需要在每个线程中共享 59 | .service(get_state) 60 | .configure(config) // 配置 61 | .configure(second_config) 62 | .service( 63 | // 所有以 /app 开头的path都将被匹配 64 | web::scope("/app") 65 | // 为 /app 资源组添加一个Header guard Http Header 的Content-Type 必须为指定的类型 66 | .guard(guard::Header("Content-Type","application/html")) 67 | // 这里会处理 /app/index.html的 get 请求 68 | .route("/index.html", web::get().to(index)) 69 | // 同一个scope下再注册一个route 70 | .route("/getAppInfo", web::get().to(app_info)) 71 | 72 | ) 73 | .route("/", web::get().to(mutable_counter)) 74 | }).bind("127.0.0.1:8080")? 75 | .run().await 76 | } 77 | 78 | async fn index() -> impl Responder { 79 | "hello actix-web 3.0" 80 | } 81 | async fn app_info() -> String { 82 | "This is app Info".to_string() 83 | } 84 | 85 | // 这个struct代表state 86 | struct AppState { 87 | app_name: String, 88 | } 89 | 90 | #[get("/state/getState")] 91 | async fn get_state(data: web::Data) -> String { 92 | let app_name = &data.app_name; 93 | format!("Hello {}!", app_name) // 返回app name 94 | } 95 | 96 | // 可变共享计数器,可以在多个线程之间共享的state 97 | struct AppStateWithCounter { 98 | counter: Mutex, // Mutex 排它锁,可以安全的在多个线程之间操作 99 | } 100 | 101 | async fn mutable_counter(data: web::Data) -> String { 102 | let mut counter = data.counter.lock().unwrap(); // lock 会阻塞当前线程,直到它可用为止 103 | *counter += 1; // 解引用访问counter中的值,并 + 1 104 | format!("Request number : {}", counter) // 返回 105 | } 106 | 107 | /// 第一种配置function 108 | fn config(cfg: &mut web::ServiceConfig) { 109 | cfg.service( 110 | web::resource("/t") 111 | .route(web::get().to(|| HttpResponse::Ok().body("This is oneConfig Response"))) 112 | ); 113 | } 114 | 115 | /// 第二种配置function 116 | fn second_config(cfg: &mut web::ServiceConfig) { 117 | cfg.service( 118 | web::scope("/secondScope") 119 | .guard(guard::Header("Content-Type", "application/text")) 120 | .route("/test",web::get().to(|| HttpResponse::Ok().body("This is Second Config Response"))) 121 | ); 122 | } -------------------------------------------------------------------------------- /src/bin/errors_custom_error_response.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{HttpServer, App, get, error, Result, dev::HttpResponseBuilder, http::header, 2 | http::StatusCode, HttpResponse, middleware::Logger}; 3 | use derive_more::{Display, Error}; 4 | use log::debug; 5 | 6 | #[actix_web::main] 7 | async fn main() -> std::io::Result<()> { 8 | // 设置环境变量参数 9 | std::env::set_var("RUST_LOG", "my_errors=debug,actix_web=debug"); // 这里需要将actix_web的日志级别设置为debug 10 | std::env::set_var("RUST_BACKTRACE", "1"); 11 | env_logger::init(); 12 | 13 | HttpServer::new(|| { 14 | App::new() 15 | // warp方法 注册一个中间件 16 | .wrap(Logger::default()) // 添加默认的日志设置 17 | .service(index) 18 | .service(user_error) 19 | }).bind("127.0.0.1:8080")? 20 | .run().await 21 | } 22 | 23 | #[derive(Debug, Display, Error)] 24 | enum MyError { 25 | #[display(fmt = "internal error")] 26 | InternalError, 27 | #[display(fmt = "bod request")] 28 | BadClientData, 29 | #[display(fmt = "timeout")] 30 | Timeout, 31 | } 32 | 33 | impl error::ResponseError for MyError { 34 | // 重写 error_response() 方法使用默认实现 35 | fn error_response(&self) -> HttpResponse { 36 | HttpResponseBuilder::new(self.status_code()) 37 | .set_header(header::CONTENT_TYPE, "text/html; charset=utf-8") 38 | .body(self.to_string()) 39 | } 40 | 41 | // 重写 status_code 42 | fn status_code(&self) -> StatusCode { 43 | match *self { 44 | MyError::InternalError => StatusCode::INTERNAL_SERVER_ERROR, 45 | MyError::BadClientData => StatusCode::BAD_REQUEST, 46 | MyError::Timeout => StatusCode::GATEWAY_TIMEOUT 47 | } 48 | } 49 | } 50 | 51 | #[get("/error")] 52 | async fn index() -> Result<&'static str, MyError> { 53 | let err =MyError::BadClientData; 54 | debug!("{}", err); 55 | Err(err) 56 | } 57 | 58 | #[derive(Debug, Display, Error)] 59 | enum UserError { 60 | #[display(fmt = "Validation error on field: {}", field)] 61 | Validation { 62 | field: String 63 | } 64 | } 65 | 66 | impl error::ResponseError for UserError { 67 | fn error_response(&self) -> HttpResponse { 68 | HttpResponseBuilder::new(self.status_code()) 69 | .set_header(header::CONTENT_TYPE, "text/html; charset = utf-8") 70 | .body(self.to_string()) 71 | } 72 | 73 | fn status_code(&self) -> StatusCode { 74 | match *self { 75 | UserError::Validation { .. } => StatusCode::BAD_REQUEST, 76 | } 77 | } 78 | } 79 | 80 | #[get("userError")] 81 | async fn user_error() -> Result<&'static str, UserError> { 82 | let error = UserError::Validation {field: "username".to_string()}; 83 | debug!("{}", error); 84 | Err(error) 85 | } 86 | -------------------------------------------------------------------------------- /src/bin/extractors_application_state_arc.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{HttpServer, App, web, Responder, get}; 2 | use std::sync::atomic::{AtomicUsize, Ordering}; 3 | 4 | use std::sync::Arc; 5 | 6 | #[actix_web::main] 7 | async fn main() -> std::io::Result<()> { 8 | 9 | let data = AppState{count: Arc::new(AtomicUsize::new(0))}; 10 | 11 | HttpServer::new(move ||{ 12 | App::new().data(data.clone()) 13 | .service(show_count) 14 | .service(add_one) 15 | }).bind("127.0.0.1:8080")? 16 | .run().await 17 | } 18 | 19 | #[derive(Clone)] 20 | struct AppState { 21 | // AtomicUsize: 一个可以安全的在多个线程是安全共享的整形 22 | count: Arc, 23 | } 24 | 25 | #[get("/")] 26 | async fn show_count(data: web::Data) -> impl Responder { 27 | 28 | format!("count: {}", data.count.load(Ordering::Relaxed)) 29 | } 30 | 31 | #[get("/add")] 32 | async fn add_one(data: web::Data) -> impl Responder { 33 | 34 | data.count.fetch_add(1, Ordering::Relaxed); 35 | 36 | format!("count: {}", data.count.load(Ordering::Relaxed)) 37 | } 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/bin/extractors_application_state_cell.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{HttpServer, web , Responder, App}; 2 | use std::cell::Cell; 3 | 4 | /// 非线程安全版本的 应用程序状态使用示例 5 | #[actix_web::main] 6 | async fn main() -> std::io::Result<()> { 7 | // 初始化data 8 | let data = AppState {count: Cell::new(0)}; 9 | HttpServer::new(move ||{ 10 | App::new().data(data.clone()) 11 | .route("/", web::get().to(show_count)) 12 | .route("/add", web::get().to(add_one)) 13 | }).bind("127.0.0.1:8080")? 14 | .run().await 15 | } 16 | 17 | #[derive(Clone)] 18 | struct AppState { 19 | // Cell 可用在内部可变场景 内部提供get/set 来修改 20 | count : Cell 21 | } 22 | 23 | async fn show_count(data: web::Data) -> impl Responder { 24 | format!("count: {}", data.count.get()) 25 | } 26 | 27 | async fn add_one(data: web::Data) -> impl Responder { 28 | let count = data.count.get(); 29 | data.count.set(count + 1); 30 | 31 | format!("count: {}", data.count.get()) 32 | } 33 | -------------------------------------------------------------------------------- /src/bin/extractors_json.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{HttpServer, App, Result, web, guard, error, HttpResponse}; 2 | use serde::Deserialize; 3 | use actix_web::web::Json; 4 | 5 | #[actix_web::main] 6 | async fn main() -> std::io::Result<()> { 7 | HttpServer::new(|| { 8 | // 单独配置json 9 | let json_config = web::JsonConfig::default().limit(4096) // 限制最大playload为 4kb 10 | .error_handler(|err, _req|{ 11 | // 创建自定义错误响应 12 | error::InternalError::from_response(err, HttpResponse::Conflict().finish()).into() 13 | }); 14 | App::new().service( 15 | web::scope("/json") 16 | .app_data(json_config) // 设置JsonConfig配置 17 | .guard(guard::Header("Content-Type", "application/json")) 18 | .route("/getInfo", web::get().to(get_info)) 19 | ) 20 | }).bind("127.0.0.1:8080")? 21 | .run().await 22 | } 23 | 24 | #[derive(Deserialize)] 25 | struct Info { 26 | username: String, 27 | } 28 | 29 | async fn get_info(info: Json) -> Result { 30 | Ok(format!("Welcome {}!", info.username)) 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/bin/extractors_type_safe_path.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{HttpServer, App, web, get, HttpRequest}; 2 | use serde::Deserialize; 3 | 4 | /// ## 类型安全的信息提取器 5 | /// actix-web 提供了一个灵活的类型安全的请求信息访问者,它被称为提取器(extractors)(实现了 impl FromRequest). 6 | /// 默认下actix-web提供了几种extractors的实现. 7 | /// 8 | /// 提取器可以被作为处理函数的参数访问. actix-web 每个处理函数(handler function)最多支持10个提取器. 它们作为参数的位置没有影响. 9 | /// 10 | ///## 路径(Path) 11 | /// Path提供了能够从请求路径中提取信息的能力. 你可以从path中反序列化成任何变量. 12 | /// 13 | /// 因此,注册一个/users/{user_id}/{friend}的路径, 你可以反序列化两个字段, user_id和 friend. 14 | /// 这些字段可以被提取到一个 tuple(元组)中去, 比如: Path 或者是任何实现了 serde trait包中的 Deserialize 15 | /// 的结构体(structure) 16 | /// 17 | /// 也可以提取信息到一个指定的实现了serde trait反序列化的类型中去. 这种serde的使用方式与使用元组等效. 18 | /// 19 | /// 另外你也可以使用 get 或者 query 方法从请求path中通过名称提取参数值 20 | /// 21 | /// 请参见下面的示例: 22 | #[actix_web::main] 23 | async fn main() -> std::io::Result<()> { 24 | HttpServer::new(|| { 25 | // 注册一个/users/{user_id}/{friend} 的路由path 26 | // user_id 被反序列化为一个u32 27 | // friend 被反序列化为一个String 28 | // {} 占位符 29 | App::new().route("/users/{user_id}/{friend}", web::get().to(get_user)) 30 | .service(get_obj) 31 | .service(query) 32 | }).bind("127.0.0.1:8080")? 33 | .run().await 34 | } 35 | 36 | /// 反序列化成一个元组 37 | async fn get_user(web::Path((user_id, friend)): web::Path<(u32, String)>) -> String { 38 | format!("Welcome {}, user_id {}!", friend, user_id) 39 | } 40 | 41 | #[get("/getObj/{user_id}/{friend}")] 42 | async fn get_obj(info: web::Path) -> String { 43 | // 创建一个myInfo 44 | let my_info = User::new(18, "dsl".to_string()); 45 | // 获取请求参数中的user信息 46 | println!("req user:{:?}", info); 47 | // 判断id是否相等 48 | if my_info.user_id == info.user_id { 49 | "Good! Equal user_id".to_string() 50 | } else { 51 | // 否则返回一个新的String 52 | format!("this is new User [user_id:{}, friend:{}]", my_info.user_id, my_info.friend) 53 | } 54 | } 55 | 56 | #[get("/query/{age}/{username}")] // 定义请求路径参数 57 | async fn query(req: HttpRequest) -> String { 58 | let age: u32 = req.match_info().get("age").unwrap().parse().unwrap(); 59 | let username:String = req.match_info().query("username").parse().unwrap(); 60 | format!("Hello {} your age:{}", username, age) 61 | } 62 | 63 | #[derive(Deserialize, Debug)] 64 | struct User { 65 | user_id: u32, 66 | friend: String, 67 | } 68 | 69 | impl User { 70 | // create MyInfo 71 | fn new(user_id: u32, friend: String) -> Self { 72 | User { user_id, friend } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/bin/handlers_different_return_types.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{HttpServer, App, get, HttpResponse, Either, Error}; 2 | use rand::Rng; 3 | 4 | 5 | /// ## 不同的返回类型(两种) Different Return Types(Either) 6 | /// 有时候你需要在响应中返回两中不同的类型, 例如,您可以进行错误检查并返回错误,返回异步响应或需要两种不同类型的任何结果。 7 | /// 8 | /// 对于这种情况, 你可以使用 Either类型, Either允许你组合两个不同类型的responder到一个单个类型中去. 9 | /// 10 | /// 请看如下示例 11 | #[actix_web::main] 12 | async fn main() -> std::io::Result<()> { 13 | HttpServer::new(||{ 14 | App::new().service(index) 15 | }).bind("127.0.0.1:8080")? 16 | .run().await 17 | } 18 | 19 | type RegisterResult = Either>; 20 | 21 | #[get("/")] 22 | async fn index() -> RegisterResult { 23 | // 产生一个 0-9的随机整数 24 | let rand_num = rand::thread_rng().gen_range(0,9); 25 | if rand_num < 5 { 26 | Either::A(HttpResponse::Ok().body("number less then 5")) 27 | }else { 28 | let res = format!("Great! This number is {}", rand_num); 29 | Either::B(Ok(res)) 30 | } 31 | } 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/bin/handlers_request_handlers.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{web, HttpServer, App, HttpRequest, get}; 2 | /// ## Request Handlers 3 | /// 一个请求处理器,它是一个异步函数,可以接收零个或多个参数,而这些参数被实现了(ie, impl FromRequest)的请求所提取, 4 | /// 并且返回一个被转换成 HttpResponse或者其实现(ie, impl Responder)的类型. 5 | /// 6 | /// 请求处理发生在两个阶段: 7 | /// 8 | /// 首先处理对象被调用,并返回一个实现了 Responder trait的任何对象.然后在返回的对象上调用 respond_to()方法,将其 9 | /// 自身转换成一个 HttpResponse 或者 Error . 10 | /// 11 | /// 默认情况下 actix-web 为 &‘static str , String 等提供了 Responder的标准实现. 12 | /// 13 | /// 完整的实现清单可以参考 [Responder documentation](https://docs.rs/actix-web/3/actix_web/trait.Responder.html#foreign-impls) 14 | /// 15 | /// 有效的 handler示例: 16 | /// 17 | /// ```rust 18 | /// async fn index(_req: HttpRequest) -> &'static str { 19 | /// "Hello World" 20 | /// } 21 | /// async fn index_two(_req: HttpRequest) -> String { 22 | /// "Hello world".to_string() 23 | /// } 24 | /// ``` 25 | /// 你也可以改变返回的签名为 impl Responder 它在要返回复杂类型时比较好用. 26 | /// ```rust 27 | /// async fn index(_req: HttpRequest) -> impl Responder { 28 | /// Bytes::from_static(b"Hello world") 29 | /// } 30 | /// async fn index(_req: HttpRequest) -> impl Responder { 31 | /// // ... 32 | /// } 33 | /// ``` 34 | #[actix_web::main()] 35 | async fn main() -> std::io::Result<()> { 36 | HttpServer::new(||{ 37 | App::new().service(index_two) 38 | .route("/", web::get().to(index)) 39 | }) 40 | .bind("127.0.0.1:8080")? 41 | .run().await 42 | } 43 | 44 | async fn index(_req: HttpRequest) -> &'static str { 45 | "Hello World" 46 | } 47 | 48 | #[get("/two")] 49 | async fn index_two(_req: HttpRequest) -> String { 50 | "Hello world".to_string() 51 | } 52 | 53 | -------------------------------------------------------------------------------- /src/bin/handlers_response_with_custom_type.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{HttpServer, App, HttpResponse, Responder, HttpRequest, Error,get}; 2 | use serde::Serialize; 3 | use futures::future::{ready, Ready}; 4 | 5 | 6 | /// ## Response with custom Type (返回自定义类型) 7 | /// 为了直接从处理函数返回自定义类型的话, 需要这个类型实现 Responder trait. 8 | /// 9 | /// 让我们创建一个自定义响应类型,它可以序列化为一个 application/json 响应. 10 | /// 先在Cargo.toml文件中添加如下依赖项: 11 | /// 12 | /// ```rust 13 | /// serde = "1.0.116" 14 | /// futures = "0.3.5" 15 | /// serde_json = "1.0.57" 16 | /// ``` 17 | #[actix_web::main] 18 | async fn main() -> std::io::Result<()> { 19 | HttpServer::new(||{ 20 | App::new().service(index) 21 | }) 22 | .bind(SERVER_ADDRESS)? 23 | .run().await 24 | } 25 | 26 | #[derive(Serialize)] 27 | struct MyObj { 28 | name: &'static str 29 | } 30 | 31 | //响应Content-Type 32 | const CONTENT_TYPE:&str = "application/json"; 33 | const SERVER_ADDRESS:&str = "127.0.0.1:8080"; 34 | 35 | /// 自定义Responder实现 36 | impl Responder for MyObj { 37 | type Error = Error; 38 | type Future = Ready>; 39 | 40 | 41 | fn respond_to(self, _req: &HttpRequest) -> Self::Future { 42 | // 先把self 序列化成一个json字符串 43 | let body = serde_json::to_string(&self).unwrap(); 44 | 45 | // 创建响应并设置Content-Type 46 | ready(Ok(HttpResponse::Ok().content_type(CONTENT_TYPE).body(body))) 47 | } 48 | } 49 | 50 | #[get("/")] 51 | async fn index() -> MyObj { 52 | MyObj{name: "user"} 53 | } 54 | 55 | -------------------------------------------------------------------------------- /src/bin/handlers_streaming_response_body.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{HttpServer, App, get, HttpResponse, Error}; 2 | use futures::stream::once; 3 | use futures::future::ok; 4 | use bytes::Bytes; 5 | 6 | /// ## 流式响应Body (Streaming response body) 7 | /// 响应也可以是异步的. 在下面的案例中, body 必须实现Steam trait(Stream) 8 | #[actix_web::main] 9 | async fn main() -> std::io::Result<()> { 10 | HttpServer::new(||{ 11 | App::new().service(stream) 12 | }).bind("127.0.0.1:8080")? 13 | .run().await 14 | } 15 | 16 | #[get("/stream")] 17 | async fn stream() -> HttpResponse { 18 | let body = once(ok::<_, Error>(Bytes::from_static(b"test"))); 19 | 20 | HttpResponse::Ok().content_type("application/json").streaming(body) 21 | } 22 | 23 | -------------------------------------------------------------------------------- /src/bin/hello_world.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder}; 2 | 3 | /// ## Hello world 示例 4 | /// * 1. Request 使用一个async 异步函数来处理,它接收0个或多个参数,这些参数能被Request提取,并且返回一个被 5 | /// 转换成HttpResponse类型的 trait. 6 | /// * 2. 下面的异步处理函数,可以直接使用内置宏来附加路由信息。这允许你指定响应方法与资源path. 7 | /// * 3. 另外你也可以不使用路由宏来注册handler函数,可以使用像v2版本的写法,例如下面的manual_hello函数. 8 | 9 | #[get("/hello")] 10 | async fn hello() -> impl Responder { 11 | HttpResponse::Ok().body("Hello world Rust!") 12 | } 13 | 14 | #[get("/test")] 15 | async fn test() -> String { "Direct Response String".to_string()} 16 | 17 | #[get("/")] 18 | async fn other() -> impl Responder { 19 | HttpResponse::Ok().body("Default Other Resp") 20 | } 21 | 22 | /// post echo server 23 | #[post("/echo")] 24 | async fn echo(req_body: String) -> impl Responder { 25 | HttpResponse::Ok().body(req_body) 26 | } 27 | 28 | /// 不使用声明式路由,手工构建响应fn 这种就是v2版本的写法 29 | async fn manual_hello() -> impl Responder { 30 | HttpResponse::Ok().body("manual hello") 31 | } 32 | 33 | #[actix_web::main] 34 | async fn main() -> std::io::Result<()> { 35 | // main 入口函数 36 | HttpServer::new(|| { 37 | App::new() 38 | // v3 版本的写法 39 | .service(hello) 40 | .service(test) 41 | .service(echo) 42 | .service(other) 43 | // v2 版本的写法 44 | .route("/hey", web::get().to(manual_hello)) 45 | }).bind("127.0.0.1:8080")? 46 | .run().await 47 | } 48 | 49 | -------------------------------------------------------------------------------- /src/bin/middleware.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{dev::ServiceRequest, dev::ServiceResponse, Error, HttpServer, App, web}; 2 | use actix_service::{Service, Transform}; 3 | 4 | use futures::future::{ok, Ready}; 5 | use futures::{Future, FutureExt}; 6 | 7 | use std::pin::Pin; 8 | use std::task::{Context, Poll}; 9 | 10 | 11 | /// 中间件使用示例所表达的意图是: 12 | /// 在请求进来时且并处理函数处理之前,我们可以对请求做一些操作。 13 | /// 在响应返回前,我们可以对响应做一些操作. 14 | /// 这种方式给了用户更多可扩展,可定制化的可能. 15 | #[actix_web::main] 16 | async fn main() -> std::io::Result<()> { 17 | HttpServer::new(|| { 18 | let app = App::new().wrap_fn(|req, srv| { 19 | println!("Hi form start. You requested: {}", req.path()); 20 | srv.call(req).map(|res| { 21 | println!("Hi form response"); 22 | res 23 | }) 24 | }); 25 | app.route("/middleware", web::get().to(|| async { 26 | "Hello Middleware" 27 | })) 28 | }).bind("127.0.0.1:8080")? 29 | .run().await 30 | } 31 | 32 | /// 在中间件处理过程器有两步. 33 | /// 1. 中间件初始化, 下一个服务链中作为一个参数中间件工厂被调用. 34 | /// 2. 中间件的调用方法被正常的请求调用. 35 | pub struct SayHi; 36 | 37 | ///中间件工厂是来自 actix_service 包下的一个 `Transform` trait. 38 | /// `S` - 下一个服务类型 39 | /// `B` - 响应body类型 40 | impl Transform for SayHi 41 | where 42 | S: Service, Error = Error>, 43 | S::Future: 'static, 44 | B: 'static, 45 | { 46 | type Request = ServiceRequest; 47 | type Response = ServiceResponse; 48 | type Error = Error; 49 | type Transform = SayHiMiddleware; 50 | type InitError = (); 51 | type Future = Ready>; 52 | 53 | fn new_transform(&self, service: S) -> Self::Future { 54 | ok(SayHiMiddleware { service }) 55 | } 56 | } 57 | 58 | pub struct SayHiMiddleware { 59 | service: S, 60 | } 61 | 62 | impl Service for SayHiMiddleware 63 | where 64 | S: Service, Error = Error>, 65 | S::Future: 'static, 66 | B: 'static, 67 | { 68 | type Request = ServiceRequest; 69 | type Response = ServiceResponse; 70 | type Error = Error; 71 | type Future = Pin>>>; 72 | 73 | fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { 74 | self.service.poll_ready(cx) 75 | } 76 | 77 | fn call(&mut self, req: ServiceRequest) -> Self::Future { 78 | println!("Hi from start. You requested: {}", req.path()); 79 | 80 | let fut = self.service.call(req); 81 | 82 | Box::pin(async move { 83 | let res = fut.await?; 84 | 85 | println!("Hi from response"); 86 | Ok(res) 87 | }) 88 | } 89 | } 90 | 91 | -------------------------------------------------------------------------------- /src/bin/middleware_error_handler.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{HttpServer, App, web, HttpResponse, dev, Result, http}; 2 | use actix_web::middleware::errhandlers::{ErrorHandlerResponse, ErrorHandlers}; 3 | 4 | /// 自己定义500错误响应 5 | fn render_500(mut res: dev::ServiceResponse) -> Result> { 6 | res.response_mut().headers_mut().insert( 7 | http::header::CONTENT_TYPE, 8 | http::HeaderValue::from_static("Error"), 9 | ); 10 | Ok(ErrorHandlerResponse::Response(res)) 11 | } 12 | 13 | #[actix_web::main] 14 | async fn main() -> std::io::Result<()> { 15 | HttpServer::new(||{ 16 | App::new().wrap( 17 | ErrorHandlers::new() 18 | .handler(http::StatusCode::INTERNAL_SERVER_ERROR, render_500) 19 | ).service(web::resource("/test") 20 | .route(web::get().to(|| HttpResponse::Ok().body("success"))) 21 | .route(web::head().to(|| HttpResponse::MethodNotAllowed())) 22 | ) 23 | }).bind("127.0.0.1:8080")? 24 | .run().await 25 | } 26 | -------------------------------------------------------------------------------- /src/bin/middleware_logging.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{HttpServer, App, HttpResponse, middleware::Logger, web, middleware}; 2 | use env_logger::Env; 3 | 4 | #[actix_web::main] 5 | async fn main() -> std::io::Result<()> { 6 | // 初始化日志 info 级别 7 | env_logger::from_env(Env::default().default_filter_or("info")).init(); 8 | 9 | HttpServer::new(|| { 10 | App::new().wrap(Logger::default()) 11 | // 设置日志格式 12 | .wrap(Logger::new("%a %{User-Agent}i")) 13 | // 包装一个中间件 设置默认响应header 14 | .wrap(middleware::DefaultHeaders::new().header("X-Version", "0.2")) 15 | .route("/logging", web::get().to(|| HttpResponse::Ok().body("Hello logging"))) 16 | }).bind("127.0.0.1:8080")? 17 | .run().await 18 | } 19 | 20 | -------------------------------------------------------------------------------- /src/bin/middleware_session.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{HttpServer, App, HttpResponse, Error, get}; 2 | use actix_session::{Session, CookieSession}; 3 | 4 | 5 | #[actix_web::main] 6 | async fn main() -> std::io::Result<()> { 7 | HttpServer::new(||{ 8 | App::new().wrap(CookieSession::signed(&[0;32]) // 基于 Session 中间件创建一个cookie 9 | .secure(false) 10 | ).service(index) 11 | }).bind("127.0.0.1:8080")? 12 | .run().await 13 | } 14 | 15 | #[get("/cookie")] 16 | async fn index(session: Session) -> Result { 17 | // 访问 session 数据 18 | if let Some(count) = session.get::("counter")? { 19 | session.set("counter", count + 1)?; 20 | } else { 21 | session.set("counter", 1)?; 22 | } 23 | 24 | Ok(HttpResponse::Ok().body( 25 | format!("Counter is : {}", 26 | session.get::("counter")?.unwrap() // get:: 类型必须声明 27 | ))) 28 | } 29 | -------------------------------------------------------------------------------- /src/bin/requests.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{HttpServer, App, post, web, Error, HttpResponse, error}; 2 | use futures::StreamExt; 3 | use serde::{Serialize, Deserialize}; 4 | use serde_json; 5 | 6 | #[actix_web::main] 7 | async fn main() -> std::io::Result<()> { 8 | HttpServer::new(|| { 9 | App::new().service(index_manual) 10 | }).bind("127.0.0.1:8080")? 11 | .run().await 12 | } 13 | 14 | 15 | #[derive(Deserialize, Serialize)] 16 | struct MyObj { 17 | name: String, 18 | number: i32, 19 | } 20 | 21 | const MAX_SIZ: usize = 262144; // 256k 最大playload 22 | 23 | /// 手动反序列化json 到一个 Object中去 24 | #[post("/manual")] 25 | async fn index_manual(mut payload: web::Payload) -> Result { 26 | // payload 是一个字节流 27 | let mut body = web::BytesMut::new(); 28 | while let Some(chunk) = payload.next().await { 29 | let chunk = chunk?; 30 | // 限制内存中 payload 最大大小 31 | if (body.len() + chunk.len()) > MAX_SIZ { 32 | return Err(error::ErrorBadRequest("overflow")); 33 | } 34 | body.extend_from_slice(&chunk); 35 | } 36 | 37 | // body 被导入了,现在我们使用 serde_json 反序列化它 38 | let obj = serde_json::from_slice::(&body)?; 39 | Ok(HttpResponse::Ok().json(obj)) // 返回响应 40 | } 41 | 42 | -------------------------------------------------------------------------------- /src/bin/responses.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{HttpServer, App, web, get, post, middleware, HttpResponse, http::ContentEncoding, Result}; 2 | use actix_web::dev::BodyEncoding; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | #[get("/default")] 6 | async fn index_default() -> HttpResponse { 7 | HttpResponse::Ok() 8 | //.encoding(ContentEncoding::Identity) // 通过这种方式可以禁用内容压缩. 9 | .body("data") 10 | } 11 | 12 | 13 | #[actix_web::main] 14 | async fn main() -> std::io::Result<()> { 15 | HttpServer::new(|| { 16 | App::new() 17 | //包装一个中间件 18 | .wrap(middleware::Compress::default()) // 使用默认压缩方式 19 | //.wrap(middleware::Compress::new(ContentEncoding::Br)) // 这种是全局指定响应的 编码方式,这样就不用在每一个handler函数中处理了. 20 | .service(index_default) 21 | .service(index_br) 22 | .service(index_json) 23 | }).bind("127.0.0.1:8080")? 24 | .run().await 25 | } 26 | 27 | 28 | #[get("/br")] 29 | async fn index_br() -> HttpResponse { 30 | HttpResponse::Ok() 31 | .encoding(ContentEncoding::Br) //通过 encoding() 方法显示指定响应的编码 32 | .body("data") 33 | } 34 | 35 | #[derive(Deserialize, Debug)] 36 | struct MyJsonReq { 37 | name: String, 38 | } 39 | 40 | #[derive(Serialize)] 41 | struct MyJsonResponse { 42 | result: String, 43 | } 44 | 45 | #[post("/json")] 46 | async fn index_json(info: web::Json) -> Result { 47 | // 打印一下info 48 | println!("request: {:?}", info); 49 | let name:String = info.into_inner().name; 50 | let resp = MyJsonResponse { result: name }; 51 | Ok(HttpResponse::Ok().json(resp)) 52 | // 注意使用Json提取器的时候 header中的 Content-Type 要为 application/json 这相当为handler 添加了个 guard 53 | } 54 | -------------------------------------------------------------------------------- /src/bin/server.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{web, App, HttpResponse, HttpServer, rt::System}; 2 | use std::sync::mpsc; 3 | use std::thread; 4 | 5 | /// ## HttpServer 6 | /// HttpServer 负责处理Http请求. 7 | /// 8 | /// HttpServer 接收一个应用程序工厂作为一个参数,且应用程序工厂必须有 Sync + Send 边界. 9 | /// 会在多线程章节解释这一点. 10 | /// 11 | /// 使用 bind() 方法来绑定一个指定的Socket地址,它可以被多次调用. 使用bind_openssl()或者bind_rustls()方法绑定 12 | /// ssl Socket地址. 使用HttpServer::run()方法来运行一个Http 服务. 13 | /// 14 | /// run()方法返回一个server类型的实例, server中的方法可以被用来管理HTTP服务器. 15 | /// * pause() - 暂停接收进来的链接. 16 | /// * resume() - 继续接收进来的链接. 17 | /// * stop() - 停止接收进来的链接,且停止所有有worker线程后退出. 18 | /// 下面的例子展示了如果在单独的线程中启动HTTP服务. 19 | /// 20 | /// ## 多线程 21 | /// HttpServer 自动启动一些 Http Workers(工作线程), 它的默认值是系统cpu的逻辑个数. 这个值你可以通过 HttpServer::workers() 22 | /// 方法来自定义并覆盖. 23 | /// 24 | /// 一旦workers被创建,它们每个都接收一个单独的应用程序实例来处理请求.应用程序State不能在这些workers线程之间共享,且处理程序可以自由操作状态副本,而无需担心并发问题. 25 | /// 26 | /// 应用程序State不需要Send或者Sync,但是应用程序工厂必须要是Send + Sync (因为它需要在不同的线程中共享与传递). 27 | /// 28 | /// 为了在worker 线程之间共享State, 可以使用Arc. 引入共享与同步后,应该格外的小心, 在许多情况下由于锁定共享状态而无意中造成了"性能成本". 29 | /// 30 | /// 在某些情况下,可以使用更加有效的锁策略来减少这种"情能成本",举个例子,可以使用读写锁(read/write locks)来代替排它锁(mutex)来实现互斥性, 31 | /// 但是性能最高的情况下,还是不要使用任何锁。 32 | /// 33 | /// 因为每一个worker线程是安顺序处理请求的,所以处理程序阻塞当前线程,会并停止处理新的请求. 34 | /// ```rust 35 | /// fn my_handler() -> impl Responder { 36 | /// std::thread::sleep(Duration::from_secs(5)); // 糟糕的实践方式,这样会导致当前worker线程停止处理新的请求.并挂起当前线程 37 | /// "response" 38 | /// } 39 | /// ``` 40 | /// 因此,任何长时间的或者非cpu绑定操作(比如:I/O,数据库操作等),都应该使用future或异步方法来处理. 41 | /// 42 | /// 异步处理程序由工作线程(worker)并发执行,因此不会阻塞当前线程的执行.例如下面的使用示例: 43 | /// ```rust 44 | /// fn my_handler() -> impl Responder { 45 | /// tokio::time::delay_for(Duration::from_secs(5)).await; // 这种没问题,工作线程将继续处理其它请求. 46 | /// } 47 | /// ``` 48 | /// 上面说的这种限制同样也存在于提取器(extractor)中. 当一个handler函数在接收一个实现了 FromRequest 的参数时,并且这个实现 49 | /// 如果阻塞了当前线程,那么worker线程也会在运行时阻塞. 50 | /// 51 | /// 因此,在实现提取器时必须特别注意,在需要的时候要异步实现它们. 52 | /// 53 | /// ## SSL 54 | /// 有两种方式来实现ssl的server. 一个是rustls一个是openssl. 在Cargo.toml文件中加入如下依赖: 55 | /// ``` rust 56 | /// [dependencies] 57 | /// actix-web = { version = "3", features = ["openssl"] } 58 | /// openssl = {version = "0.10"} 59 | /// ``` 60 | /// ```rust 61 | /// #[get("/")] 62 | /// async fn index(_req: HttpRequest) -> impl Responder { 63 | /// "Welcome" 64 | /// } 65 | /// #[actix_web::main] 66 | /// async fn main() -> std::io::Result<()> { 67 | /// // 载入ssl key 68 | /// // 为了测试可以创建自签名的证书 69 | /// // 'openssl req -x509 -newkey ras:4096 -nodes -keyout key.pem -out cert.pem -days 365 -subj '/CN=localhost' ' 70 | /// let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); 71 | /// builder 72 | /// .set_private_key_file("key.pem", SslFiletype::PEM).unwrap(); 73 | /// builder.set_certificate_chain_file("cert.pem").unwrap(); 74 | /// 75 | /// HttpServer::new(||{ 76 | /// App::new().service(index) 77 | /// }).bind_openssl("127.0.0.1:8080")? 78 | /// .run().await 79 | /// } 80 | /// ``` 81 | /// **注意:** HTTP2.0需要[tls alpn](https://tools.ietf.org/html/rfc7301)支持,目前仅仅只有openssl有alpn支持. 82 | /// 更多的示例可以参考[examples/openssl](https://github.com/actix/examples/blob/master/openssl) 83 | /// 84 | /// 为了创建生成key.pem与cert.pem,可以使用如下示例命令. **其它需要修改的地方请填写自己的主题** 85 | /// ```shell 86 | /// openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -sha256 -subj "/C=CN/ST=Fujian/L=Xiamen/O=TVlinux/OU=Org/CN=muro.lxd" 87 | /// ``` 88 | /// 要删除密码,然后复制 nopass.pem到 key.pem 89 | /// ```shell 90 | /// openssl rsa -in key.pem -out nopass.pem 91 | /// ``` 92 | #[actix_web::main] 93 | async fn main() { 94 | // 声明一个 多生产者单消费者的channel 95 | let (tx, rx) = mpsc::channel(); 96 | thread::spawn(move || { 97 | let sys = System::new("http-server"); 98 | let server = HttpServer::new(|| { 99 | App::new().service( 100 | web::scope("/app") 101 | .route("/test", web::get().to(|| HttpResponse::Ok().body("Ok"))) 102 | ) 103 | }).workers(4) // 自定义workers数量 104 | .bind("127.0.0.1:8080")? 105 | .shutdown_timeout(60)// 设置shutdown 时间为60秒 106 | .run(); 107 | let _ = tx.send(server); 108 | println!("New Http Server Started op Port 8080"); 109 | sys.run() // 会启动一个 event loop 服务直到 stop()方法被调用 110 | }); 111 | let serv = rx.recv().unwrap(); 112 | //暂停接收新的链接 113 | serv.pause().await; 114 | // 继续接收新的链接 115 | serv.resume().await; 116 | // 停止服务 117 | serv.stop(true).await; 118 | println!("Http Server has been Stopped"); 119 | } 120 | -------------------------------------------------------------------------------- /src/bin/server_graceful_shutdown.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{get,App, HttpServer}; 2 | 3 | /// ## Graceful shutdown 4 | /// HttpServer 支持优雅关机. 在接收到停机信号后,worker线程有一定的时间来完成请求. 超过时间后的所有worker都会被强制drop掉. 5 | /// 默认的shutdown 超时时间设置为30秒. 你也可以使用 HttpServer::shutdown_timeout() 方法来改变这个时间. 6 | /// 7 | /// 您可以使用服务器地址向服务器发送停止消息,并指定是否要正常关机, server的start()方法可以返回一个地址. 8 | /// 9 | /// HttpServer 处理几种OS信号. ctrl-c 在所有操作系统上都适用(表示优雅关机), 也可以在其它类unix系统上使用如下命令: 10 | /// * SIGINT - 强制关闭worker 11 | /// * SIGTERM - 优雅关闭worker 12 | /// * SIGQUIT - 强制关闭worker 13 | /// 另外也可以使用 HttpServer::disable_signals()方法来禁用信号处理 14 | #[actix_web::main] 15 | async fn main() -> std::io::Result<()> { 16 | HttpServer::new(|| App::new().service(index)) 17 | .shutdown_timeout(60)// 设置关闭时间为60秒 超时后强制关闭worker 18 | .bind("127.0.0.1:8080")? 19 | .run().await 20 | } 21 | 22 | #[get("/index")] 23 | async fn index() -> String { 24 | "Rust Graceful Shutdown Demo".to_string() 25 | } -------------------------------------------------------------------------------- /src/bin/server_keepalive.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{web, App, HttpServer, HttpResponse}; 2 | 3 | /// ## Keep-Alive 4 | /// Actix 可以在keep-alive 链接上等待请求. 5 | /// 6 | /// keep alive 链接行为被 server设置定义. 7 | /// * 75, Some(75), KeepAlive::Timeout(75) - 开启keep alive 保活时间 8 | /// * None or KeepAlive::Disable - 关闭keep alive设置 9 | /// * KeepAlive::Tcp(75) - 使用 tcp socket SO_KEEPALIVE 设置选项 10 | /// 如果第一下选项被选择,那么keep alive 状态将会根据响应的connection-type类型来计算.默认的 HttpResponse::connection_type没有被定义 11 | /// 这种情况下会根据 http的版本来默认是否开启keep alive 12 | /// 13 | /// keep alive 在 HTTP/1.0是默认关闭的,在HTTP/1.1和HTTP/2.0是默认开启的. 14 | /// 15 | /// 链接类型可以使用 HttpResponseBuilder::connection_type() 方法来改变. 16 | /// ```rust 17 | /// use actix_web::{http, HttpRequest, HttpResponse}; 18 | /// async fn index(req: HttpRequest) -> HttpResponse { 19 | /// HttpResponse::Ok().connection_type(http::ConnectionType::Close) // 关闭链接 20 | /// .force_close() // 这种写法与上面那种写法二选一 21 | /// .finish() 22 | /// } 23 | /// ``` 24 | #[actix_web::main] 25 | async fn main() -> std::io::Result<()> { 26 | let one = HttpServer::new(||{ 27 | App::new().route("/", web::get().to(|| HttpResponse::Ok().body("Ok"))) 28 | }).keep_alive(75); // 设置keep alive 时间为75秒 29 | // let _two = HttpServer::new(||{ 30 | // App::new().route("/", web::get().to(|| HttpResponse::Ok().body("Ok"))) 31 | // }).keep_alive(); // 使用"SO_KEEPALIVE" socket 选项 32 | 33 | let _three = HttpServer::new(||{ 34 | App::new().route("/", web::get().to(|| HttpResponse::Ok().body("Ok"))) 35 | }).keep_alive(None); // 关闭keep alive 36 | 37 | one.bind("127.0.0.1:8080")?.run().await 38 | } 39 | 40 | -------------------------------------------------------------------------------- /src/bin/static_file.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{HttpServer, App, get, HttpRequest, Result}; 2 | use actix_files::NamedFile; 3 | use std::path::PathBuf; 4 | 5 | #[actix_web::main] 6 | async fn main() -> std::io::Result<()> { 7 | HttpServer::new(||{ 8 | App::new().service(get_file_by_name) 9 | // 使用.service()方法注册一个目录,并调用show_files_listing方法列出所有文件清单 10 | // show_files_listing() 返回的是一个 html格式 的response 且 response header中 content-type: text/html, 11 | .service(actix_files::Files::new("/getDir", "D://testDir").show_files_listing()) 12 | }).bind("127.0.0.1:8080")? 13 | .run().await 14 | } 15 | 16 | 17 | /// 通过一个指定的文件名获取一个文件 18 | /// filename: 必须是一个文件的绝对路径比如在 windows上 D://a.txt 19 | #[get("/getFile/{filename:.*}")] // 使用正则表达式 .* 表示任意扩展名的文件 20 | async fn get_file_by_name(req: HttpRequest) -> Result { 21 | // 得到一个PathBuf 它是一个mut 的path 22 | let path: PathBuf = req.match_info().query("filename").parse().unwrap(); 23 | 24 | let file = NamedFile::open(path)?; 25 | Ok(file) // 返回文件的内容 26 | } 27 | 28 | -------------------------------------------------------------------------------- /src/bin/url_dispatch_scoping.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{HttpServer, App, HttpResponse, web, get, HttpRequest, http, middleware}; 2 | use serde::Deserialize; 3 | use actix_web::guard::Guard; 4 | use actix_web::dev::RequestHead; 5 | 6 | #[actix_web::main] 7 | async fn main() -> std::io::Result<()> { 8 | std::env::set_var("RUST_LOG", "my_errors=debug,actix_web=debug"); // 这里需要将actix_web的日志级别设置为debug 9 | std::env::set_var("RUST_BACKTRACE", "1"); 10 | env_logger::init(); 11 | 12 | HttpServer::new(|| { 13 | App::new().service( 14 | web::scope("/users") 15 | // 路径规范化默认情况下会,总是在path尾部添加一个 / 16 | // 这意味着不管是使用声明式宏,还是手动.route()方式注册的 path都要以 / 结尾 17 | // 否则将不能访问, 但Client 请求path /user/show/ 或 /user/show 都可以 18 | // 甚至你的 path = /users//show/// 都能正常访问, 这就是NormalizePath的优点 19 | .wrap(middleware::NormalizePath::default()) 20 | .guard(ContentTypeHeader) 21 | // .guard(guard::Not(ContentTypeHeader)) // 这一句会反转guard 含义,表示所有带 Content-Type 的请求都不能过. 22 | .service(show_users) 23 | .service(user_detail) 24 | .service(get_matches) 25 | .service(get_username) 26 | ).service(external_resource) 27 | .external_resource("youtube", "https://youtube.com/watch/{video_id}") 28 | }).bind("127.0.0.1:8080")? 29 | .run().await 30 | } 31 | 32 | 33 | #[get("/show/")] 34 | async fn show_users() -> HttpResponse { 35 | HttpResponse::Ok().body("show_users") 36 | } 37 | 38 | #[get("/show/{id}/")] 39 | async fn user_detail(path: web::Path<(u32, )>) -> HttpResponse { 40 | HttpResponse::Ok().body(format!("User detail: {}", path.into_inner().0)) 41 | } 42 | 43 | #[get("/matcher/{v1}/{v2}/")] 44 | async fn get_matches(req: HttpRequest) -> String { 45 | // 直接根据替换表达式名获取一个值 46 | let v1:u8 = req.match_info().get("v1").unwrap().parse().unwrap(); 47 | 48 | let v2: String = req.match_info().query("v2").parse().unwrap(); 49 | 50 | // 还可以使用 元组的模式匹配 51 | let (v3, v4): (u8, String) = req.match_info().load().unwrap(); 52 | 53 | format!("Values {}, {}, {}, {}", v1, v2, v3, v4) 54 | } 55 | 56 | #[derive(Debug, Deserialize)] 57 | struct Info { 58 | username: String, 59 | } 60 | 61 | #[get("/{username}/index.html/")] 62 | async fn get_username(data: web::Path) -> String { 63 | format!("{}", data.username) 64 | } 65 | 66 | #[get("/external")] 67 | async fn external_resource(req: HttpRequest) -> HttpResponse { 68 | let url = req.url_for("youtube", &["oHg5SJYRHA0"]).unwrap(); 69 | 70 | assert_eq!(url.as_str(),"https://youtube.com/watch/oHg5SJYRHA0"); 71 | 72 | // 手动修改一下header中的内容 73 | HttpResponse::Ok().header("Content-Type","text/plain").body(url.into_string()) 74 | } 75 | 76 | struct ContentTypeHeader; 77 | 78 | impl Guard for ContentTypeHeader { 79 | fn check(&self, request: &RequestHead) -> bool { 80 | request.headers().contains_key(http::header::CONTENT_TYPE) 81 | } 82 | } 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /src/bin/websocket_echo.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{HttpServer, web, App, Error, HttpRequest, HttpResponse}; 2 | use actix::{Actor, StreamHandler}; 3 | use actix_web_actors::ws; 4 | use actix_web_actors::ws::{Message, ProtocolError}; 5 | 6 | 7 | /// 定义一个 HTTP actor 8 | struct MyWs; 9 | 10 | impl Actor for MyWs { 11 | type Context = ws::WebsocketContext; 12 | } 13 | 14 | impl StreamHandler> for MyWs { 15 | fn handle(&mut self, msg: Result, ctx: &mut Self::Context) { 16 | match msg{ 17 | Ok(ws::Message::Ping(msg)) => ctx.pong(&msg), 18 | Ok(ws::Message::Text(text)) => ctx.text(text), 19 | Ok(ws::Message::Binary(bin)) => ctx.binary(bin), 20 | _ => () 21 | } 22 | } 23 | } 24 | 25 | async fn index(req: HttpRequest, stream: web::Payload) -> Result { 26 | let resp = ws::start(MyWs {}, &req, stream); 27 | println!("{:?}", resp); 28 | resp 29 | } 30 | 31 | #[actix_web::main] 32 | async fn main() -> std::io::Result<()> { 33 | HttpServer::new(||{ 34 | App::new().route("/ws/", web::get().to(index)) 35 | }).bind("127.0.0.1:8080")? 36 | .run().await 37 | } 38 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Welcome to actix-web 3.0 demo"); 3 | } 4 | --------------------------------------------------------------------------------