Open8
Actix WebでAPI作る
CRUDのリクエストを受け付ける
対応するリクエストメソッドを指定すれば良い。
カッコ内にパスを書く。(今回は全部ルート)
use actix_web::{get, post, put, delete, App, HttpResponse, HttpServer, Responder};
#[get("/")]
async fn get() -> impl Responder {
HttpResponse::Ok().body("get ok")
}
#[post("/")]
async fn post() -> impl Responder {
HttpResponse::Ok().body("post ok")
}
#[put("/")]
async fn put() -> impl Responder {
HttpResponse::Ok().body("put ok")
}
#[delete("/")]
async fn delete() -> impl Responder {
HttpResponse::Ok().body("delete ok")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(get)
.service(post)
.service(put)
.service(delete)
})
// ローカルホストのport8080で起動
.bind("127.0.0.1:8080")?
.run()
.await
}
パスから値を取る
use actix_web::{get, web, HttpResponse, Responder};
#[get("/users/{user_id}")]
async fn get(web::Path(user_id): web::Path<String>) -> impl Responder {
HttpResponse::Ok().body(format!("user_id is {}", user_id))
}
// mainはさっきと同じ
$ curl http://localhost:8080/users/hoge
> user_id is hoge
複数の場合
#[get("/users/{user_id}/articles/{article_id}")]
async fn get_article(web::Path((user_id, article_id)): web::Path<(String, i32)>) -> impl Responder {
HttpResponse::Ok().body(format!("user_id is {}. article_id is {}.", user_id, article_id))
}
$ curl http://localhost:8080/users/hoge/articles/1000
> user_id is hoge. article_id is 1000.
Query Parameterを取得する
#[derive(Deserialize)]
struct ArticleQuery {
start: i32,
results: i32
}
#[get("/articles")]
async fn get_articles(query: web::Query<ArticleQuery>) -> impl Responder {
HttpResponse::Ok().body(format!("start is {}. results is {}.", query.start, query.results))
}
$ curl http://localhost:8080/articles?start=1&results=100
> start is 1. results is 100.
bodyを取る
#[derive(Deserialize)]
struct User {
user_id: String,
name: String,
age: i32
}
#[post("/user")]
async fn post(user_data: web::Json<User>) -> impl Responder {
HttpResponse::Ok().body(format!("user_id is {}. name is {}. age is {}.", user_data.user_id, user_data.name, user_data.age))
}
$ curl -X POST -H "Content-Type: application/json" -d '{"user_id": "user", "name": "user", "age": 20}' localhost:8080/user
> user_id is user. name is user. age is 20.
JSON形式のレスポンス
#[derive(Deserialize)]
struct User {
user_id: String,
name: String,
age: i32
}
#[derive(Serialize)]
struct UserResponse {
user_id: String,
name: String,
age: i32
}
impl Responder for UserResponse {
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();
ready(Ok(HttpResponse::Ok().content_type("application/json").body(body)))
}
}
#[post("/user")]
async fn post(user_data: web::Json<User>) -> impl Responder {
UserResponse{ user_id: user_data.user_id.clone(), name: user_data.name.clone(), age: user_data.age }
}
httpリクエストを送る
actixではデフォでHttp Client Libraryが入っているのでそれを使い、httpリクエストが送れるところまで試す。
Cargo.toml更新
デフォで使えるので更新する必要は無いが、httpsに対応していないのでそれだけできるようにする
Cargo.toml
[dependencies]
actix-web = {version = "3.3.0", features = ["openssl"]}
openssl = "0.10.28"
httpリクエストを送るとこ作成
ファイル分割してみる
http_client.rs
pub mod http_client_module {
use actix_web::client::{Client, Connector};
use openssl::ssl::{SslConnector, SslMethod};
pub async fn get() -> String {
let builder = SslConnector::builder(SslMethod::tls()).unwrap();
let client = Client::builder()
.connector(Connector::new().ssl(builder.build()).finish())
.finish();
let result = client
// 適当なjsonを返すサーバー
.get("https://sample-domain/api/v1/json")
.send()
.await
.unwrap()
.body()
.limit(20_000_000) // 最大payloadサイズ
.await
.unwrap();
result.iter().map(|&s| s as char).collect::<String>()
}
}
main.rs修正
main.rs
mod http_client;
#[get("/json")]
async fn get_json() -> impl Responder {
let result = http_client::http_client_module::get().await;
HttpResponse::Ok()
.content_type("application/json")
.body(result)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(get)
.service(get_article)
.service(get_articles)
// ここを追加
.service(get_json)
.service(post)
.service(put)
.service(delete)
})
// ローカルホストのport8080で起動
.bind("127.0.0.1:8080")?
.run()
.await
}
参考
DB用意する
dockerでmysql用意
# 必要なファイルの準備
$ touch docker-compose.yaml
$ mkdir db
# mysql用Dockerfile
$ touch db/Dockerfile
# 初期設定用SQL
$ touch db/init.sql
# mysql設定用
$ touch db/my.cnf
ファイルの中身
docker-compose.yaml
version: '3'
services:
db:
build: ./db/
volumes:
- ./db:/docker-entrypoint-initdb.d
environment:
- MYSQL_ROOT_PASSWORD=hogehoge
Dockerfile
FROM mysql
EXPOSE 3306
COPY ./my.cnf /etc/mysql/conf.d/my.cnf
CMD ["mysqld"]
init.sql
CREATE DATABASE sample_db;
use sample_db;
CREATE TABLE users (
id int(10) unsigned not null auto_increment,
name varchar(255) not null,
created_time datetime not null default current_timestamp,
updated_time datetime not null default current_timestamp on update current_timestamp,
primary key (id)
);
my.cnf
[mysqld]
character-set-server=utf8
[mysql]
default-character-set=utf8
[client]
default-character-set=utf8
参考
アプリケーション側のdockerfile
FROM rust AS builder
WORKDIR /actix-api-sample
COPY Cargo.toml Cargo.toml
COPY Cargo.lock Cargo.lock
RUN mkdir src &&\
echo "fn main(){}" > src/main.rs &&\
cargo build --release
COPY ./src ./src
RUN rm -f target/release/deps/actix-api-sample* &&\
cargo build --release
FROM debian
COPY --from=builder /actix-api-sample/target/release/actix-api-sample /usr/local/bin/actix-api-sample
CMD ["actix-api-sample"]