Apache Traffic ServerでWordpressのページをキャッシュするのを試してみた

# はじめに

CentOS 6上でepelからyumでインストールできるApache Traffic Server v5.3.0を試してみました (*1)。Ansibleのプレーブックを https://github.com/hnakamur/trafficserver-ansible-playbook に置いています。記事執筆時点のバージョンは https://github.com/hnakamur/trafficserver-ansible-playbook/tree/8efa6790d29bd8e62e488ceb7486a435e327d795 です。

*1: 当初epelからyumでtrafficserverをインストールしていたのですが、Cache Inspectorが複数のURLを分割する処理にバグ（後述します）があったため、 修正版をCopr上の[hnakamur/apache-traffic-server](https://copr.fedoraproject.org/coprs/hnakamur/apache-traffic-server/)レポジトリからインストールするようにしました。その後このレポジトリのrpmはApache Traffic Serverのバージョンを5.x系の最新の5.3.2に上げました。

# Apache Traffic Serverとは
Apache Traffic Serverは[ホームページ](http://trafficserver.apache.org/)の説明によると、速くてスケーラブルで拡張可能なHTTP/1.1準拠のキャッシュ機能付きプロキシサーバです。OverviewのProvenのところを見ると、フォワードプロキシとリバースプロキシとしてyahoo.comで1日あたり400TBのトラフィックを処理しているそうです。

[News](https://trafficserver.apache.org/#News)のところに書かれていますが、2015年9月21日にリリースされたv6.0.0ではHTTP/2もサポートしています。設定方法は[http2 - Apache Traffic Server で HTTP/2 を有効にする方法 - Qiita](http://qiita.com/masaori335/items/9e908e05ee8dd56df48b)という記事が参考になりそうです。

[Traffic Server - Wikipedia, the free encyclopedia](https://en.wikipedia.org/wiki/Traffic_Server)によると元々[Inktomi](https://en.wikipedia.org/wiki/Inktomi)が開発してInktomi Traffic Serverという商用製品だったのが、Yahoo!が2003年にInktomiを買収し、2009年にTraffic ServerのソースコードをApache Incubator projectに寄贈してオープンソースになったということです。

ですので、名前にApacheとついていますがApache HTTP Serverとは全く別物です。[Choosing A Proxy Server - Apachecon 2014](http://www.slideshare.net/bryan_call/choosing-a-proxy-server-apachecon-2014)の[29枚目のスライド](http://www.slideshare.net/bryan_call/choosing-a-proxy-server-apachecon-2014/29)を見るとApache Traffic Server (ATS)はイベントドリブンのI/Oをマルチスレッドで行うアーキテクチャとなっています。

# Varnishと違ってプロセス再起動してもキャッシュが消えない

キャッシュサーバとしてはApache Traffic Serverの他にVarnishも有名です。[The Varnish Users Guide — Varnish version 4.1.0 documentation](https://www.varnish-cache.org/docs/4.1/users-guide/index.html)の[Storage Backends](https://www.varnish-software.com/book/4.0/chapters/Tuning.html#storage-backends)を見るとVarnishには以下の4つのストレージバックエンドが用意されています。

* malloc
* file
* persistent (deprecated)
* Varnish Massive Storage Engine (MSE) in Varnish Plus only

このうちmallocとfileはvarnishdのプロセスを再起動するとキャッシュの内容が消えてしまいます。persistentはdeprecatedなので使わないほうが良さそうです。Varnish Massive Storage EngineはVarnish Plusという有償製品でのみ利用可能で、私は使ったことがないのでプロセス再起動時にキャッシュが消えるかは不明です。

Apache Traffic Serverではプロセスを再起動してもキャッシュが消えずに残ります。

# ソースコード
githubの[apache/trafficserver](https://github.com/apache/trafficserver)にミラーがありました。71.1%がC++、22.2%がCで書かれているそうです。ちょっと覗いてみた感じでは、boostなどのテンプレートライブラリは使用しておらず、15年以上C++を書いてない私でもなんとか読めそうな感じでした。

# 今回の構成

Vagrant + VirtualBoxで構築した仮想マシンに192.168.33.131というIPアドレスを設定しています。

Apache Traffic Serverを80番ポート、オリジンサーバとして使うWordpress用のApache HTTP Serverを8081番ポートで稼働させ、Apache Traffic ServerからApache HTTP Serverへリバースプロキシする構成としました。

Wordpressのセットアップは[Remi's RPM repository](http://rpms.famillecollet.com/)のphp70パッケージ、[MySQL :: Download MySQL Yum Repository](http://dev.mysql.com/downloads/repo/yum/)のmysql-community-serverパッケージと[Command line interface for WordPress | WP-CLI](http://wp-cli.org/)を使いました。

Wordpressのコンテンツは[jawordpressorg/theme-test-data-ja](https://github.com/jawordpressorg/theme-test-data-ja)のWordpressテストデータ日本語版をベースに[all-free-download.com](http://all-free-download.com/free-photos/)からダウンロードしたPublic domainの写真の投稿を追加したものを利用しています。

# 設定ファイル

`/etc/trafficserver/*.config` に設定ファイルが複数あります。設定方法の概要は[Configuring Traffic Server](https://docs.trafficserver.apache.org/en/latest/admin-guide/configuring-traffic-server.en.html)に説明があります。

各ファイルの設定項目については[Configuration Files](https://docs.trafficserver.apache.org/en/latest/admin-guide/files/index.en.html)に説明があります。

なお、ドキュメントのページの左下のv:latestをクリックしてVersionsの5.3.xをクリックするとバージョン5.3.xのドキュメントを見られると期待したのですが、SORRY This page does not exist yet.となってしまったので、ドキュメントは最新版を見ることにします。

# オリジンサーバへのプロキシ設定

リバースプロキシの設定は[Reverse Proxy and HTTP Redirects](https://docs.trafficserver.apache.org/en/latest/admin-guide/configuration/redirecting-http-requests.en.html)に説明があります。

今回の構成では以下の設定としました。

```:/etc/trafficserver/records.config
CONFIG proxy.config.reverse_proxy.enabled INT 1
```

```:/etc/trafficserver/remap.configs
map http://192.168.33.131/ http://localhost:8081/
reverse_map http://localhost:8081/ http://192.168.33.131/
```

# キャッシュストレージの設定
[Cache Storage](http://trafficserver.readthedocs.org/en/latest/admin-guide/storage/index.en.html)と[storage.config](http://trafficserver.readthedocs.org/en/latest/admin-guide/files/storage.config.en.html)にキャッシュストレージについての説明があります。

生のディスクパーティションを使うほうがパフォーマンスは良いそうなのですが、今回は手軽にファイルをストレージとして使う設定にしました。キャッシュがあふれるケースを試したいのでなるべく小さいサイズにしたいのですが、サイズは最低128MBが必要と書いてあったので128Mにしました。

```:/etc/trafficserver/storage.config
/var/cache/trafficserver 128M
```

## RAMキャッシュ
[Changing the Size of the RAM Cache](http://trafficserver.readthedocs.org/en/latest/admin-guide/storage/index.en.html#changing-the-size-of-the-ram-cache)を見ると、よく参照される小さなオブジェクトにはRAMキャッシュが使われるそうです。

RAMキャッシュのサイズは明示的に指定することも出来ますが、 `-1` にすればディスクストレージのサイズに応じて自動で設定されます。1GBのディスクストレージにつき約1MBになるそうです。

```:/etc/trafficserver/records.config
CONFIG proxy.config.cache.ram_cache.size INT -1
```

[proxy-config-cache-ram-cache-cutoff](http://trafficserver.readthedocs.org/en/latest/admin-guide/files/records.config.en.html#proxy-config-cache-ram-cache-cutoff)という設定項目で、RAMキャッシュに格納するオブジェクトの最大サイズを指定することも出来ます。デフォルト値は4MB (4194304)となっています。

```:/etc/trafficserver/records.config
CONFIG proxy.config.cache.ram_cache_cutoff INT 4194304
```

# ログ形式の設定

ログ出力のドキュメントの目次が[Event and Error Monitoring](http://trafficserver.readthedocs.org/en/latest/admin-guide/monitoring/index.en.html)にあります。

標準のログ形式については[Log Formats](http://trafficserver.readthedocs.org/en/latest/admin-guide/monitoring/logging/log-formats.en.html#standard-formats)に説明があります。以下の4つの形式があるそうです。

* [Squid形式](http://trafficserver.readthedocs.org/en/latest/admin-guide/monitoring/logging/log-formats.en.html#squid)
* [Netscape Common形式](http://trafficserver.readthedocs.org/en/latest/admin-guide/monitoring/logging/log-formats.en.html#netscape-common)
* [Netscape Extended形式](http://trafficserver.readthedocs.org/en/latest/admin-guide/monitoring/logging/log-formats.en.html#netscape-extended)
* [Netscape Extended-2形式](http://trafficserver.readthedocs.org/en/latest/admin-guide/monitoring/logging/log-formats.en.html#netscape-extended2)

一番情報量の多いNetscape Extended-2形式を使う設定例を以下に示します。

```:/etc/trafficserver/records.config
# Use the Netscape Extended-2 format
#   http://trafficserver.readthedocs.org/en/5.3.x/admin/working-log-files.en.html
CONFIG proxy.config.log.extended2_log_enabled INT 1
CONFIG proxy.config.log.extended2_log_is_ascii INT 1
CONFIG proxy.config.log.extended2_log_name STRING proxy.log
```

## LTSV形式でのログ出力設定

カスタムのログ形式を使うための設定方法が[Custom Formats](http://trafficserver.readthedocs.org/en/latest/admin-guide/monitoring/logging/log-formats.en.html#custom-formats)に書かれています。

今回は以下の設定にしました。

```:/etc/trafficserver/records.config
# Enable the custom logging
#   http://trafficserver.readthedocs.org/en/5.3.x/admin/working-log-files.en.html#using-the-custom-format
CONFIG proxy.config.log.custom_logs_enabled INT 1
```

```xml:/etc/trafficserver/logs_xml.config
<LogFormat>
  <Name = "ltsv"/>
  <Format = "host:%<chi>        user:%<caun>    time:%<cqtn>    req:%<cqtx>     s1:%<pssc>      c1:%<pscl>      s2:%<sssc>      c2:%<sscl>      b1:%<cqbl>      b2:%<pqbl>      h1:%<cqhl>      h2:%<pshl>      h3:%<pqhl>      h4:%<sshl>      xt:%<tts>       route:%<phr>    pfs:%<cfsc>     ss:%<pfsc>      crc:%<crc> chm:%<chm>   cwr:%<cwr>      ua:%<{User-Agent}cqh>   referer:%<{Referer}cqh>"/>
</LogFormat>

<LogObject>
  <Format = "ltsv"/>
  <Filename = "proxy.ltsv.log"/>
</LogObject>
```

[Netscape Extended-2](http://trafficserver.readthedocs.org/en/latest/admin-guide/monitoring/logging/log-formats.en.html#netscape-extended-2)にカスタムのログ形式でNetsape Extended-2形式と同じログを出力する設定が書いてあるので、それをベースにLTSV形式にし、User-AgentやRefererなどいくつか項目を追加しています。

## キャッシュリザルトコード: crc
上記の `crc:%<crc>` の項目がキャッシュのヒットなどの状態を表します。[crc](https://docs.trafficserver.apache.org/en/latest/admin-guide/monitoring/logging/log-formats.en.html#crc)に項目の説明があり、[Cache Result Codes](https://docs.trafficserver.apache.org/en/latest/admin-guide/monitoring/logging/log-formats.en.html#cache-result-codes)に取り得る値の説明があります。

実際に試していたら `TCP_MEM_HIT` というここに載っていない値が出ることもありました。v5.3.xとv6.0.xの違いなのか、ドキュメントが不完全なのかは未調査です。

また、[crc](https://docs.trafficserver.apache.org/en/latest/admin-guide/monitoring/logging/log-formats.en.html#crc)の他に[chm](https://docs.trafficserver.apache.org/en/latest/admin-guide/monitoring/logging/log-formats.en.html#chm)や[cwr](https://docs.trafficserver.apache.org/en/latest/admin-guide/monitoring/logging/log-formats.en.html#cwr)といった項目も有るようです。

## デバッグログを有効にして出力対象にcache_inspectorタグを追加

後述のCache Inspectorのバグ調査のため、デバッグログを有効にして出力対象にcache_inspectorタグを追加する必要がありました。

[Using Debug Tags](https://docs.trafficserver.apache.org/ja/latest/sdk/troubleshooting-tips/unable-to-debug-tags.en.html)によると、 [proxy.config.diags.debug.enabled](https://docs.trafficserver.apache.org/en/latest/admin-guide/files/records.config.en.html#proxy-config-diags-debug-enabled)を `1` にして[proxy.config.diags.debug.tags](https://docs.trafficserver.apache.org/en/latest/admin-guide/files/records.config.en.html#proxy-config-diags-debug-tags)に出力したいタグを指定するとデバッグログが出力されるそうです。

yumでインストールした状態の設定ファイルは以下のようになっていました。

```:/etc/trafficserver/records.config
CONFIG proxy.config.diags.debug.enabled INT 0
CONFIG proxy.config.diags.debug.tags STRING http.*|dns.*
```

そこで以下のように設定を変更しました。

```:/etc/trafficserver/records.config
CONFIG proxy.config.diags.debug.enabled INT 1
CONFIG proxy.config.diags.debug.tags STRING http.*|dns.*|cache_inspector.*
```

[Using Debug Tags](https://docs.trafficserver.apache.org/ja/latest/sdk/troubleshooting-tips/unable-to-debug-tags.en.html)に説明がありますが、上記の設定方法の場合デバッグログの出力先は `/var/log/trafficserver/traffic.out` になります。

## ログをUnix名前付きパイプに出力
今回は試していませんが、[ASCII Log Pipes](https://docs.trafficserver.apache.org/en/latest/admin-guide/monitoring/logging/pipes.en.html)によるとログをUnix名前付きパイプに出力することもできるそうです。

# Cache Inspectorなど各種HTTP UIエンドポイントを使うための設定

[Inspecting the Cache](https://docs.trafficserver.apache.org/en/latest/admin-guide/storage/index.en.html?highlight=cache%20inspector#inspecting-the-cache)にCache Inspectorというユーティリティを使うための設定について説明があります。

例えば以下のように書いて `http://yourhost.com/myCI/` にアクセスするとCache Inspectorが表示されるようになります。 `@src_ip` で指定した172.28.56.1-172.28.56.254のIPアドレスからのみアクセス可能です。

```:/etc/trafficserver/records.config
CONFIG proxy.config.http_ui_enabled INT 1
```

```:/etc/trafficserver/remap.config
map http://yourhost.com/myCI/ http://{cache} @action=allow @src_ip=172.28.56.1-172.28.56.254
```

Cache Inspectorを開くと以下の5つのリンクが表示されており、リンクをたどると各機能を使うためのフォームが表示されます。これらの機能については後述します。

* Lookup url
* Delete url
* Regex lookup
* Regex delete
* Regex invalidate

[proxy.config.http_ui_enabled](http://trafficserver.readthedocs.org/en/latest/admin-guide/files/records.config.en.html?highlight=proxy.config.http.enable_http_info#proxy-config-http-ui-enabled)の説明を見ると `http://{cache}` 以外のUIエンドポイントもあります。

`http://{http}` エンドポイントを使うための設定 [proxy.config.http.enable_http_info](https://docs.trafficserver.apache.org/en/latest/admin-guide/files/records.config.en.html#proxy-config-http-enable-http-info)とアクセス元IPアドレス制限を一括で行うための[Named Filters](https://docs.trafficserver.apache.org/en/latest/admin-guide/files/remap.config.en.html?highlight=src_ip#named-filters)を参考にして、今回は以下の様な設定にしました。

```:/etc/trafficserver/records.config
CONFIG proxy.config.http_ui_enabled INT 3
CONFIG proxy.config.http.enable_http_info INT 1
```

```:/etc/trafficserver/remap.config
.definefilter local_only @action=allow @src_ip=192.168.33.1 @src_ip=192.168.33.131 @src_ip=127.0.0.1
.activatefilter local_only
# NOTE: These mapping must be placed before the mapping for http://192.168.33.131/.
# NOTE: The trailing slashes for /_ats_stat/cache/ is needed for the cache inspector to work properly.
map http://192.168.33.131/_ats_stat/cache-internal/ http://{cache-internal}
map http://192.168.33.131/_ats_stat/cache/ http://{cache}
map http://192.168.33.131/_ats_stat/hostdb/ http://{hostdb}
map http://192.168.33.131/_ats_stat/http/ http://{http}
map http://192.168.33.131/_ats_stat/net/ http://{net}
map http://192.168.33.131/_ats_stat/stat/ http://{stat}
map http://192.168.33.131/_ats_stat/test/ http://{test}
.deactivatefilter local_only
```

なお、上記の設定 `/_ats_stat/cache/` の最後のスラッシュが無いとCache Inspectorのページにある `Lookup url` などのリンクをクリックした時に404 Not Foundとなってしまったのでご注意ください。これについては[\[TS-1438\] Cache inspector should add trailing slash to CI URL - ASF JIRA](https://issues.apache.org/jira/browse/TS-1438)というイシューがありました。

他のエンドポイントでは最後のスラッシュは不要かも知れませんが、一貫性が無いと混乱するのでスラッシュ有りに統一しています。

ちなみにApache Traffic ServerのUIエンドポイントはどれもCSS無しの必要最低限なページ構成になっています。

# キャッシュ破棄

## 全てのキャッシュをクリア
[Clearing the Cache](https://docs.trafficserver.apache.org/en/latest/admin-guide/storage/index.en.html?highlight=cache%20inspector#clearing-the-cache)に説明がありました。サーバを一旦停止する必要があります。以下のように実行します。

```shell-session
sudo service trafficserver stop
sudo traffic_server -Cclear
sudo service trafficserver start
```

## httpのカスタムメソッドPURGEで個別のキャッシュオブジェクトを削除

[Removing an Object From the Cache](https://docs.trafficserver.apache.org/en/latest/admin-guide/storage/index.en.html?highlight=cache%20inspector#removing-an-object-from-the-cache)に説明があります。

HTTPのカスタムメソッド `PURGE` を使ってオブジェクトをキャッシュから削除することができます。デフォルトでは `localhost` からのアクセスのみ許可します。これは[ip_allow.config](https://docs.trafficserver.apache.org/en/latest/admin-guide/files/ip_allow.config.en.html?highlight=purge)で変更可能のようです（今回は試していません）。

[Removing an Object From the Cache](https://docs.trafficserver.apache.org/en/latest/admin-guide/storage/index.en.html?highlight=cache%20inspector#removing-an-object-from-the-cache)には `$ curl -vX PURGE --resolve example.com:80:127.0.0.1 http://example.com/remove_me.jpg` という例が書いてありますが、[cURL - How To Use](http://curl.haxx.se/docs/manpage.html)によると `--resolve` はcurlのバージョン7.21.3で追加されたそうで、CentOS 6のcurl 7.19.7では使えませんでした。そこで、 `-H` オプションでHostヘッダを指定するようにしたら削除に成功しました。

例として `http://192.168.33.131/?p=2328` をブラウザで表示した後、以下のように実行すると PURGE できました。

```shell-session
# curl -vX PURGE -H 'Host: 192.168.33.131' http://127.0.0.1/wp-content/uploads/2015/12/wisconsin_summer_farm-1200x900.jpg
* About to connect() to 127.0.0.1 port 80 (#0)
*   Trying 127.0.0.1... connected
* Connected to 127.0.0.1 (127.0.0.1) port 80 (#0)
> PURGE /wp-content/uploads/2015/12/wisconsin_summer_farm-1200x900.jpg 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
> Accept: */*
> Host: 192.168.33.131
>
< HTTP/1.1 200 OK
< Date: Thu, 31 Dec 2015 08:06:46 GMT
< Connection: keep-alive
< Server: ATS/5.3.0
< Content-Length: 0
<
* Connection #0 to host 127.0.0.1 left intact
* Closing connection #0
```

なお、キャッシュが無い状態で再度上記のリクエストを送ると404 Not Foundになりました。

## Cache Inspector Utilityを使ったキャッシュの削除
[Using the Cache Inspector Utility](https://docs.trafficserver.apache.org/en/latest/admin-guide/storage/index.en.html#using-the-cache-inspector-utility)に説明があります。

### Lookup URLフォームの検索結果からの単一URL削除

Cache Inspector (今回の構成では `http://192.168.33.131/_ats_stat/cache/` )のLookup urlのリンクをクリックし、URLを入力して[Lookup]ボタンを押すと検索結果が表示されます。

例として `http://192.168.33.131/?p=2328` をブラウザで表示した後、Lookup urlのフォームで `http://192.168.33.131/wp-content/uploads/2015/12/wisconsin_summer_farm-1200x900.jpg` と入力して[Lookup]ボタンを押すと結果が表示されます。

検索結果のActionの行の[Delete URL]ボタンを押すとキャッシュを削除できます。

URLを入力して[Lookup]ボタンを押したときに、キャッシュに存在しない場合はCache Lookup Failed, or missing in clusterと表示されました。

### Delete urlフォームからの複数URL一括削除

Cache InspectorのDelete urlのリンクをクリックするとCache Deleteというページが表示されます。
`Type the list urls that you want to delete in the box below. The urls MUST be separated by new lines` との説明があります。その下のテキストエリアに複数のURLを1行に1つずつ入力し[Delete]ボタンを押すと指定した複数URLのキャッシュを一括削除できます。

例として `http://192.168.33.131/?p=2328` をブラウザで表示した後、
`http://192.168.33.131/_ats_stat/cache/delete_url_form` で

```
http://192.168.33.131/wp-content/uploads/2015/12/wisconsin_summer_farm-1200x900.jpg
http://192.168.33.131/wp-includes/js/wp-emoji-release.min.js?ver=4.4
```

のように入力して[Delete]ボタンを押すと、結果のページには以下のように表示されました。

| | |
|-----|------|
| `http://192.168.33.131/wp-content/uploads/2015/12/wisconsin_summer_farm-1200x900.jpg http://192.168.33.131/wp-includes/js/wp-emoji-release.min.js?ver=4.4` | Delete succeeded |
| `http://192.168.33.131/wp-includes/js/wp-emoji-release.min.js?ver=4.4` | Delete succeeded |

### Regex lookupフォームの結果画面からの削除

Cache InspectorのRegex lookupのリンクをクリックするとCache Regex Lookupというページが表示されます。
`Type the list of regular expressions that you want to lookup in the box below. The regular expressions MUST be separated by new lines` との説明があります。テキストエリアに複数のURLの正規表現を1行に1つずつ入力し[Lookup]ボタンを押すと、キャッシュを検索できそうなのですが、試してみるとうまく行きませんでした。

例として `http://192.168.33.131/?p=2328` をブラウザで表示した後、

```
http://.*\.js
http://.*\.jpg
```

のように入力して[Lookup]ボタンを押すと、結果ページに以下のようにチェックボックスとヒットしたURLが並んだテーブルが表示されました。

|   |   |
|---|---|
| □ |	`http://localhost:8081/wp-includes/js/jquery/jquery.js?ver=1.11.3` |
| □ |	`http://localhost:8081/wp-includes/js/jquery/jquery-migrate.min.js?ver=1.2.1` |
| □ |	`http://localhost:8081/wp-content/themes/twentysixteen/js/skip-link-focus-fix.js?ver=20150825` |
| □ |	`http://localhost:8081/wp-content/themes/twentysixteen/js/functions.js?ver=20150825` |
| □ |	`http://localhost:8081/wp-includes/js/comment-reply.min.js?ver=4.4` |
| □ |	`http://localhost:8081/wp-includes/js/wp-embed.min.js?ver=4.4` |
| □ |	`http://localhost:8081/wp-includes/js/wp-emoji-release.min.js?ver=4.4` |

キャッシュを削除したいURLのチェックボックスをオンにして、テーブルの下の[Delete]ボタンを押せばキャッシュが削除できそうな感じです。

ところが1行目と2行目にチェックをつけて[Delete]ボタンを押してみると、以下のように失敗してしまいました。

|   |   |
|---|---|
| `http://localhost:8081/wp-includes/js/jquery/jquery.js?ver=1.11.3` | Delete failed |
| `http://localhost:8081/wp-includes/js/jquery/jquery-migrate.min.js?ver=1.2.1` | Delete failed |

Chromeの開発ツールで確認すると以下の様なリクエストになっていました。

```
Request URL:http://192.168.33.131/_ats_stat/cache/delete_url?url=http%3A%2F%2Flocalhost%3A8081%2Fwp-includes%2Fjs%2Fwp-emoji-release.min.js%3Fver%3D4.4%0D%0Ahttp%3A%2F%2Flocalhost%3A8081%2Fwp-includes%2Fjs%2Fjquery%2Fjquery-migrate.min.js%3Fver%3D1.2.1%0D%0A
Request Method:GET
```

以下のように `url` パラメータ内の `localhost%3A8081` を `192.168.33.131` にしてcurlから実行すれば削除成功しました。

```shell-session
$ curl -v http://192.168.33.131/_ats_stat/cache/delete_url?url=http%3A%2F%2F192.168.33.131%2Fwp-includes%2Fjs%2Fjquery%2Fjquery.js%3Fver%3D1.11.3%0D%0Ahttp%3A%2F%2F192.168.33.131%2Fwp-includes%2Fjs%2Fjquery%2Fjquery-migrate.min.js%3Fver%3D1.2.1%0D%0A
*   Trying 192.168.33.131...
* Connected to 192.168.33.131 (192.168.33.131) port 80 (#0)
> GET /_ats_stat/cache/delete_url?url=http%3A%2F%2F192.168.33.131%2Fwp-includes%2Fjs%2Fjquery%2Fjquery.js%3Fver%3D1.11.3%0D%0Ahttp%3A%2F%2F192.168.33.131%2Fwp-includes%2Fjs%2Fjquery%2Fjquery-migrate.min.js%3Fver%3D1.2.1%0D%0A HTTP/1.1
> Host: 192.168.33.131
> User-Agent: curl/7.43.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Thu, 31 Dec 2015 21:01:14 GMT
< Connection: close
< Server: ATS/5.3.0
< Content-Length: 428
< Content-Type: text/html
<
<HTML>
<HEAD><TITLE>Delete URL</TITLE>
<BODY BGCOLOR="#ffffff" FGCOLOR="#00ff00">
<H1>Delete URL</H1>
<B><TABLE border=1>
<TR><TD>http://192.168.33.131/wp-includes/js/jquery/jquery.js?ver=1.11.3</TD><td>Delete <font color=green>succeeded</font></td></tr>
<TR><TD>http://192.168.33.131/wp-includes/js/jquery/jquery-migrate.min.js?ver=1.2.1</TD><td>Delete <font color=green>succeeded</font></td></tr>
</TABLE></B>
</BODY>
</HTML>
* Closing connection 0
```

## Regex deleteフォームでURLの正規表現を指定してキャッシュ削除

Cache InspectorのRegex deleteのリンクをクリックするとCache Regex deleteというページが表示されます。
`Type the list of regular expressions that you want to delete in the box below. The regular expressions MUST be separated by new lines` との説明があります。テキストエリアに複数のURLの正規表現を1行に1つずつ入力し[Delete]ボタンを押すと、キャッシュを削除できます。

例として `http://192.168.33.131/?p=2328` をブラウザで表示した後、

```
http://.*\.js
http://.*\.jpg
```

のように入力して[Delete]ボタンを押すと、結果ページに以下のように表示されました。

|   |   |
|---|---|
| `http://localhost:8081/wp-includes/js/jquery/jquery.js?ver=1.11.3` | deleted |
| `http://localhost:8081/wp-content/uploads/2015/12/wisconsin_summer_farm-1200x900.jpg` | deleted |
| `http://localhost:8081/wp-includes/js/jquery/jquery-migrate.min.js?ver=1.2.1` | deleted |
| `http://localhost:8081/wp-content/themes/twentysixteen/js/skip-link-focus-fix.js?ver=20150825` | deleted |
| `http://localhost:8081/wp-content/themes/twentysixteen/js/functions.js?ver=20150825` | deleted |
| `http://localhost:8081/wp-includes/js/comment-reply.min.js?ver=4.4` | deleted |
| `http://localhost:8081/wp-includes/js/wp-embed.min.js?ver=4.4` | deleted |
| `http://localhost:8081/wp-includes/js/wp-emoji-release.min.js?ver=4.4` | deleted |

## Regex invalidateフォームでURLの正規表現を指定してキャッシュ無効化

[Using the Cache Inspector Utility](https://docs.trafficserver.apache.org/en/latest/admin-guide/storage/index.en.html#using-the-cache-inspector-utility)のRegex Invalidateの説明を読むと、指定した正規表現にマッチしたオジェクトを新鮮でない (stale) 状態にし、次にリクエストが来た時にオリジンサーバにキャッシュオブジェクトが有効で新鮮かを確認して、必要に応じてオリジンからコンテンツを取得してキャッシュを更新してから返すそうです。

Cache InspectorのRegex invalidateのリンクをクリックするとCache Regex Invalidateというページが表示されます。
`Type the list of regular expressions that you want to invalidate in the box below. The regular expressions MUST be separated by new lines` との説明があります。テキストエリアに複数のURLの正規表現を1行に1つずつ入力し[Invalidate]ボタンを押すと、キャッシュを無効化できます。

例として `http://192.168.33.131/?p=2328` をブラウザで表示した後、

```
http://.*\.js
http://.*\.jpg
```

のように入力して[Invalidate]ボタンを押すと、結果ページに以下のように表示されました。

|   |   |
|---|---|
| `http://localhost:8081/wp-includes/js/jquery/jquery-migrate.min.js?ver=1.2.1` | Invalidate |
| `http://localhost:8081/wp-includes/js/jquery/jquery.js?ver=1.11.3` | Invalidate |
| `http://localhost:8081/wp-content/themes/twentysixteen/js/skip-link-focus-fix.js?ver=20150825` | Invalidate |
| `http://localhost:8081/wp-content/themes/twentysixteen/js/functions.js?ver=20150825` | Invalidate |
| `http://localhost:8081/wp-includes/js/wp-embed.min.js?ver=4.4` | Invalidate |
| `http://localhost:8081/wp-includes/js/comment-reply.min.js?ver=4.4` | Invalidate |
| `http://localhost:8081/wp-content/uploads/2015/12/wisconsin_summer_farm-1200x900.jpg` | Invalidate |
| `http://localhost:8081/wp-includes/js/wp-emoji-release.min.js?ver=4.4` | Invalidate |

その後、 `http://192.168.33.131/?p=2328` を表示していたChromeのタブを Shift+Command+R でリロードしてみました。 `/var/log/trafficserver/proxy.ltsv.log` に出力されたログの一部を引用します（markdownのコードとして書くと行が長くて見にくいのであえて引用にしています。引用だとタブが空白のように見えてしまいますがご了承ください）。

> host:192.168.33.1       user:-  time:01/Jan/2016:06:12:06 +0900 req:GET http://localhost:8081/wp-includes/js/jquery/jquery-migrate.min.js?ver=1.2.1 HTTP/1.1   s1:200  c1:7200 s2:304  c2:0    b1:0    b2:0    h1:865  h2:262  h3:1055 h4:135  xt:0    route:DIRECT    pfs:FINss:FIN  crc:TCP_REFRESH_HIT chm:MISS_PRE_EXPIRED        cwr:-   ua:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36   referer:http://192.168.33.131/?p=2328
> host:192.168.33.1       user:-  time:01/Jan/2016:06:12:06 +0900 req:GET http://localhost:8081/wp-includes/js/jquery/jquery.js?ver=1.11.3 HTTP/1.1      s1:200  c1:95977        s2:304  c2:0    b1:0    b2:0    h1:854  h2:264  h3:1045 h4:136  xt:0    route:DIRECT    pfs:FINss:FIN  crc:TCP_REFRESH_HIT chm:MISS_PRE_EXPIRED        cwr:-   ua:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36   referer:http://192.168.33.131/?p=2328
> host:192.168.33.1       user:-  time:01/Jan/2016:06:12:06 +0900 req:GET http://localhost:8081/wp-content/uploads/2015/12/wisconsin_summer_farm-1200x900.jpg HTTP/1.1   s1:200  c1:490166       s2:304  c2:0    b1:0    b2:0    h1:898  h2:260  h3:1089 h4:136  xt:0    route:DIRECT   pfs:FIN ss:FIN  crc:TCP_REFRESH_HIT chm:MISS_PRE_EXPIRED        cwr:-   ua:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36   referer:http://192.168.33.131/?p=2328

`crc` が `TCP_REFRESH_HIT` となっていて The object was in the cache, but it was stale. という状態だったことが確認できました。

ただ、 `http://192.168.33.131/?p=2328` をChromeで Shift+Command+R でリロードして、別のタブでCache Regex Invalidateのページで無効化するというのを、何度か試していると結果が空の場合もありました。この件は未調査です。

# Cache Inspectorでの複数URLの分離バグ

Cache Inspectorの以下の4つのフォームにはテキストエリアがあり、1行に1つずつURLを書いて複数指定可能となっています。

* Delete url
* Regex lookup
* Regex delete
* Regex invalidate

ただし、複数行の分割処理に不具合があるため、各行から最後の行まで連なった正規表現として認識されてしまっていました。

例えばブラウザで `http://192.168.33.131/?p=2328` を表示した後、Regex invalidateのフォームで

```
http://.*\.js
http://.*\.jpg
```

のように入力して[Invalidate]ボタンを押してもjsファイルは無効化されず、jpgファイルのキャッシュのみが無効化されました。

|   |   |
|---|---|
| `http://localhost:8081/wp-content/uploads/2015/12/wisconsin_summer_farm-1200x900.jpg` | Invalidate |

Regex invalidateフォームで[Invalidate]ボタンを押した時のリクエストをChromeの開発ツールで確認すると以下のようなリクエストになっていました。
 
```
Request URL:http://192.168.33.131/_ats_stat/cache/invalidate_regex?url=http%3A%2F%2F.*%5C.js%0D%0Ahttp%3A%2F%2F.*%5C.jpg
Request Method:GET
```

キャッシュを無効化するという状態を変更するリクエストに `GET` というhttpメソッドを使うとはイマイチだなと思いますが、それは置いておいてソースコードを見てみました。

https://github.com/apache/trafficserver/blob/413dd51d5dc17bf388805071efdb8f882014b847/iocore/cache/CachePages.cc#L190-L191

```
  } else if (STREQ_PREFIX(path, "invalidate_regex")) {
    SET_CONTINUATION_HANDLER(theshowcache, &ShowCache::invalidate_regex);
```

`ShowCache::invalidate_regex` の定義とそこから呼び出している `ShowCache::handleCacheScanCallback` は https://github.com/apache/trafficserver/blob/413dd51d5dc17bf388805071efdb8f882014b847/iocore/cache/CachePages.cc#L566-L606 あたりにあります。

```
int
ShowCache::invalidate_regex(int event, Event *e)
{
  CHECK_SHOW(begin("Regex Invalidate"));
  CHECK_SHOW(show("<B><TABLE border=1>\n"));
  scan_flag = scan_type_invalidate; // invalidate
  SET_HANDLER(&ShowCache::handleCacheScanCallback);
  cacheProcessor.scan(this);
  return EVENT_DONE;
}


int
ShowCache::handleCacheScanCallback(int event, Event *e)
{
  switch (event) {
  case CACHE_EVENT_SCAN: {
    cache_vc = (CacheVC *)e;
    return EVENT_CONT;
  }
  case CACHE_EVENT_SCAN_OBJECT: {
    HTTPInfo *alt = (HTTPInfo *)e;
    char xx[501], m[501];
    int ib = 0, xd = 0, ml = 0;

    alt->request_get()->url_print(xx, 500, &ib, &xd);
    xx[ib] = '\0';

    const char *mm = alt->request_get()->method_get(&ml);

    memcpy(m, mm, ml);
    m[ml] = 0;

    int res = CACHE_SCAN_RESULT_CONTINUE;

    for (unsigned s = 0; show_cache_urlstrs[s][0] != '\0'; s++) {
      const char *error;
      int erroffset;
      pcre *preq = pcre_compile(show_cache_urlstrs[s], 0, &error, &erroffset, NULL);

      Debug("cache_inspector", "matching url '%s' '%s' with regex '%s'\n", m, xx, show_cache_urlstrs[s]);
```

`show_cache_urlstrs` が行ごとに分割された正規表現のようです。

複数行の分割処理は https://github.com/apache/trafficserver/blob/413dd51d5dc17bf388805071efdb8f882014b847/iocore/cache/CachePages.cc#L113-L132 にありました。

```
      // initialize url array
      show_cache_urlstrs = new char[nstrings + 1][500];
      memset(show_cache_urlstrs, '\0', (nstrings + 1) * 500 * sizeof(char));

      char *q, *t;
      p = strstr(unescapedQuery, "url=");
      if (p) {
        p += 4; // 4 ==> strlen("url=")
        t = strchr(p, '&');
        if (!t)
          t = (char *)unescapedQuery + strlen(unescapedQuery);
        for (int s = 0; p < t; s++) {
          show_cache_urlstrs[s][0] = '\0';
          q = strstr(p, "%0D%0A" /* \r\n */); // we used this in the JS to separate urls
          if (!q)
            q = t;
          ink_strlcpy(show_cache_urlstrs[s], p, sizeof(show_cache_urlstrs[s]));
          p = q + 6; // +6 ==> strlen(%0D%0A)
        }
      }

      Debug("cache_inspector", "there were %d url(s) passed in", nstrings == 1 ? 1 : nstrings - 1);
```

ink_strlcpyの第3引数が間違っていました。 `sizeof(show_cache_urlstrs[s])` と指定しているために常に文字列の最後までコピーされてしまっていました。

`cache_inspector` のデバッグログを見てみると以下のように `URL 1` が1行目と2行目が連なった状態になっています。

```:/var/log/trafficserver/traffic.out
[Dec 31 20:49:25.677] Server {0x2b45e18837a0} DEBUG: (cache_inspector) query params: 'url=http%3A%2F%2F.*%5C.js%0D%0Ahttp%3A%2F%2F.*%5C.jpg' len 33 [unescaped]
[Dec 31 20:49:25.677] Server {0x2b45e18837a0} DEBUG: (cache_inspector) query params: 'url=http://.*\.js
http://.*\.jpg' len 33 [escaped]
[Dec 31 20:49:25.677] Server {0x2b45e18837a0} DEBUG: (cache_inspector) there were 1 url(s) passed in
[Dec 31 20:49:25.677] Server {0x2b45e18837a0} DEBUG: (cache_inspector) URL 1: 'http%3A%2F%2F.*%5C.js%0D%0Ahttp%3A%2F%2F.*%5C.jpg'
[Dec 31 20:49:25.677] Server {0x2b45e18837a0} DEBUG: (cache_inspector) URL 1: 'http://.*\.js
http://.*\.jpg'
[Dec 31 20:49:25.677] Server {0x2b45e18837a0} DEBUG: (cache_inspector) URL 2: 'http%3A%2F%2F.*%5C.jpg'
[Dec 31 20:49:25.677] Server {0x2b45e18837a0} DEBUG: (cache_inspector) URL 2: 'http://.*\.jpg'
```

`sizeof(show_cache_urlstrs[s])` を `q ? q - p + 1: sizeof(show_cache_urlstrs[s])` に修正すれば正常に動作するようになりました。

この問題は[\[TS-4106\] Cache Inspector fails to split multiline URLs - ASF JIRA](https://issues.apache.org/jira/browse/TS-4106)にイシューを作成して報告済みです。

2016-01-13追記: このバグはmasterでは修正済みです。
その後プルリクを送ってmasterにマージされました。
https://github.com/apache/trafficserver/commit/a70e9473cabba89861965d5ab775dbe4f20c720b

# Cache Inspectorのテキストエリアの各行は最大499文字

https://github.com/apache/trafficserver/blob/413dd51d5dc17bf388805071efdb8f882014b847/iocore/cache/CachePages.cc#L114 のようにテキストエリアの各行の内容を保持する領域を `show_cache_urlstrs = new char[nstrings + 1][500];` というコードで確保しているので、複数行で指定するURLの正規表現は1行あたり499文字以下にする必要があります。ご注意ください。

# 多段キャッシュ

今回は試していませんが、[Hierarchical Caching](http://trafficserver.readthedocs.org/en/latest/admin-guide/configuration/hierachical-caching.en.html)に多段キャッシュについても説明がありました。

# おわりに
初めてApache Traffic Serverを試してみたのですが、良い感じですね。今後活用していきたいです！

あと、6.x用のレポジトリ[hnakamur/apache-traffic-server-6 Copr](https://copr.fedoraproject.org/coprs/hnakamur/apache-traffic-server-6/)も作りました。こちらも今後試してみようと思います。