rakudaでWebアプリケーションのHTTPクライアントを"段階的に"作る

まえおき

最近、会社でつかっている勤怠入力システムが刷新されて、イケイケなSPAっぽいものになった。

最近のSPAは、ReactとかVueとかで作っているならだいたい、裏でJSON APIを叩いて結果をレンダリングするみたいな作りをしている。ということは、自分用にWebアプリケーションのふりをして必要な機能だけ詰め込んだコンソールアプリケーションを作ることもできる。

そんなときに、「curlやHTTPieでJSON APIの挙動を調べてGoやDartコマンドラインツールを作る」といったプロセスを経ることが多いが、それって結構非効率じゃないだろうか?

動作確認の際に、いちいちAuthorizationヘッダーを付けるのがめんどくさい!となってrakudaというものを作ってみたわけだが。

yusukeiwaki.hatenablog.com

ただそれでも、デバッグ用にHTTPクライアントを作って、さらにコマンドラインツール本体向けにHTTPクライアントを作って、というのはあまりに非効率じゃないだろうか。

rakudaのデバック用HTTPクライアントでまずは挙動を探って(=scaffolding)、HTTPリクエストすべき内容がわかったところで、その部分だけ足場を解体して(段階的に)本実装をする、みたいなのがなんとなく理想の開発プロセスに思える。そして rakuda をそういうものにしてみた。

rakudaは何がラクじゃなかったのか

rakudaはもともと「dart createして、baseURLとinterceptorさえ実装すれば、あとはコマンドライン引数を渡すだけでイイカンジのデバッグ用HTTPクライアントが作れる」ライブラリであった。

// bin/androidmanagement.dart

Future<Response> auth(PerformRequest performRequest, Request request) async {
  final response = await performRequest(request);
  // 認証ヘッダーを付ける処理など
  return response;
}


Future<void> main(List<String> arguments) async {
    await createJSONClient(
      arguments,
      baseURL: 'https://androidmanagement.googleapis.com/v1',
      interceptors: [auth],
    );
  }
}

コマンドライン引数を渡すと通信結果が勝手にprintされるので、たとえば「ログインAPIは仕様がつかめたので本実装しよう」とする。

// bin/androidmanagement.dart

Future<Response> auth(PerformRequest performRequest, Request request) async {
  final response = await performRequest(request);
  // 認証ヘッダーを付ける処理など
  return response;
}


Future<void> main(List<String> arguments) async {
    if (arguments[0] == 'login') {
      // ログイン処理.
      const  response = await http.post('/login', body: { username: await prompt(), password: await prompt() })
      if (reponse. statusCode == 200) {
        // 成功したら認証トークンの保存
      } 
    }

    await createJSONClient(
      arguments,
      baseURL: 'https://androidmanagement.googleapis.com/v1',
      interceptors: [auth],
    );
  }
}

Dartのhttpやdioなどを使うことになるだろう。そして、そこでもまたhttpやdio向けの認証トークンの保存処理だったり認証アクセス用のinterceptorだったりを書くことになる。

ようするに、rakuda向けに作ったauthインターセプターだったり、その内部で使っている認証トークンの保存処理などが再利用できない

改善してみた

コマンドライン引数を渡すと、あとはいい感じに通信して結果をプリントしてくれる」という特性がよくないことはわかった。

これを「コマンドライン引数を渡すと、いい感じにHTTPリクエストを生成してくれる」部分と「HTTPリクエストを渡すと素直にHTTPレスポンスを返してくれる」部分とに分けることで、再利用できない問題を解決できそうである。

Request(HTTPリクエスト内容を表すもの)とRequestContext(リクエストを実行する環境)とに分けて考えることで、一部のリクエストはデバッグ用途でコマンドライン引数から受け取って実行し、一部のリクエストはクライアントを本実装したものから実行する、interceptorsは共通実装、という構成にできる。

いやぁ、素直になった。うん。(自己満足)


まとめ

「最初は挙動を探って、挙動がつかめたやつから本実装する」というプロセスにマッチするような便利なやつを作った。みんなSPAのフリをするコマンドラインツールみたいなの作りたくなったら、ぜひ使ってみてね。

github.com

ちなみに、同じ改善をいれたJS版 (ライブラリ名は @zatsu/core) もある。

github.com