LoginSignup
81
78

More than 3 years have passed since last update.

Node.js: 外部パッケージを使わずに14行で作るHTTPリバースプロキシ

Last updated at Posted at 2019-10-15

この投稿では、外部パッケージを一切使わずに、Node.jsビルトインモジュールだけで作る、たった14行のHTTPリバースプロキシの実装を紹介する。なお、本稿のサンプルコードはTypeScriptで提示する。

14行のHTTPリバースプロキシの実装

ここで紹介するリバースプロキシの実装は、主に次の流れでHTTPリクエストをさばく:

  1. リバースプロキシは普通のHTTPサーバとして起動する。
  2. リバースプロキシは、クライアントからのリクエストを受け取ったとき、バックエンドサーバ向けのHTTPリクエストを新たに生成する。
  3. クライアントからのリクエストをコピーし、バックエンドにリクエストを送る。
  4. リバースプロキシは、バックエンドからレスポンスを受け取ったとき、それをクライアント向けレスポンスにコピーし、クライアントにレスポンスを返す。
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ドメインソケット)の場合、バックエンドサーバーがストリームレスポンスを返す場合、バックエンドサーバーのレスポンスが遅すぎてプロキシサーバでタイムアウトさせる場合などのサンプルがある。

81
78
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
81
78