Actix-web 基本使用
Actix-web 是基于Actix actor 异步并发框架以及tokio异步IO系统的web高性能框架,具有类型安全,易于扩展,特性丰富等优势。 是一个轻量且实用的web框架。Actix-web 不仅可以用作微服务部署于nginx等HTTP server之后,而且可以作为单独的HTTP server使用,支持HTTP 1, HTTP 2以及SSL/TSL。 Actix-web 需要在rust 1.39之后运行.
1. APP
1.1 启动
actix-web提供了各种原型构建server以及app。提供了routing,middleware, pre-processing request,post-processing response。一个HttpServer实例接受一个web::app作为参数,server用以绑定端口,运行异步服务,app用以注册资源路由、中间件、存储应用状态(可以被同一个命名域下handlers共享)。应用的scope扮演了所有路由的命名域,在此scope下的路由规则具有相同的资源前缀。
使用actix-web以及actix-rt,在Cargo.toml添加对应依赖.
use actix_web::{web, App, HttpResponse, HttpServer};
#[actix_rt::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(
|| App::new()
.service(web::resource("/").to(|| HttpResponse::Ok())))
.bind("127.0.0.1:59090")?
.run()
.await
}使用await延迟执行,直到Future处于ready状态。
1.2 应用状态
应用状态可以被处于同一个scope下的所有router共享,使用web::Data<T>进行存储, 应用状态对于中间件来讲也是可用的。HttpServer接受一个app 工厂作为参数而不是实例,HttpServer为每一个线程构建一个app实例,因此应用数据一定被多次创建。如果需要在多个线程间共享数据,必须使用共享对象,例如Arc. 内部的Data类型使用了Arc,所以Data可以在app factory外创建,并且使用clone在App::app_data()方法调用中存储。
use std::cell::Cell;
use actix_web::{web, App, HttpResponse, Responder};
struct MyData {
counter: Cell<usize>,
}
async fn index(data: web::Data<MyData>) -> impl Responder {
data.counter.set(data.counter.get() + 1);
HttpResponse::Ok()
}
let app = App::new()
.data(MyData{ counter: Cell::new(0) })
.service(
web::resource("/index.html").route(
web::get().to(index)));App::app_data() 方法可以存放应用全局数据,可以使用HttpRequest::app_data在运行时获取 。
app_data()与data()的区别:
data(): 用于设置应用状态,数据由Data<T>类型包装,可用于同一个命名域下所有route、middleware 使用而且对于应用状态,默认每一个线程都有一份数据拷贝,所以需要同时使用时需要借助
Arc或者原子变量app_data(): 用于存储应用级别的任意数据,也可以用于存储web::Data<T>用于应用状态提取
1.3 命名域
使用web::scope方法设定具有相同前缀的routers。
#[actix_rt::main]
async fn main() {
App::new()
.service(
web::scope("/users")
.route("/show", web::get().to(show_users)));
}1.4 route guard
guard通过设定相应的条件,例如method,headers等,过滤不符合条件的请求。
App::new()
.app_data(data.clone()) // register data
.configure(config)
.service(
web::scope("/api")
// use guard to set route or scope filter
.guard(guard::Header("Token-access", "justatoken"))
.service(index)
.service(sim_obj)
// 由于Scope并没有指定唯一的资源实体,所以需要path参数指定
// 唯一URI,然后指定相应的router
.route("/hello", web::get().to(hello))
.service(name)
)1.5 服务配置
为了提高App以及web::scope的可重用性,提供了configure方法,可以将配置部分移动至不同的模块或者不同的库中.
use actix_web::{web, App, HttpResponse, HttpServer};
// this function could be located in different module
fn scoped_config(cfg: &mut web::ServiceConfig) {
cfg.service(
web::resource("/test")
.route(web::get().to(|| HttpResponse::Ok().body("test")))
.route(web::head().to(|| HttpResponse::MethodNotAllowed())),
);
}
// this function could be located in different module
fn config(cfg: &mut web::ServiceConfig) {
cfg.service(
web::resource("/app")
.route(web::get().to(|| HttpResponse::Ok().body("app")))
.route(web::head().to(|| HttpResponse::MethodNotAllowed())),
);
}
#[actix_rt::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.configure(config)
.service(web::scope("/api").configure(scoped_config))
.route("/", web::get().to(|| HttpResponse::Ok().body("/")))
})
.bind("127.0.0.1:8088")?
.run()
.await
}响应请求结果:
/ -> "/"
/app -> "app"
/api/test -> "test"2. Router
对于app或者web::scope对象配置路由时可以使用route或者service方法:
route方法接受两个参数,第一个指定url路径,第二个指定了一个Router对象,Router可以使用method方法指定请求的方法,使用to方法绑定到具体的handler。为了方便使用可以直接用web::get, web::post, web::patch等方法。其内部使用了Router::method(actix_http::Method::Get)等。
use actix_web::{web, App, HttpResponse};
let app = App::new().service(
web::resource("/{project_id}")
.route(web::get().to(|| HttpResponse::Ok()))
);也可以使用pub fn service<T: IntoPattern>(path: T) -> WebService: 配合handler宏调用定义路由。
#[post("/users")]
async fn create_users(info: web::Json<User>) -> impl Responder {
info.into_inner()
}
let app = App::new().service(
web::scope("/app")
// use a fn_guard to filter
.guard(guard::fn_guard(
|req| req.headers()
.contains_key("content-type")
))
.service(create_users)
)3. 请求处理
一个handler是一个异步方法接受可以从一个HTTP 请求中提取的,0个或多个参数(ie. impl FromRequest),并且返回一个可以转化为HttpRequest的类型(ie. impl Responder)
Request handler 的处理过程可以分为两阶段,首先handler 对象被调用,返回一个实现了Responder trait的对象,接着调用respond_to 转化为HttpResponse或Error。默认很多标准类型都实现了Responder trait,例如:
&'a String&'static [u8]&'static strOption<T>Result<T, E>String(T, actix_web::http::StatusCode)
3.1 自定义类型作为Responder
实现Responder trait 的类型都可以作为handler返回
use actix_web::{Error, HttpRequest, HttpResponse, Responder};
use serde::Serialize;
use futures::future::{ready, Ready};
#[derive(Serialize)]
struct MyObj {
name: &'static str,
}
// Responder
impl Responder for MyObj {
type Error = Error;
type Future = Ready<Result<HttpResponse, Error>>;
fn respond_to(self, _req: &HttpRequest) -> Self::Future {
let body = serde_json::to_string(&self).unwrap();
// Create response and set content type
ready(Ok(HttpResponse::Ok()
.content_type("application/json")
.body(body)))
}
}
async fn index() -> impl Responder {
MyObj { name: "user" }
}3.2 streaming
Response 的响应体可以异步生成,body必须实现Stream<Item=Bytes, Error=Error> trait
use actix_web::{web, App, HttpServer, Error, HttpResponse};
use bytes::Bytes;
use futures::stream::once;
use futures::future::ok;
async fn index() -> HttpResponse {
let body = once(ok::<_, Error>(Bytes::from_static(b"test")));
HttpResponse::Ok()
.content_type("application/json")
.streaming(body)
}
#[actix_rt::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| App::new().route("/async", web::to(index)))
.bind("127.0.0.1:8088")?
.run()
.await
}3.3 返回两种类型
可以使用actix_web::Either包装两种不同的类型作为返回值:
use actix_web::{Either, Error, HttpResponse};
type RegisterResult = Either<HttpResponse, Result<&'static str, Error>>;
fn index() -> RegisterResult {
if is_a_variant() {
// <- choose variant A
Either::A(HttpResponse::BadRequest().body("Bad data"))
} else {
// <- variant B
Either::B(Ok("Hello!"))
}
}4. 基于类型安全的信息提取
actix-web有基于类型安全的request信息的访问方式,被称为extractor(ie. impl FromRequest),默认提供了:
Path
Path提供了任意可以从Request‘s Path提取的信息,可以通过指定资源url格式中的变量(可以被反序列化),使用Pathwraper 获取。变量段可以使用图tuple的数据格式,变量可以是基本类型或者任何实现了serde::Deserializetrait 的结构体Query
Query类型提供了可用于request query的提取功能Json
Json可以将一个请求体反序列化为一个结构体,该结构体必须实现了DeserializetraitForm
当只可以使用
url-encodered格式数据时,需要使用Form类型
2. 增量更新
进行web开发时为了实现快速开发以及测试,可以借助systemfd, cargo-watch 进行自动加载。systemfd会开启一个socket连接,并传递给cargo-watch,cargo-watch负责监控代码变化,并且进行实时编译,并且将服务暴露于systemfd提供的socket端口下。
安装插件
cargo install systemfd cargo-watch[dependencies]
listenfd = "0.3"代码增添
use actix_web::{web, App, HttpRequest, HttpServer, Responder}; use listenfd::ListenFd; async fn index(_req: HttpRequest) -> impl Responder { "Hello World!" } #[actix_rt::main] async fn main() -> std::io::Result<()> { let mut listenfd = ListenFd::from_env(); let mut server = HttpServer::new(|| App::new().route("/", web::get().to(index))); server = if let Some(l) = listenfd.take_tcp_listener(0).unwrap() { server.listen(l)? } else { server.bind("127.0.0.1:3000")? }; server.run().await }服务启动
systemfd --no-pid -s http::3000 -- cargo watch -x runLast updated
Was this helpful?