はじめに
前回は、Express と http-proxy-middleware を使って、最小構成のリバースプロキシを作りました。
今回はその続きとして、リバースプロキシを通したときに、リクエストヘッダやパスがどのように転送されるのかを確認します。
動作確認には、受け取ったリクエスト内容を返してくれる httpbingo.org を使います。
この記事では、主に次の2つのエンドポイントを使います。
| エンドポイント | 確認できること |
|---|---|
/headers |
転送先に届いたリクエストヘッダをJSONで確認できる |
/dump/request |
転送先に届いたリクエストをHTTPリクエストに近い形で確認できる |
前提
前回作成したリバースプロキシを使います。
クライアントからは http://localhost:3000/api/... にアクセスし、実際には https://httpbingo.org/... へ転送する構成です。
.env は次の内容にしています。
PORT=3000
TARGET_URL=https://httpbingo.org
また、プロキシ設定では次の2つを指定しています。
changeOrigin: true,
pathRewrite: {
"^/api": "",
},
changeOrigin と pathRewrite の挙動を、httpbingo のレスポンスを見ながら確認していきます。
ヘッダの転送を確認する
まずは /headers を使って、転送先に届いたリクエストヘッダを確認します。
基本のヘッダを見る
まずは httpbingo.org/headers をプロキシ経由で呼び出します。
curl -s http://localhost:3000/api/headers
レスポンスには、転送先に届いたリクエストヘッダが含まれます。
{
"headers": {
"Accept": [
"*/*"
],
"Host": [
"httpbingo.org"
],
"User-Agent": [
"curl/8.x.x"
],
"Via": [
"..."
],
"X-Forwarded-For": [
"..."
],
"X-Forwarded-Proto": [
"https"
]
}
}
ここで見たいのは Host です。
{
"Host": [
"httpbingo.org"
]
}
クライアントからは localhost:3000 にアクセスしていますが、転送先の httpbingo から見ると Host は httpbingo.org になっています。
これは http-proxy-middleware に指定している次の設定によるものです。
changeOrigin: true
changeOrigin: true を指定すると、転送先へ送る Host ヘッダがターゲットのホストに合わせて書き換えられます。
今回であれば、イメージとしては次のような変化です。
Host: localhost:3000
↓
Host: httpbingo.org
外部APIへプロキシする場合、転送先のサーバが Host ヘッダを見てリクエストを処理することがあります。そのため、外部API向けのプロキシでは changeOrigin: true にしておくと扱いやすいです。
なお、Via や X-Forwarded-* のようなヘッダも含まれることがあります。
これらは今回の Express サーバで明示的に付けたものではありません。httpbingo 側の実行基盤や途中のプロキシによって追加されるヘッダもあります。そのため、実行するタイミングや環境によって、レスポンスに含まれるヘッダは変わります。
独自ヘッダを付けて確認する
次に、クライアントから独自ヘッダを付けてリクエストします。
curl -s http://localhost:3000/api/headers \
-H "X-Request-Id: local-test-001" \
-H "X-Client-Name: express-proxy-sample"
レスポンスには、指定したヘッダが含まれます。
{
"headers": {
"X-Client-Name": [
"express-proxy-sample"
],
"X-Request-Id": [
"local-test-001"
]
}
}
クライアントから送った X-Request-Id と X-Client-Name が、Express のプロキシを通って httpbingo まで届いています。
つまり、この構成では、特に削除や上書きをしない限り、クライアントから送ったヘッダは転送先にも渡されます。
パスとリクエスト行を確認する
次に、/dump/request を使って、転送先から見たリクエストの形を確認します。
/dump/request で確認する
/headers はJSONで見やすい一方で、リクエスト行やパスの見え方までは確認しづらいです。
そこで、次は /dump/request を使います。
curl -s http://localhost:3000/api/dump/request \
-H "X-Request-Id: dump-test-001"
レスポンス例です。
GET /dump/request HTTP/1.1
Host: httpbingo.org
Accept: */*
Connection: keep-alive
User-Agent: curl/8.x.x
Via: ...
X-Forwarded-For: ...
X-Forwarded-Proto: https
X-Request-Id: dump-test-001
実際のレスポンスには Fly-* や X-Forwarded-* など、httpbingo 側の実行基盤によって追加されるヘッダが含まれることがあります。
注目したいのは先頭行です。
GET /dump/request HTTP/1.1
クライアントから呼び出したURLは次のとおりです。
http://localhost:3000/api/dump/request
しかし、httpbingo に届いた時点では /api が外れて、/dump/request として見えています。
これは、プロキシ設定で次のように pathRewrite を指定しているためです。
pathRewrite: {
"^/api": "",
}
ローカル側では /api をプロキシの入口として使い、転送先には /api を含めない形にしています。
クエリ文字列も確認する
クエリ文字列付きの場合も確認します。
curl -s "http://localhost:3000/api/dump/request?name=sample&lang=ja" \
-H "X-Request-Id: query-test-001"
レスポンス例です。
GET /dump/request?name=sample&lang=ja HTTP/1.1
Host: httpbingo.org
User-Agent: curl/8.x.x
X-Request-Id: query-test-001
/api は外れていますが、?name=sample&lang=ja のクエリ文字列は維持されています。
つまり、今回の pathRewrite ではパス先頭の /api だけを取り除き、クエリ文字列はそのまま転送されています。
changeOrigin の挙動を確認する
ここまでの確認で、changeOrigin: true によって Host ヘッダが httpbingo.org になることを確認しました。
changeOrigin: false にするとどうなるか
ここまでの確認では、changeOrigin: true の状態を見てきました。
では、changeOrigin: false にするとどうなるでしょうか。
設定としては、次のような状態です。
changeOrigin: false,
この状態で /api/headers を呼び出します。
curl -s http://localhost:3000/api/headers
今回の環境では、次のレスポンスになりました。
{
"error": "Bad Gateway",
"message": "Failed to connect to the target server."
}
サーバ側には次のようなログが出ました。
[Proxy] GET /api/headers -> https://httpbingo.org/headers
Client network socket disconnected before secure TLS connection was established
changeOrigin: false にした場合、クライアントから来た Host を維持する方向の挙動になります。
ただし、外部の HTTPS サービスへ接続する場合、転送先が期待するホスト名と合わないことで、レスポンスを確認する前に接続が成立しないことがあります。
少なくとも今回の https://httpbingo.org 宛てでは、changeOrigin: false の状態では接続できませんでした。
そのため、外部APIへリクエストを転送する用途では、基本的には changeOrigin: true にして、転送先に合わせた Host ヘッダで送る方が扱いやすいです。
サーバ側ログを確認する
最後に、Express 側のログも見ておきます。
プロキシ時のログを見る
今回の実装では、プロキシ時に次のログを出しています。
proxyReq: (proxyReq, req) => {
console.log(`[Proxy] ${req.method} ${req.originalUrl} -> ${config.targetUrl}${req.url}`);
}
たとえば /api/headers と /api/dump/request を呼び出すと、サーバ側には次のようなログが出ます。
[Proxy] GET /api/headers -> https://httpbingo.org/headers
[Proxy] GET /api/dump/request -> https://httpbingo.org/dump/request
左側の /api/headers は、クライアントから Express に届いたパスです。
右側の https://httpbingo.org/headers は、転送先として見ているURLです。
このログからも、ローカル側の入口として使っている /api が、転送先では外れていることが分かります。
おわりに
今回は、Express と http-proxy-middleware で作ったリバースプロキシを通したときに、リクエストヘッダやパスがどのように転送されるかを確認しました。
確認できたことは次のとおりです。
-
/headersを使うと、転送先に届いたヘッダをJSONで確認できる - 独自ヘッダも、特に削除しなければ転送先へ届く
-
/dump/requestを使うと、転送先から見たパスやヘッダを確認しやすい -
pathRewriteによって、ローカル側の/apiを外して転送できる -
changeOrigin: trueにすると、Hostヘッダが転送先のホストに合わせられる - 外部 HTTPS サービスへ転送する場合、
changeOrigin: falseでは接続できないケースがある
リバースプロキシは「リクエストを転送するだけ」に見えますが、実際には Host、独自ヘッダ、パス、クエリ文字列など、確認しておきたいポイントがいくつかあります。
転送先にどう見えているかを確認できるエンドポイントを使うと、プロキシ設定の挙動をひとつずつ切り分けやすくなります。