TypeScript: node-http-proxyを用いリクエスト/レスポンスを書き換えるリバースプロキシを作る方法

本稿では、node-http-proxyを使い、TypeScriptでプログラマブルなリバースプロキシを作る方法を紹介する。

# 本稿のゴール

HTTPリクエストやレスポンスの内容を書き換えるリバースプロキシをTypeScriptで書けるようになる。

# node-http-proxy とは

[node-http-proxy]は、プログラマブルなHTTPプロキシライブラリで、WebSocketもサポートしている。リバースプロキシやロードバランサを実装するのに向いている。

# node-http-proxyをインストールする

```bash
yarn add http-proxy
```

```bash
yarn add -D @types/http-proxy
```

# 最も単純なリバースプロキシを実装する

ここでは一番シンプルな形でリバースプロキシを実装してみる。

```ts
import http, {IncomingMessage, ServerResponse} from 'http'
import httpProxy from 'http-proxy'

// HTTPサーバ
http.createServer((req: IncomingMessage, res: ServerResponse): void => {
    res.writeHead(200, {'Content-Type': 'text/plain'})
    res.write('request successfully proxied!' + '\n' + JSON.stringify(req.headers, null, 2))
    res.end()
}).listen(9000)

// プロキシサーバ
httpProxy.createProxyServer({target: 'http://127.0.0.1:9000'}).listen(8000)
```

このサンプルコードでは、次のことをする。

* プロキシサーバをポート8000で起動する。
* HTTPサーバをポート9000で起動する。
* プロキシサーバはHTTPサーバにリクエストを転送する。
* プロキシサーバはリクエストやレスポンスを変更したりはしない。

この実装のプロキシサーバに次のHTTPリクエストを送ると、

```http
GET /foo/bar HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Host: localhost:8000
User-Agent: HTTPie/1.0.2


```

下記のとおりのレスポンスが来る:

```http
HTTP/1.1 200 OK
connection: close
content-type: text/plain
date: Tue, 24 Sep 2019 07:42:20 GMT
transfer-encoding: chunked

request successfully proxied!
{
  "connection": "close",
  "accept": "*/*",
  "accept-encoding": "gzip, deflate",
  "user-agent": "HTTPie/1.0.2",
  "host": "localhost:8000"
}
```

# リクエストやレスポンスを書き換えるリバースプロキシを実装する

ここでは、HTTPリクエストやHTTPレスポンスを書き換えるロジックを持ったリバースプロキシを実装する。下記コードがその実装例の完成形になる。

```ts
import http, {ClientRequest, IncomingMessage, ServerResponse} from 'http'
import httpProxy from 'http-proxy'

// HTTPサーバ
http.createServer((req: IncomingMessage, res: ServerResponse): void => {
    res.writeHead(200, {'Content-Type': 'text/plain'})
    res.write('request successfully proxied!' + '\n' + JSON.stringify(req.headers, null, 2))
    res.end()
}).listen(9000)

// プロキシサーバ
const proxy = httpProxy.createProxyServer({target: 'http://127.0.0.1:9000'})

// HTTPリクエストの書き換え
proxy.on('proxyReq', (proxyReq: ClientRequest): void => {
    proxyReq.setHeader('X-Proxy', 'special header for request')
})

// HTTPレスポンスの書き換え
proxy.on('proxyRes', (proxyRes: IncomingMessage): void => {
    proxyRes.headers['X-Proxy'] = 'special header for response'
})

proxy.listen(8000)
```

このサンプルコードの要点をかいつまんで説明する。

まず、HTTPリクエストを書き換えるためには、`proxyReq`イベントのコールバックを実装する。この実装例では、HTTPサーバにリクエストを転送する前に、`X-Proxy`ヘッダをセットするロジックにしている。

```ts
// HTTPリクエストの書き換え
proxy.on('proxyReq', (proxyReq: ClientRequest): void => {
    proxyReq.setHeader('X-Proxy', 'special header for request')
})
```

次に、HTTPレスポンスを書き換えるロジックは、`proxyRes`イベントのコールバック関数として実装する。例では、HTTPサーバから返されたレスポンスに、`X-Proxy`ヘッダを追加するロジックになっている。

```ts
// HTTPレスポンスの書き換え
proxy.on('proxyRes', (proxyRes: IncomingMessage): void => {
    proxyRes.headers['X-Proxy'] = 'special header for response'
})
```

このサンプルコードで起動したプロキシサーバに次のようなHTTPリクエストを送信すると、

```http
GET /foo/bar HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Host: localhost:8000
User-Agent: HTTPie/1.0.2


```

下記のようなレスポンスが返ってくる:

```http
HTTP/1.1 200 OK
X-Proxy: special header for response
connection: close
content-type: text/plain
date: Tue, 24 Sep 2019 08:05:48 GMT
transfer-encoding: chunked

request successfully proxied!
{
  "connection": "close",
  "accept": "*/*",
  "accept-encoding": "gzip, deflate",
  "user-agent": "HTTPie/1.0.2",
  "host": "localhost:8000",
  "x-proxy": "special header for request"
}
```

# 参考文献

* [http-party/node-http-proxy: A full-featured http proxy for node.js](https://github.com/http-party/node-http-proxy)

# 関連記事

* [TypeScript: node-http-proxyでリクエストに応じてサーバを振り分けるリバースプロキシを作る方法 - Qiita](https://qiita.com/suin/items/b6c7db2d31d7e9cb5c5c)

[node-http-proxy]: https://github.com/http-party/node-http-proxy