15 min/d

ぼうずやのにっき

S3でBackbone.jsのHistory(pushState)に対応できるかをためした

昨日の続きなのだけれど、S3でBackbone.jsのHistory(HTML5 History APIのpushState/popState)に対応できるかを検証してみた

結論から言うと対応できる。デモはここから見れるリポジトリbouzuya/backbone-pushstate-exampleにある。

ためしたことを説明しようと、ごちゃごちゃ書いたのでメモ。別に読まなくていい。

Backbone.jsやKnockout.jsやAngularJSといったクライアントサイドMVCフレームワークが流行っている。クライアントサイドのJavaScriptも構造化を持ち込まないと対応できない状態になっているからだろう。

クライアントサイドMVCフレームワークでWebアプリケーションをつくるとき、SPAかMPAかの選択を迫られる。SPAはSingle Page Applicationで、その名前のとおり単一ページのアプリケーションであり、MPAは複数ページである。

MPAもそうだが、SPAをつくるときは特にページの遷移を避けて、かわりにJavaScriptでページを書き換える。要するにJavaScriptでイベントをとり、非同期通信を行い、画面を描画する。この動きは別に新しいものではなく、GoogleAjaxどうこう騒ぎはじめた時点で既にできあがっていた流れである。SPAはこれを当然のものにしている。何せ1ページしかないわけだから、その中で描画していくよりほかない。

JavaScriptで描画する流れが加速するにつれて死にかけていたのがURLである。

ロケーションバーにURLを入れると、サーバーにリクエストが送られて、レスポンスを受けて、ページ全体が再読み込みされる。この当たり前だった動作が、いまでは当たり前ではなくなってしまったわけだ。

一方で、JavaScriptで描画する仕組みはHTTPとしてはあまり自然ではない動きである。URLとコンテンツとが一致しないことがままあるわけで。検索エンジンへの対応を考えてもURLとコンテンツとの対応は重要である。

対策としてhashChangeを使うものやpushStateを使うものがある。

hashChangeを使うものは、URLのfragmentの変更を使ったものである。Twitterなどが一時期採用していた#!を含むURLがその例である。

pushStateを使うものは、Historyの変更を使う。まだ一部のブラウザは対応していないが、こちらはURLにfragmentを含む必要もないため、JavaScriptで描画しない場合との区別はつかない。ただし、サーバーサイドにも工夫が要る。

サーバーサイドの工夫について説明するために例を挙げる。

例えば、次のリクエストが投げられた場合、サーバーサイドには/へのリクエストが送られる。/JavaScript#!/users/user1を解釈できれば、何も問題はない。

GET /#!/users/user1

では、次のリクエストが投げられた場合はどうか。サーバーサイドには/users/user1へのリクエストが送られる。/JavaScriptが送られるわけではないので、/users/user1用に独自のJavaScriptを返すなりなんなりをしないといけない。

GET /users/user1

簡単な対応方法としては、/をレスポンスとして返しつつ、users/user1を何かしらの方法で渡す。/#!/users/user1へリダイレクトさせる方法もある。上記の検索エンジンなどの対応からすれば問題のあるやり方だろうが、/しかURLを持たないよりよっぽど良い。

と、前提知識を書いたところで本題。ここで冒頭と同じところまで戻る。

昨日も書いたのだけれど、HerokuをRoute53から使おうとしたところ、"root domain"にCNAMEレコードを使えないし、AレコードもHerokuはIPアドレス固定でないため対応できない。そこで諦めて、S3に静的なコンテンツをホストしつつ、APIサーバーとしてHerokuを使うことにした。

そのためにS3で上記のようなHTML5 History APIのpushState/popStateに対応できるかを検証してみた。

S3にはRoutingRuleを設定できる。これを使えば存在しないページへアクセスしたときのルールを設定できる。例えば、次のようにすれば404ならBUCKET_NAME.s3-website-ap-northeast-1.amazon.comにプリフィックス#をつけてリダイレクトする。

<RoutingRules>
    <RoutingRule>
        <Condition>
            <HttpErrorCodeReturnedEquals>404</HttpErrorCodeReturnedEquals>
        </Condition>
        <Redirect>
            <HostName>BUCKET_NAME.s3-website-ap-northeast-1.amazonaws.com</HostName>
            <ReplaceKeyPrefixWith>#</ReplaceKeyPrefixWith>
        </Redirect>
    </RoutingRule>
</RoutingRules>

Backbone.jsのHistory側は次のようにしておく。

Backbone.history.start({ pushState: true });

これでpushStateが使える場合はそれを使い、そうでない場合はhashChangeを使う。

今回のデモページは上記設定を使って実現している。

リンクをクリックしてのページ書き換えだけでなく、ロケーションバーに/users/testなどを入力してみてほしい。期待通りに動いてくれる。

動きは/users/testへのリクエストが404になり、それを#/users/testにリダイレクトさせる。/のBackbone.jsのHistoryがそれを解釈し、変換してくれる。

S3に静的なWebサイトをホストする設定については書いていないけど、そちらはリソースが山ほどあるので割愛。

思い通りにできたので、これを使ってbwilike.meの移植を進めるよ。