iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 17
0

大家好~今天要帶大家來做一點專案,其實是因為筆者下禮拜要準備讀書會所以偷換一下主題。/images/emoticon/emoticon39.gif

Actix-Web

那麼就先介紹一下 actix-web,他是 Rust 眾多 web framework 的其中一套,他的功能精簡而這正好符合我的需求,因為我只需要他可以呼叫我的一支 python 程式然後把值處理之後返回。

那麼我們會需要下面兩個功能

  • 支援 restful api
  • 支援 json 格式
  • 可以用 cli 指令呼叫 python 程式

前面兩個功能 framework 基本上都有提供而最後一個功能,恩...我還不知道後面慢慢研究。

有興趣的讀者可以到這裡找找看有沒有適合你的 framework,不過要注意喔 Rust 的 web framework 大多都還不是很成熟所以筆者我不會推薦把它用在工作上,後果我不負責哦。/images/emoticon/emoticon01.gif

Hello World

那麼經過前面 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
最後就會呈現出來,

https://ithelp.ithome.com.tw/upload/images/20191003/20119807EqAzKosIMi.png

所以基本上就完成了那我們就可以來改寫看看剛剛的 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

就會看到這個頁面

https://ithelp.ithome.com.tw/upload/images/20191003/201198070vY37lZBdr.png

所以基本上我們可以在這邊寫很多的 api 來符合我們的需要~

Auto Reload

每次都要重新 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 就會重新編譯程式了!/images/emoticon/emoticon07.gif

API Scope

最後我們再試著把剛剛的 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

Path 參數

接著我們要讓 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

最後就是把 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(),
    }))
}

恭喜完成了~

https://ithelp.ithome.com.tw/upload/images/20191003/20119807Vhho7sl4ZE.png

下面是完整的程式碼

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 的程式串接再一起,那麼就讓我們明天見吧!

最後一樣有問題歡迎發問

/images/emoticon/emoticon07.gif


上一篇
[Day 16] Rust Slice Type
下一篇
[Day 18] Rust Actix Python 程式呼叫 (1)
系列文
WebAssembly + Rust 的前端應用30

尚未有邦友留言

立即登入留言