リバースプロキシ(ProxyPass,ProxyPassReverse)の動作を調べてみました。

# 1.本記事について
本記事は、Apache(version:2.2)のProxyPass、ProxyPassReverseについてHTTPヘッダやログを見ながら実際に挙動を確認した内容になります。

ProxyPassについては、公式ドキュメント[1]のページを見れば大体内容がわかりましたが、
ProxyPassReverseについては分らなかったので、実際にサーバを動かして確認してみました。

# 2.試す

以下のようにVagrantとVirtualBoxで確認用のサーバを作りました。
また、確認用のリソースとして、webサーバにapp/foo.htmlとapp/redirect_to.htmlを準備しています。
構成は以下の通り。

![env_image.PNG](https://qiita-image-store.s3.amazonaws.com/0/89391/5f5f2582-e410-a25a-e1aa-65c8623dd4e2.png)

```ruby:Vagrantfile
Vagrant.configure(2) do |config|
  config.vm.define "client" do |node|
        node.vm.box = "opscode-centos-6.7"
        node.vm.hostname = "client"
        node.vm.network "private_network", ip: "192.168.100.100"
  end
  config.vm.define "proxy" do |node|
        node.vm.box = "opscode-centos-6.7"
        node.vm.hostname = "proxy"
        node.vm.network "private_network", ip: "192.168.100.200"
  end
  config.vm.define "web" do |node|
        node.vm.box = "opscode-centos-6.7"
        node.vm.hostname = "web"
        node.vm.network "private_network", ip: "192.168.110.200"
  end
end
```

Vagrantについては、皆さんよくご存じでVagrantfileを見れば雰囲気はわかると思いますので、詳細は割愛します。
今回作成したサーバの役割は

 - client：リクエストするclientサーバ
 - proxy ：ゲートウェイであるproxyサーバ。clientからのリクエストをwebに転送する。
 - web   ：各種リソースが存在する内部webサーバ。clientからの直接のリクエストは受け付けない。

になります。

# 3.まずProxyPass

ProxyPassについては公式ドキュメントのページに以下のようにあります。
>このディレクティブはリモートサーバをローカルサーバの名前空間に マップできるようにします。
>ローカルサーバは通常の意味でのプロキシと しては動作せず、リモートサーバのミラーとして振る舞います。
>ローカルサーバはしばしば リバースプロキシ や ゲートウェイ と呼ばれます。
>path はローカルの仮想パスの名前です。
>url は リモートサーバの部分 URL になり、クエリー文字列を含むことはできません。

やりたいことは、proxyサーバ経由でwebのapp/foo.htmlを取得できるようにすることです。
以下のようにproxyサーバをconfigします。

```/etc/httpd/conf/httpd.conf

ProxyRequests Off

ProxyPass /app/ http://192.168.110.200/app/

```


configが完了したので、clientサーバからリクエストして確認します。

```
[vagrant@client ~]$ curl -v http://192.168.100.200/app/foo.html
* About to connect() to 192.168.100.200 port 80 (#0)
*   Trying 192.168.100.200... connected
* Connected to 192.168.100.200 (192.168.100.200) port 80 (#0)
> GET /app/foo.html HTTP/1.1
> User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.19.1 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2
> Host: 192.168.100.200
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Sun, 17 Dec 2017 13:41:12 GMT
< Server: Apache/2.2.15 (CentOS)
< Last-Modified: Sun, 17 Dec 2017 03:11:48 GMT
< ETag: "2003f3-8c-56080982c2783"
< Accept-Ranges: bytes
< Content-Length: 140
< Content-Type: text/html; charset=UTF-8
< Connection: close
<
<!DOCTYPE HTML>
<html lang="ja">
<head>
<title>HTMLのテストページ</title>
</head>
<body>
内部WEBサーバです！
</body>
</html>
* Closing connection #0
```

clientには問題なくコンテンツが返却されました。
またproxyサーバにはclientサーバ、webサーバにはproxyサーバ経由でリスエストが飛んでいることがわかります。

```
[root@proxy ~]# tail -f /var/log/httpd/access_log
192.168.100.100 - - [17/Dec/2017:13:48:00 +0000] "GET /app/foo.html HTTP/1.1" 200 140 "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.19.1 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2"
```

```
[root@web ~]# tail -f /var/log/httpd/access_log
192.168.110.1 - - [17/Dec/2017:13:48:01 +0000] "GET /app/foo.html HTTP/1.1" 200 140 "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.19.1 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2"
```

# 4.続いてProxyPassReverse

以下は公式ドキュメントのページからの引用です。
>このディレクティブは Apache に HTTP リダイレクト応答の Location, Content-Location, URI ヘッダの調整をさせます。
>これは、Apache がリバースプロキシ (ゲートウェイ) として使われているときに、
>リバースプロキシを通らないアクセスを防止するのに重要です。
>このようなアクセスは、リバースプロキシの背後にいるバックエンドサーバへの HTTP リダイレクトが原因で起きます。

>上記の特別なリダイレクト用の HTTP レスポンスヘッダのみが書き換えられます。
>Apache は他のレスポンスヘッダを書き換えたり、HTML ページの中の URL 参照を 書き換えたりすることはありません。
>つまり、リバースプロキシされた HTML ページ内に 絶対 URL 参照が存在すると、
>プロキシを通さずにアクセスする可能性があります。

この説明だけ見てもなにやらわかりませんので、こちらも実際にconfigして挙動を確認します。
ポイントは、 **webサーバ(＝公式ドキュメントの説明でいうところのバックエンドサーバ)でリダイレクト** が発生する場合です。
リダイレクトが発生しない場合はProxyPassの時に確認できたように、コンテンツをproxyサーバから取得できました。

webサーバのconfigは以下。app/redirect_from.htmlからapp/redirect_to.htmlにリダイレクトされるようにしました。

```/etc/httpd/conf/httpd.conf
Redirect permanent /app/redirect_from.html /app/redirect_to.html
```

仮にproxyサーバのProxyPassReverseをconfigしない場合にどのような挙動になるのか確認します。
※なおclientサーバから直接webサーバへアクセスできないように
※clientサーバで事前に

```
[root@client ~]# route add 192.168.110.200 reject
```

※としています。

それでは、clientサーバからリクエストします。

```
[vagrant@client ~]$ curl -v -L http://192.168.100.200/app/redirect_from.html
* About to connect() to 192.168.100.200 port 80 (#0)
*   Trying 192.168.100.200... connected
* Connected to 192.168.100.200 (192.168.100.200) port 80 (#0)
> GET /app/redirect_from.html HTTP/1.1
> User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.19.1 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2
> Host: 192.168.100.200
> Accept: */*
>
< HTTP/1.1 301 Moved Permanently
< Date: Sun, 17 Dec 2017 14:12:24 GMT
< Server: Apache/2.2.15 (CentOS)
< Location: http://192.168.110.200/app/redirect_to.html
< Content-Length: 332
< Content-Type: text/html; charset=iso-8859-1
< Connection: close
<
* Closing connection #0
* Issue another request to this URL: 'http://192.168.110.200/app/redirect_to.html'
* About to connect() to 192.168.110.200 port 80 (#0)
*   Trying 192.168.110.200... Failed to connect to 192.168.110.200: Network is unreachable
* Success
* couldn't connect to host
* Closing connection #0
curl: (7) Failed to connect to 192.168.110.200: Network is unreachable
```

proxyとwebには以下のようにログが出力されています。

```
[root@proxy ~]# tail -f /var/log/httpd/access_log
192.168.100.100 - - [17/Dec/2017:14:13:28 +0000] "GET /app/redirect_from.html HTTP/1.1" 301 332 "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.19.1 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2"
```

```
[root@web ~]# tail -f /var/log/httpd/access_log
192.168.110.1 - - [17/Dec/2017:14:13:29 +0000] "GET /app/redirect_from.html HTTP/1.1" 301 332 "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.19.1 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2"
```

これは、clientサーバからリクエストし、proxyサーバ経由でwebサーバにアクセスしたときwebサーバでリダイレクトが発生しているため、
clientサーバに戻されたHTTPヘッダのLocationがリダイレクト先のwebサーバのURLになっていることが原因です。
実際にcurlの出力で  **Location: http://192.168.110.200/app/redirect_to.html** となっています。
clientサーバ側でLocation先のURLにリダイレクトしようとするが、
clientサーバからのwebサーバへの直接のネットワーク経路が存在しないことから、clientサーバからのリクエストが届いていません。

ここでproxyサーバのconfigにProxyPassReverseを追加し、再度clientサーバから同じリクエストを飛ばします。

```/etc/httpd/conf/httpd.conf
ProxyPassReverse /app/ http://192.168.110.200/app/
```

```
[vagrant@client ~]$ curl -v -L http://192.168.100.200/app/redirect_from.html
* About to connect() to 192.168.100.200 port 80 (#0)
*   Trying 192.168.100.200... connected
* Connected to 192.168.100.200 (192.168.100.200) port 80 (#0)
> GET /app/redirect_from.html HTTP/1.1
> User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.19.1 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2
> Host: 192.168.100.200
> Accept: */*
>
< HTTP/1.1 301 Moved Permanently
< Date: Sun, 17 Dec 2017 14:27:34 GMT
< Server: Apache/2.2.15 (CentOS)
< Location: http://192.168.100.200/app/redirect_to.html
< Content-Length: 332
< Content-Type: text/html; charset=iso-8859-1
< Connection: close
<
* Closing connection #0
* Issue another request to this URL: 'http://192.168.100.200/app/redirect_to.html'
* About to connect() to 192.168.100.200 port 80 (#0)
*   Trying 192.168.100.200... connected
* Connected to 192.168.100.200 (192.168.100.200) port 80 (#0)
> GET /app/redirect_to.html HTTP/1.1
> User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.19.1 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2
> Host: 192.168.100.200
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Sun, 17 Dec 2017 14:27:34 GMT
< Server: Apache/2.2.15 (CentOS)
< Last-Modified: Sun, 17 Dec 2017 11:43:55 GMT
< ETag: "200407-a7-56087bfa32aae"
< Accept-Ranges: bytes
< Content-Length: 167
< Content-Type: text/html; charset=UTF-8
< Connection: close
<
<!DOCTYPE HTML>
<html lang="ja">
<head>
<title>HTMLのテストページ</title>
</head>
<body>
内部WEBサーバでリダイレクトしました！
</body>
</html>
* Closing connection #0
```


```
192.168.100.100 - - [17/Dec/2017:14:27:34 +0000] "GET /app/redirect_from.html HTTP/1.1" 301 332 "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.19.1 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2"
192.168.100.100 - - [17/Dec/2017:14:27:34 +0000] "GET /app/redirect_to.html HTTP/1.1" 200 167 "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.19.1 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2"
```

```
192.168.110.1 - - [17/Dec/2017:14:27:34 +0000] "GET /app/redirect_from.html HTTP/1.1" 301 332 "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.19.1 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2"
192.168.110.1 - - [17/Dec/2017:14:27:34 +0000] "GET /app/redirect_to.html HTTP/1.1" 200 167 "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.19.1 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2"
```

curlから出力されたLocation句が、 **Location: http://192.168.100.200/app/redirect_to.html** となっており、
clientサーバで、リダイレクトされたリクエストがproxyサーバ経由で再度リクエストされています。
結果として、clientサーバにリダイレクト先のコンテンツがproxyサーバ経由で取得できました。

# 5.まとめ
ApacheのProxyPass、ProxyPassReverseについて、HTTPヘッダやログを中心に挙動を確認しました。
ProxyPassReverseについてはproxy先のサーバでリダイレクトが発生した時にHTTPヘッダのLocation句を調整していることが分かりました。

# 参考

 - [1]Apache HTTP サーバ バージョン 2.2:[https://httpd.apache.org/docs/2.2/ja/mod/mod_proxy.html#proxypass](https://httpd.apache.org/docs/2.2/ja/mod/mod_proxy.html#proxypass)
 - [2]mod_proxy再入門:[https://dev.classmethod.jp/server-side/server/introduction_mod_proxy/](https://dev.classmethod.jp/server-side/server/introduction_mod_proxy/)
 - [3]鶴長鎮一著,「サーバ構築の実際がわかるApache運用/管理」,技術評論社,2012