スマホアプリ向けのAPIサーバの品質ガイドライン

ふと思い立ってかいた。

スマホアプリを開発する際、mixiさんが公開しているようなスマホ向けガイドラインと同様にAPIサーバでも、ある程度の確認用のガイドラインを個人的に設けていますので、メモとして公開してみます。クラウド環境の登場により誰でも簡単にサービス公開できるようになりましたが、結局のところインフラ構築コストが無くなるだけであり、サーバ品質は自身で担保しないとなりません。

前提

  • ガイドラインの目的
    • リリースしてから、問題が発見されるようなことが無いように、サービスを運用する上で確認すべきことをリスト化しています。機能要件の試験は済んでいることを前提とし、運用レベル確認のためのリストになっています。当然、品質目標はミッションクリティカルなレベルの品質ではなく、toC向けの無料スマホアプリレベル品質になります。
  • 対象
  • 環境
    • AWS等のスケーラブルなクラウド環境。私がAWSユーザなので、AWSベースに表記が一部なっています。
  • 前提
    • 結合試験レベルまでの機能レベルテストは完了していること。条件網羅系試験、境界値試験、同値分割、フルパス等一般的な試験は実施済みであることが前提です。

全体的な機能確認

  • 実データ確認
    • 本番環境、若しくはそれに近い実データでオンライン処理、バッチ処理を実行させ基本機能が実現できることを確認
  • オンライン・バッチ連動
    • オンライン処理データをバッチ処理で加工し、またオンライン処理で使う等の場合、オンライン→データベース→バッチ処理→データベース→オンラインとデータが正しく引き継がれ、処理が実行されること
  • 業務処理・運用処理連動
    • マスタメンテ等の運用系機能からデータを設定し、それがオンライン処理側で利用できることが確認できていること。
  • アプリ連携
    • アプリ to アプリ、アプリ to ブラウザ、ブラウザ to アプリのようにアプリ連携するようなサービスはそれらの全パスを網羅し確認出来ていること
  • 環境差分
    • 開発環境と本番環境の差による確認が出来ていること。
    • パス等は全て本番に向いていること。
    • 外部API接続アカウントは本番用になっていること。
  • 国際化
    • アプリ、サーバ、バッチ全てにおいて国際化が行われていること。
    • ここで言う国際化は文言だけではなく、データフォーマット、文化による振る舞いの変化等も含む。

☆あるある:本番データを入れると動かない。本番環境の0件データ状態だと落ちる。
☆あるある:オンライン側の処理の入力チェック仕様とマスタメンテの入力チェック仕様が不整合。

対外接続確認

  • 疎結合
    • 接続先外部サービス(TwitterFacebook等)が落ちている場合、その影響を受け、自身のシステムが利用停止にならないこと。
    • 接続先からエラーが出た場合に、丸め込み等が出来ていること。
  • 性能確認
    • 接続先サービスの性能限界(TwitterAPIでいうRateLimit)を把握しており、それにあわせた対応実装がなされていること。
  • 規約遵守
    • 接続先サービスのプラットフォームポリシーガイドライン等に準拠していることが確認できていること。|

☆あるある:Facebookプラットフォームポリシーガイドラインに準拠せずにアプリ登録をBANされる。

性能確認

  • 単性能
    • スケール前の最小構成にて、レイテンシーが許容値以内である確認が出来ていること。
  • 最大性能
    • スケール前の最小構成にて、性能の最大値を把握できていること。
    • ボトルネックになる部分を把握していること。
  • 過負荷
    • 負荷をかけた状態にて、通常の処理の確認が出来ていること。
  • 最大データ量
    • 想定される最大データをDB等に設定し、機能の確認が行えていること。

☆あるある:データが多くなりバッチ処理時間が伸び、後続バッチ、またはそれを必要とするオンライン処理の開始時刻に間に合わなくなる。
☆あるある:大量データのCSVアップロード、ダウンロードでセッションタイムを超えたときに切断してしまう。

拡張性確認

  • スケール
    • スケーラブルな実装になっていること。
    • ElastiCache、DynamoDB等オートスケールしないものに対するスケール性の担保の仕方が決定し、対策が講じられていること。
    • EC2起動時に必要な全てのサービスが起動するようになっていること
    • スケールインする際に必要なログ等のデータはストレージに退避するようになっていること

☆あるある:push用のバッチもAPIサーバで稼働しており、スケールさせるとバッチ処理がサーバ毎に重複起動し同じプッシュがユーザに複数配信される。

