ラベル Rust の投稿を表示しています。 すべての投稿を表示
ラベル Rust の投稿を表示しています。 すべての投稿を表示

2020年8月31日月曜日

Rustを学ぶ(2.5) : RustでAPIサーバー (Responseを返す)

はじめに

前回は、GETとPOSTができるようになりました。

ただし、誤ったリクエストを送った時に、その誤りの理由までは教えてくれません。

また、任意のステータスコードを返せるようになったら、連携するアプリケーション側で考慮するステータスコードを絞れます。

今回は、以下の2つを実装します。

  1. GETとPOSTで/system/error にアクセスすると404が返り、「wrong resource access.」を返す
  2. POSTで/system/pingに'{"wrong":"error"}' でアクセスすると400が返り、「wrong request.」を返す


実装する前に

そもそも現在はどういうステータスコードが返るのでしょうか?

まずは1のGETをしてみます。

% curl localhost:8080/system/error -v
*   Trying ::1...
* TCP_NODELAY set
* Connection failed
* connect to ::1 port 8080 failed: Connection refused
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /system/error HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 404 Not Found
< content-length: 0
< date: Sun, 30 Aug 2020 00:16:52 GMT
<
* Connection #0 to host localhost left intact
* Closing connection 0

404が返ります。1のPOSTもしてみます。

% curl -XPOST localhost:8080/system/error -v
*   Trying ::1...
* TCP_NODELAY set
* Connection failed
* connect to ::1 port 8080 failed: Connection refused
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> POST /system/error HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 404 Not Found
< content-length: 0
< date: Sun, 30 Aug 2020 14:21:27 GMT
<
* Connection #0 to host localhost left intact
* Closing connection 0

こちらも404です。理由は特に書かれていません。

2もやってみます。


% curl -XPOST localhost:8080/system/ping -d '{"wrong":"error"}'  -v
Note: Unnecessary use of -X or --request, POST is already inferred.
*   Trying ::1...
* TCP_NODELAY set
* Connection failed
* connect to ::1 port 8080 failed: Connection refused
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> POST /system/ping HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
> Content-Length: 17
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 17 out of 17 bytes
< HTTP/1.1 422 Unprocessable Entity
< content-length: 0
< date: Sun, 30 Aug 2020 14:24:20 GMT
<
* Connection #0 to host localhost left intact
* Closing connection

422 (Unprocessable Entity)が返っています。これは正しそうな挙動ですが、アプリケーション側で422を考慮するコードを書く必要が出てきます。今のところ私しか使う人もいないですし、ここは400 (Bad Request)でいいと考えています。もし必要ならコードを読めばいいのです。

余談ですが、curlに"Note: Unnecessary use of -X or --request, POST is already inferred."と出ています。初めて知りましたが、実はXPOSTを付けなくても-dのPOSTデータがあれば、curlはPOSTで送ることを推測してくれるみたいです。


「GETとPOSTで/system/error にアクセスすると404が返り、「wrong resource access.」を返す」を実装する


1を実装します。

9~18行目までにtide::utils::Afterを使って、外にResponseを出す前に返すレスポンス処理を加えます。

curlでGETしてみます。


% curl localhost:8080/system/error -v
*   Trying ::1...
* TCP_NODELAY set
* Connection failed
* connect to ::1 port 8080 failed: Connection refused
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /system/error HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 404 Not Found
< content-length: 30
< date: Sun, 30 Aug 2020 15:25:11 GMT
< content-type: text/plain;charset=utf-8
<
Error: wrong resource access.
* Connection #0 to host localhost left intact
* Closing connection 0

POSTもしてみます。


 % curl localhost:8080/system/error -d '{"wrong":"error"}' -v
*   Trying ::1...
* TCP_NODELAY set
* Connection failed
* connect to ::1 port 8080 failed: Connection refused
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> POST /system/error HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
> Content-Length: 17
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 17 out of 17 bytes
< HTTP/1.1 404 Not Found
< content-length: 30
< date: Sun, 30 Aug 2020 15:26:14 GMT
< content-type: text/plain;charset=utf-8
<
Error: wrong resource access.
* Connection #0 to host localhost left intact
* Closing connection 0

