Serverpodの通信周りを調べてみた
はじめに
サーバーの知識は一切ないが、せっかくFlutter使いのアプリエンジニアとして仕事しているのでFlutterで利用しているDartでサーバー側のアプリケーションが作れるのであれば触れてみるのもいいのではなかろうかと思いました。
ただexample等をみてみる限り通信の処理をライブラリ内部でやっているため、具体的にどんなエンドポイントにアクセスしているのか、どんな形式で通信をしているのかが見えてこなかったので、調べてみました。
この記事でわかること
- Serverpodの「Get started」の流れ
- 作成したプロジェクト内のフォルダの役割
- Serverpodで自動生成されるコード内でどんな通信が内部で行われているか
Get started
準備
GetStartedがあるので、手順通り進めていってみます。
Flutterは既に普段使いのバージョンが3.16.xであったのでOK
Dockerは以前ダウンロードしていたので、そのまま次のコマンドでserverpodをインストール
(以下のインストールコマンドを実行する際はDockerを起動している必要がありそうでした。)
dart pub global activate serverpod_cli
serverpod
serverpodのコマンドで何かしら反応があればインストール完了
次のコマンドでプロジェクトを作成(mypod
は任意のプロジェクト名)
serverpod create mypod
完了すると、以下のフォルダができました
- mypod_client
- mypod_serverの編集内容で自動生成されるパッケージプロジェクト。このパッケージは編集しないでと注意書きがありました。
- mypod_flutter
- クライアントアプリケーションのプロジェクト。いつも見るFlutterプロジェクトでAndroid,iOSはもちろんWebやWindows等のプラットフォームも自動でできていました。
- main.dartを見るとlocalhostに対して通信するようセットアップされており、起動するだけで何かしら通信しそうな感じでした。
- おそらくmypod_clientを外部パッケージとして経由して通信を行いそうです。(dioとか使いたい場合は、mypod_clientは不要そう)
- mypod_server
- route設定やエンドポイントの返却値など名称からして当たり前だがサーバー側のDartプロジェクト。
- サーバーアプリを作っていく場合はこのこのプロジェクトを編集していくことになりそう。
- 現状、何をやっているかよくわかっていないので見ていく。
起動
プロジェクト作成時に実行すべきコマンドが表示されるので、その通りにコマンドを叩いて起動してみる。
cd mypod/mypod_server
docker compose up --build --detach
dart bin/main.dart
サーバーが立ち上がったので、クライアントアプリを立ち上げてみる。
cd mypod/mypod_flutter
flutter run
アプリを起動した時の画面
名前を入力して「Send to Server」をタップしたら、Helloと言われたので何となく正常に動いていると思われる。
中身の確認
何をやっているかよくわからないmypod_server
の中身を見ていく。
フォルダ一覧
- bin
- 先ほどのサーバー起動コマンドを実行したフォルダ。
- 中身は
main.dart
のファイルがあり、main関数があった。 - ここがエントリーポイント。
- config
- yamlファイルが5つほどあるフォルダ。
-
development
,staging
,production
のyamlがあるので、それぞれ環境ごとに設定するのだろうと思われる -
passwords
はdatabaseとredisのパスワードが書いてあった -
generator
はパッと利用用途が不明だがおそらく、mypod_client
を自動生成するための設定ファイルだと思われる(今後変更することがあるのかは不明)
- deploy
-
aws
フォルダとgcp
フォルダがあり、ここら辺を利用するとそれぞれのサービスにデプロイする用の設定が書かれていそうだった。 - それぞれ、
script
とtransform
というフォルダがあり、やり方によってはいい感じでサービス毎に環境を作成とデプロイを一緒にやってくれるのかなという印象がした。
-
- generated
-
protocol.yaml
と~~.pgsql
ファイルがあった。 - generatedなので自動生成されそう。
-
- lib
- 数が多いので別途まとめます
- web
-
static
とtemplate
というフォルダがあり、static
にはcssやimage、template
にはhtmlが格納されていた。 - htmlには
{{}}
で囲われた変数があるので、その変数に対して値をバインドする仕組みがありそう。 - htmlがここにあるということは、SPAを作りたい場合は
mypod_flutter
でアプリを作るが、mypod_server
でもWebページを作成できるのか
-
libの中身
libはsrc
というフォルダとserver.dart
というファイルで構成されていますが、src
フォルダにはさらに5つのフォルダがあるので、もっと詳細に見ていきます。
- server.dart
- routingをしていそうなファイル
-
webServer.addRoute(RouteRoot(), '/')
とあるので、Webページを増やす際はここに追加していきそう - 他にも前述の
static
フォルダに対してもPathを繋げていそうだった
srcの中身
- endpoints
-
example_endpoint.dart
というファイルがある。 -
ExampleEndpoint
というクラスがあり、その中にhelloという関数が宣言されている。 -
mypod_flutter
の通信部分を見ると、example
とhello
というキーワードでエンドポイントにアクセスしているようだった。 - なのでクラス名のxxxEndpointのxxxの部分とそのクラスに書かれている関数がエンドポイントになりそう。
-
- future_calls
-
example_future_call.dart
というファイルがあるが、特に何かをしているわけではなさそう - コメントに
Schedule
等の記載があることから、スケジュール指定のバッチ実行などができるのかなという印象
-
- generated
- 自動生成されそうなフォルダ
- protocol
- example.yamlというyamlファイルがある
-
class: Example
という記載があり、endpoints
フォルダにあったExampleEndpointクラスに対応指定そうではある -
fields
という設定があり、name: String
とdata: int
という記載がある。エンドポイントに対するRequestBodyの定義だろうか?
- web
-
routes
とwidgets
というフォルダがある -
routes
にはroot.dart
をいうファイルがあり、build関数でWidgetを返却している。 - おそらく、build関数で返却するのがhtml等のページであると思われそう。
-
widgets
には👆で返却していたWidgetが定義されている。 -
super(name: 'default')
とdefault
の文字列を定義しており、これがwebフォルダ内にある、default.html
を指しているのであろう - また、serverdとrunmodeというキーに対して値を入れており、これが
default.html
内にある{{}}
で囲われている変数に値をバインドする仕組みと思われる。
-
まとめると、endpoints
とprotocol
でWebAPIの実装を行い、定期的に実行するバッチ的な処理はfuture_calls
、HTMLを返却する場合はweb
を使うといったことだと思われる。
どういう通信?
コードや設定ファイルを見ているがHTTP通信をする際の具体的なエンドポイントのPathやGET
やPOST
などの設定を見ていない気がする。。
クライアントアプリ上では以下の一文で通信をしていました。
final result = await client.example.hello(requestText);
まさかソケット通信などで常時受付可能にしているのか?と思ったので調べてみる。
「Serverpod curl」などで検索すると出てきました。
どうやら、以下のcurlコマンドで実行できるようだった。
pathにある/example
がクラス名、body
のmethod
が関数名となっているようでした。
curl --location --request GET 'http://localhost:8080/example' \
--header 'Content-Type: application/json' \
--data '{
"method": "hello",
"name": "test test"
}'
また、GET
やPOST
などのメソッドについてmypod_flutterのプロジェクトで通信をしている箇所でメソッド指定できるのかなと、引数や設定等を探してみましたがなさそうでした。
Serverpod内のコードを確認してみる。
serverpod_client_io.dart
のcallServerEndpoint
が最終的に内部で呼ばれているサーバーへのリクエストをしている場所っぽい。
var request = await _httpClient.postUrl(url);
request.headers.contentType = ContentType('application', 'json', charset: 'utf-8');
request.contentLength = utf8.encode(body).length;
request.write(body);
特になんの条件式もなく、postUrlが利用されているので強制的にPOSTが利用されていそうでした。
(普段の開発では、GETは取得、POSTは作成などの機能別で使い分けているイメージがあったので、結構不自然と感じました)
よくよくドキュメントを読み進めていくと、Web serverの項目に次の文章があった。
You can also use the web server to create webhooks or generate custom REST APIs to communicate with 3rd party services.
--- Google翻訳
Web サーバーを使用して Webhook を作成したり、サードパーティのサービスと通信するためのカスタム REST API を生成したりすることもできます。
どうやら、Serverpodでサーバーの開発をしたいが自分が用意したhttpClientライブラリ(主にdio)を使いたい場合はWebサーバー上にエンドポイントを作った方が良さそうでした。
現状はhtml,jsonの返却、redirectができるようでした。(十分な気はしていますが)
またCookie操作などもできそうでした。
(2023/12/05時点、Webサーバーの機能は実験段階であり、本稼働しているサービスに対して利用するのはやめた方が良さそうです。)
まとめ
Serverpodは1つのプロジェクト内に、アプリと通信をする用のサーバーアプリと汎用的な媒体と通信を行うためのWebサーバーの2種類がありました。
アプリと通信をするエンドポイントについては、全てPOSTで通信が行われ
アプリ上では以下コードで通信が可能で、
final result = await client.example.hello(requestText);
内部的には以下の通信が行われていました。
(POST) http://localhost/[クラス名]/
{
method: [関数名],
args: [json形式のパラメータ]
}
もし、dioなどのFlutterで利用可能な他のHttpClient系のプラグインを使う場合やPOST
,GET
を使い分ける場合はWebサーバーの機能を利用した方が良さそう。
また、今回は試していないが認証やDBへの書き込みといった機能はセットアップ時点でデフォルトでServerpodに用意されており、簡単に実装できそうなので試してみようと思います。
Discussion