Rocket (Rust) で CORS の設定をする

今回も前回の軽い続きです。
ohshige.hatenablog.com

APIは作れるようになったので、今度はこれを呼び出したい思いです。
同じドメインなら問題無いですが、異なるドメインから呼び出したい場合に設定しないといけないがCORSです。

前回の状態で適当にJSからXHRを試して http://localhost:8000/users を呼び出そうとすると、以下のようにエラーとなります。

Access to XMLHttpRequest at 'http://localhost:8000/users' from origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

実際にレスポンスヘッダーを見ても、当たり前ですがCORSの設定は何もありません。

HTTP/1.1 200 OK
Content-Type: application/json
Server: Rocket
Content-Length: 73
Date: Mon, 24 Feb 2020 19:00:00 GMT

というわけで、 rocket_cors を使います。
docs.rs

Cargo.toml

rocket_cors = "*" を追記します。

[package]
name = "demo"
version = "0.1.0"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
rusqlite = "*"
rocket = "*"
rocket_contrib = { version = "*", features = ["json"] }
rocket_cors = "*"
serde = "*"
serde_derive = "*"

main.rs

CorsOptions::default() をアタッチすることでCORSを設定できます。
デフォルトだとオールオッケーな感じなので、適宜必要な範囲に絞る必要があります。

#![feature(proc_macro_hygiene, decl_macro)]

#[macro_use] extern crate rocket;
#[macro_use] extern crate serde_derive;

extern crate rusqlite;
extern crate rocket_contrib;
extern crate rocket_cors;

use rusqlite::{Connection, NO_PARAMS};
use rocket_contrib::json::Json;
use rocket_cors::CorsOptions;

(略)

fn main() {
  rocket::ignite()
      .mount("/", routes![get_users, get_user])
      .attach(CorsOptions::default().to_cors().expect("error"))
      .launch();
}

上記を実行すると以下のようになります。

$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.10s
     Running `target/debug/demo`
🔧 Configured for development.
(略)
🛰  Mounting /:
    => GET /users (get_users)
    => GET /users/<user_id> (get_user)
🛰  Mounting /cors:
    => GET /cors/<status>
📡 Fairings:
    => 1 request: CORS
    => 1 response: CORS
🚀 Rocket has launched from http://localhost:8000

それらしい起動ができている気がします。

この状態で新たにJSからXHRを試してみると、エラーは起きず無事にできていることがわかります。
レスポンスヘッダーを見てみると、 null ではありますが Access-Control-Allow-OriginVary が増えています。

HTTP/1.1 200 OK
Content-Type: application/json
Server: Rocket
Access-Control-Allow-Origin: null
Vary: Origin
Content-Length: 73
Date: Mon, 24 Feb 2020 19:00:00 GMT

RustでRocketを使ってAPIを作ってみる

なんとなくの前回の続きで、だいたい自分用メモです。
ohshige.hatenablog.com

RustでAPIを実現するためのフレームワークはいくつかあるようですが、今回はRocketを使います。
基本的にはチュートリアルの通りにやっています。
rocket.rs

やりたいことは前回作ったDBからデータを取得するメソッドをAPI化して、JSONを返却するようにするというものです。

以降は、rustc 1.42.0-nightly で動作確認済みです。

Cargo.toml

[package]
name = "demo"
version = "0.1.0"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
rusqlite = "*"
rocket = "*"
rocket_contrib = { version = "*", features = ["json"] }
serde = "*"
serde_derive = "*"

main.rs

#![feature(proc_macro_hygiene, decl_macro)]

#[macro_use] extern crate rocket;
#[macro_use] extern crate serde_derive;

extern crate rusqlite;
extern crate rocket_contrib;

use rusqlite::{Connection, NO_PARAMS};
use rocket_contrib::json::Json;

#[derive(Debug, Serialize)]
struct User {
    user_id: usize,
    name: String,
}

