cover

この記事はGithubが落ちている時間で書かれました。@kyo_agoです。

今日はkarma middlewareでlocal proxyを立ててテストからサーバと通信する方法を紹介したいと思います。

karma middlewareとは

karma middlewareとはexpressのmiddlewareと同じようなもので、karmaがサーバとして動作している時に受け取った通信に対して何かしらの処理を行えるようにするものです。

実際karma自身のいくつかの機能もmiddlewareとして提供されており、設定のproxiesやfilesも実態はmiddlewareになっています。

karma middlewareの書き方

公式ドキュメントまんまですが、karma.conf.jsに以下のような記述を行うことで定義できます。
(実際の処理はpluginとして定義する)

middleware: ['customMiddleware'],
plugins: [
  {'middleware:customMiddleware': ['factory', function (config) {
    // configはこの設定ファイルが生成しているconfig objectに初期値等が追加されたもの
    return function (request, response/*, next */) {
      // 自分の処理対象外の通信の場合はnext()を呼ぶ
      response.writeHead(200);
      return response.end("content!");
    };
  }]}
],

上記のmiddlewareを定義するとテストコードからXHR等でkarma serverへ通信した場合、常にcontent!という内容が返ります。

proxy middlewareとは

これはkarma自体が標準で提供している機能で、設定ファイルにproxiesを書くことでkarma serverへの通信を別のサーバへ転送する機能です。

この機能を使うことにより、localhost上で実行されているkarma環境から実サーバへXHR等を転送して通信することができます。

このままならふつうに設定ファイルへ定義を記述しテストコードから実サーバへ通信すればいいのですが、通信対象のAPIがkarma環境からproxyされることを想定していない場合面倒なことになります。
(HostやOriginが違っているとか、headerをいじらないと困る場合等)

proxy middlewareの書き方

弊社ではcustomContextFileを使ってkarma上にアプリケーションコードを読み込む構成でテストしていますが、せっかくなのでAPIに対しての通信をすべてproxyできないか考えました。

実際いくつかのヘッダを書き換えることでAPIに対して通信できることがわかったのですが、karma標準のproxiesではSSLのエラー無視と通信時のOriginを書き換えられる程度で細かい制御ができません。

そこで、標準のProxy middlewareを無視して独自のProxy middlewareを差し込むことで通信の差し替えを行うことにしました。

具体的にはkarma/proxy.js at master · karma-runner/karmaを手元に落とし、karma.conf.jsに以下の記述を行います。

libProxies: {
  '/example/': 'https://www.example.com/'
},
proxyValidateSSL: false,
middleware: ['libProxy'],
plugins: [
  {'middleware:libProxy': ['factory', function (config) {
    var proxy = require('./proxy');
    return proxy.create(config, config.libProxies);
  }]}
],

さらにproxy.js内のhttpProxy.createProxyServerの後あたりを以下のように修正します。

proxy.on('proxyReq', function(proxyReq, req, res, options) {
  proxyReq.setHeader('Host', 'www.example.com')
  proxyReq.setHeader('Origin', 'https://www.example.com')
  proxyReq.setHeader('Referer', 'https://www.example.com/')
})
proxy.on('proxyRes', function (proxyRes) {
  if (proxyRes.headers['set-cookie']) {
    proxyRes.headers['set-cookie'] = proxyRes.headers['set-cookie'].map(function (cookie) {
      // karma内はhttpなのでsecureを消す。
      return cookie.replace(/\s*secure;?/i, '')
    })
  }
  if (proxyRes.headers['location']) {
    // リダイレクト先を変更(http的には不正だけど、まあ動くので)
    proxyRes.headers['location'] = proxyRes.headers['location'].replace(/^https:\/\/www.example.com/i, '/example/')
  }
})

このproxy objectはnodejitsu/node-http-proxy: A full-featured http proxy for node.jsなので、詳細はドキュメントを参照ください。

この方法を使うことで/example/hogeへアクセスするとhttps://www.example.com/hogeへかなり透過的にアクセスできるようになるため、APIと直接通信してテストを実行することができました。

もちろんkarmaの他に別ポートで何らかのlocal proxyを作成することもできます。
ただ、その場合、karma以外のprocessの管理が必要だったり、originが別になることでJSから扱いが面倒だったりします。
そんな場合、karma middlewareを使うことで非常に簡単に同じoriginのlocal proxyが作成できるので、もしテストケースから柔軟な通信を行いたい場合は試してみてください。