🕶️

Serverpodの通信周りを調べてみた

2023/12/23に公開

はじめに

サーバーの知識は一切ないが、せっかくFlutter使いのアプリエンジニアとして仕事しているのでFlutterで利用しているDartでサーバー側のアプリケーションが作れるのであれば触れてみるのもいいのではなかろうかと思いました。
ただexample等をみてみる限り通信の処理をライブラリ内部でやっているため、具体的にどんなエンドポイントにアクセスしているのか、どんな形式で通信をしているのかが見えてこなかったので、調べてみました。

この記事でわかること

  • Serverpodの「Get started」の流れ
  • 作成したプロジェクト内のフォルダの役割
  • Serverpodで自動生成されるコード内でどんな通信が内部で行われているか

https://serverpod.dev/

Get started

準備

GetStartedがあるので、手順通り進めていってみます。
https://docs.serverpod.dev/

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フォルダがあり、ここら辺を利用するとそれぞれのサービスにデプロイする用の設定が書かれていそうだった。
    • それぞれ、scripttransformというフォルダがあり、やり方によってはいい感じでサービス毎に環境を作成とデプロイを一緒にやってくれるのかなという印象がした。
  • generated
    • protocol.yaml~~.pgsqlファイルがあった。
    • generatedなので自動生成されそう。
  • lib
    • 数が多いので別途まとめます
  • web
    • statictemplateというフォルダがあり、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の通信部分を見ると、examplehelloというキーワードでエンドポイントにアクセスしているようだった。
    • なのでクラス名のxxxEndpointのxxxの部分とそのクラスに書かれている関数がエンドポイントになりそう。
  • future_calls
    • example_future_call.dartというファイルがあるが、特に何かをしているわけではなさそう
    • コメントにSchedule等の記載があることから、スケジュール指定のバッチ実行などができるのかなという印象
  • generated
    • 自動生成されそうなフォルダ
  • protocol
    • example.yamlというyamlファイルがある
    • class: Exampleという記載があり、endpointsフォルダにあったExampleEndpointクラスに対応指定そうではある
    • fieldsという設定があり、name: Stringdata: intという記載がある。エンドポイントに対するRequestBodyの定義だろうか?
  • web
    • routeswidgetsというフォルダがある
    • routesにはroot.dartをいうファイルがあり、build関数でWidgetを返却している。
    • おそらく、build関数で返却するのがhtml等のページであると思われそう。
    • widgetsには👆で返却していたWidgetが定義されている。
    • super(name: 'default')defaultの文字列を定義しており、これがwebフォルダ内にある、default.htmlを指しているのであろう
    • また、serverdとrunmodeというキーに対して値を入れており、これがdefault.html内にある{{}}で囲われている変数に値をバインドする仕組みと思われる。

まとめると、endpointsprotocolでWebAPIの実装を行い、定期的に実行するバッチ的な処理はfuture_calls、HTMLを返却する場合はwebを使うといったことだと思われる。

どういう通信?

コードや設定ファイルを見ているがHTTP通信をする際の具体的なエンドポイントのPathやGETPOSTなどの設定を見ていない気がする。。

クライアントアプリ上では以下の一文で通信をしていました。

final result = await client.example.hello(requestText);

まさかソケット通信などで常時受付可能にしているのか?と思ったので調べてみる。

「Serverpod curl」などで検索すると出てきました。

どうやら、以下のcurlコマンドで実行できるようだった。
pathにある/exampleがクラス名、bodymethodが関数名となっているようでした。

curl --location --request GET 'http://localhost:8080/example' \
--header 'Content-Type: application/json' \
--data '{
    "method": "hello",
    "name": "test test"
}'

また、GETPOSTなどのメソッドについてmypod_flutterのプロジェクトで通信をしている箇所でメソッド指定できるのかなと、引数や設定等を探してみましたがなさそうでした。

Serverpod内のコードを確認してみる。
serverpod_client_io.dartcallServerEndpointが最終的に内部で呼ばれているサーバーへのリクエストをしている場所っぽい。

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