本稿では、node-http-proxyを使い、TypeScriptでプログラマブルなリバースプロキシを作る方法を紹介する。
本稿のゴール
HTTPリクエストやレスポンスの内容を書き換えるリバースプロキシをTypeScriptで書けるようになる。
node-http-proxy とは
node-http-proxyは、プログラマブルなHTTPプロキシライブラリで、WebSocketもサポートしている。リバースプロキシやロードバランサを実装するのに向いている。
node-http-proxyをインストールする
yarn add http-proxy
yarn add -D @types/http-proxy
最も単純なリバースプロキシを実装する
ここでは一番シンプルな形でリバースプロキシを実装してみる。
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リクエストを送ると、
GET /foo/bar HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Host: localhost:8000
User-Agent: HTTPie/1.0.2
下記のとおりのレスポンスが来る:
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レスポンスを書き換えるロジックを持ったリバースプロキシを実装する。下記コードがその実装例の完成形になる。
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
ヘッダをセットするロジックにしている。
// HTTPリクエストの書き換え
proxy.on('proxyReq', (proxyReq: ClientRequest): void => {
proxyReq.setHeader('X-Proxy', 'special header for request')
})
次に、HTTPレスポンスを書き換えるロジックは、proxyRes
イベントのコールバック関数として実装する。例では、HTTPサーバから返されたレスポンスに、X-Proxy
ヘッダを追加するロジックになっている。
// HTTPレスポンスの書き換え
proxy.on('proxyRes', (proxyRes: IncomingMessage): void => {
proxyRes.headers['X-Proxy'] = 'special header for response'
})
このサンプルコードで起動したプロキシサーバに次のような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/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"
}