大家好~今天要帶大家來做一點專案,其實是因為筆者下禮拜要準備讀書會所以偷換一下主題。
那麼就先介紹一下 actix-web,他是 Rust 眾多 web framework 的其中一套,他的功能精簡而這正好符合我的需求,因為我只需要他可以呼叫我的一支 python 程式然後把值處理之後返回。
那麼我們會需要下面兩個功能
前面兩個功能 framework 基本上都有提供而最後一個功能,恩...我還不知道後面慢慢研究。
有興趣的讀者可以到這裡找找看有沒有適合你的 framework,不過要注意喔 Rust 的 web framework 大多都還不是很成熟所以筆者我不會推薦把它用在工作上,後果我不負責哦。
那麼經過前面 10 幾天的練習這個 Hello World 對我們來說真是太容易了,首先我們先新增一個專案,
$ cargo new actixweb
然後在 Cargo.toml
上面加一個 dependencies
[dependencies]
actix-web = "1.0.8"
接著把 main.rs
改成這樣,
use actix_web::{web, App, HttpServer, Responder};
fn index(info: web::Path<(u32, String)>) -> impl Responder {
format!("Hello {}! id:{}", info.1, info.0)
}
fn main() -> std::io::Result<()> {
HttpServer::new(
|| App::new()
.service(web::resource("/{id}/{name}/index.html").to(index))
)
.bind("127.0.0.1:8080")?
.run()
}
最後下
$ cargo run
然後我們到這個頁面 http://localhost:8080/9453/handsome/index.html
最後就會呈現出來,
所以基本上就完成了那我們就可以來改寫看看剛剛的 main 那支程式,首先我們多加一支程式
use actix_web::{web, App, HttpServer, HttpResponse, Responder, get};
#[get("/hello")]
fn hello() -> impl Responder {
HttpResponse::Ok().body("hello")
}
然後我們在 main 裡面新增這個 service,
fn main() -> std::io::Result<()> {
HttpServer::new(
|| App::new()
.service(web::resource("/{id}/{name}/index.html").to(index))
// 新增的 service
.service(hello)
)
.bind("127.0.0.1:8080")?
.run()
}
然後重新 cargo run
之後再打一次網址 http://localhost:8080/hello
就會看到這個頁面
所以基本上我們可以在這邊寫很多的 api 來符合我們的需要~
每次都要重新 cargo run 確實有點麻煩我們來試試看官網提供的 autore laod 的方法,首先我們先下這個指令,
$ cargo install systemfd cargo-watch
接著在專案添加一個 listenfd
依賴,
[dependencies]
actix-web = "1.0.8"
listenfd = "0.3"
然後把 main 改寫成這樣,
fn main() {
let mut listenfd = ListenFd::from_env();
let mut server = HttpServer::new(
|| App::new()
.service(web::resource("/{id}/{name}/index.html").to(index))
.service(hello)
);
server = if let Some(l) = listenfd.take_tcp_listener(0).unwrap() {
server.listen(l).unwrap()
} else {
server.bind("127.0.0.1:3000").unwrap()
};
server.run().unwrap();
}
接著先執行一次 cargo run
然後離開接著下這行指令開啟 auto reload 模式,
$ systemfd --no-pid -s http::3000 -- cargo watch -x run
這樣只要有修改檔案 rust 就會重新編譯程式了!
最後我們再試著把剛剛的 hello 改成 restful api 的格式讓他可以帶參數以及回傳 json 格式的 body,
首先我們先給他一個 scope
讓所有的 api 都有 /api
前綴,把 server 改寫成這樣
let mut server = HttpServer::new(
|| App::new()
.service(web::resource("/{id}/{name}/index.html").to(index))
.service(
web::scope("/api").service(hello)
)
);
接著就可以這樣下 http://localhost:3000/api/hello
接著我們要讓 api 可以帶參數,官網用的是網址路徑的方式例如 /api/hello/{name}
。
來改寫 hello 這支程式吧,
#[get("/hello/{name}")]
fn hello(name: web::Path<String>) -> impl Responder {
println!("{}", name);
HttpResponse::Ok().body(format!("{}", name))
}
Note: 筆者尚未找到處理這種 ?query=test 路徑的方式,好像在新版本只能拿得到 string 但沒有自動轉換他,或許可以用第三方套件解決。
最後就是把 response 改成 json 格式,但是我們必須先增加一個 dependencies
[dependencies]
actix-web = "1.0.8"
listenfd = "0.3"
serde = { version = "1.0", features = ["derive"] }
保存之後 cargo run
一下,然後在 main.rs
裡面增加這段程式碼,
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct MyObj {
name: String,
}
接著改寫 hello,
use actix_web::{web, App, HttpServer, Result, HttpResponse, Responder, get};
#[get("/hello/{name}")]
fn hello(obj: web::Path<MyObj>) -> Result<HttpResponse> {
Ok(HttpResponse::Ok().json(MyObj {
name: obj.name.to_string(),
}))
}
恭喜完成了~
下面是完整的程式碼
Cargo.toml
[package]
name = "actixweb"
version = "0.1.0"
authors = ["SP_Penguin <abrcdf1023@gmail.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
actix-web = "1.0.8"
listenfd = "0.3"
serde = { version = "1.0", features = ["derive"] }
main.rs
use actix_web::{web, App, HttpServer, Result, HttpResponse, Responder, get};
use listenfd::ListenFd;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct MyObj {
name: String,
}
fn index(info: web::Path<(u32, String)>) -> impl Responder {
format!("Hello {}! id:{}", info.1, info.0)
}
#[get("/hello/{name}")]
fn hello(obj: web::Path<MyObj>) -> Result<HttpResponse> {
Ok(HttpResponse::Ok().json(MyObj {
name: obj.name.to_string(),
}))
}
fn main() {
let mut listenfd = ListenFd::from_env();
let mut server = HttpServer::new(
|| App::new()
.service(web::resource("/{id}/{name}/index.html").to(index))
.service(
web::scope("/api").service(hello)
)
);
server = if let Some(l) = listenfd.take_tcp_listener(0).unwrap() {
server.listen(l).unwrap()
} else {
server.bind("127.0.0.1:3000").unwrap()
};
server.run().unwrap();
}
啟動指令
$ systemfd --no-pid -s http::3000 -- cargo watch -x run
今天用很快的速度完成了一個很簡單的 restful api 下一篇就會來試試看把他跟 python 的程式串接再一起,那麼就讓我們明天見吧!