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
添加对应依赖.
Copy 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()
方法调用中存储。
Copy 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。
Copy #[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
等,过滤不符合条件的请求。
Copy 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
方法,可以将配置部分移动至不同的模块或者不同的库中.
Copy 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
}
响应请求结果:
Copy / -> "/"
/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)
等。
Copy 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宏调用定义路由。
Copy #[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,例如:
(T, actix_web::http::StatusCode)
3.1 自定义类型作为Responder
实现Responder
trait 的类型都可以作为handler返回
Copy 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
Copy 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
包装两种不同的类型作为返回值:
Copy 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
格式中的变量(可以被反序列化),使用Path
wraper 获取。变量段可以使用图tuple的数据格式,变量可以是基本类型或者任何实现了serde::Deserialize
trait 的结构体
Query
Query
类型提供了可用于request query的提取功能
Json
Json
可以将一个请求体反序列化为一个结构体,该结构体必须实现了Deserialize
trait
Form
当只可以使用url-encodered
格式数据时,需要使用Form
类型
2. 增量更新
进行web开发时为了实现快速开发以及测试,可以借助systemfd
, cargo-watch
进行自动加载。systemfd
会开启一个socket
连接,并传递给cargo-watch
,cargo-watch
负责监控代码变化,并且进行实时编译,并且将服务暴露于systemfd
提供的socket
端口下。
Copy cargo install systemfd cargo-watch
Copy [dependencies]
listenfd = "0.3"
代码增添
Copy 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
}
Copy systemfd --no-pid -s http::3000 -- cargo watch -x run