#[get("/users")]
fn get_users() -> Json<Vec<User>> {
    let conn = Connection::open("my.db").unwrap();
    let mut stmt = conn.prepare("
        SELECT
            user_id, name
        FROM
            users
        ORDER BY
            user_id ASC
    ",
    ).unwrap();

    let users = stmt.query_map(NO_PARAMS, |row| {
        Ok(User {
            user_id: row.get::<_, i64>(0).unwrap() as usize,
            name: row.get::<_, String>(1).unwrap(),
        })
    }).unwrap();

    let mut result: Vec<User> = vec![];
    for user in users {
        result.push(user.unwrap());
    }

    Json(result)
}

#[get("/users/<user_id>")]
fn get_user(user_id: usize) -> Option<Json<User>> {
    let conn = Connection::open("my.db").unwrap();
    let mut stmt = conn.prepare("
        SELECT
            user_id, name
        FROM
            users
        WHERE
            user_id = ?
    ",
    ).unwrap();

    let id = user_id as i64;
    let mut users = stmt.query_map(&[&id], |row| {
        Ok(User {
            user_id: row.get::<_, i64>(0).unwrap() as usize,
            name: row.get::<_, String>(1).unwrap(),
        })
    }).unwrap();

    let user = users.next();
    match user {
        Some(user) => Some(Json(user.unwrap())),
        None => None,
    }
}

fn main() {
  rocket::ignite().mount("/", routes![get_users, get_user]).launch();
}

メインの処理はほぼ変わっておらず、User構造体をシリアライズ可能にしたのとレスポンスをJSONにしたくらいです。

上記を実行すると以下のようになります。

$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.09s
     Running `target/debug/demo`
🔧 Configured for development.
    => address: localhost
    => port: 8000
    => log: normal
    => workers: 32
    => secret key: generated
    => limits: forms = 32KiB
    => keep-alive: 5s
    => tls: disabled
🛰  Mounting /:
    => GET /users (get_users)
    => GET /users/<user_id> (get_user)
🚀 Rocket has launched from http://localhost:8000

curlで確認してみると、問題なく動いています。

$ curl http://localhost:8000/users
[{"user_id":1,"name":"田中太郎"},{"user_id":2,"name":"鈴木次郎"}]
$ curl http://localhost:8000/users/1
{"user_id":1,"name":"田中太郎"}
$ curl http://localhost:8000/users/999
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>404 Not Found</title>
</head>
<body align="center">
    <div align="center">
        <h1>404: Not Found</h1>
        <p>The requested resource could not be found.</p>
        <hr />
        <small>Rocket</small>
    </div>
</body>
</html>

現状だと、存在しないユーザIDを指定した場合でも404は返りますが、デフォルトのままHTMLになってしまっているので、しっかりやるならエラーハンドリングが必要です。

PHPerKaigi 2020 に参加する

今年も参加します PHPerKaigi。

 

去年のはこちら。

 

今年もPHPerチャレンジがあるらしいですね

トークン…

 

今年も楽しみたいと思います!

ちなみに、今年は弊社スポンサーです。

 

Rustを初めて触ってその流れでDBからデータ取得までを試してみた

とある理由で、ほぼ遊び感覚で、Rustを使う機会がありました。
多少勉強しましたが、新しく難しい概念に悩まされ、イマイチ理解は進みません。
とりあえず手を動かしたかったので、DB取得までをやってみました。
メモとして残しますが、もっとRustらしく書けるんだろうなと感じています。
rusqliteを使って、sqlite3からデータを取得し表示するまでです。

環境構築は本家を参考にしました。
www.rust-lang.org

以降は、 rustc 1.42.0-nightly で動作確認済みです。
また、 my.db という名前でsqlite3のDBを用意していて、スキーマ等は以下の通りです。

$ sqlite3 my.db
sqlite> .schema users
CREATE TABLE users (user_id int primary key, name text not null);
sqlite> select * from users;
1|田中太郎
2|鈴木次郎

