Actix Web Official
# Introduction
# Welcome
Actix Web lets you quickly and confidently develop web services in Rust and this guide will get you going in no time.
The documentation on this website focuses primarily on the Actix Web framework. For information about the actor framework called Actix, check out the Actix chapter (or the lower level actix API docs). Otherwise, head on to the getting started guide. If you already know your way around and you need specific information you might want to read the Actix Web API docs.
# What is Actix Web
Long ago, Actix Web was built on top of the actix actor framework. Now, Actix Web is largely unrelated to the actor framework and is built using a different system. Though actix is still maintained, its usefulness as a general tool is diminishing as the futures and async/await ecosystem matures. At this time, the use of actix is only required for WebSocket endpoints.
We call Actix Web a powerful and pragmatic framework. For all intents and purposes, it's a micro-framework with a few twists. If you are already a Rust programmer, you will probably find yourself at home quickly. But even if you are coming from another programming language, you should find Actix Web easy to pick up.
An application developed with Actix Web will expose an HTTP server contained within a native executable. You can put this behind another HTTP server like nginx or serve it up as-is. Even in the complete absence of another HTTP server, Actix Web is powerful enough to privide HTTP/1 and HTTP/2 support as well as TLS(HTTPS). This makes it useful for building small services ready for production.
Most importantly: Actix Web runs on Rust 1.72 or later and it works with stable releases.
# Basics
# Getting Started
# Installing Rust
If you don't have Rust yet, we recommend you use rustup to manage your Rust installation. The official rust guide has a wonderful section on getting started.
Actix Web currently has a minimum supported Rust version (MSRV) of 1.72. Running rustup update will ensure you have the latest and greatest Rust version available, this guide assumes you are running Rust 1.72 or later.
# Hello, world
Start by creating a new binary-based Cargo project and changing into the new directory:
cargo new actix-web-train
cd actix-web-train
2
Add actix-web as a dependency of your project by adding the following to your Cargo.toml file.
[package]
name = "actix-web-train"
version = "0.1.0"
edition = "2024"
[dependencies]
actix-web = "4.4.0"
2
3
4
5
6
7
Request handlers use async functions that accept zero or more parameters. These parameters can be extracted from a request (see FromRequest trait), and the function returns a type that can be converted into an HttpResponse (see Responder trait):
Replace the contents of src/main.rs with the following:
use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder};
#[get("/")]
async fn index() -> impl Responder {
HttpResponse::Ok().body("Hello, world!")
}
#[post("/echo")]
async fn echo(req_body: String) -> impl Responder {
HttpResponse::Ok().body(req_body)
}
async fn manual_hello() -> impl Responder {
HttpResponse::Ok().body("Hey there!")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(index)
.service(echo)
.route("/manual", web::get().to(manual_hello))
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Notice that some of these handlers have routing information attached directly using the built-in macros. These allow you to specify the method and path that the handler should respond to. You will see below how to register manual_hello (i.e. routes that do not use a routing macro).
Next, create an App instance and register the request handlers. Use App::service for the handlers using routing macros and App::route for manually routed handlers, declaring the path and method. Finally, the app is started inside an HttpServer which will serve incoming requests using your App as an "application factory".
That's it! Compile and run the program with cargo run. The #[actix_web::main] macro executes the async main function within the actix runtime. Now you can go to http://127.0.0.1:8080/ or any of the other routes you defined to see the results.
# Application
actix-web provides various primitives to build web servers and applications with Rust. It provides routing, middleware, preprocessing of requests, post-processing of responses, etc.
All actix-web servers are built around the App instance. It is used for registering routes for resources and middleware. It also stores application state shared across all handlers within the same scope.
An application's scope acts as a namespace for all routes, i.e. all routes for a specific application scope have the same url path prefix. The application prefix always contains a leading "/" slash. If a supplied prefix does not contain leading slash, it is automatically inserted. The prefix should consist of value path segments.
For an application with scope
/app, any request with the paths/app,/app/, or/app/testwould match; however, the path/applicationwould not match.
use actix_web::{web, App, HttpServer, Responder};
async fn index() -> impl Responder {
"Hello, world!"
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new().service(
// prefixes all resources and routes attached to it ...
web::scope("/app")
// ... so this handlers requests for `GET /app/index.html`
.route("/index.html", web::get().to(index)),
)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
In this example, an application with the /app prefix and an index.html resource is created. This resource is available through the /app/index.html url.
For more information, check the URL Dispatch section.
# State
Application state is shared with all routes and resources within the same scope. State can be accessed with the web::Data<T> extractor where T is the type of the state. State is also accessible for middleware.
Let's write a simple appliation and store the application name in the state.
Next, pass in the state when initializing the App and start the application.
Any number of state types could be registered within the application.
use actix_web::{get, web, App, HttpServer};
// This struct represents state
struct AppState {
app_name: String,
}
#[get("/")]
async fn index(data: web::Data<AppState>) -> String {
let app_name = &data.app_name; // <- get app_name
format!("Hello {app_name}!") // <- response with app_name
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.app_data(web::Data::new(AppState {
app_name: String::from("Actix Web"),
}))
.service(index)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# Shared Mutable State
HttpServer accepts an application factory rather than an application instance. An HttpServer constructs an application instance for each thread. Therefore, application data must be constructed multiple times. If you want to share data between different threads, a sharable object should be used, e.g., Send + Sync.
Internally, web::Data uses Arc. So in order to avoid creating two Arcs, we should create our Data before registering it using App::app_data().
In the following example, we will write an application with mutable, shared state. First, we define our state and create our handler, and register the data in an App, Key takeaways:
- State initialized inside the closure passed to
HttpServer::newis local to the worker thread and may become de-synced if modified. - To achieve globally shared state, it must be created outside of the closure passed to
HttpServer::newand moved/cloned in.
use actix_web::{web, App, HttpServer};
use std::sync::Mutex;
struct AppStateWithCounter {
counter: Mutex<i32>, // <- Mutex is necessary to mutate safely across threads
}
async fn index(data: web::Data<AppStateWithCounter>) -> String {
let mut counter = data.counter.lock().unwrap(); // <- get counter's MutexGuard
*counter += 1; // <- access counter inside MutexGuard
format!("Request number: {counter}") // <- response with counter
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
// Note: web::Data created _outside_ HttpServer::new closure
let counter = web::Data::new(AppStateWithCounter {
counter: Mutex::new(0),
});
HttpServer::new(move || {
// move counter into the closure
App::new()
.app_data(counter.clone()) // <- register the created data
.route("/", web::get().to(index))
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# Using an Application Scope to Compose Applications
The web::scope() method allows setting a resource group prefix. This scope represents a resource prefix that will be prepended to all resource patterns added by the resource configuration. This can be used to help mount a set of routes at a different location than the original author intended while still maintaining the same resource names.
For example:
#[actix_web::main]
async fn main() {
let scope = web::scope("/users").service(show_users);
App::new().service(scope);
}
2
3
4
5
In the above example, the show_users route will have an effective route pattern of /users/show instead of /show because the application's scope argument will be prepended to the pattern. The route will then only match if the URL path is /users/show, and when the HttpRequest.url_for() function is called with the route name show_users, it will generate a URL with that same path.
提示
App::service()不能直接接收Rust的"模块"(module), 因为模块是编译时的代码组织概念, 而service()需要的是实现了HttpServiceFactory trait的运行时对象. 但是, Actix Web提供了两种非常成熟的方式来实现"模块化配置":
- 使用
web::scope(): 将一组服务打包, 放入service()中(对应"Using an Application Scope"). - 使用
App::configure(): 这是最推荐的模块化方式, 专门用于将路由配置分散到不同的文件中.
以下是具体操作方法:
方法一: 使用App::configure()(推荐, 真正的模块化), 这是大型项目的标准做法, 可以在不同的文件(模块)中定义路由, 然后在main.rs中注册.
- 创建模块文件
src/user.rs
// src/user.rs
use actix_web::{get, web, HttpResponse, Responder};
// 定义处理函数
#[get("/{id}")]
async fn get_user(path: web::Path<u32>) -> impl Responder {
HttpResponse::Ok().body(format!("User ID: {}", path))
}
// 关键步骤: 定义一个配置函数
// 这个函数接收一个 ServiceConfig 的可变引用
pub fn config(cfg: &mut web::ServiceConfig) {
cfg.service(
web::scope("/users") // 在这里定义同一前缀
.service(get_user) // 注册上面的函数
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- 在
src/main.rs中注册模块
// src/main.rs
use actix_web::{App, HttpServer};
mod users; // 引入模块
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
// 使用 .configure() 来加载模块中的配置
.configure(users::config)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
方法二: 在service()中嵌套web::scope()
如果你不想分文件, 只是想在逻辑上分组, service()可以接收web::scope()对象. Scope可以包含多个函数.
use actix_web::{get, web, App, HttpServer, HttpResponse, Responder};
#[get("/list")]
async fn list_users() -> impl Responder {
HttpResponse::Ok().body("List of users")
}
#[get("/detail")]
async fn user_detail() -> impl Responder {
HttpResponse::Ok().body("User detail")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
// service() 里面放 Scope
// scope 里面再放具体的 service (函数)
.service(
web::scope("/api")
.service(list_users) // 访问路径: /api/list
.service(user_details) // 访问路径: /api/detail
)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# Application guards and virtual hosting
You can think of a guard as a simple function that accepts a request object reference and returns true or false. Formally, a guard is any object that implements the Guard trait. Actix Web provides serveral guards. You can check the functions section of the API docs.
One of the provided guards is Host. It can be used as a filter based on request header information.
use actix_web::{App, HttpResponse, HttpServer, guard, web};
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(
web::scope("/")
.guard(guard::Host("www.rust-lang.org"))
.route("", web::to(|| async { HttpResponse::Ok().body("www") })),
)
.service(
web::scope("/")
.guard(guard::Host("users.rust-lang.org"))
.route("", web::to(|| async { HttpResponse::Ok().body("users") })),
)
.route("/", web::to(HttpResponse::Ok))
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Configure
For simplicit and reusability both App and web::Scope provide the configure method. This function is useful for moving parts of the configuration to a different module or even library. For example, some of the resource's configuration could be moved to a different module.
use actix_web::{web, App, HttpResponse, HttpServer};
// this function could be located in a different module
fn scoped_config(cfg: &mut web::ServiceConfig) {
cfg.service(
web::resource("/test")
.route(web::get().to(|| async { HttpResponse::Ok().body("test") }))
.route(web::head().to(HttpResponse::MethodNotAllowed)),
);
}
// this function could be located in a different module
fn config(cfg: &mut web::ServiceConfig) {
cfg.service(
web::resource("/app")
.route(web::get().to(|| async { HttpResponse::Ok().body("app") }))
.route(web::head().to(HttpResponse::MethodNotAllowed)),
);
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.configure(config)
.service(web::scope("/api").configure(scoped_config))
.route(
"/",
web::get().to(|| async { HttpResponse::Ok().body("/") }),
)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
The result of the above example would be:
/ -> "/"
/app -> "app"
/app/test -> "test"
2
3
Each ServiceConfig can have its own data, routes, and services.
分模块, 目录结构使用`api/user`, `api/role`分, 还是用`user/api`, `role/api`分合适, 为什么?
在Rust和Actix-web项目中, 通常推荐按业务领域/功能划分(Domain/Feature-based), 即使用user/api, role/api这种结构. 以下是两种方式的对比和推荐理由:
- 按业务领域划分(推荐), 结构示例:
src/
user/
mod.rs
api.rs (或 routes.rs)
model.rs
service.rs
role/
mod.rs
api.rs
model.rs
2
3
4
5
6
7
8
9
10
为什么合适:
- 高内聚(High Cohesion): 所有关于"用户"的代码(路由, 数据库模型, 业务逻辑)都在一个文件夹里, 当你需要修改用户相关的功能时, 不需要在
api和model文件夹之间来回跳转. - 可扩展性: 如果项目变大, 这种结构更容易拆分成独立的Crate(库)或者微服务.
- 清晰的边界: 模块之间的依赖关系更清晰,
user模块只暴露必要的接口给外部.
- 技术层级划分, 结构示例:
src/
api/
mod.rs
user.rs
role.rs
model/
mod.rs
user.rs
role.rs
2
3
4
5
6
7
8
9
为什么(通常)不推荐用于中大型项目:
- 低内聚: 为了实现一个"用户注册"功能, 需要在
api/user.rs写控制器, 去model/user.rs写结构体, 代码在物理上是分离的. - 文件膨胀: 随着API越来越多,
api文件夹会变得非常杂乱.
那么按业务领域分使用`mod.rs`还是使用新的模块定义写法呢?
推荐使用新写法(Rust 2018 edition style). 为什么?
主要原因是开发体验. 如果你使用mod.rs, 当你在VSCode中同时打开user模块和role模块的入口文件时, 你的标签页会显示两个mod.rs, 很难区分. 而使用新写法, 标签页会显示user.rs和role.rs, 非常清晰. 目录结构对比:
推荐结构(新写法):
src/
main.rs
user.rs <-- user 模块的声明文件 (替代 user/mod.rs)
user/ <-- user 模块的子文件目录
api.rs
model.rs
role.rs
role/
api.rs
2
3
4
5
6
7
8
9
代码修改, 首先, 修改main.rs引入业务模块:
use actix_web::{App, HttpServer};
// 移除旧的层级划分
// mod model;
// mod api;
// 引入业务模块
mod user;
mod role;
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| App::new())
.bind(("127.0.0.1", 8080))?
.run()
.await
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
然后, 你需要创建对应的模块文件.
- 创建
src/user.rs(模块入口):
pub mod api;
pub mod model;
2
- 创建
src/user/api.rs(具体逻辑):
use actix_web::{get, HttpResponse, Responder};
#[get("/user")]
pub async fn get_user() -> impl Responder {
HttpResponse::Ok().body("User API")
}
2
3
4
5
6
那这样的话user和role里面不一样会有`api.rs`, `model.rs`等重名文件吗?
虽然使用新写法避免了mod.rs的地狱, 但如果每个模块内部都叫api.rs和model.rs, 在编辑器标签页里确实还是会出现重名的情况(例如两个api.rs). 为了彻底解决这个问题, 在Rust社区中通常由以下两种处理习惯:
方案一: 接受api.rs重名(最常见), 大多数开发者接受这一点, 因为现代编辑器(如VSCode)通常会在重名文件旁显示父目录名(例如user/api.rs和role/api.rs).
- 优点: 文件名短, 结构统一, 约定俗成.
- 缺点: 标签页确实会有重名.
方案二: 在文件名中带上模块前缀(最清晰), 如果非常介意标签页重名, 可以在子模块文件名中重复父模块的名字.
src/
user.rs
user/
user_api.rs <-- 替代 api.rs
user_model.rs <-- 替代 model.rs
role.rs
role/
role_api.rs
2
3
4
5
6
7
8
对应的src/user.rs写法:
pub mod user_api;
pub mod user_model;
2
use actix_web::{App, HttpServer};
use actix_web_train::user::user_api;
use actix_web_train::role::role_api;
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.configure(user_api::config)
.configure(role_api::config)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
这个`main.rs`里面有没有可以优化的地方?
从工程化和最佳实践的角度来看, 有几个值得优化的地方:
- 添加日志中间件(Logger Middleware): 在开发Web服务时, 看到请求日志(状态码, 耗时, 路径)是分厂重要的. Actix-web提供了内置的Logger.
- 使用
web::scope统一API前缀: 通常API不会直接挂载在根路径/下, 而是挂载在/api或/api/v1下, 这样方便前端调用, 也方便后续做版本控制. - 避免硬编码端口: 虽然现在写死
8080没问题, 但建议习惯性地把地址提取出来, 或者至少准备好从环境变量获取.
优化后的代码建议: 需要在Cargo.toml中添加env_logger依赖:
[package]
name = "actix-web-train"
version = "0.1.0"
edition = "2024"
[dependencies]
actix-web = "4.4.0"
env_logger = "0.11.8"
2
3
4
5
6
7
8
然后修改main.rs;
use actix_web::{middleware, web, App, HttpServer};
use std::env;
use crate::role::role_api;
use crete::user::user_api;
mod user;
mod role;
#[actix_web::main]
async fn main() -> std::io::Result<()> {
// 1. 初始化日志 (设置默认级别为 info)
env::set_var("RUST_LOG", "actix_web=info");
env_logger::init();
let bind_address = ("127.0.0.1", 8080);
println!("Starting server at: http://{}:{}", bind_address.0, bind_address.1);
HttpServer::new(|| {
App::new()
// 2. 注册日志中间件
.wrap(middleware::Logger::default())
// 3. 使用 scope 统一管理 API 前缀 (例如所有接口都在 /api 下)
.service(
web::scope("/api")
.configure(user_api::config)
.configure(role_api::config)
)
})
.bind(bind_address)?
.run()
.await
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
这样修改的好处:
- 可观测性: 运行后, 每次请求终端都会打印类似
[2025-11-28T11:20:23Z INFO actix_web::middleware::logger] 127.0.0.1 "GET /role/1 HTTP/1.1" 200 10 "-" "Thunder Client (https://www.thunderclient.com)" 0.000180的日志. - 路由整洁: 用户接口变成了
/api/user, 角色接口变成了/api/role, 结构更清晰. - 启动提示: 控制台会明确打印出服务启动的地址, 方便点击访问.
`mod user;` `mod role;` 是写到`main.rs`中, 还是`lib.rs`中?
推荐做法: 写到lib.rs中. 虽然写在main.rs里也能运行, 但对于一个正规的web项目, 将业务逻辑块放在lib.rs是最佳实践.
为什么推荐lib.rs?
- 集成测试(Integration Tests): Rust的集成测试(通常在
tests/目录下)只能引用你的lib.rs(库), 而不能引用main.rs(二进制). 如果你把user和role放在main.rs里, 就无法在tests/目录里编写针对这些模块的测试代码. - 逻辑与启动分离:
main.rs应该只负责"启动"应用(读取配置, 初始化日志, 绑定端口). 具体的业务逻辑, 路由定义应该封装在库(Library)中. - 多二进制支持: 如果未来你想写一个CLI工具来管理用户, 或者有多个入口, 它们都可以复用
lib.rs里的代码, 而不需要复制代码.
如何重构? 需要做两步操作:
- 创建
src/lib.rs并声明模块: 注意要加上pub, 这样main.rs才能访问它们.
pub mod user;
pub mod role;
2
- 修改
main.rs: 在main.rs中, 你不再声明模块(删除mod user;), 而是像使用第三方库一样使用你自己的项目.
注意: 你需要查看你的Cargo.toml中的[package] name字段. 假设你的项目名是actix-web-train, 在代码中引用时会自动变成下划线actix_web_train.
use std::env;
use actix_web::{App, HttpServer, middleware};
use actix_web_train::role::role_api;
use actix_web_train::user::user_api;
#[actix_web::main]
async fn main() -> std::io::Result<()> {
unsafe {
env::set_var("RUST_LOG", "actix_web=info");
}
env_logger::init();
HttpServer::new(|| {
App::new()
.wrap(middleware::Logger::default())
.configure(user_api::config)
.configure(role_api::config)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
遇到问题?
a similar path exists: `actix_web_train::role`rustcE0432
main.rs(4, 12): original diagnostic
unresolved import `crate::role`
unresolved importrustcClick for full compiler diagnostic
main.rs(4, 12): a similar path exists: `actix_web_train::role`
2
3
4
5
这个错误是因为你把mod user; 和 mod role; 移到了lib.rs中. 在Rust中, main.rs(二进制crate)和lib.rs(库crate)是两个独立的编译单元. 当你在main.rs中使用crate::时, 它指的是main.rs自己定义的模块.
既然模块定义在lib.rs里, main.rs就需要像引用第三方库一样引用它们, 需要使用Cargo.toml中的包名(将横杠-替换为下划线_).
扩展
假设我现在要做一个新的项目test_web(Package), 希望使用当前actix_web_train里面的lib.rs定义的Module, 可以用吗? 怎么用?
可以用到, 这正是将代码放入lib.rs的最大优势之一: 你的项目变成了一个库(Library), 可以被其他任何Rust项目引用. 这里有两种主要的使用方式, 取决于你的test_web和actix_web_train的位置关系.
方法一: 本地路径依赖(Path Dependency) 适用场景: 两个项目都在你的本地电脑上, 比如都在train目录下. 假设目录结构如下:
/home/sunyy/Sources/train/
├── actix-web-train/ (现有项目)
└── test_web/ (新项目)
2
3
操作步骤:
- 创建新项目:
cd /home/sunyy/Sources/train/
cargo new test_web
2
- 修改新项目的
Cargo.toml: 在test_web/Cargo.toml中, 通过path指向旧项目路径.
# filepath: /home/sunyy/Sources/train/test_web/Cargo.toml
[package]
name = "test_web"
version = "0.1.0"
edition = "2021"
[dependencies]
# 必须添加 actix-web,因为你要运行它的组件
actix-web = "4"
# 引用你的本地库
# path 可以是相对路径,也可以是绝对路径
actix-web-train = { path = "../actix-web-train" }
2
3
4
5
6
7
8
9
10
11
12
13
- 在新项目中使用代码: 现在可以在
main.rs中直接使用actix_web_train了.
// filepath: /home/sunyy/Sources/train/test_web/src/main.rs
use actix_web::{App, HttpServer};
// 就像引用第三方库一样引用它
use actix_web_train::user::user_api;
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
// 复用旧项目定义的路由配置
.configure(user_api::config)
})
.bind(("127.0.0.1", 8081))? // 注意端口别冲突
.run()
.await
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
方法二: Git依赖(Git Dependency) 适用场景: 你已经把actix-web-train推送到了Gitee或GitHub, 想在另一台电脑或者不依赖本地路径的情况下使用.
操作步骤: 修改test_web/Cargo.toml:
[dependencies]
actix-web = "4"
# 指向你的 Git 仓库地址
actix-web-train = { git = "https://gitee.com/ainsn/actix-web-train.git", branch = "master" }
2
3
4
注意事项:
- 可见性(Visibility): 只有在
actix-web-train的lib.rs中标记为pub的模块, 以及模块内部标记为pub的函数/结构体, 才能被test_web访问.pub mod user;(在lib.rs) -> 必须pub mod user_api;(在user.rs) -> 必须pub fn config(...)(在user_api.rs) -> 必须
- 依赖传递: 如果actix-web-train里的函数使用了
actix_web::web::ServiceConfig等类型,test_web也必须在其Cargo.toml中添加actix-web依赖, 否则编译器会报错.
# Server
The HttpServer type is responsible for serving HTTP requests.
HttpServer accepts an application factory as parameter, and the application factory must have Send + Sync boundaries. More about that in the multi-threading section.
To start the web server it must first be bound to a network socket. Use HttpServer::bind() with a socket address tuple or string such as ("127.0.0.1", 8080) or "0.0.0.0:8080". This will fail if the socket is being used by another application.
After the bind is successful, use HttpServer::run() to return a Server instance. The Server must be awaited or spawned to start processing requests and will run until it receives a shutdown signal (such as, by default, a ctrl-c; read more here).
use actix_web::{web, App, HttpResponse, HttpServer};
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| App::new().route("/", web::get().to(HttpResponse::Ok)))
.bind(("127.0.0.1", 8080))?
.run()
.await
}
2
3
4
5
6
7
8
9
# Multi-Threading
HttpServer automatically starts a number of HTTP workers, by default this number is equal to the number of physical CPUs in the system. This number can be overridden with the HttpServer::workers().
use actix_web::{web, App, HttpResponse, HttpServer};
#[actix_web::main]
async fn main() {
let _ = HttpServer::new(|| App::new().route("/", web::get().to(HttpResponse::Ok))).workers(4);
// <- Start 4 workers
}
2
3
4
5
6
7
Once the workers are created, they each receive a separate application instance to handle requests. Application state is not shared between the threads, and handlers are free to manipulate their copy of the state with no concurrency concerns.
Application state does not need to be Send or Sync, but application factories must be Send + Sync.
To share state between worker threads, use an Arc / Data. Special care should be taken once sharing and synchronization are introduced. In many cases, performance costs are inadvertently introduced as a result of locking the shared state for modifications.
In some cases these costs can be alleviated using more efficient locking strategies, for example using read/write locks instead of mutexes to achieve non-exclusive locking, but the most performant implementations often tend to be ones in which no locking occurs at all.
Since each worker thread processes its requests sequentially, handlers which block the current thread will cause the current worker to stop processing new requests:
fn my_handler() -> impl Responder {
std::thread::sleep(Duration::from_secs(5)); // <-- Bad practice! Will cause the current worker thread to hang!
"response"
}
2
3
4
For this reason, any long, non-cpu-bound operation (e.g. I/O, database operations, etc.) should be expressed as futures or asynchronous functions. Async handlers get executed concurrently by worker threads and thus don't block execution:
async fn my_handler() -> impl Responder {
tokio::time::sleep(Duration::from_secs(5)).await; // <-- Ok. Worker thread will handle other requests here
"response"
}
2
3
4
The same limitation applies to extractors as well. When a handler function receives an argument which implements FromRequest, and that implementation blocks the current thread, the worker thread will block when running the handler. Special attention must be given when implementing extractors for this very reason, and they should also be implemented asynchronously where needed.
# TLS/HTTPS
Actix Web supports two TLS implementations out-of-the-box: rustls and openssl.
The rustls crate feature is for rustls integration and openssl is for openssl integration.
[dependencies]
actix-web = { version = "4", features = ["openssl"] }
openssl = { version = "0.10" }
2
3
use actix_web::{get, App, HttpRequest, HttpServer, Responder};
use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod};
#[get("/")]
async fn index(_req: HttpRequest) -> impl Responder {
"Welcome!"
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
// load TLS keys
// to create a self-signed temporary cert for testing:
// `openssl req -x509 -newkey rsa:4096 -nodes -keyout key.pem -out cert.pem -days 365 -subj '/CN=localhost'`
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
builder
.set_private_key_file("key.pem", SslFiletype::PEM)
.unwrap();
builder.set_certificate_chain_file("cert.pem").unwrap();
HttpServer::new(|| App::new().service(index))
.bind_openssl("127.0.0.1:8080", builder)?
.run()
.await
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
To create the key.pem and cert.pem use the command. Fill in your own subject:
$ 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"
2
To remove the password, then copy nopass.pem to key.pem
openssl rsa -in key.pem -out nopass.pem
# Keep-Alive
Actix Web keeps connections open to wait for subsequent requests.
keep alive connection behavior is defined by server settings.
Duration::from_secs(75)orKeepAlive::Timeout(75): enables 75 second keep-alive timer.KeepAlive::Os: uses OS keep-alive.NoneorKeepAlive::Disabled: disables keep-alive.
use actix_web::{http::KeepAlive, HttpServer};
use std::time::Duration;
#[actix_web::main]
async fn main() -> std::io::Result<()> {
// Set keep-alive to 75 seconds
let _one = HttpServer::new(app).keep_alive(Duration::from_secs(75));
// Use OS's keep-alive (usually quite long)
let _two = HttpServer::new(app).keep_alive(KeepAlive::Os);
// Disable keep-alive
let _three = HttpServer::new(app).keep_alive(None);
Ok(())
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
If the first option above is selected, then keep-alive is enabled for HTTP/1.1 requests if the response does not explicitly disallow it by, for example, setting the connection type to Close or Upgrade. Force closing a connection can be done with the force_close() method on HttpResponseBuilder.
Keep-alive is off for HTTP/1.0 and is on for HTTP/1.1 and HTTP/2.0.
use actix_web::{http, HttpRequest, HttpResponse};
async fn index(_req: HttpRequest) -> HttpResponse {
let mut resp = HttpResponse::Ok()
.force_close() // <- Close connection on HttpResponseBuilder
.finish();
// Alternatively close connection on the HttpResponse struct
resp.head_mut().set_connection_type(http::ConnectionType::Close);
resp
}
2
3
4
5
6
7
8
9
10
11
12
# Graceful shutdown
HttpServer supports graceful shutdown. After receiving a stop signal, workers have a specific amount of time to finish serving requests. Any workers still alive after the timeout are force-dropped. By default the shutdown timeout is set to 30 seconds. You can change this parameter with the HttpServer::shutdown_timeout().
HttpServer handles several OS signals. CTRL-C is available on all OSes, other signals are availabe on unix systems.
- SIGINT - Force shutdown workers
- SIGTERM - Graceful shutdown workers
- SIGQUIT - Force shutdown workers
It is possible to disable signal handling with HttpServer::disable_signals() method.
# Extractors
Actix Web provides a facility for type-safe request information access called extractors (i.e., impl FromRequest). There are lots of built-in extractor implementations (see implementors).
An extractor can be accessed as an argument to a handler function. Actix Web supports up to 12 extractors per handler function.
In most cases, argument position does not matter. However, if an extractor takes the body (i.e., it reads any bytes from the request body stream), then only the first extractor will succeed. If you need fallback behavior such as "read body as JSON or just give me the raw bytes if that fails", then use the Either extractor (e.g., Either<Json<T>, Bytes>).
Some other specific use cases where request bodies need reading twice can be supported:
- For body (any extractor) + it's hash/digest, see the actix-hash crate.
- For body (any extractor) + custom request signature scheme: see the RequestSignature extractor from
actix-web-lab.
Simple example showing extraction of two positional dynamic path segments and a JSON body:
async fn index(path: web::Path<(String, String)>, json: web::Json<MyInfo>) -> impl Responder {
let path = path.into_inner();
format!("{} {} {} {}", path.0, path.1, json.id, json.username)
}
2
3
4
# Path
Path provides information that is extracted from the request's path. Parts of the path that are extractable are called "dynamic segments" and are marked with curly braces. You can deserialize any variable segment from the path.
For instance, for resource that registered for the /user/{user_id}/{friend} path, two segments could be deserialized, user_id and friend. These segments could be extracted as a tuple in the order they are declared (e.g., Path<(u32, String)>).
use actix_web::{get, web, App, HttpServer, Result};
// extract path info from "/users/{user_id}/{friend}" url
// {user_id} - deserializes to a u32
// {friend} - deserializes to a String
#[get("/users/{user_id}/{friend}")] // <- define path parameters
async fn index(path: web::Path<(u32, String)>) -> Result<String> {
let (user_id, friend) = path.into_inner();
Ok(format!("Welcome {}, user_id {}!", friend, user_id))
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| App::new().service(index))
.bind("127.0.0.1", 8080)?
.run()
.await
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
It is also possible to extract path information to a type that implements the Deserialize trait from serde by matching dynamic segment names with field names. Here is an equivalent example that uses a deserialization struct using serde (make sure to enable its derive feature) instead of a tuple type.
use actix_web::{get, web, Result};
use serde::Deserialize;
#[derive(Deserialize)]
struct Info {
user_id: u32,
friend: String,
}
/// extract path info using serde
#[get("/users/{user_id}/{friend}")] // <- define path parameters
async fn index(info: web::Path<Info>) -> Result<String> {
Ok(format!("Welcome {}, user_id {}!", info.friend, info.user_id))
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
use actix_web::{App, HttpServer};
HttpServer::new(|| App::new().service(index))
.bind(("127.0.0.1", 8080))?
.run()
.await
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
As a non-type-safe alternative, it's also possible to query (see match_info docs) the request for path parameters by name within a handler:
#[get("/users/{user_id}/{friend}")] // <- define path parameters
async fn index(req: HttpRequest) -> Result<String> {
let name: String = req.match_info().get("friend").unwrap().parse().unwrap();
let userid: i32 = req.match_info().query("user_id").parse().unwrap();
Ok(format!("Welcome {}, user_id {}!", name, userid))
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
use actix_web::{App, HttpServer};
HttpServer::new(|| App::new().service(index))
.bind(("127.0.0.1", 8080))?
.run()
.await
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Query
The Query<T> type provides extraction functionality for the request's query parameters. Underneath it uses serde_urlencoded crate.
use actix_web::{get, web, App, HttpServer};
use serde::Deserialize;
#[derive(Deserialize)]
struct Info {
username: String,
}
// this handler gets called if the query deserializes into `Info` successfully
// otherwise a 400 Bad Request error response is returned
#[get("/")]
async fn index(info: web::Query<Info>) -> String {
format!("Welcome {}!", info.username)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# JSON
Json<T> allows deserialization of a request body into a struct. To extract typed information from a request's body, the type T must implement serde::Deserialize.
use actix_web::{post, web, App, HttpServer, Result};
use serde::Deserialize;
#[derive(Deserialize)]
struct Info {
username: String,
}
/// deserialize `Info` from request's body
#[post("/submit")]
async fn submit(info: web::Json<Info>) -> Result<String> {
Ok(format!("Welcome {}!", info.username))
}
2
3
4
5
6
7
8
9
10
11
12
13
Some extractors provide a way to configure the extraction process. To configure an extractor, pass its configuration object to the resource's .app_data() method. In the case of Json extractor it returns a JsonConfig. You can configure the maximum size of the JSON payload as well as a custom error handler function.
The following example limits the size of the payload to 4kb and uses a custom error handler.
use actix_web::{error, web, App, HttpResponse, HttpServer, Responder};
use serde::Deserialize;
#[derive(Deserialize)]
struct Info {
username: String,
}
/// deserialize `Info` from request's body, max payload size is 4kb
async fn index(info: web::Json<Info>) -> impl Responder {
format!("Welcome {}!", info.username)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
let json_config = web::JsonConfig::default()
.limit(4096)
.error_handler(|err, _req| {
// create custom error response
error::InternalError::from_response(err, HttpResponse::Conflict().finish())
.info()
});
App::new().service(
web::resource("/")
// change json extractor configuration
.app_data(json_config)
.route(web::post().to(index)),
)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# URL-Encoded Forms
A URL-encoded form body can be extracted to a struct, much like Json<T>. This type must implement serde::Deserialize.
FormConfig allows configuring the extraction process.
use actix_web::{post, web, App, HttpServer, Result};
use serde::Deserialize;
#[derive(Deserialize)]
struct FormData {
username: String,
}
/// extract form data using serde
/// this handle gets called only if the content type is *x-www-form-urlencode*
/// and the content of the rquest could be deserialized to a `FormData` struct
#[post("/")]
async fn index(form: web::Form<FormData>) -> Result<String> {
Ok(format!("Welcome {}!", form.username))
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Other
Actix Web also provides many other extractors, here's a few important ones:
- Data - For accessing pieces of application state.
- HttpRequest -
HttpRequestis itself an extractor, in case you need access to other parts of the request. String- You can convert a request's payload to aString. An example in the rustdoc.- Bytes - You can convert a request's payload into Bytes. An example is available in the rustdoc.
- Payload - Low-level payload extractor primarily for building other extractors. An example is available in the rustdoc.
# Application State Extractor
Application state is accessible from the handler with the web::Data extractor; however, state is accessible as a read-only reference. If you need mutable access to state, it must be implemented.
Here is an example of a handler that stores the number of processed requests:
use actix_web::{web, App, HttpServer, Responder};
use std::cell::Cell;
#[derive(Clone)]
struct AppState {
count: Cell<usize>,
}
async fn show_count(data: web::Data<AppState>) -> impl Responder {
format!("count: {}", data.count.get())
}
async fn add_one(data: web::Data<AppState>) -> impl Responder {
let count = data.count.get();
data.count.set(count + 1);
format!("count: {}", data.count.get())
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let data = AppState {
count: Cell::new(0),
};
HttpServer::new(move || {
App::new()
.app_data(web::Data::new(data.clone()))
.route("/", web::to(show_count))
.route("/add", web::to(add_one))
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Although this handler will work, data.count will only count the number of requests handled by each worker thread. To count the number of total requests across all threads, one should use shared Arc and atomics.
use actix_web::{get, web, App, HttpServer, Responder};
use std::{
cell::Cell,
sync::atomic::{AtomicUsize, Ordering},
sync::Arc,
}
#[derive(Clone)]
struct AppState {
local_count: Cell<usize>,
global_count: Arc<AtomicUsize>,
}
#[get("/")]
async fn show_count(data: web::Data<AppState>) -> impl Responder {
format!(
"global_count: {}\nlocal_count: {}",
data.global_count.load(Ordering::Relaxed),
data.local_count.get()
)
}
#[get("/add")]
async fn add_one(data: web::Data<AppState>) -> impl Responder {
data.global_count.fetch_add(1, Ordering::Relaxed);
let local_count = data.local_count.get();
data.local_count.set(local_count + 1);
format!(
"global_count: {}\nlocal_count: {}",
data.global_count.load(Ordering::Relaxed),
data.local_count.get()
)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let data = AppState {
local_count: Cell::new(0),
global_count: Arc::new(AtomicUsize::new(0)),
};
HttpServer::new(move || {
App::new()
.app_data(web::Data::new(data.clone()))
.service(show_count)
.service(add_one)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
Note: If you want the entire state to be shared across all threads, use web::Data and app_data as described in Shared Mutable State.
Be careful when using blocking synchronization primitives like Mutex or RwLock within your app state. Actix Web handles requests asynchronously. It is a problem if the critical section in your handler is too big or contains an .await point. If this is a concern, we would advise you to also read Tokio's advice on using blocking Mutex in async code.
# Handlers
A request handler is an async function that accepts zero or more parameters that can be extracted from a request (i.e., impl FromRequest) and returns a type that can be converted into an HttpResponse (i.e., impl Responder).
Request handling happens in two stages. First the handler object is called, returning any object that implements the Responder trait. Then, respond_to() is called on the returned object, converting itself to a HttpResponse or Error.
By default Actix Web provides Responder implementations for some standard types, such as &'static str, String, etc.
For a complete list of implementations, check the Responder documentation.
Examples of valid handlers:
async fn index(_req: HttpRequest) -> &'static str {
"Hello world!"
}
2
3
async fn index(_req: HttpRequest) -> String {
"Hello world!".to_owned()
}
2
3
You can also change the signature to return impl Responder which works well if more complex types are involved.
async fn index(_req: HttpRequest) -> impl Responder {
web::Bytes::from_static(b"Hello world!")
}
2
3
async fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
...
}
2
3
# Response with custom type
To return a custom type directly from a handler function, the type needs to implement the Responder trait.
Let's create a response for a custom type that serializes to an application/json response:
use actix_web::{
body::BoxBody, http::header::ContentType, HttpRequest, HttpResponse, Responder,
};
use serde::Serialize;
#[derive(Serialize)]
struct MyObj {
name: &'static str,
}
// Responder
impl Responder for MyObj {
type Body = BoxBody;
fn respond_to(self, _req: &HttpRequest) -> HttpResponse<Self::Body> {
let body = serde_json::to_string(&self).unwrap();
// Create response and set content type
HttpResponse::Ok()
.content_type(ContentType::json())
.body(body)
}
}
async fn index() -> impl Responder {
MyObj { name: "user" }
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# Streaming response body
Response body can be generated asynchronously. In this case, body must implement the stream trait Stream<Item = Result<Bytes, Error>>, i.e.:
use actix_web::{get, web, App, Error, HttpResponse, HttpServer};
use futures::{future::ok, stream::once};
#[get("/stream")]
async fn stream() -> HttpResponse {
let body = once(ok::<_, Error>(web::Bytes::from_static(b"test")));
HttpResponse::Ok()
.content_type("application/json")
.streaming(body)
}
2
3
4
5
6
7
8
9
10
11
use std::time::Duration;
use tokio::time::sleep;
use crate::role::role_model::MyObj;
use actix_web::{Error, HttpResponse, Responder, get, web, web::Bytes};
use futures::{future::ok, stream::once, stream::unfold};
#[get("/{id:\\d+}")]
async fn get_role(path: web::Path<(u32,)>) -> impl Responder {
let role_id = path.into_inner().0;
format!("Role ID: {}", role_id)
}
#[get("/index")]
async fn index() -> impl Responder {
MyObj {
name: "Hello, World!",
}
}
#[get("/stream")]
async fn stream() -> HttpResponse {
let body = once(ok::<_, Error>(web::Bytes::from_static(b"test")));
HttpResponse::Ok()
.content_type("application/json")
.streaming(body)
}
#[get("/stream2")]
async fn stream2() -> HttpResponse {
// 创建一个产生 5 哥数据块的流
let my_stream = unfold(0, |count| async move {
if count >= 5 {
return None; // 结束流
}
// 模拟耗时操作
sleep(Duration::from_secs(1)).await;
let chunk = format!("data:Chunk {}\n\n", count);
let bytes = Bytes::from(chunk);
// 返回(当前块数据, 下一次的状态)
Some((Ok::<Bytes, Error>(bytes), count + 1))
});
HttpResponse::Ok()
.insert_header(("Cache-Control", "no-cache"))
.content_type("text/event-stream")
.streaming(my_stream)
}
pub fn config(cfg: &mut web::ServiceConfig) {
cfg.service(
web::scope("/role")
.service(get_role)
.service(index)
.service(stream)
.service(stream2),
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# Different return types (Either)
Sometimes, you need to return different types of responses. For example, you can error check and return errors, return async responses, or any result that requires two different types.
For this case, the Either type can be used. Either allows combining two different responder types into a single type.
use actix_web::{Either, Error, HttpResponse};
type RegisterResult = Either<HttpResponse, Result<&'static str, Error>>;
async fn index() -> RegisterResult {
if is_a_variant() {
// choose Left variant
Either::Left(HttpResponse::BadRequest().body("Bad data"))
} else {
// choose Right variant
Either::Right(Ok("Hello!"))
}
}
2
3
4
5
6
7
8
9
10
11
12
13