安装Rust环境
Windows
-
Microsoft Visual Studio C++ 生成工具 ,需要下载Visual Studio 2022生成工具。进行安装时选择C++生产工具和Windows 10 sdk
-
通过下载安装包安装,或者在PowerShell使用命令安装
winget --id Rustlang.Rustup
注意安装后需要重启命令窗口,并且生成工具需要安装完成,不然识别不到rustc命令
macOS
-
CLang和macOS开发依赖项
$ xcode-select --install -
Rust
$ curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh
Linux
- 版本较多具体看文末参考链接
创建项目
- 使用命令创建一个基于二进制的Cargo项目,并进入项目目录
cargo new hello-world
cd hello-world
- 将依赖的crate加入Cargo.toml
[dependencies]
actix-web = "4"
- 创建请求处理函数
use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder};
#[get("/")]
async fn hello() -> 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!")
}
值得注意的是:这里manual_hello函数没有设置路由
- 创建服务
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(hello)
.service(echo)
.route("/hey", web::get().to(manual_hello))
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
这样一个简单的web服务器就完成了。服务绑定在本地的8080端口,现在我们有了三个接口。
拆分模块
rust提供了强大的模块系统,可以将代码拆分成多个模块。使得模块的功能分工明确,可以更好的组织、重用代码。 actix在Application实例上提供了一个configure方法
- 首先在src文件夹下创建一个user.rs 文件
- user.rs 模块需要向外暴露一个config方法
pub fn config(cfg:&mut web::ServiceConfig){
cfg.service(
web::scope("user") //这里代表路由地址以user开头,例如/user/info。其次这里写”user“或者”/user“都可以actix会自动补上”/“
.service(index)
.service(vaildtest)
);
}
可以看到该方法接受一个可变借用cfg,在cfg上面注册路由服务与和main中完全相同。
3.回到main.rs
mod user; //引入创建的user模块
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(hello)
.configure(user::config) //添加到application上面
.service(echo)
.route("/hey", web::get().to(manual_hello))
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
请求处理
- 获取请求路径中的参数,例如/user/123/前端,需要获取某个id为123,工作为前端的用户的信息
#[get("/{user_id}/{job}")]
async fn get_user_info(info:web::Path<(u32,String)>)->impl Responder{
let (user_id,job)=info.into_inner();
format!("user_id:{},job:{}",user_id,job)
}
不要忘记注册这个路由服务,其次可以注意到这里返回了一个字符串,同时函数前面写的是返回实现了Responder这个trait,actix_web提供了一些类型的默认实现更多可以查阅
- 处理json数据的post请求
- 添加依赖
[dependencies]
serde_json = "0.6.2"
futures = "0.3"
serde = { version = "1.0", features = ["derive"] }
- 代码实现
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Deserialize, Serialize)]
struct Info {
user_id: i32,
job: String,
}
#[post("/submit")]
async fn submit(info: web::Json<Info>) -> Info{
Info{
user_id:info.user_id,
job:info.job.clone()
}
}
代码很简单创建了一个Info结构体,给它加了两个trait,然后通过web::json解析出了数据。值得注意的是这里我直接返回了Info。没错,因为我这里为Info实现了Responder这个trait
impl Responder for Info {
type Body = BoxBody;
fn respond_to(self, req: &actix_web::HttpRequest) -> HttpResponse<Self::Body> {
let body = serde_json::to_string(&self).unwrap();
HttpResponse::Ok()
.content_type(ContentType::json())
.body(body)
}
}
- 处理formdata的post请求,前端经常会回传表单数据,里面除了一些信息外还可能有文件
- 老规矩先添加依赖
[dependencies]
actix-multipart = "0.6.0"
actix-files = "0.6.0"
- 定义结构体
use actix_multipart::{
form::{
tempfile::{TempFile, TempFileConfig},
text::Text,
MultipartForm,
},
Multipart,
};
use serde_json::Value;
#[derive(Debug,MultipartForm)]
struct UploadForm{
#[multipart(rename="file")]
files:Vec<TempFile>,
name:Text<String>,
id:Text<i32>
crates:Text<String>
}
- 请求处理函数
#[post("/form")]
async fn save_files(MultipartForm(form): MultipartForm<UploadForm>) -> Result<impl Responder> {
for f in form.files {
let path = format!("./tmp/{}", f.file_name.unwrap());
f.file.persist(path).unwrap();
}
let res = format!("id:{},name:{}", form.id.0, form.name.0);
let crates = form.crates.0.as_str();
let crates: Value = serde_json::from_str(crates)?; //将合法json字符串转json
println!("crates:{:#?}", crates);
Ok(HttpResponse::Ok().body(res))
}
自定义错误
- 定义结构体
use derive_more::{Display,Error}
#[derive(Debug,Display,Error)]
enum MyError {
#[display(fmt="internal error")]
InternalError,
#[display(fmt = "bad request")]
BadClientData,
#[display(fmt = "timeout")]
Timeout,
#[display(fmt="vaildation error on filed:{}",filed)]
ValidationError{filed:String}
}
这里使用了derive_more来简化Display这个trait的实现,因为我们后面需要用到to_string方法
- 自定义错误需要实现actix_web::error::ResponseError这个trait,它上面有两个方法需要实现具体代码如下
use actix_web::{get,web, error, HttpResponse, http::{header::ContentType, StatusCode}};
impl error::ResponseError for MyError {
fn error_response(&self) -> actix_web::HttpResponse<actix_web::body::BoxBody> {
HttpResponse::build(self.status_code())
.insert_header(ContentType::html())
.body(self.to_string())
}
fn status_code(&self) -> actix_web::http::StatusCode {
match *self {
MyError::InternalError=>StatusCode::INTERNAL_SERVER_ERROR,
MyError::BadClientData=>StatusCode::BAD_REQUEST,
MyError::Timeout=>StatusCode::GATEWAY_TIMEOUT,
MyError::ValidationError { ..}=>StatusCode::BAD_REQUEST
}
}
}
status_code返回请求的错误码,error_response对请求进行返回
- 自定义错误的使用
#[get("/test")]
async fn index()->Result<&'static str,MyError>{
Err(MyError::BadClientData)
}
#[get("/vaildtest")]
async fn vaildtest() ->Result<&'static str,MyError>{
Err(MyError::ValidationError { filed: "input is invalid".to_string() })
}
日志(中间件)
Actix Web的中间件系统允许我们为请求/响应处理添加额外的行为。通过日志中间件可以记录下接口请求的日志,
- 使用crate
[dependencies]
env_logger = "0.8" //设置环境变量
- 使用
#[rustfmt::skip]
#[actix_web::main]
async fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "error");// 设置日志级别
std::env::set_var("RUST_BACKTRACE", "1"); //此处暂时不明白,望懂的人解惑
env_logger::init();
HttpServer::new(|| {
let logger=Logger::default();
App::new()
.wrap(logger)
.configure(basic::config)
.service(
web::resource("/user/{name}")
.name("user_detail")
.guard(guard::Header("content-type", "application/json"))
.route(web::get().to(HttpResponse::Ok))
.route(web::put().to(HttpResponse::Ok)),
)
.service(echo)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
日志级别:error>warn>info>debug>trace ,这个级别有什么用?级别越低日志越详细,举个例子:当设置error时普通的接口请求不会记录日志
静态文件使用
use actix_files::Files;
.service(Files::new("/static","dist/static/").show_files_listing())
.service(Files::new("/","dist/").index_file("index.html"))
这里不放完整的代码了,这边第一个service请求“/static”开头路径时,映射到dist/static文件夹下的所有文件 ,第二个serivce 访问”/“即根路由时返回index.html。注:该路径配置下dist文件夹放在与src同级
构建与运行
- 构建
cargo build --release
文件会生成在target/release/hello-word,因为项目名为hello-world 2. 创建一个新的文件夹将dist文件和hello-word拷贝到这个新文件夹,从终端打开该文件夹
$ nohup ./hello-word &
启动后会生成一个nohup.out的日志文件