実際に書いたコードは以下です。

Cargo.toml

[package]
name = "demo"
version = "0.1.0"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
rusqlite = "*"

main.rs

extern crate rusqlite;

use rusqlite::{Connection, NO_PARAMS};

#[derive(Debug)]
struct User {
    user_id: usize,
    name: String,
}

fn get_users() -> Vec<User> {
    let conn = Connection::open("my.db").unwrap();
    let mut stmt = conn.prepare("
        SELECT
            user_id, name
        FROM
            users
        ORDER BY
            user_id ASC
    ",
    ).unwrap();

    let users = stmt.query_map(NO_PARAMS, |row| {
        Ok(User {
            user_id: row.get::<_, i64>(0).unwrap() as usize,
            name: row.get::<_, String>(1).unwrap(),
        })
    }).unwrap();

    let mut result: Vec<User> = vec![];
    for user in users {
        result.push(user.unwrap());
    }

    result
}

fn get_user(user_id: usize) -> Option<User> {
    let conn = Connection::open("my.db").unwrap();
    let mut stmt = conn.prepare("
        SELECT
            user_id, name
        FROM
            users
        WHERE
            user_id = ?
    ",
    ).unwrap();

    let id = user_id as i64;
    let mut users = stmt.query_map(&[&id], |row| {
        Ok(User {
            user_id: row.get::<_, i64>(0).unwrap() as usize,
            name: row.get::<_, String>(1).unwrap(),
        })
    }).unwrap();

    let user = users.next();
    match user {
        Some(user) => Some(user.unwrap()),
        None => None,
    }
}

fn main() {
    // 一覧取得
    println!("{:?}", get_users());
    // 存在するidを取得
    println!("{:?}", get_user(1));
    // 存在しないidを取得
    println!("{:?}", get_user(999));
}

上記を実行すると以下のようになります。

$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.02s
     Running `target/debug/demo`
[User { user_id: 1, name: "田中太郎" }, User { user_id: 2, name: "鈴木次郎" }]
Some(User { user_id: 1, name: "田中太郎" })
None

ユーザ一覧の取得とユーザ単体の取得(いる場合といない場合)についてうまくできました。
もうちょっとRustらしく書けそうなところもあり、 unwrap() しすぎな感じもありますが、今後の自分に期待します。

外部勉強会で刺激を得て社内でリファクタリングのライブコーディング会を開催してみた

はじめに

業務としてがっつりリファクタリングをやるタイミングがあって、その内容が割と良くて、部署内で共有できたら面白いなと思っていました。

そのときに思い出したのが、前に参加した 大改修!PHPレガシーコードビフォーアフター という勉強会で、独立したコアレイヤーパターンをよく扱っていて「PHPの現場」でもおなじみの shin1x1 さんによる講演(?)です。
それは、PHPer Kaigiなどで使われている fortee というシステムをライブコーディングで独立したコアレイヤーパターンにリファクタリングしていくという内容でした。
色々な設計に関するアーキテクチャを勉強してもよくわからないという気持ちもあるなかで、実際にリアルタイムでリファクタリングしていく様を見るのはやり方等が良くわかり、とても勉強になりました。
同時に、アーキテクチャに関することだけでなく、PhpStormの使い方やひいてはMacの使い方など、得られるものは多岐に渡りました。

それを思い出し、同じようなことを部署でやって共有すれば面白いのではないかと思い、リファクタリングのライブコーディング会を開催してみました。

過去のツイートの有言実行でもあります。

目的

表立った目的として、

  • リファクタリングのやり方を知る。
  • 設計を知る。
  • PhpStormの操作を知る。(会社では主にPHPを使っています)
  • 良い例を知る。
  • 反面教師を知る。

などを掲げました。

それ以外にも裏目的として、どんどん野次を飛ばしてもらうことで自分自身が書き方やツールの使い方等で知らない部分を知ることができたらと目論んでいました。

