Actix
Actix 是一个Rust异步Webserver,它提供了构建webserver需要的多种能力,包括路由、中间件、请求预处理、相应后处理等等。所有的actix server均围绕App构建,用于为资源和中间件注册路由。在相同的scope里面的所有handler存储程序状态。scope类似于所有路由的namespace。应用程序总是以/
开始,如果没有这个prefix系统将自动加入。这个prefix将由路径段组成。例如:/app
开头的scope,任何/app
或者/app/test
都将匹配路由。但是/application
将不会匹配。
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(web::scope("/app").route("index.html", web::get().to(index)))
})
.bind(("127.0.0.1", 8888))?
.run()
.await
}
上面的代码我们声明了一个scope叫做app
,然后路由index.html
,路由相应函数为index。我们可以使用:curl -X GET http://127.0.0.1:8888/app/index.html
访问并获取index()函数输出hello world
。
状态
在一个scope下应用程序状态在所有路由和资源之间共享,状态可以通过web::Data<T>
访问,这里的T是状态的类型。状态对中间件也是可以访问的。
use actix_web::{get, web, App, HttpServer};
struct AppState {
app_name: String,
}
#[get("/")]
async fn index(data: web::Data<AppState>) -> String {
let app_name = &data.app_name;
format!("Hello {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", 8888))?
.run()
.await
}
在上面的代码中我们定义了一个结构体AppState
用来存储用户的APP名称信息。HttpServer启动的时候创建了一个实例,实例通过web::Data::new()
构造。然后service实现对/
路由的绑定index,index会对拿到的web::Data结构体输出内容格式化返回输出。我们可以curl -X GET http://127.0.0.1:8888
获取/
路由的输出Hello Actix web
。
可修改的共享状态
HttpServer更愿意接受一个应用程序工厂。一个HttpServer为每个线程构建一个应用。因此应用程序的数据可能被多次构建,不同的线程间共享对象需要实现Send+Sync trait。web::Data内部使用Arc,为了避免创建两个Arc。我们应该在使用App::app_data
之前注册它。
::{get, web, App, HttpServer};
use std::sync::Mutex;
struct AppStateWithCounter {
counter: Mutex<i32>, //线程锁
}
async fn index(data: web::Data<AppStateWithCounter>) -> String {
let mut counter = data.counter.lock().unwrap();
*counter += 1;
format!("请求次数: {counter}")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
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", 8888))?
.run()
.await
}
上面的代码中:
- 状态在闭包中传给HttpServer::new的本地工作线程如果被修改则会同步。
- 为了获得全局的共享状态,它必须在闭包外部创建然后传入HttpServer::new然后move或者clone它。
使用Application Scope整合应用
web::scope()方法允许设置资源组prefix。代表它将被加入资源配置的前面,通过它可以挂在一些不同位置的资源,同时保证资源的名称好访问。
应用guard和virtual hosing
你可以认为guard是一个接受request对象返回true或者false的简单函数。一个guard实现了Guard trait。Actix web提供了一些guard,一个常见的guards是Host。他可以用作请求头信息的filter。
#[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("user") })),
)
.route("/", web::to(HttpResponse::Ok))
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
configure
简化和重用App和web::Scope提供了配置方法。这个函数用于移动配置的一部分到不同的module或者库。
请求handler
请求handler是一个异步函数,从请求中接收0或者更多参数返回一个可以被转换为HttpResponse类型。请求handling发生在两个阶段,首先handler对象调用返回任意实现了Respinder的trait,然后respond_to()调用转换其为HttpResponse或者Error。
默认Actix web提供了对一些标准类型的Responder实现。比如&'static str'、String
等等。可用handder的例子:
async fn index(_req: HttpRequest) -> &'static str {
"Hello world!"
}
async fn index(_req: HttpRequest) -> String {
"Hello world!".to_owned()
}
async fn index(_req: HttpRequest) -> impl Responder {
web::Bytes::from_static(b"Hello world!")
}
async fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
...
}
自定义类型的相应
为了直接从handler函数返回自定义类型,类型需要实现Responder trait。创建一个自订立类型序列化为application/json
的相应:
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" }
}
响应体流
Response body 可以异步生成,在这个case下,body必须实现stream trait Stream<Item=Bytes,Error=Error>:
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)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| App::new().service(stream))
.bind(("127.0.0.1", 8080))?
.run()
.await
}
不同的返回类型
有时候你需要返回不同类型的response。例如,错误检查和错误返回,返回异步相应或者任何要求两个不同类型结果的情况。再这种情况下Either类型可以使用,Either允许结合不同类型的responder类型为单个类型。
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!"))
}
}