可用性確認

  • 耐障害性
    • 障害時に復旧するための手順があり、それの確認が出来ていること
    • 障害時に自動復旧する仕組みが講じられていること。
  • SPOF
    • あるサーバの停止がシステム全体を止めてしまわないこと。
    • あるモジュールの停止がサーバを止めてしまわないこと。

☆あるある:オンライン処理とバッチ処理を同一サーバ上にで実行している場合、バッチの過負荷によりオンライン側が停止。

運用確認

  • バックアップ
    • 必要なデータがバックアップ出来ていること、またそれをリカバルする方法が決まっており、確認が出来ていること
  • 監視
    • システムの状態を監視する仕組みがあること。
    • 不具合の解析に必要な情報がログから取得出来るようになっていること。
  • 日廻し試験
  • 月廻し試験
    • 上記同様
  • 特定日試験
    • 祝日、祭日、閏年等により振る舞いを変える場合、その動作が確認出来ていること。
  • 問い合わせ運用確認
    • データ削除等のユーザからの問い合わせによる運用がある場合、運用担当者を交え、運用手順に従い運用を実施出来ることを確認できていること。
    • ユーザ問い合わせへの調査に必要な情報がログから取得出来るようになっていること。
  • ログ運用
    • ローテーション、退避。
  • 長時間運用時の資源
    • サーバの再起動無しで長時間運用され続けている状況で、資源(メモリ、CPU、ディスク、..etc)の枯渇を起こさないことが確認できていること。

☆あるある:ログローテートが出来ておらず(またはファイルサイズ見積もりが甘く)、サーバ容量を圧迫し、サーバ停止。

セキュリティ確認

上記の確認ポイントを確認しつつ、バグが見つかった場合は、以下のような基本動作の徹底が必要です。

バグ発生時の基本動作

開発終盤になってくるとバグチケットの山になり、それに埋もれた終わりの見えない状態になると思いますが、そんなときこそ、基本動作を徹底することは大切です。バグでましたーなおしますー、こっちでも出ましたーなおしますーという場当たり的な修正ではなく、品質を作り込むことを意識しないといけません。今回のさいごの仕上げ部分に閉じず、どの試験でも同じスタンスが必要です。

  • バグに対して、以下の切り分けを行うこと
    • このバグを発見するための試験観点があり、それによって検出された場合
      • その場合はハッピーですね。摘出すべくして、摘出した。試験項目がヒットしたということです。
    • このバグは既に試験実施済みの観点なのにも関わらず、検出された場合
      • これは、完全に試験すり抜けなので、何故すり抜けたかを原因を確認し、必要であれば、その観点で再試験が必要です。実施済み観点でバグが出てしまうと、今までやった試験自体の信頼性を疑わざるを得ない状況になってしまいます。
    • このバグが試験をしていない観点のバグであった場合
      • 観点漏れです。試験観点を追加し、追加試験の実施が必要です。
  • 横並びチェック・歯止め・真の原因の追求
    • また、不具合に対してはある程度真の原因を追求し、その原因に対する歯止めの実施、および影響範囲を洗い出し横並びチェックを実施すべきです。横並びチェックとしては、究極的に、同じ開発者が同じ勘違いを複数箇所でしていた場合は、その開発者のコミットしたソースを全部確認する(自動or手動)ことになります。こういう泥臭いことを行わないと、結局、デグレード、同じようなバグの発生が止まらない、とかとか、結局バグ発生曲線が収束しないのです。そして、同じような確認を何度も再帰的に行うので、テストを自動化しておけば良かったということになります。

API管理もこれで簡単!Apigeeが凄い

Web2.0で火がついたマッシュアップブームにより、WebAPIが脚光を浴びました。
当時は、GoogleMap上に何かを表示して、すげーーーってなっていただけですが、
最近は、スマホアプリのバックエンドとしてのより実用的な需要が高まっております。

スマホアプリを開発する場合は、サーバサイドはJSONをレスポンスするREST形式のAPIにすることが一般的だと思いますが、それらのAPI提供社側はJavaDocのような統一したAPIリファレンスがあるわけではなく、各社独自にリファンスドキュメントを公開しております。もっとフォーマットが統一されていると見やすいですよね。(個人的にはGoogleAppsAPI Consoleが好きです。APIリファレンスであり、実行も出来るという。)

以下に、いくつかAPI公開されているもののリファレンスをあげてみます。

ATND API
http://api.atnd.org
f:id:i2key:20130524183226p:plain:w600

