Node.js
AWS
Express
S3

node.js + Expressでリクエスト単位でプロキシするにはrequestをpipeするだけで良い

More than 1 year has passed since last update.

概要

"express proxy" で普通に検索すると、express-http-proxyとか、express-request-proxyとか言うパッケージが見つかると思います。ただ、どちらもあるサイトをまるごと別のサイトにプロキシするという目的のために作られていて、キャッシュとかインジェクションの仕組みがあって便利なものの、「あるリクエストをプロキシしたい」というシンプルな用途には向いていません。また、いずれも、URLのエンコードにバグがあって、それで苦労させられたりしました。

最初、上記のパッケージをフォークしていろいろコードをいじっていたのですが、コードを見ていたら、単にリクエスト単位でプロキシをするだけなら、Expressの機能だけで、ものすごく簡単にできるということが分かりました。要するに、requestの結果をresにパイプするだけで、プロキシできるのです。まぁ、言われてみれば、Expressのresponseは、WritableStreamなので、そりゃそうか・・・という感じですが、これを知ったときは、拍子抜けしました。

当然、生のHTTPがそのまま返されるだけなので、レスポンスの加工には向いていませんが、レスポンスの加工が必要ないのなら、これで十分です(その気になれば、テキストレベルでのヘッダーの微調整くらいなら簡単にできる思いますが・・・・)。

サンプルコード

request側は、基本的には、req.headersをそのまま渡せば良いのですが、hostだけは削除しないといけないようです。セキュリティも考慮すると、 'authorization'と'cookie'も削除するのが妥当でしょう。用途によってはx-forwarded-* の変更(あるいは削除)が必要かもしれません。

const request = require('request');

function (req, res, next) {
  const proxyRequestHeaders = Object.assign({}, req.headers);
  for(key of ['host', 'authorization', 'cookie']){
    delete proxyRequestHeaders.headers[key]
  }
  const proxyUrl = 'http://calculated.proxy.url/';
  request({
    url: proxyUrl,
    method: req.method,
    headers: proxyRequestHeaders,
  }).pipe(res);
}

用途

ちなみに、なんでこんなことをやろうとしたかというと、AWS S3のsignedURLをプロキシで返したかったのです。基本的には、302 redirectで問題ないのですが、後方互換性のためproxyする必要がありました。proxyだと、S3側で、if-none-matchヘッダとかrangeヘッダの処理とか細かいことを全部やってくれるので便利です。