2023-1-13
Posted by
今回はactix-webの基本的な機能を解説します
actixとは
actix-webはrustの中ではgithubで一番starを獲得しているフレームワークです
ドキュメントも比較的多くあります、また数多くの機能がありやりたい事を簡単に実装出来ます
またスピードが早くTechEmpower Performance Ratingでは100以上のフレームワークがある中ランクが5位と高い順位を記録してます
今回はそんなactixを独断と偏見で必要ないと思った機能と高度な機能をそぎ落としたり追加したりした解説です
プロジェクトを作成する
まずは適当な名前の新しいcargoプロジェクトを作成します
例ではactix-test
にしています
$ cargo new actix-test
$ cd actix-test
コマンドを実行したら以下のようなファイルが作成されます
actix-test
.git
src
main.rs
.gitignore
Cargo.toml
後はCargo.tomlを以下のように変更します
1
2
3
4
5
6
7
8
9
+
[package]
name = "actix-test"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
actix-web = "4"
これで後はプログラムを書いて実行するだけです
実行はcargo run
で、できます
初回は実行時にライブラリがインストールされるので少し時間がかかります
$ cargo run
まだプログラムを書いてないので
Hello, world!
と表示されるだけですけど
Hello, world!を実行する
今main.rs
はまだデフォルトの状態ですけど変更しましょう
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
use actix_web::{App, HttpServer, Responder, HttpResponse, get};
#[get("/")]
async fn hello() -> impl Responder {
HttpResponse::Ok().body("Hello, world!")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(hello)
})
.bind(("127.0.0.1", 8080))?.run().await
}
これが最小のアプリです
info
macの場合は14行目を下のように変えてください
14
-
15
+
.bind(("127.0.0.1", 8080))?.run().await
.bind("0.0.0.0:8080")?.run().await
#[get("/")]
で関数hello
はgetでパスが/
の時に応答すると定義し
.service(hello)
で登録しています
HttpResponse
でレスポンスを返しています
#[actix_web::main]
マクロはasync main
関数をいじくってactixランタイム内で非同期に実行してくれたりする便利なやつです
実行したら
$ cargo run
Compiling actix-test v0.1.0 (A:\abc\osaka\actix-test)
Finished dev [unoptimized + debuginfo] target(s) in 32.43s
Running `target\debug\actix-test.exe`
このようになりhttp://127.0.0.1:8080/
にアクセスしたら
response
Hello, world!
response header
HTTP/1.1 200 OK
content-length: 13
date: ___, __ Jan 2023 __:__:__ GMT
このようなレスポンスがかえって来るはずです
ロギング
今のままでも十分良いのですがアクセスしてもログが出力されません
ログが出力されないとちゃんとアクセス出来ているのか不安になるのでログを出力するようにしましょう
まずは出力するためのライブラリを追加します
Cargo.toml
1
2
3
4
5
6
7
8
9
10
+
11
+
12
+
[package]
name = "actix-test"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
actix-web = "4"
log = "0.4.0"
env_logger = "0.9.0"
そしてmain.rs
にログを出力するミドルウェアを追加しましょう
今回は作成せずにデフォルトで入っているミドルウェアを使います
main.rs
1
-
2
+
3
+
4
5
6
7
8
9
10
11
12
+
13
14
15
16
+
17
18
19
use actix_web::{App, HttpServer, Responder, HttpResponse, get};
use actix_web::{App, HttpServer, Responder, HttpResponse, get, middleware::Logger};
use env_logger::Env;
#[get("/")]
async fn hello() -> impl Responder {
HttpResponse::Ok().body("Hello, world!")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
env_logger::init_from_env(Env::default().default_filter_or("info"));
HttpServer::new(|| {
App::new()
.service(hello)
.wrap(Logger::default())
})
.bind(("127.0.0.1", 8080))?.run().await
}
12行目でロガーを初期化しています
16行目の.warp
でミドルウェアを登録しています
今回は全体に適応されますがScope
(ルーティングで出てくるが/user/list
,/user/{id}
などを/user
のグループに入れて/list
と/{id}
に出来る)
などの限定的な範囲に適応させる事も可能です
これでログを出力するようになりました
実行してみるとこのようになります
$ cargo run
Compiling actix-test v0.1.0 (A:\abc\osaka\actix-test)
Finished dev [unoptimized + debuginfo] target(s) in 7.44s
Running `target\debug\actix-test.exe`
[2023-01-17___:__:___ INFO actix_server::builder] Starting 8 workers
[2023-01-17___:__:___ INFO actix_server::server] Actix runtime found; starting in Actix runtime
[2023-01-17___:__:___ INFO actix_web::middleware::logger] 127.0.0.1 "GET / HTTP/1.1" 200 13 "-" "Mozilla/5.0 (__________) AppleWebKit/___._ (KHTML, like Gecko) Chrome/___._._._ Safari/___._" 0.000245
今回はLogger::default()
を使用していますがカスタマイズする事も可能です
.wrap(Logger::new("%r %s %D"))
%r
などのフォーマットは色々な種類がありますがここでは一部を紹介します
名前 | 説明 | 結果 |
---|---|---|
%% | % | % |
%a | リモートIPアドレス | 127.0.0.1 |
%t | リクエストの処理が開始された時刻 | 2023-01-17T__:__:__._______Z |
%r | リクエストの最初の行 | GET/HTTP/1.1 |
%s | レスポンスのステータスコード | 200 |
%b | レスポンスの、HTTPヘッダーを含むバイト単位のサイズ | 13 |
%T | リクエストの処理にかかった時間(秒) | 0.000299 |
%D | リクエストの処理にかかった時間(ミリ秒) | 0.318400 |
%{Foo}i | リクエストのヘッダー情報 | %{Accept-Language}iの場合はja |
%{Bar}o | レスポンスのヘッダー情報 | |
%{FooBar}e | 環境変数 |
ルーティング
ルーティングには幾つかの方法があります
属性のようなマクロを使用した方法
これはここまでに登場した方法です
use actix_web::{App, HttpServer, Responder, HttpResponse, get};
#[get("/")]
async fn hello() -> impl Responder {
HttpResponse::Ok().body("Hello, world!")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(hello)
})
.bind(("127.0.0.1", 8080))?.run().await
}
一番簡単な方法かもしれません
またこの方法でも色々な事が出来ます
現在このようなマクロですが
#[get("/")]
少し方法を変えてみたり
#[route("/", method = "GET", method = "POST")]
あるいは複数のマクロを使ってみたり
#[routes]
#[get("/")]
#[get("/index")]
#[post("/")]
色々な方法があるので試してみるといいかもしれません
routeを使用した方法
こちらは少し複雑かもしれませんが保守性は高いかもしれません
use actix_web::{App, HttpServer, Responder, HttpResponse, web};
async fn hello() -> impl Responder {
HttpResponse::Ok().body("Hello, world!")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.route("/", web::get().to(hello))
})
.bind(("127.0.0.1", 8080))?.run().await
}
デフォルト
デフォルトでは存在しない場所にアクセスされたとき404 Not Found
だけを返しますが
response header
HTTP/1.1 404 Not Found
content-length: 0
date: ___, __ Jan 2023 __:__:__ GMT
その他にも色々返したい時は.default_service
を使えば解決できます
HttpServer::new(|| {
App::new()
.service(hello)
.default_service(web::to(|| HttpResponse::NotFound()))
})
勿論関数を使う事も出来ます
async fn not_found() -> impl Responder {
HttpResponse::NotFound().body("Not found!")
}
HttpServer::new(|| {
App::new()
.service(hello)
.default_service(web::to(not_found))
})
そもままでは全体に適応されてしまいますがスコープに適応することで範囲を一部に絞ることもできます
スケープ
スケープは例えば以下のようなページがあったとします
get /
post /user/{id}
get /user/{id}
get /user/{id}/profile
get /user/{id}/follower
get /user/{id}/post
get /user/{id}/すべてのパス
その時愚直に実装したら
HttpServer::new(|| {
App::new()
.route("/", web::get().to(hello))
.route("/user/{id}", web::post().to(hello))
.route("/user/{id}", web::get().to(hello))
.route("/user/{id}/profile", web::get().to(hello))
.route("/user/{id}/follower", web::get().to(hello))
.route("/user/{id}/post", web::get().to(hello))
})
.bind(("127.0.0.1", 8080))?.run().await
となります、最後のが無いと思われるかもしれませんが最後のはスコープを使わないと無理です
そしてスコープを使ったコードがこちらです
HttpServer::new(|| {
App::new()
.route("/", web::get().to(hello))
.service(
web::scope("/user/{id}")
.service(web::resource("")
.route(web::post().to(hello))
.route(web::get().to(hello))
)
.service(web::resource("/profile").route(web::get().to(hello)))
.route("/follower",web::get().to(hello))
.route("/post",web::get().to(hello))
.default_service(web::to(not_found))
)
})
.bind(("127.0.0.1", 8080))?.run().await
info
上の例では諸事情により"/profile"も.serviceとweb::resourceを使用していますが.route("/profile",web::get().to(hello))でも同じ結果になります(多分)
複雑化したと思うかもしれませんが、階層が分かりやすくなったような気がします
一番最初のweb::resource
の引数が""
なのは"/"
だと/user/{id}/
からしかアクセスできない為です、逆に""
だと/user/{id}/
からはアクセスできません
地味に初めて(多分)でてきたweb::resource
は同じパターンに応答する時に便利です、例では""
にはpost
とget
の二つが同じ""
に応答するのでweb::resource
を使っています
またweb::resource("/profile")
などは一つだけですが本家のscope
の例で最初に出てきたので使用しています
勿論.route("/post",web::get().to(hello))
みたいな感じも良いと思いますよ?
マクロでスコープを使用する
忘れていましたがマクロでもscope
を使用することができます
#[get("/test")]
async fn test() -> impl Responder {
HttpResponse::Ok().body("test, world!")
}
HttpServer::new(|| {
App::new()
.route("/", web::get().to(hello))
.service(
web::scope("/user/{id}")
.service(test)
)
})
.bind(("127.0.0.1", 8080))?.run().await
メソッド
これまでget
ばかり使ってきましたがほかのメソッドが無いわけではありません
#[get("/")]
#[post("/")]
#[put("/")]
#[delete("/")]
#[head("/")]
#[connect("/")]
#[options("/")]
#[trace("/")]
#[patch("/")]
#[route("/", method = "GET", method = "POST", method = "PUT", method = "DELETE", method = "HEAD", method = "CONNECT", method = "OPTIONS", method = "TRACE", method = "PATCH")]
web::get()
なども複数あります
web::get()
web::post()
web::delete()
web::head()
web::connect()
web::options()
web::trace()
web::patch()
CORS
Access to XMLHttpRequest at 'http://127.0.0.1:8080/' from origin 'http://127.0.0.1:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
などとエラーが出た場合CORSが原因である場合が多いです
それにエラーが出なくてもCORSを変えたい時があります
そんな時actix-cors
を使えば簡単に解決できます
Cargo.toml
1
2
3
4
5
6
7
8
9
10
+
11
12
13
[package]
name = "actix-test"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
actix-web = "4"
actix-cors = "0.6.4"
log = "0.4.0"
env_logger = "0.9.0"
使い方は簡単でミドルウェアのように追加できます
use actix_web::{App, HttpServer, Responder, HttpResponse, get};
use actix_cors::Cors;
#[get("/")]
async fn hello() -> impl Responder {
HttpResponse::Ok().body("Hello, world!")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
let cors = Cors::default()
.send_wildcard()
.allow_any_origin()
.allow_any_method()
.allow_any_header();
App::new()
.wrap(cors)
.service(hello)
})
.bind(("127.0.0.1", 8080))?.run().await
}
今回は特定のoriginだけ許可をするなどがめんどくさいので.allow_any_origin
などを使っていますが、本番環境などでは使わない事をお勧めします
その場合.allowed_origin
などがあります
今回はcorsメインでは無いので本当にさわりだけ書きましたが詳しくは本家を参照してください
リクエスト
リクエストされたデータにアクセスするには関数の引数を使います
関数ごとに最大12個の引数を追加できます、引数の位置や名前、順番などは関係ありません
パスの値を取り出す
/user/{id}
などのpathからデータを得るにはweb::Path<()>
を使います、web::Path<()>
の中には得たいデータの型を入れてください/user/{id}
の場合はweb::Path<(u32)>
です
取り出すときはpath.into_inner()
を使います、順番はpathと同じです
use actix_web::{App, HttpServer, Responder, HttpResponse, get, web};
#[get("/user/{id}/post/{title}")]
async fn hello(path: web::Path<(u32, String)>) -> impl Responder {
let (id, title) = path.into_inner();
HttpResponse::Ok().body(format!("user id: {}, title: {}", id, title))
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(hello)
})
.bind(("127.0.0.1", 8080))?.run().await
}
/user/{id}
のid
に文字列などを入れた場合は変換できずエラーメッセージ(can not parse "hello" to a u32
(id
にhello
を入れたため))とともに404
が返されます
response header
HTTP/1.1 404 Not Found
content-length: 30
content-type: text/plain; charset=utf-8
date: ___, __ Jan 2023 __:__:__ GMT
またトレイトが必要ですがpathと同じ名前にすれば構造体として取得する事もできます
use actix_web::{App, HttpServer, Responder, HttpResponse, get, web};
use serde::Deserialize;
#[derive(Deserialize)]
struct Paths {
id: u32,
title: String,
}
#[get("/user/{id}/post/{title}")]
async fn hello(path: web::Path<Paths>) -> impl Responder {
HttpResponse::Ok().body(format!("user id: {}, title: {}", path.id, path.title))
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(hello)
})
.bind(("127.0.0.1", 8080))?.run().await
}
serde::Deserialize
を使用するにはserde
が必要です
Cargo.toml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
+
[package]
name = "actix-test"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
actix-web = "4"
log = "0.4.0"
env_logger = "0.9.0"
serde = { version = "1.0", features = ["derive"] }
クエリ文字列を取り出す
クエリ文字列はhttps://abc.osaka?ref=google.com&theme=dark
のref=google.com&theme=dark
部分の事ですが取得するにはweb::Query
を使います
またこちらもトレイトが必要になります
use actix_web::{App, HttpServer, Responder, HttpResponse, get, web};
use serde::Deserialize;
#[derive(Deserialize)]
struct Queries {
name: String,
}
#[get("/")]
async fn hello(info: web::Query<Queries>) -> impl Responder {
HttpResponse::Ok().body(format!("name: {}", info.name))
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(hello)
})
.bind(("127.0.0.1", 8080))?.run().await
}
もし指定したクエリ文字列が見つからない場合は400
,Query deserialize error: missing field 'name'
エラーが出るので注意が必要です
クエリ文字列にoptionを使用する
上の方法でも良いのですがクエリ文字列が見つからない場合に400
を返してしまいます、解決策としてはoption型を使う事です
#[derive(Deserialize)]
struct Queries {
name: Option<String>,
}
これでクエリ文字列にname
が含まれない場合も400
を返すことが無くなりました
後は普通に使えます
#[get("/")]
async fn hello(info: web::Query<Queries>) -> impl Responder {
if let Some(name) = &info.name {
return HttpResponse::Ok().body(format!("hey {}!", name))
}
HttpResponse::Ok().body("what is your name?")
}
任意のクエリ文字列を取得する
また任意のクエリ文字列を取得する方法もあります、この場合クエリ文字列をHashMap
で取得できます
#[get("/")]
async fn hello(info: web::Query<HashMap<String, String>>) -> impl Responder {
HttpResponse::Ok().body(format!("name: {:#?}", info))
}
もちろん型を変える事も出来ます例えば数字にしたりweb::Query<HashMap<String, u32>>
bodyの値を取り出す
値を取り出すにはweb::Json<>
を使います
基本的にはweb::Query
と同じように取得できます
option
やHashMap<String, String>
なども使えます
#[derive(Deserialize)]
struct Bodies {
name: Option<String>,
}
#[post("/")]
async fn hello(info: web::Json<Bodies>) -> impl Responder {
if let Some(name) = &info.name {
return HttpResponse::Ok().body(format!("hey {}!", name))
}
HttpResponse::Ok().body("what is your name?")
}
平文で取り出す
あまりお勧めはしませんがもしあなたが物好きなら平文で取り出す事も可能です
async fn hello(body: String) -> impl Responder {
println!("{}", body);
HttpResponse::Ok().body("Hello, world!")
}
ヘッダーの値を取り出す
こちらは扱いが少し不便ですがよく使うことになります
web::Headerを使う方法
この方法では簡単に取得することはできますが自由度が低いです
取得するデータを変更するにはheader::ContentType
を変える必要があります
#[get("/")]
async fn hello(info: web::Header<header::ContentType>) -> impl Responder {
HttpResponse::Ok().body(format!("{}", info.to_string()))
}
HttpRequestを使う方法
この方法では少し複雑になりますが好きな値を取得することができます
認証ヘッダーなどを取得するにはこの方法が良いでしょう
#[get("/")]
async fn hello(req: HttpRequest) -> impl Responder {
if let Some(x) = req.headers().get("User-Agent") {
let x = x.to_str().unwrap();
return HttpResponse::Ok().body(format!("{}", x))
}
HttpResponse::Ok().body("User-Agent not found")
}
画像やファイルを受け取る
画像やファイルを取り出す場合は次のようにします
async fn save_file(req: HttpRequest, mut payload: web::Payload) -> HttpResponse {
let mut file = File::create("test.png").unwrap();
while let Some(chunk) = payload.try_next().await.unwrap() {
file.write_all(&chunk).unwrap();
}
HttpResponse::Ok().body("File saved successfully")
}
HttpRequest
あまり使う事は無いかもしれませんが、色々な値にアクセスできます
上のヘッダーの値を取得する時に使う事が多い気がします
async fn hello(req: HttpRequest) -> impl Responder {
println!("req : {:#?}", req);
println!("uri : {:#?}", req.uri());
println!("method : {:#?}", req.method());
println!("version : {:#?}", req.version());
println!("headers : {:#?}", req.headers());
println!("path : {:#?}", req.path());
println!("query_string : {:#?}", req.query_string());
println!("match_info : {:#?}", req.match_info());
println!("match_pattern : {:#?}", req.match_pattern());
println!("match_name : {:#?}", req.match_name());
println!("cookies : {:#?}", req.cookies());
HttpResponse::Ok()
}
req :
HttpRequest HTTP/1.1 POST:/
headers:
"content-length": "13"
"user-agent": "HTTPie"
"connection": "close"
"content-type": "application/json"
"host": "127.0.0.1:8080"
uri : /
method : POST
version : HTTP/1.1
headers : HeaderMap {
inner: {
"content-length": Value {
inner: [
"13",
],
},
"user-agent": Value {
inner: [
"HTTPie",
],
},
"connection": Value {
inner: [
"close",
],
},
"content-type": Value {
inner: [
"application/json",
],
},
"host": Value {
inner: [
"127.0.0.1:8080",
],
},
},
}
path : "/"
query_string : ""
match_info : Path {
path: Url {
uri: /,
path: None,
},
skip: 0,
segments: [],
}
match_pattern : None
match_name : None
cookies : Ok(
[],
)
レスポンス
HttpResponse
レスポンスには基本的にHttpResponse
を使います
#[get("/")]
async fn hello() -> impl Responder {
HttpResponse::Ok()
}
色々な種類がありますがここではよく使ういくつかを紹介しましょう
HttpResponse::Ok()
HttpResponse::Created()
HttpResponse::BadRequest()
HttpResponse::MethodNotAllowed()
HttpResponse::Unauthorized()
HttpResponse::NotFound()
HttpResponse::TooManyRequests()
これだけでもいいですがこれではただステータスコードを送信するだけです
.insert_header
や.body
を付ける事でbodyやheaderをカスタマイズできます
HttpResponse::Ok()
.insert_header(("Content-Type", "text/plain"))
.body("hello")
Jsonを返す
jsonを返すにはSerialize
のトレイトのついた構造体を使う必要があります
#[derive(Serialize)]
pub struct User {
pub id: i32,
pub name: String,
}
#[get("/")]
async fn hello() -> impl Responder {
HttpResponse::Ok()
.json(User {
id: -1,
name: "example".to_string()
})
}
複数ある場合はVec
が使えます
#[get("/")]
async fn test() -> impl Responder {
let mut all = Vec::new();
all.push(User {
id: 1,
name: "apple".to_string()
});
all.push(User {
id: 2,
name: "osaka".to_string()
});
HttpResponse::Ok()
.json(all)
}
因みにSerialize
はserde
からインポートできます
use serde::{Deserialize, Serialize};
serde
自体はCargo.toml
の[dependencies]
のしたに一行追加すれば使えます
1
2
+
3
[dependencies]
serde = { version = "1.0", featurs = ["derive"] }
actix-web = "4"
画像を返す
use actix_web::{App, HttpServer, Responder, HttpResponse, get, web};
#[get("/image/{path}")]
async fn image(path: web::Path<String>) -> impl Responder {
let name = path.into_inner();
let image = web::block(move || std::fs::read(format!("./static/{}", name)).unwrap()).await.unwrap();
HttpResponse::Ok()
.content_type("image/jpeg")
.body(image)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(image)
})
.bind(("127.0.0.1", 8080))?.run().await
}
少し複雑なのでコードの解説をしましょう
まず、web::block
は時間がかかる処理をしている時にアクセスが来た時、通常二つ目のアクセスは一つ目の処理が終わるのを待つ必要があるのですが、web::block
を使う事で二つ目のアクセスを処理しながら、一つ目のアクセスの処理が終わるのを待つことで二つ目のアクセスが待つ必要が無くなることで全体的な速度が向上します
そしてstd::fs::read
でファイルを読み込んでいます
こうして無事に読み込まれたファイルをbody
に入れてレスポンスを返しています
.content_type
はレスポンスを受け取ったブラウザなどに画像である事を伝えています、最近のブラウザは賢いので画像である事を認識しますがあまり賢くない時もあるので一応つけています
上のプログラムはもし画像が見つからない場合場合サーバー側でエラーが発生して何も返しませんがもしサーバーが応答しないのが嫌なのならmatch
などでエラー処理をするべきです
#[get("/image/{path}")]
async fn image(path: web::Path<String>) -> impl Responder {
let name = path.into_inner();
let image = match web::block(move || std::fs::read(format!("./static/{}", name))).await {
Ok(image) => match image {
Ok(image) => image,
Err(__) => return HttpResponse::NotFound().body("File not found")
},
Err(__) => return HttpResponse::NotFound().body("Blocking Error")
};
HttpResponse::Ok()
.content_type("image/jpeg")
.body(image)
}
言い忘れていましたがこのプログラムは以下のようなディレクトリ構成である事を前提に書いているので、formt!("./static/{}")
の場所は環境に合わせて変更してください
actix-test
.git
scr
main.rs
static
img.png
img2.png
img3.jpg
.gitignore
Cargo.lock
Cargo.toml
データベース
本当は分けようと思ったのですが既に(/rust/db)解説しているのでさわりだけ解説します、そのため少しスピードをあげます
今回はsqlite3
でrusqlite
を使って実装します
まずいつものようにライブラリをプロジェクトに追加します
Cargo.toml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
+
17
+
18
+
[package]
name = "rusqlite"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serde = { version = "1.0", features = ["derive"] }
actix-web = "4"
log = "0.4.0"
env_logger = "0.9.0"
[dependencies.rusqlite]
version = "*"
features = ["bundled"]
そしていつものHello, world!
を書きます
use actix_web::{App, HttpServer, Responder, HttpResponse, get};
#[get("/")]
async fn hello() -> impl Responder {
HttpResponse::Ok().body("Hello, world!")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(hello)
})
.bind(("127.0.0.1", 8080))?.run().await
}
そこにライブラリをインポートし
1
-
2
+
3
+
4
+
use actix_web::{App, HttpServer, Responder, HttpResponse, get};
use actix_web::{web, App, HttpServer, Responder, HttpResponse, get, post};
use rusqlite::Connection;
use serde::{Serialize, Deserialize};
dbと接続し、ない場合は自動的に作成されるプログラムを書きます
11
12
13
+
14
15
16
17
18
19
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let conn = Connection::open("app.db").unwrap();
HttpServer::new(|| {
App::new()
.service(hello)
})
.bind(("127.0.0.1", 8080))?.run().await
}
そしてテーブルを作成するプログラミングを書き
11
12
13
14
+
15
+
16
+
17
+
18
+
19
+
20
+
21
+
22
+
23
24
25
26
27
28
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let conn = Connection::open("app.db").unwrap();
conn.execute(
"CREATE TABLE IF NOT EXISTS post (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title STRING,
body STRING
)",
()
).unwrap();
HttpServer::new(|| {
App::new()
.service(hello)
})
.bind(("127.0.0.1", 8080))?.run().await
}
ハンドラーと色々するプログラムを書いたら完成です
3
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+
17
+
18
+
19
+
20
+
21
+
22
+
23
+
24
+
25
+
26
+
27
+
28
+
29
+
30
+
31
+
32
+
33
+
34
+
35
+
36
+
37
+
38
+
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+
47
+
48
+
49
+
50
+
51
+
52
+
53
54
use serde::{Serialize, Deserialize};
#[derive(Serialize)]
struct Post {
id: i32,
title: String,
body: String,
}
#[derive(Deserialize)]
struct CreatePostRequest {
title: String,
body: String,
}
#[post("/create")]
async fn create_post(body: web::Json<CreatePostRequest>) -> impl Responder {
let conn = Connection::open("app.db").unwrap();
let mut stmt = conn.prepare("INSERT INTO post ( title, body ) values ( ?1, ?2 )").unwrap();
let _ = stmt.execute([&body.title, &body.body]).unwrap();
let post = conn.query_row("SELECT id, title, body FROM post WHERE id = last_insert_rowid()", [], |row| {
Ok(Post {
id: row.get(0)?,
title: row.get(1)?,
body: row.get(2)?
})
}).unwrap();
HttpResponse::Ok().json(post)
}
#[get("/")]
async fn get_post() -> impl Responder {
let conn = Connection::open("app.db").unwrap();
let mut stmt = conn.prepare("SELECT id, title, body FROM post").unwrap();
let posts = stmt.query_map([], |row| {
Ok(Post {
id: row.get(0)?,
title: row.get(1)?,
body: row.get(2)?
})
}).unwrap();
let mut posts_vec = Vec::new();
for item in posts {
if let Ok(item) = item {
posts_vec.push(item)
}
}
HttpResponse::Ok().json(posts_vec)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
終わり
これで一通り出来ました、多分
リンク
actixのgithub
actixのdocs.rs
このドキュメントどう?