Dartでサーバサイドの開発とクライアントサイドの開発を行いたい場合、サーバサイドはともかくとして、クライアントサイドの開発はpub serve
を使わないと全然うまくいかないように思います。pubspec.yamlにtransformersを定義するのですが、pub serve
で起動したWebサーバでしか、それが有効ではないので、それ以外の方法では、AngularDartとかは使えないんじゃないかと思います(私が詳しくないだけかもしれないけれど)。
サーバサイドはサーバサイドで起動して、クライアントサイドはpub serve
で起動して、それぞれをやりとりするようにすればよさそうなのですが、ポート番号も違うし、どうすりゃいいんだろうか?と色々調べてみました。
proxyを使えばいいことに気づいた
AngelというDartのフレームワークがあるのですが、それでは、angel_proxyという機能を使ってサーバサイドのAngelとクライアントサイドのAngularDartのやりとりを行っているようでした。
ざっくりいうと、Angelにアクセスすると、pub serve
で起動したサーバに対してリクエストを飛ばして、その結果を返すようでした。この発想は思いつきませんでした。いや、よくある構成とかなんですが、なんかこういうパターンの解決策をど忘れしていて、パッと思いつくことができません…。くやしい…。
pubで検索してみた
proxyというキーワードがわかればこちらのものだと思い、pubでキーワード「proxy」で検索したら、shelf用のproxyなどもあるようでした。
shelfに則ったWebフレームワークなら、これを使うと良さそう。ですが、今はrikulo streamを使おうとしています。rikuloはshelfに対応してないので、他の方法を探してみました。
http_request_proxyはすごくシンプルなproxyです。手始めにこれを使ってみることにしました。
http_request_proxyはダメだった
結論から書くと、これは使えませんでした。ちゃんとproxyの役割はしてくれていたのですが、pub serve
が返すレスポンスのステータスが304のときに、content typeがtext/plain
になってしまい、cssファイルやjsファイルにアクセスできても「content typeがtext/plainだから何もしないよー」というログがコンソールに表示されていました。悲しい…。
http_request_proxyを参考にして作り直す
とはいえ、http_request_proxyはファイルを返すことには成功していたので、これを参考にcontent typeを任意のものに書き換えるように修正したproxyを作りました。libディレクトリ以下に定義しました。http_request_proxyとの違いは、pub serve
から返ってきたcontent typeをそのまま設定するのではなく、request.uri.path
の拡張子を元にcontent typeを設定している点です(それ以外はほぼ同じ)。
library stream_sample; import 'dart:io'; import 'dart:async'; import "package:stream/stream.dart"; import "package:rikulo_commons/io.dart" show getContentType; import 'package:path/path.dart' as path; class PubServeProxy { String _host; int _port; PubServeProxy(this._host, this._port); Future forward(HttpConnect connect) async { HttpRequest request = connect.request; final HttpClient client = new HttpClient(); final HttpClientRequest proxyRequest = await client.open( request.method, _host, _port, request.uri.path); await proxyRequest.addStream(request); final HttpClientResponse proxyResponse = await proxyRequest.close(); final HttpResponse response = request.response; response.statusCode = proxyResponse.statusCode; response.headers.contentType = getContentType(path.extension(request.uri.path)); await response.addStream(proxyResponse); await response.flush(); await response.close(); client.close(); return null; } }
これを、rikuloのurlマッピングに使います。
PubServeProxyを使う
では、実際に使ってみましょう。
rikulo streamはport 8080で、pub serveはport 8000で起動させました。コードはrikulo streamのサンプルを実行した名残があるのですが、重要なのは_mapping
のところです。/assets/以下と、/packages/以下にアクセスがあった場合、pub serve
に対して取得しにいっています。
library stream_sample; import 'dart:io'; import 'dart:async'; import "dart:convert" show JSON; import "package:stream/stream.dart"; import "package:rikulo_commons/io.dart" show getContentType; import 'package:stream_sample/pub_serve_proxy.dart'; part "client/helloView.rsp.dart"; var proxy = new PubServeProxy('localhost', 8000); //URI mapping var _mapping = { "/server-info": serverInfo, "/hello": helloView, "/assets/.*": proxy.forward, "/packages/.*": proxy.forward }; void serverInfo(HttpConnect connect) { final info = {"name": "Rikulo Stream", "version": connect.server.version}; connect.response ..headers.contentType = getContentType("json") ..write(JSON.encode(info)); } void main() { new StreamServer( homeDir: 'client', uriMapping: _mapping, ).start(); }
これで、http://localhost:8080のみにアクセスしつつ、フロントエンドもDartで快適に開発できるようになりました!
注意点
現時点では、単純にpub serve
ファイルが取得できるようになっただけなので、buildして実際にファイルが存在する場合などのルーティングは全く考慮できていません。ファイルが存在する場合はproxyを使わないような処理を書かないといけませんね。