Twitter API Refference(例:Timeline)
https://dev.twitter.com/docs/api/1.1/get/statuses/mentions_timeline
f:id:i2key:20130524184438p:plain:w600

Twitterはもう一つこれも Twitter API Console
https://dev.twitter.com/console
f:id:i2key:20130524184459p:plain:w600

最後のTwitterAPI ConsoleはAPIをリファレンス上で実行できるのです。このTwitterが使っているコンソールはApigeeというAPI管理サービスを使っており、これは誰でも無料で利用できます。

そこで、上記のATND APIをapigeeでAPIコンソール化してみました。全画面で見たい場合は、右上の全画面ボタンを。

これ、リファレンス兼デバッカーになって凄く便利。
認証が必要なAPIの場合は、OAuthも出来たりします。

作り方は簡単
(1)Apigeeにサインアップ
 http://apigee.com/

(2)ApigeeのAPI Consoleをクリック
f:id:i2key:20130524184725p:plain:w600

(3)Create API Consoleをクリック
f:id:i2key:20130524184835p:plain:w600

(4)作成するリファレンスの情報を入力
f:id:i2key:20130524184905p:plain:w600

(5)WADLをアップロードする
f:id:i2key:20130524184937p:plain:w600

これで完成。WADL手で作るのメンドーだけど、変数くらいはAPIのサーバサイドのI/O関係のデータクラスとかから自動生成できそうね。

ちなみにWADLはSOAPにおけるWSDLみたいなもの。wiki
WADLってしらなかったー。となると、java2wsdl.shやwsdl2java.shのようなものを期待してしまう。

意外に簡単!LINEのような電話番号認証の作り方(Twilio-SMS編)

LINEで使われているような電話番号による認証を試しに作ってみたので、ブログにまとめておきます。
電話番号認証とはこんなのです。
f:id:i2key:20130210184107p:plain:w200f:id:i2key:20130210184112p:plain:w200

まず、電話番号を用いた認証ですが、方式が2通りありますので、それらの概要を説明します。

  • SMS利用パターン

電話番号をアプリ側で入力すると、サーバから4桁の暗証番号入りのSMSが届き、その4桁の暗証番号をアプリ側で入力することで電話番号が正しいことを証明するやりかたです。
SMSを送信するには送信元の番号が必要になります。また、例えばアメリカ、カナダのようにショートコード(SMSで利用される事業者コード)を取得する必要のある国もあります。ショートコードは審査が入るため、数ヶ月取得に要することがあります。今回は送信者番号は通常の電話番号にします。

  • IVR利用パターン

IVRとはInteractive Voice Responseの略で、自動音声応答による認証です。
事前にサービスに電話番号を登録しておき、ユーザにサービスの電話番号へ電話をかけさせることで、その番号を認証するInbound方式と、サービス側から登録した電話番号に電話をかけるOutbound方式があります。これはサービスを展開する国の電話料金、国際電話、国内電話の条件にあわせて使い分けることになります。
SMS認証だけでは、SMSが到達しなかったりとエラーをハンドリングした運用が必要になるため、補足的にIVRをリカバリ手段として提供するケースが多いです。

今回はSMS利用パターンで電話番号認証を作ってみました。
まずは、SMSを送信するためのサービスプロバイダーが何社かありますので、そのなかの一つを使ってみます。

SMS/IVR両方可能 無料枠が$20あり、開発用に自分に電話するだけであればある程度無料で利用可。

IVRのみ可能。日本のサービスなのでサポートが安心?

日本企業ですが、B2B型なので、個人サービサーがさくっとはじめるには敷居が高いかも。

ということで、Twilioの無料枠内で実験してみました。
SMS送信元番号で利用する電話番号はTwilio内で取得可能なので、それを使います。
Twilioで日本の番号を取得しようとするとSMSがno availableです。月$5で電話番号が取得できます。
f:id:i2key:20130210200259p:plain:w400
なので、USでてきとーに取得します。USの番号はSMS利用可能です。USは月$1で電話番号が取得できます・・・。すごい。
f:id:i2key:20130210200307p:plain:w400

次にSMS送信方法ですが、以下のAPIで簡単にSMSが送信できます。

curl -X POST 'https://api.twilio.com/2010-04-01/Accounts/{AccountSid}/SMS/Messages.json' \
-d 'From={+81のような国際電話形式の送信元電話番号}' \
-d 'To={+81のような国際電話形式の送信先電話番号}' \
-d 'Body={メッセージ本文}' \
-u {AccountSid}:{AuthToken}

Twilio REST APIのリファレンスはこちら