できているようです。


「POSTで/system/pingに'{"wrong":"error"}' でアクセスすると400が返り、「wrong request.」を返す」を実装する


2を実装していきます。

先ほどと同じように実装を追加していきます。16~20行目に追記しました。

curlしてみます。


% curl localhost:8080/system/ping -d '{"wrong":"error"}' -v
*   Trying ::1...
* TCP_NODELAY set
* Connection failed
* connect to ::1 port 8080 failed: Connection refused
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> POST /system/ping HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
> Content-Length: 17
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 17 out of 17 bytes
< HTTP/1.1 400 Bad Request
< content-length: 22
< date: Sun, 30 Aug 2020 15:33:26 GMT
< content-type: text/plain;charset=utf-8
<
Error: wrong request.
* Connection #0 to host localhost left intact
* Closing connection 0

できてる!


おわりに

今回はResponseを任意のステータスコードで、任意のメッセージを返す実装をしました。

次回はDBへの疎通確認をしたいと思います。


参考文献

先輩と覚える HTTP ステータスコード


2020年8月24日月曜日

Rustを学ぶ(2) : RustでAPIサーバー (GETとPOST)

はじめに

前回、Rustの環境構築をしたので今回はサーバーを実装します。

単純なAPIサーバーとして以下の実装をしていきます。

  1. GET /system/pingでpongを返す
  2. POST /system/ping{"ping" : "value"}を送ると、{"pong" : "value"}を返す

WebサーバーはTideを使います。Warpのような立ち位置にあり、簡単なWebサーバーを作ることができます。ちなみに、Tideはv0.13.0からProductionで使えるようになったようです。

Tideの起動


Tideを動かしてみます。

任意の場所でプロジェクトを作ります。

% cargo new example-tide
% cd example-tide

Cargo.tomlのdependenciesに以下を追記します。

[dependencies]
tide = "0.13.0"
async-std = { version = "1.6.0", features = ["attributes"] }

main.rsを以下に変更します。


ここまでできたらcargo runします。
試しにcurlしてみましょう。

% curl localhost:8080
Hello, world!

Webサーバーの起動に成功しました。

「GET /system/pingでpongを返す」を実装


以下のように5行目を変更します。


curlしてみましょう。

% curl localhost:8080/system/ping
pong

できましたが、APIサーバーとしては扱いやすいようにjsonで返したいです。

Rustでjsonといえばserde_jsonですが、Tideはすでにjson macroを持っています。
ですので、serde_jsonを使わずともjsonを返すことが可能です。
以下のように5行目を変更します。


curlしてみましょう。

% curl localhost:8080/system/ping
{"ok":"pong"}

jsonで返すことができました。

「POST /system/ping{"ping" : "value"}を送ると、{"pong" : "value"}を返す」を実装する


6行目にPOSTを受け付ける記述を追記します。(この時点ではPOSTパラメータを無視しています)


curlしてみましょう。

% curl -XPOST localhost:8080/system/ping -d '{"ping":"value"}'
{"ok":"pong"}

やった!POSTで受付に成功しています。

それではPOSTパラメータを受け取り、値を返すようにします。
まずはserdeとserde_jsonをdependenciesに追加します。

[dependencies]
tide = "0.13.0"
async-std = { version = "1.6.0", features = ["attributes"] }
serde = "1.0.114"
serde_json = "1.0"

serdeは構造体をSerialize、Deserializeする時に使います。これで構造体を簡単にjsonにすることができます。(ここはもしかしたらTideがやってくれる...?)

以下のようにmainを修正します。


curlしてみましょう。

% curl -XPOST localhost:8080/system/ping -d '{"ping":"value"}'
{"pong":"value"}

できました!

おわりに


