この投稿では、外部パッケージを一切使わずに、Node.jsビルトインモジュールだけで作る、たった14行のHTTPリバースプロキシの実装を紹介する。なお、本稿のサンプルコードはTypeScriptで提示する。
14行のHTTPリバースプロキシの実装
ここで紹介するリバースプロキシの実装は、主に次の流れでHTTPリクエストをさばく:
- リバースプロキシは普通のHTTPサーバとして起動する。
- リバースプロキシは、クライアントからのリクエストを受け取ったとき、バックエンドサーバ向けのHTTPリクエストを新たに生成する。
- クライアントからのリクエストをコピーし、バックエンドにリクエストを送る。
- リバースプロキシは、バックエンドからレスポンスを受け取ったとき、それをクライアント向けレスポンスにコピーし、クライアントにレスポンスを返す。
import {createServer, request} from 'http'
createServer((clientReq, clientRes) => {
// クライアントから受け取ったリクエストを、バックエンドサーバに送る処理
const serverReq = request({
host: '127.0.0.1', // バックエンドHTTPサーバのホスト
port: 9000, // バックエンドHTTPサーバのポート
method: clientReq.method,
path: clientReq.url,
headers: clientReq.headers,
}).on('error', () => clientRes.writeHead(502).end()) // バックエンドサーバとの通信エラーが発生した場合
.on('timeout', () => clientRes.writeHead(504).end()) // バックエンドサーバとの通信がタイムアウトした場合
.on('response', serverRes => {
// バックエンドから受け取ったレスポンスをクライアントに送る処理
// ステータスコードやヘッダをそのまま送る
clientRes.writeHead(serverRes.statusCode!, serverRes.headers)
// HTTPボディはストリームパイプで流す
serverRes.pipe(clientRes)
})
// リクエストのHTTPボディはストリームパイプで流す
clientReq.pipe(serverReq)
}).listen(8000) // リバースプロキシサーバのポート
HTTPボディについては、大きなデータが来てもメモリを消費しにくいよう、ストリームのパイプで受け渡すようにしている。
使用例
バックエンドサーバとリバースプロキシサーバを組み合わせた完全なコードを以下に示す。
import {createServer, request} from 'http'
// バックエンドサーバ
createServer((req, res) => {
res.writeHead(200, {'X-Custom-Header': 'Header from backend server'})
res.end('Response from backend server')
}).listen(9000)
// リバースプロキシサーバ
createServer((clientReq, clientRes) => {
const serverReq = request({
host: '127.0.0.1',
port: 9000,
method: clientReq.method,
path: clientReq.url,
headers: clientReq.headers,
}).on('error', () => clientRes.writeHead(502).end())
.on('timeout', () => clientRes.writeHead(504).end())
.on('response', serverRes => {
clientRes.writeHead(serverRes.statusCode!, serverRes.headers)
serverRes.pipe(clientRes)
})
clientReq.pipe(serverReq)
}).listen(8000, () => {
// リバースプロキシサーバにリクエストを送ってみる処理
request({host: '127.0.0.1', port: 8000, method: 'GET', path: '/foo/bar'})
.on('response', response => {
console.log(response.statusCode)
console.log(response.headers)
response.pipe(process.stdout)
})
.end()
})
実行結果:
200
{
'x-custom-header': 'Header from backend server',
date: 'Tue, 15 Oct 2019 07:05:56 GMT',
connection: 'close',
'transfer-encoding': 'chunked'
}
Response from backend server
様々なユースケースを想定した動作デモ
GitHubのほうにこのプロキシサーバの使用例をいくつか実装している。
バックエンドサーバーがIPC(Unixドメインソケット)の場合、バックエンドサーバーがストリームレスポンスを返す場合、バックエンドサーバーのレスポンスが遅すぎてプロキシサーバでタイムアウトさせる場合などのサンプルがある。