更にTwilioは各言語のためのSDKを用意してくれているので、それを使えばさくっとAPI利用できます。
Javaでは以下の感じです。

// Install the Java helper library from twilio.com/docs/libraries
import com.twilio.sdk.TwilioRestClient;
import com.twilio.sdk.TwilioRestException;
import com.twilio.sdk.resource.factory.SmsFactory;
import com.twilio.sdk.resource.instance.Sms;
import com.twilio.sdk.resource.list.SmsList;
import java.util.HashMap;
import java.util.Map;
 
public class Example { 
 
  // Find your Account Sid and Token at twilio.com/user/account
  public static final String ACCOUNT_SID = "SIDを設定してね";
  public static final String AUTH_TOKEN = "{{ auth_token }}";
 
  public static void main(String[] args) throws TwilioRestException {
    TwilioRestClient client = new TwilioRestClient(ACCOUNT_SID, AUTH_TOKEN);
 
    // Build a filter for the SmsList
    Map<String, String> params = new HashMap<String, String>();
    params.put("Body", "メッセージ本文");
    params.put("To", "+81901234XXXX");
    params.put("From", "+81909876XXXX");
     
    SmsFactory messageFactory = client.getAccount().getSmsFactory();
    Sms message = messageFactory.create(params);
    System.out.println(message.getSid());
  }
}

これを利用して、AWS環境でSMS認証サーバを構築しました。下図に処理フローをまとめてあります。認証用の一時データ保存領域としてMemcache(ElastiCache)を、QueueとしてSQSを使いました。
f:id:i2key:20130210193754p:plain:w800

基本的には図に書いてある通りです。
補足としては... Twilioはショートコードを利用するとSMSの一括大量送信が可能になるのですが、今回はショートコードを取得していない関係上、Rate Limitで1送信元電話番号あたり1秒に1通しか送信できないという制限にひっかかってしまいます。そこで、どうせ月1ドルなので、スケールさせたい数だけ電話番号を取得して、取得した番号の数だけ、スレッドプールにSMS送信スレッドをプールする方式をとりました。それで購入した電話番号の数だけ並列送信が可能になります。そのためにQueueを利用しています。

  • 実装環境

サーバ : AWS EC2
Memcache : AWS ElastiCache
Queue : AWS SQS
FW : Play2.0.4
言語 : Java

今更だけどEnumでSingleton

Java1.5からEnum型が追加され、定数の定義等に利用されていますが、EffectiveJava久々に読み直していたら、EnumでSingletonをやるのがおしゃれさんだよと書いてあったので、ブログにメモしときます。
周知の事実すぎるので、今更かよ感ありますが、知らなかった人は明日からやってみてください。

Java1.5より昔は以下の様にSingletonにしていたと思います。ここでは遅延初期化とか、スレッドセーフかどうかとか、シリアライズで複製されちゃうじゃんとかそういうことは分かり難くなるので、省きます。そうすると、こんな感じですね。

package singleton;

public class TraditionalSingleton {
	private static final TraditionalSingleton INSTANCE = new TraditionalSingleton();

	private TraditionalSingleton(){}
	
	public static TraditionalSingleton getInstance(){
		return INSTANCE;
	}
	
	public void method(){
		System.out.println("this is singleton");
	}
}

それをEnumでやると以下のようになります。Enumで定数化したときに、コンパイルすると実際は、public static final hogeになっているので、結局は同じことなんですが、可読性及び、シリアライズに対応していたり、リフレクションによるprivateコンストラクタ実行への防御とか、モロモロいい感じにしてくれます。

package singleton;

public enum ModernSingleton {
	INSTANCE;
	
	public void method(){
		System.out.println("this is singleton");
	}
}

実行は以下。

TraditionalSingleton.getInstance().method();
ModernSingleton.INSTANCE.method();

今まで知らなかったのが恥ずかしい。

ReverseAuthの使い方 #twtr_hack

Account.frameworkで既にTwitter認証済みのACAccountを使い回してiOS側からSLRequestやTWRequestでAPIコールをしているだけであれば、必要ないのですが、どうしてもサーバにもAccsessTokenやAccessTokenSecretを渡したい場合があります。

しかしながら、ServiceTypeがTwitterの場合、ACAccountにはACCredentialがあるもののその中のoauthTokenは空っぽです。Facebookの場合は、入っています。
つまり、ACAccountStoreでのTwitter認証の場合、tokenの生データに触れることができません。
そこで必要になるのがReverseAuthになります。