今回はTideでルーティング、GETとPOSTができるようになりました。

次回は、ちゃんとResponseを返す(ステータスコードなど)ところまでやりたいと思います。

2020年6月1日月曜日

Rustを学ぶ(1) : Rustの環境構築

はじめに


Rust langに入門します。
モチベーションは次の2つです。
  • 新しいプログラミング言語パラダイム(所有権やライフタイムなど)を学びたい。
  • システムプログラミングとして低レイヤを学びたい。

環境


Mac miniを使います。RustはMacをサポートしています。
  • CPU: 6コア Intel Core i7
  • メインメモリ: 16GB
  • シェル: zsh
%はプロンプトを示し、<code>ぽいところは実際の画面出力やコードです。

Installation


公式にあるrustupを使います。TerminalやiTermなどからcurlを実行します。

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

curlを実行すると3つのツールがインストールされます。
  • rustc
    • Rustのソースコードをコンパイルします。
  • cargo
    • ビルドシステムとパッケージマネージャの役割をします。一番よく使います。
  • rustup
    • Rustのバージョンアップや異なるリリースチャネルからのインストールやクロスコンパイル(異なる機種へのコンパイル)をします。
途中で次の選択肢が出ます。初心者のためデフォルトの1を選択します。
やりたいことが他にあれば考える項目だと思います。

Current installation options:
    
    
       default host triple: x86_64-apple-darwin
         default toolchain: stable
                   profile: default
      modify PATH variable: yes
    
    1) Proceed with installation (default)
    2) Customize installation
    3) Cancel installation
    >1

ここでシェルを読み込み直す(. ~/.zshrc)かTerminalを閉じると、rustcなどのツールが使えるようになります。

% rustc --version
    rustc 1.43.1 (8d69840ab 2020-05-04)

今回インストールされたのは1.43.1のようです。
ちなみにRustにはstableとnightlyとbetaのチャネルがあります。基本は安定しているstableか、新実装の入ったnightlyで良いと思います。僕はstableにしています。

コンパイルと実行


Rustを任意の場所で動かします。僕はRustを書くときにVS Codeを使いたいと思いますので、事前にVS CodeのRust Extensionを入れています。

% mkdir rust_projects
   workspace % cd rust_projects
   rust_projects % mkdir hello_world
   rust_projects % cd hello_world
   hello_world % code .

コードは次の通りです。記念すべき最初の1歩、"Hello, World"です。

fn main() {
      println!("Hello, world!");
}

コンパイルして実行します。rustcを使います。

hello_world % rustc main.rs
hello_world % ./main
Hello, world!

出ました!
ところで、rustcではなくcargoを使うともっと簡単に実行できます。

プロジェクトの作成


せっかくcargoを使うのでプロジェクトから作ってみます。先の場所とは違う任意の場所に移動します。

% cargo new sample --bin
Created binary (application) `sample` package
% cd sample
% ls
Cargo.toml src

Cargo.tomlはビルドの設定ファイルです。その中身はgitに設定された内容が入っています。次のようになっているはずです。yournameとmailは個別に置き換えてください。

   [package]
   
   name = "sample"
   version = "0.0.1"
   authors = [ "yourname <mail@example.com>" ]

src/main.rsには先ほど手で作った"Hello, World"と同じ内容が書かれているはずです。では実行します。

% cargo run
       Finished dev [unoptimized + debuginfo] target(s) in 0.00s
        Running `target/debug/sample`
Hello, world!

出ました!
cargo runでコンパイルと実行をセットでやってくれます。
コンパイルだけしたいときはcargo buildでできます。
リリースビルドをしたいときはcargo build --releaseです。

ちなみに、次の2つは等価です。
  • cargo new sample --bin
  • cargo new sample
ライブラリを作る場合はcargo new sample --libにします。

おわりに


Rustの環境構築をしました。ほとんどの場合はRustがサポートされているため環境構築は難しくないと思います。次回はRustでサーバーを作りたいと思います。


参考