Otter、シングルページアプリケーションに特化したウェブサーバ


webアプリにてHTMLビューの作成はサーバーサイドとクライアントサイド、どちらでも可能です。迷って色々調べた所面白いフレームワークが見つかったので紹介します。

##サーバーサイド？クライアントサイド？
サーバーサイドレンダリングのサーバーはRESTful APIに専念して、極力HTMLビューの作成をさけ、クライアント側に任せるアーキテクチャーで、[2010年時のツイッター](http://engineering.twitter.com/2010/09/tech-behind-new-twittercom.html)がそうです。

クライアントレンダリングの問題点として、よく取り上げられるのが

* リクエストはクライアント側からサーバ側に、一往復して帰ってくる、ネット状態によっては往復する分時間がかかる。
* curl、又はJavaScriptが制限されたブラウザではアクセス不能。
* 同様、**サーチエンジン**と[相性が悪い](http://danwebb.net/2011/5/28/it-is-about-the-hashbangs)、SEOに不便。

で、ツイッターは2012年からレンダリングを大半サーバーサイドに戻した、と[ブログ](http://engineering.twitter.com/2012/05/improving-performance-on-twittercom.html)に書かれてます。

それに対してLinkedinの場合、クライアント側でjsonからHTMLへとレンダリングする事自体は軽い、そもそもHTMLをクライアントに転送する方が遅いと評価し、[duskjs](http://akdubya.github.com/dustjs/)を中心にしてアーキテクチャーを築き上げた。

サーバー／クライアント両方でレンダリングしたいが、見知らぬライブラリに頼り切るのはどうだろう、また何の位まで使いこなせるかが問題。そこで今回見つけたのが[Otter](https://github.com/bfirsh/otter)

##Otter
[Otter](https://github.com/bfirsh/otter)はシングルページアプリに特化されたウェブサーバで、その特点は

* サーバー／クライアント、両方同じAPIを使う。
* DOM操作は馴染のあるjQueryで
* HTTPはそのままXMLHTTPRequestで
* 一回書いたコードはサーバー／クライアント**両方で使う**

公式サンプルではこの様なBackboneアプリで説明してます。動くサンプルは[こちら](http://kawauso.herokuapp.com)。

```HTML:index.html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <title></title>
</head>

<body>
  <div class="content"></div>
</body>
<script src="/vendor/jquery-1.8.1.min.js"></script>
<script src="/vendor/underscore.js"></script>
<script src="/vendor/backbone.js"></script>
<script src="/vendor/backbone-otter.js"></script>
<script src="/app.js"></script>
</html>
```

```JavaScript:app.js
var User = BackboneOtter.Model.extend({
    url: function() {
        return 'http://api.twitter.com/1/users/show/'+this.id+'.json?callback=?';
    },
});

var BaseView = Backbone.View.extend({
    hasRendered: function() {
        return this.$el.children().length > 0;
    },

    render: function() {
        this.$el.html(this.template(this.templateData()));
        return this;
    },

    templateData: function() {
        return {};
    }
});

var IndexView = BaseView.extend({
    template: _.template('<h1>Twitter!</h1><a href="users/14510231">bfirsh</a> <a href="users/14149919">aanand</a>')
});

var UserView = BaseView.extend({
    template: _.template("<% if (name) { %><h1><%= name %></h1><p><%= description %></p><p><%= location %></p><% } else { %><p>Loading...</p> <% } %>"),

    initialize: function() {
        this.model.on('change', this.render, this);
    },

    templateData: function() {
        return this.model.attributes;
    }
})

var Router = Backbone.Router.extend({
    routes: {
        '': 'index',
        'users/:id': 'user'
    },

    initialize: function() {
        this.on('route', function() {
            this.hasRouted = true;
        });
    },

    index: function() {
        var view = new IndexView({el: $('.content')});
        // Render this view if we are not on first route or server has not filled content.
        if (this.hasRouted || !view.hasRendered()) view.render();
    },

    user: function(id) {
        var user = new User({id: id});
        user.fetch();
        var view = new UserView({el: $('.content'), model: user});
        if (this.hasRouted || !view.hasRendered()) view.render();
    }
});

$(function() {
    var router = new Router();
    Backbone.history.start({pushState: true});

    $(document).on('click', 'a', function(e) {
        e.preventDefault();
        router.navigate($(this).attr('href'), true);
    });
});
```

##404を出さないサーバ？
理想的なシングルページアプリケーションを実現するため、[Otter](https://github.com/bfirsh/otter)は存在しないリページをリクエストされても404を返さず、ヘッドレスブラウザの[zombie.js](http://zombie.labnotes.org)を使ってindex.htmlを開きます。indexのJavaScript routerによりレンダリングが終わると`document.outerHTML`をクライアントに送ります。これによりウェブクローラなどJavaScriptが制限されてるブラウザからでもサーバーサイドレンダリングによってコンテンツがちゃんと見えます。

ココまで来たらもうお分かりでしょうが[Otter](https://github.com/bfirsh/otter)の売りは、**既存のアプリケーションを変更せず**クライアント側のフレームワークをサーバー側で使える事です。

##それなら普通のサーバーサイドレンダリングじゃ無いのか？
いいえ、クライアント側では`Backbone.history.start`が作用してクライアントサイドレンダリングが働きます。

しかもサーバー側で一度`fetch`したModelを`window.otter.cache`にプロパティに指定すればクライアント側からもアクセスする事が出来ます。

[サンプルサイト](http://kawauso.herokuapp.com)で`bfirsh`をクリックするのとクライアントサイドレンダリング、実在しない[users/14510231](http://kawauso.herokuapp.com/users/14510231)を使ってアクセスするとサーバーサイドレンダリングになります。コンソールを使って`window.otter.cache`を確かめて下さい、サーバー側のキャシュ情報が残っています。