Heroku上でスクリーンショットサーバを動かす
前回、PhantomJSのbuildpackを作成したので、Heroku上でPhantomJSのプロセスを自由に稼働させることができるようになった。
PhantomJSはGUI環境のない(Headless) WebKitブラウザであるため、ブラウザ上のJavaScriptの単体テストor結合テストをサーバ上で走らせて、継続的インテグレーションに組み込むなどの利用方法もあるだろう。これも興味深いトピックではあるけど、ここでは触れない。
今回はPhantomJSの画面レンダリングの機能を使って、スクリーンショットサーバをHeroku上に構築する。
構成と処理フロー
PhantomJSにはwebサーバ機能も含まれており、単独でHTTPサーバとしてリクエストを受け取る事もできるが、実サービスで利用するようなシロモノではない。そのためクライアントからリクエストを受け取るNode.jsのサーバを別に立てる。今回はこれもHeroku上で行なっている。
Node.jsのアプリケーションは、Socket.IOサーバとして稼働し、2つのchannelを持つ。
1つめのrequest channelでは、ブラウザからのスクリーンショット取得リクエストを受け取り、ページのURLをキューに入れる。
もう1つのrender channelでは、PhantomJSのスクリーンショットサーバと接続する。PhantomJSはスクリーンショットサーバであるが、Socket.IO的にはクライアントになる。スクリーンショットのリクエストは、接続中のPhantomJSクライアントのうちの1つにdispatchされる。
PhantomJSのアプリケーションは、PushサーバとしてのNode.jsにSocket.IOで接続し、スクリーンショットのリクエストを待つ。ページのURLがプッシュされた時点で、PhantomJSはページにアクセスし、スクリーンショットのレンダリングを開始する。
スクリーンショットは画像ファイルとしてPhantomJSのローカルファイルシステムに保存されるが、そのままではどこからもアクセスできないので、イメージサーバであるAmazon S3にアップロードする。ここで、ファイルデータをNode.jsに戻すことなく、PhantomJSから直接Browser Post Formを利用してS3にアップロードする(postの際に必要になるsignatureは事前にNode.js側で生成されており、プッシュ通知の際にページURLと共に同時に渡ってくる)
最後に、PhantomJSはアップロードしたスクリーンショットのURLをNode.jsに通知する。その後、Node.jsはrequest channelに接続しているクライアント全てに対してスクリーンショットのURLをブロードキャストする(ブラウザはそれを受け取って描画する)
利点
スクリーンショットサーバをクラウド上で構築する場合、今まではEC2などのIaaS上でGUI環境を含むOSを稼働させることが多かったが、常時起動によるリソースの有効利用およびスケール時のVMの上げ下げのコストが問題点としてあった。
これを、HeadlessのPhantomJSでスクリーンショットを撮り、それをHeroku上で動作させることにより、スクリーンショットサーバのスケールアウトがスピーディかつ容易になっている。
たとえば、以下のコマンドでインスタンスを瞬時に*1上げ下げ可能である。
$ heroku ps:scale renderer=4
資料
- スライド: Phantomjs Screenshot Server on Heroku
- ソース: https://github.com/stomita/heroku-screenshot
- 実際にデプロイしたアプリ(デモ用途のみ:予告なく落とす場合があります): http://heroku-screenshot-nodejs.herokuapp.com