今見てるのは本番?それともステージング?

今見てるのは本番?それともステージング?

序章

Webサイトの最終動作確認をするべくしてそこに鎮座ましますステージングサーバ。
その存在たるや本番サーバと見紛うばかり、容易に違いを見抜けるなどどゆめゆめ思うことなかれ。

そう、ステージングサーバって基本的には本番と同じ構成・ソースで動かしたいので出来る限りソースには手を入れたくないんです。
でも今見てるのが本番じゃなくてステージングサーバだよ!っていうアピールをして欲しい。

そんな我儘に応える方法があるのか、その謎を解き明かすべく我々は南米へ飛んだ。

邂逅

そこで我々が出会ったのはApacheモジュール[mod_ext_filter]だった。
その神々しきまでの存在は我々の望む機能を簡単に提供してくれるに違いない。
そう思わせるのに十分な輝きを放っていたのだった。

解説

さて、そんなmod_ext_filterですが、詳しい説明は本家日本語訳)を見ていただくとして、簡単な使い方と仕組みを説明してみたいと思います。
なお、CentOS7.3でyum install httpdした状態のApache2.4を基準に説明していきます。

Apacheは/etc/httpd/conf.d以下の*.confを全て読み込む設定になっているので、ここに仮にserver_banner.confというファイルを作ります。
中身はこんな感じです。

ExtFilterDefine banner mode=output intype=text/html cmd=""

<Directory /var/www/html>
    SetOutputFilter banner
</Directory>

このままだとこれっぽっちも動作しないのですが、これが基本形となります。
ちなみにSetOutputFilterはApacheのcoreモジュールですね。
1行目はフィルタの定義となります。フィルタには好きな名前を付けることができ(ここではbannerとしました)、その動作モードをoutputとします。
これは「Apacheがコンテンツを出力する時に動作する」という意味になります。
また、これから出力しようとするもの、つまりフィルタにとっては「入力されるもの」ですが、それがtext/htmlの場合だけに動作が限られます。
最後のcmdはこのフィルタの実体となるコマンドを指定します。
このコマンドは標準入力から受け取った物を標準出力に返す形のものであればなんでも大丈夫です。
つまりRubyでもPerlでもシェルスクリプトでも構いません。
※ オーバーヘッドは出てしまいますが・・・

3~5行目はフィルタを適用するコンテンツを指定しますが、今回はステージングサーバで動作しているもの全てに適用したいのでDirectoryディレクティブで/var/www/htmlを指定しています。
4行目が実際のフィルタ適用になります。

実践

さて、実際にcmdにフィルタを書いていって動作させてみましょう。
私が最終的にたどり着いたのはこんなフィルタでした。

cmd="/bin/sed -e 's|</\\s*body>|<script>if(!document.getElementById(\"__server_banner__\"))document.write(\\\'<style>#__server_banner__:hover{opacity:0;}#__server_banner__{transition:.25s;position:fixed;right:-50px;bottom:-50px;z-index:999999;margin:0;padding:0;transform:rotateZ(-45deg);}#__server_banner__ span{margin:0;padding:0;display:table-cell;text-align:center;vertical-align:top;width:100px;height:100px;color:#fff;background:red;font:12px\\/1.2 Arial,sans-serif;}<\\/style><div id=\"__server_banner__\"><span>NEW<br>STAGING<\\/span><\\/div>\\\');<\\/script></body>|i'"

わけわかりませんね。
順に解説してみます。
まずはみんな大好きsedです。
書き換えると言えばこの子です。
今回使用している基本的な構文は

sed -e 's/before/after/i'

なんですが、HTMLを扱う際はどうしてもsコマンドの区切り文字に使われている/がややこしくなってしまうので

sed -e 's|before|after|i'

と、パイプを使用しました。
このsコマンドの区切り文字は実は結構色々な記号を使うことができるので色々試してみると良いと思います。
意外なところでは「!」なんかも使えたりしますね。

基本的な構文の中でのbeforeにあたる部分、何を置換するのかですが

</\s*body>

としています。
実際にconfファイルに書く時にはバックスラッシュをエスケープしてあげる必要がありますがここからは意味を解説していくので省略させていただきました。
本来であれば(HTMLの構造としては間違いですが)intypeでtext/htmlを指定しているので、コンテンツの最後に

的な奴を付けてやりゃー大丈夫だろうと思っていたところ、一部のコンテンツがContent-Type: text/htmlのままJSONを返却していたりしたので苦肉の策とも言えますが、安全面(何の?)からは良いんじゃないかなと思います。
更に言うと</body>でも良いとは思うんですが、万が一スペースとかが入っていても良いように「\s*」を入れてあります。

続いてafterにあたる部分、何に置換しているのかですが、ちょっとわかりにくすぎるので一旦分解して説明します。

<script>
if(!document.getElementById("__server_banner__")) {
  document.write(...);
}
</script>
</body>

まずはこんな感じです。
</body>の直前にscriptタグを挿入した形です。
そのscriptタグの中で、__server_banner__というIDを持つ要素が見当たらない場合のみ、document.write()によって実際のコンテンツを出力する形となっています。
なぜこんな手間のかかることをしているかというと、実際に表示される要素は1つで良いのでそれを判定している感じです。

さて実際に表示させている要素ですが、ここはもうただのHTML(をJavaScriptの文字列化したもの)なので自由なんですが、今回は「どこかに常時表示されている、でも邪魔にはならない」ようにしたかったので、右下にfixed配置、さらにいざという時には邪魔にならないようにマウスオーバーでスッと消える、という無駄に凝った表示にしてみた物を参考までにご紹介しておきます。

<style>
#__server_banner__:hover {
  opacity: 0;
}

#__server_banner__ {
  transition: .25s;
  position: fixed;
  right: -50px;
  bottom: -50px;
  z-index: 999999;
  margin: 0;
  padding: 0;
  transform: rotateZ(-45deg);
}

#__server_banner__ span {
  margin: 0;
  padding: 0;
  display: table-cell;
  text-align: center;
  vertical-align: top;
  width: 100px;
  height: 100px;
  color: #fff;
  background: red;
  font: 12px/1.2 Arial,sans-serif;
}
</style>

<div id="__server_banner__">
  <span>STAGING</span>
</div>

最後にこれらの設定が実際に適用された画面をご紹介しつつお別れとなります。

すごーい!ちゃんと著作権に配慮できるフレンズなんだね!

TAG

  • このエントリーをはてなブックマークに追加
やまま
スペシャリスト やまま yamama

マンガとアニメとゲームから錬成された宇宙大好きエンジニア。 軌道エレベーターで行ける静止軌道上のコロニーに住まいを移し、ゲームやってマンガ読んでアニメ見て爆睡、ゲームやってマンガ読んでアニメ見て爆睡、という生活を夢見ながら今日もコードを書き続けるのだった。