内容

どんなコードをどのようにリファクタリングしたかは言えませんが、過去に業務で行ったリファクタリングを再現したライブコーディングを行いました。

まず、会の趣旨とリファクタリングの方針や進め方を会の冒頭で説明しました。
趣旨はもちろんとして、リファクタリングの方針をしっかり理解してもらえないとライブコーディングを理解できないだろうなということで、ここでは丁寧に説明しました。

全員が理解できたら、その後で実際にリファクタリングをライブコーディング形式で行いました。 「まず、ここの処理を丸っと移します」「そして、ここをこう書き換えます」「このとき、こうすると簡単に実現できます」などと喋りながらコーディングしていきました。
こうやっていると、「ここでコミットしたいですね」「そこはこうやったら早いですよ」などと野次(褒め言葉)も飛んでくるので、「わかるわかる」「なるほど」と言いながら思いながら進めます。
一方的な説明にはしたくなかったので、ときどきそういうことがあるのは良かったです。

再現リファクタリングとは言え、圧倒的に時間は足りません。
そこで、2/3ほどの時間が経つとライブコーディングは終え、3分クッキングの「そして完成したのがこちらになります」方式で、実際に再現元となったコードをGitHub上で説明しました。

そうして、ライブコーディングと完成版の説明を全て終えると、質疑応答と参加者によるこういうやり方もあるよといった紹介タイムになり、会は終了となりました。

やってみて

ペアプロやモブプロの経験はあるものの、ライブコーディングは初めてで、ペアプロとモブプロとはまた違った難しさがありました。
多少有意義な野次を入れていただき、モブプロのような感じもありましたが、基本的には1人で喋りながらコーディングするので、とても緊張しました。
特に、通るはずのテストが通らなかったときの公開デバッグは内心ドキドキでした。
また、これほどまでに喋りながらコーディングした経験もなく、無言にならないように注意しながら進めるのも大変でした。
ゲーム実況者ってすごいなとしみじみ感じた瞬間でした。

全体的に、主催としてはとても有意義なものになったと思います。
自分のおすすめのやり方を説明しているときに、「なるほど」や「すごい」といった言葉が出ているのを聞けて、とても満足しました。
逆に、野次などで知らなかった色々な技や手法を聞けて、自分自身も何かを得るという裏目的も達成し、満足でした。

反省点

わかりきってはいましたが、自分が今どこを見ていて何をしようと考えているのかをしっかりと参加者に伝えていかないと、PhpStorm上の激しいスクロールやファイル移動だけだと本当に何もわからないだろうなと思います。
なにかをやったあとに、事後説明で済ませたことも多く反省です。

また、せっかく少人数(そのときは6人ほど)でやっていたので、ただのライブコーディングではなく、途中途中でもう少し議論など見るだけではない何かがあっても良かったと思います。
野次もあったとは言え、それ自体は稀で、基本的にこちらが喋っているだけでした。
次回はもう少しやり方を工夫したいところです。

細かいことで言うと、会議室でプロジェクターで写して開催していて、最初はChromecastで投影していて見づらかったのですが、途中でHDMIに変えて見やすくなりました。
面倒だったのでやらなかったのですが、最初からそうするべきでした。
また、どうしても多少部屋を暗くせざるを得ず、暗いPhpStormと明るいGitHubの行き来が見てる側としては苦痛だった気がします。
そのため、心構えができるように、行き来しようとするたびに「移ります」「戻ります」と言うようにしていました。

とは言え、参加者も喜んでくれた(と信じている)ので、開催して良かったです。

おわりに

外部勉強会での経験を経て自分でもやりたいと思い、しかもちょうど良い題材があったために、開催を決意し上長に相談して開催しました。
初めての経験で多少緊張もしましたが、とても楽しかったです。
また良い題材があればより良い方法で再度開催しようと思います。