詳細はこちらにかいてあります。
https://dev.twitter.com/docs/ios/using-reverse-auth

事前準備
ReverseAuthをはじめるまえに、TwitterDeveloperページにてアプリケーション登録を済ませ、コンシューマーキーとコンシューマーシークレットを取得しておいてください。

f:id:i2key:20130130021401p:plain
2つのリクエストをTwitterに対して投げるとトークンを取得することが出来ます。
Step1 Special Request Tokenの取得
HTTP HEADERに認証や署名情報を含んだSingedRequestをbodyにx_auth_modeをreverseauthとして

https://api.twitter.com/oauth/request_token

に対して送信すると、AccessToken取得時に使用するtoken類一式を取得できます。

Step2 Access Tokenの取得
step1で取得したresponseの内容をそのままinputとして

https://api.twitter.com/oauth/access_token

に対して送信するとoauth_token、oauth_tokenSecret、screen_name、user_id等を取得できます。

Step1 Special Request Tokenの取得(詳細)
Special Request Tokenの取得では、Singed Requestを送信する必要があります。
例えば以下のリクエストのようなヘッダを付与する必要があります。
https://dev.twitter.com/docs/auth/authorizing-request より転載

POST /1/statuses/update.json?include_entities=true HTTP/1.1
Accept: */*
Connection: close
User-Agent: OAuth gem v0.4.4
Content-Type: application/x-www-form-urlencoded
Authorization: 
        OAuth oauth_consumer_key="xvz1evFS4wEEPTGEFPHBog", 
              oauth_nonce="kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg", 
              oauth_signature="tnnArxj06cWHq44gCs1OSKk%2FjLY%3D", 
              oauth_signature_method="HMAC-SHA1", 
              oauth_timestamp="1318622958", 
              oauth_token="370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb", 
              oauth_version="1.0"
Content-Length: 76
Host: api.twitter.com

status=Hello%20Ladies%20%2b%20Gentlemen%2c%20a%20signed%20OAuth%20request%21

作り方は以下になります。
Building the header string

To build the header string, imagine writing to a string named DST.

Append the string "OAuth " (including the space at the end) to DST.
For each key/value pair of the 7 parameters listed above:
Percent encode the key and append it to DST.
Append the equals character '=' to DST.
Append a double quote '"' to DST.
Percent encode the value and append it to DST.
Append a double quote '"' to DST.
If there are key/value pairs remaining, append a comma ',' and a space ' ' to DST.
Pay particular attention to the percent encoding of the values when building this string. For example, the oauth_signature value of tnnArxj06cWHq44gCs1OSKk/jLY= must be encoded as tnnArxj06cWHq44gCs1OSKk%2FjLY%3D.

Performing these steps on the parameters collected above results in the following string:

OAuth oauth_consumer_key="xvz1evFS4wEEPTGEFPHBog", oauth_nonce="kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg", oauth_signature="tnnArxj06cWHq44gCs1OSKk%2FjLY%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1318622958", oauth_token="370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb", oauth_version="1.0"

This value should be set as the Authorization header for the request.

この一連のSingnedRequestを作る処理をTwitterの中の方?がGithubにサンプルを公開してくれていますので、それを使うと凄く簡単です。
https://github.com/seancook/TWReverseAuthExample


Step2 Access Tokenの取得(詳細)

#define TW_X_AUTH_MODE_KEY                  @"x_auth_mode"
#define TW_X_AUTH_MODE_REVERSE_AUTH         @"reverse_auth"
#define TW_X_AUTH_REVERSE_PARMS             @"x_reverse_auth_parameters"
#define TW_X_AUTH_REVERSE_TARGET            @"x_reverse_auth_target"
#define TW_OAUTH_URL_REQUEST_TOKEN          @"https://api.twitter.com/oauth/request_token"
#define TW_OAUTH_URL_AUTH_TOKEN             @"https://api.twitter.com/oauth/access_token"

- (IBAction)reverseAuth:(id)sender {
    //  Step 1)  Ask Twitter for a special request_token for reverse auth
	NSURL *url = [NSURL URLWithString:TW_OAUTH_URL_REQUEST_TOKEN];
	
	// "reverse_auth" is a required parameter
	NSDictionary *dict = [NSDictionary dictionaryWithObject:TW_X_AUTH_MODE_REVERSE_AUTH forKey:TW_X_AUTH_MODE_KEY];
	TWSignedRequest *signedRequest = [[TWSignedRequest alloc] initWithURL:url parameters:dict requestMethod:TWSignedRequestMethodPOST];
	
	[signedRequest performRequestWithHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
		if (!data) {
			[self dismissProgress:@"Error occurred in Step 1."];
		}
		else {
			NSString *signedReverseAuthSignature = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
			
			//  Step 2)  Ask Twitter for the user's auth token and secret
			//           include x_reverse_auth_target=CK2 and x_reverse_auth_parameters=signedReverseAuthSignature parameters
			dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
				NSDictionary *step2Params = [NSDictionary dictionaryWithObjectsAndKeys:[TWSignedRequest consumerKey], TW_X_AUTH_REVERSE_TARGET,
                                                                                          signedReverseAuthSignature, TW_X_AUTH_REVERSE_PARMS, nil];
				NSURL *authTokenURL = [NSURL URLWithString:TW_OAUTH_URL_AUTH_TOKEN];
                
                //-- iOS5 -----
			//TWRequest *step2Request = [[TWRequest alloc] initWithURL:authTokenURL parameters:step2Params requestMethod:TWRequestMethodPOST];
                //-------------
                //-- iOS6 -----
				SLRequest *step2Request = [SLRequest requestForServiceType:SLServiceTypeTwitter 
                                                                   requestMethod:SLRequestMethodPOST 
                                                                             URL:authTokenURL 
                                                                      parameters:step2Params];
                //-------------
                
				[step2Request setAccount:self.selectedAccount];
				[step2Request performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
					if (!responseData || ((NSHTTPURLResponse*)response).statusCode >= 400) {
						[self dismissProgress:@"Error occurred in Step 2."];
					} else {
						NSString *responseStr = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
                                 NSLog(@"AuthData : %@",responseStr);                        
 					}
				}];
			});
		}
	}];
}

サンプルアプリをGithubに置いているのでご利用ください。
該当部分は、ACAccountDetailViewControllerあたりになります。
https://github.com/i2key/SocialframeworkExample

Play2系でplayコマンド実行時にsbtが無いと言われる場合の対処法

下記のようなエラーが発生したときは、project/build.propertiesのsbtのバージョンを確認すること。
ソースは古いplayのままでframework側のsbtが新しいかもしれない。
project/build.properties内のsbtバージョンを正しいバージョンに書き換えれば動く。

$ play run
Picked up _JAVA_OPTIONS: -Xms1024m -Xmx1024m
Getting org.scala-sbt sbt 0.11.3 ...

:: problems summary ::
:::: WARNINGS
		module not found: org.scala-sbt#sbt;0.11.3

	==== local: tried

	  /Users/Shared/play-2.1-RC3/repository/local/org.scala-sbt/sbt/0.11.3/ivys/ivy.xml

	==== Maven2 Local: tried

	  file:///Users/kurodaitsuki/.m2/repository/org/scala-sbt/sbt/0.11.3/sbt-0.11.3.pom

	==== typesafe-ivy-releases: tried

	  http://repo.typesafe.com/typesafe/ivy-releases/org.scala-sbt/sbt/0.11.3/ivys/ivy.xml

	==== Maven Central: tried

	  http://repo1.maven.org/maven2/org/scala-sbt/sbt/0.11.3/sbt-0.11.3.pom

		::::::::::::::::::::::::::::::::::::::::::::::

		::          UNRESOLVED DEPENDENCIES         ::

		::::::::::::::::::::::::::::::::::::::::::::::

		:: org.scala-sbt#sbt;0.11.3: not found

		::::::::::::::::::::::::::::::::::::::::::::::



:: USE VERBOSE OR DEBUG MESSAGE LEVEL FOR MORE DETAILS
unresolved dependency: org.scala-sbt#sbt;0.11.3: not found
Error during sbt execution: Error retrieving required libraries
  (see /Users/Shared/play-2.1-RC3/framework/sbt/boot/update.log for complete log)
Error: Could not retrieve sbt 0.11.3

hatena blogのデザイン変更メモ(横幅を広げる)

hatena blogデフォルトだと横幅が狭く、ソースコード等が見難いのでカスタマイズしたのでメモ。

/* <system section="theme" selected="report"> */
@import "/css/theme/report/report.css";
/* </system> */

/* <system section="background" selected="fff"> */
body{background:#fff;}
/* </system> */

pre.code{
   font-size: 70%;
   background:#EEEEEE;
   max-height: 500px;
   white-space: pre;
   overflow: auto;
}

#container {
  width: 1200px;
}

#content {
  padding-left: 70px;
}

#main {
  width: 810px;
}