patorashのブログ

方向性はまだない

pub serveに対してproxyを経由してアクセスする

Dartでサーバサイドの開発とクライアントサイドの開発を行いたい場合、サーバサイドはともかくとして、クライアントサイドの開発はpub serveを使わないと全然うまくいかないように思います。pubspec.yamlにtransformersを定義するのですが、pub serveで起動したWebサーバでしか、それが有効ではないので、それ以外の方法では、AngularDartとかは使えないんじゃないかと思います(私が詳しくないだけかもしれないけれど)。

サーバサイドはサーバサイドで起動して、クライアントサイドはpub serveで起動して、それぞれをやりとりするようにすればよさそうなのですが、ポート番号も違うし、どうすりゃいいんだろうか?と色々調べてみました。

proxyを使えばいいことに気づいた

AngelというDartフレームワークがあるのですが、それでは、angel_proxyという機能を使ってサーバサイドのAngelとクライアントサイドのAngularDartのやりとりを行っているようでした。

github.com

ざっくりいうと、Angelにアクセスすると、pub serveで起動したサーバに対してリクエストを飛ばして、その結果を返すようでした。この発想は思いつきませんでした。いや、よくある構成とかなんですが、なんかこういうパターンの解決策をど忘れしていて、パッと思いつくことができません…。くやしい…。

pubで検索してみた

proxyというキーワードがわかればこちらのものだと思い、pubでキーワード「proxy」で検索したら、shelf用のproxyなどもあるようでした。

pub.dartlang.org

shelfに則ったWebフレームワークなら、これを使うと良さそう。ですが、今はrikulo streamを使おうとしています。rikuloはshelfに対応してないので、他の方法を探してみました。

pub.dartlang.org

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を使わないような処理を書かないといけませんね。