2012年11月5日月曜日

Fluentdってなんじゃ?

今回はfluentdについて触れてみたいと思います。

普段サーバーの運用をしていて、規模が大きくなりサーバーが分散されるとWebサーバーやアプリケーションのログが各サーバーに散在することになり、ユーザーからの問い合わせや集計などのためにログをかき集めたりする必要が出てきます。
fluentdはイベントログをJSONフォーマットで転送して一箇所に集約するためのツールです。

集計などを行う場合、Apacheなどの生ログはテキストデータなのですが、fluentdを経由しJSON化することで、ログのデータをオブジェクトとして扱うことが出来るので、DBに入れてフィルタをかけて集計なども簡単に行えます。

また、fluentdは入力元となるログの種類や出力先の指定をプラグイン形式で様々なフォーマットに対応して、それぞれを組み合わせることで数多くの用途に答えることができます。
例えば以下のような入出力ができます。

入力元:
  • fluent apiを利用してアプリケーションから直接出力
  • 既存ログのtail
  • TCPソケットからのデータ
  • コマンドの実行結果
出力先:
  • ファイルに書き出み
  • 各種DBへ保存
  • SQSでキューにメッセージ送信
  • S3へ保存
  • Growlで通知
  • メール送信
  • Twitterへ投稿

こうみるとPlaggerを思い出しますね。

fluentdはruby環境が必要ですが、TreasureDataというデータウェアハウスがtd-agentというruby環境込みのstableパッケージを提供しています。td-agentを使用すると、ruby環境がない場合や既存のruby環境を壊したくない場合にとても有用です。今回はtd-agentを利用してみます。

まずはtd-agentをインストールします。
TreasureDataのyumリポジトリを登録してから、yumでインストールを行います。
# vi /etc/yum.repos.d/td.repo
---
[treasuredata]
name=TreasureData
baseurl=http://packages.treasure-data.com/redhat/$basearch
gpgcheck=0

---

# yum install td-agent -y
インストールはこれで完了です。

次に、設定ファイルを見てみます。

設定ファイルはシンプルで、source定義で指定した入力を、マッチしたmatch定義の設定で出力します。
まずは、flluentdのQuickStartにあるように付属のflluent-catを使用して、擬似的なTCPからの入力をローカルのファイルに出力するように設定します。
#vi /etc/td-agent/td-agent.conf

---
<source>
  type forward
  tag test
</source>

<match test>
  type file
  path /var/log/fluent/test
</match>
---

これは、

source
 forward Input Pluginを使用してtcpからの入力にtestというタグをつける

match test 
 file Output Pluginを使用して、タグが「test」にマッチした入力があれば、/var/log/fluent/test*** というファイルに受け取ったデータを出力する

という意味の設定になります。 設定を記述したら起動します。
#/etc/init.d/td-agent start
そしてfluent-catで以下のようにコマンドからJSONを送ってみます。
その際、引数として、タグとして設定した「test」を渡します。
# echo '{"msg":"Hello World!"}' | /usr/lib/fluent/ruby/bin/fluent-cat test
すると、以下のようなファイルが生成され、時間+タグ+JSONの形式で出力されていることがわかります。
# tail -1000f /var/log/fluent/test.20121105.b4cdae0705726cb91
2012-11-05T01:40:32+09:00 test {"msg":"Hello World!"}


ここまででは、ただファイルに入力したのと変わらないので、便利さがわかりづらいと思います。
次回はログをリモートのサーバーに転送してみたいと思います。
以上です。

2012年9月19日水曜日

Cassandraってなんじゃ?(API編)

Cassandraを利用するケースは、Webアプリケーションやバッチアプリケーションなど、プログラムからのアクセスが多いと思います。
そこで今回は、Cassandraにphpからアクセスしてみたいと思います。

CassandraにphpからアクセスするためのAPIライブラリとして以下のようなものがあるようです。
このうち、Thriftは、Cassandra専用でもPHP専用でもなく、複数の言語間でRPC通信を可能にする汎用フレームワークで、設定コードを元に各言語用のプログラムを生成します。他のライブラリはThriftで生成されたCassandraのlow-level APIをラップしたものになります。

もともとThriftを使おうと思っていましたが、Thriftの最新版 0.8.0は、Cassandra1.1.5では出力されたコードが不十分だったため、一度断念しました。機会があれば再度試してみたいと思います。

今回はphpcassaを試してみました。
phpcassaもThriftを使用しているので、Thriftが組み込まれ、すでにコード生成もされているので、インストールも非常に簡単です。

それでは早速インストールしてみます。
$ cd /usr/local/src
$ wget https://github.com/downloads/thobbs/phpcassa/phpcassa-1.0.a.5.tar.gz
$ tar xzvf phpcassa-1.0.a.5.tar.gz
$ cd phpcassa-1.0.a.5
$ phpize
$ ./configure
$ make
# make install

これでインストールは完了です。

次にアプリケーション用ディレクトリを作成し、phpcassaのライブラリをコピーします。
$ mkdir -p /home/memorycraft/app
$ cd /usr/local/src/phpcassa-1.0.a.5
$ cp -r lib /home/memorycraft/app/
$ cd /home/memorycraft/app/
$ touch test.php
$ tree -L 2
.
|-- lib
|   |-- autoload.php
|   |-- phpcassa
|   `-- thrift
`-- test.php


これでライブラリがそろったので、これらを利用してcassandraにアクセスするコードを書いてみます。
$ vim test.php
<?php
    require(dirname(__FILE__).'/lib/autoload.php');

    use phpcassa\ColumnFamily;
    use phpcassa\ColumnSlice;
    use phpcassa\Connection\ConnectionPool;

    try{
        $servers = array('localhost:9160');
        $pool = new ConnectionPool('Hogebook', $servers);
        $user = new ColumnFamily($pool, 'User');

        //データの挿入
        $user->insert('memorycraftgirl',
            array(
                'email' => 'ng@gmail.com',
                'gender' => 'female',
            )
        );

        //データの更新
        $user->insert('memorycraftgirl', array('email'=>'memorycraft+girl@gmail.com'));

        //キーで全カラムを取得  
        $girl = $user->get('memorycraftgirl');
        echo 'girl = ' . print_r($girl, true);

        //キーでカラムを指定して取得
        $email = $user->get('memorycraftgirl', null, array('email'));
        echo 'email = ' . print_r($email, true);

        //複数キーを指定して取得
        $users = $user->multiget(array('memorycraft', 'memorycraftgirl'));
        echo 'users = ' . print_r($users, true);


        //カラムの削除
        $user->remove('memorycraftgirl', array('email'));
        echo 'girl after remove column = ' . print_r($user->get('memorycraftgirl'), true);

        //行の削除
        $user->remove('memorycraftgirl');
        echo 'girl after remove row = ' . print_r($user->get('memorycraftgirl'), true);

        $pool->close();
    }
    catch(Exception $e){
        echo 'ERROR : ' . print_r($e, true);
    }
?>

このコードでは前回作成したHogebookのUserというColumn Familyに対して、挿入/更新/取得/削除を行い、その都度結果や、Column Familyの状態を表示するようにしました。

それでは実行してみます。
$ php test.php
girl = Array
(
    [email] => memorycraft+girl@gmail.com
    [gender] => female
)
email = Array
(
    [email] => memorycraft+girl@gmail.com
)
users = Array
(
    [memorycraft] => Array
        (
            [email] => memorycraft@gmail.com
            [gender] => male
        )

    [memorycraftgirl] => Array
        (
            [email] => memorycraft+girl@gmail.com
            [gender] => female
        )

)
girl after remove column = Array
(
    [gender] => female
)
ERROR : cassandra\NotFoundException Object
(
    [message:protected] => 
    [string:Exception:private] => 
    [code:protected] => 0
    [file:protected] => /home/memorycraft/app/lib/phpcassa/ColumnFamily.php
    [line:protected] => 308
    [trace:Exception:private] => Array
        (
            [0] => Array
                (
                    [file] => /home/memorycraft/app/lib/phpcassa/ColumnFamily.php
                    [line] => 299
                    [function] => _get
                    [class] => phpcassa\ColumnFamily
                    [type] => ->
                    [args] => Array
                        (
                            [0] => memorycraftgirl
                            [1] => cassandra\ColumnParent Object
                                (
                                    [column_family] => User
                                    [super_column] => 
                                )

                            [2] => cassandra\SlicePredicate Object
                                (
                                    [column_names] => 
                                    [slice_range] => phpcassa\ColumnSlice Object
                                        (
                                            [start] => 
                                            [finish] => 
                                            [reversed] => 
                                            [count] => 100
                                        )

                                )

                            [3] => 
                        )

                )

            [1] => Array
                (
                    [file] => /home/memorycraft/app/test.php
                    [line] => 43
                    [function] => get
                    [class] => phpcassa\ColumnFamily
                    [type] => ->
                    [args] => Array
                        (
                            [0] => memorycraftgirl
                        )

                )

        )

    [previous:Exception:private] => 
)

概ね期待通りの動作になっています。
また、存在しないキーを取得しようとすると例外が返る仕様のようです。

前回少し触れましたが、Cassandraはメモリテーブルを使用するため、このテストプログラムを実行したときの感覚としてはmemcacheへアクセスしたときと同じような体感速度でした。
今回は、localhostへのアクセスでしたが、クラスタを形成した場合のパフォーマンスなど、今後さらに調査してみたいと思います。

2012年9月17日月曜日

Cassandraってなんじゃ?

今回はCassandraをさわってみました。
Cassandraは、もともとFacebookが開発したKVSで、現在はApache Cassandraとしてオープンソース化されています。

Cassandraには以下の特徴があります。
  • 分散方式はAmazonのDynamoと同様なConsistentHashing方式
  • ストレージ方式はGoogleのBigTagleと同様なコミットログ/メモリテーブル/ディスクテーブル方式
  • CAP定理の3つの要素では、一貫性よりも可用性と分割耐性を重視
  • データモデルは列指向
  • アプリケーションからはThrift APIを使用

KVSのモデルはどれも一長一短で、Cassandraの場合デフォルトでは一貫性保証が弱いとされていますが、一貫性オプションの選択ができ、すべてのサーバーが最新の値に更新されてから読み出しができるようにすることも可能です。

Cassandraのデータの保持構造は以下のような階層で保存されます。
  • Keyspace:RDBのデータベースにあたるもの
  • Column Family:RDBのテーブルにあたるもの
  • Row:RDBのレコードにあたるもの
  • Column:RDBのカラムにあたるもの、name, value, timestampから構成される
この他にもSuper Columnがありますが必須要素ではありません。

それではインストールしてみます。

JDKのインストール

CassandraはJVM上で動作するため、まずはJDKをインストールします。
OpenJDKはCassandra1.1.5ではセグメンテーション違反が起こったため、Sun SDKを使用しました。
また、RPM版はインストールエラーがでたのでTAR版でインストールします。

ダウンロードはOracleのダウンロードページから行いますが、linuxシェル上では、Acceptボタンの操作ができないので、ブラウザでAcceptをチェックしダウンロードを開始した直後に一時停止した後、通信コンソール上のURLをコピーしてターミナル上でwgetしました。
今回は記事の都合上すべてrootで作業しましたが、適宜専用ユーザーで行なってください。

# yum install -y wget
# cd /usr/local/src
# wget http://download.oracle.com/otn-pub/java/jdk/7u7-b10/jdk-7u7-linux-i586.tar.gz?AuthParam=1347660783_4d176083902db0eb4404c8b5fcd542d0
# mv jdk-7u7-linux-i586.tar.gz\?AuthParam\=1347660783_4d176083902db0eb4404c8b5fcd542d0 jdk-7u7-linux-i586.tar.gz
# tar xzvf jdk-7u7-linux-i586.tar.gz 
# mkdir -p /usr/java/
# mv jdk1.7.0_07  /usr/java/jdk1.7.0_07

JAVA用の環境変数を定義します。
# vim /etc/profile
-----
JAVA_HOME=/usr/java/jdk1.7.0_07
PATH=$PATH:$JAVA_HOME/bin
CLASSPATH=.:$JAVA_HOME/jre/lib:$JAVA_HOME/lib/tools.jar
export JAVA_HOME CLASSPATH
------
# source /etc/profile
# cd /usr/local/src

簡単なコードを書き、コンパイルして動作確認します。
# vim HellowWorld.java
public class HelloWorld
{
    public static void main(String args[])
    {   
        System.out.println("Hello World!");
    }   
}
# javac HelloWorld.java
# java HelloWorld
Hello World!
これでJDKがインストールされました。


Cassandraのインストール

それではCassandraをインストールします。 CassandraはApache Cassandraのダウンロードページのリンクからwgetします。

# cd /usr/local/src/
# wget http://ftp.riken.jp/net/apache/cassandra/1.1.5/apache-cassandra-1.1.5-bin.tar.gz
# tar xzvf apache-cassandra-1.1.5-bin.tar.gz 
# mv apache-cassandra-1.1.5 /usr/local/
# cd /usr/local/
# ln -s apache-cassandra-1.1.5/ cassandra


Cassandraの起動

 今回はフォアグラウンドで起動してみます。 正常に動作すれば起動ログが出力されます。
# cd /usr/local/cassandra
# ./bin/cassandra -f


Cassandraのクライアントコンソールの起動

別のシェルからクライアントを起動します。
# ./bin/cassandra-cli --host localhost

[default@unknown]

[default@unknown]と表示されれば、Cassandraのコンソールに入れたことになります。 ここで、keyspaceの操作や、データのCRUDを行います。

以降、コンソール内での操作になります。
本来であればRDBとは概念が異なるため、するべきではないかも知れませんが、 とっつきやすくするために、対応するMySQLの操作を参考に記載します。


Keyspaceの作成

Hogebookという名前でkeyspaceを登録してみます。
MySQLでいうところのCREATE DATABASEにあたります。
[default@unknown] create keyspace Hogebook;
983a327c-bab3-37c3-958d-d856986f4f2b
Waiting for schema agreement...
... schemas agree across the cluster


Keyspaceの切り替え

使用したいKeyspaceをHogebookに切り替えます。
MySQLのUSE databaseにあたります。
[default@unknown] use Hogebook;
Authenticated to keyspace: Hogebook


Column Familyの作成

Column Familyを作成します。
UserというColumn Familyを作成してみます。
MySQLではCREATE TABLEに相当します。
作成する際にColumnのソート順を決めるComparatorを指定します。 ここではUTF8Typeを指定しますが、他にBytesType、AsciiType、UTF8Type、LexicalUUIDType、TimeUUIDType、LongTypeがあります。
[default@Hogebook] create column family User with comparator = UTF8Type;      
4ce3585d-9f66-36cb-8433-76bd1acf7827
Waiting for schema agreement...
... schemas agree across the cluster


Column Familyの設定の変更

emailとgenderというカラムのデータ型を定義します。
Column Familyの設定を変更します。
MySQLでのALTER TABLEに該当します。
[default@Hogebook] update column family User with column_metadata =  
... [
... {column_name: email, validation_class: UTF8Type},
... {column_name: gender, validation_class: UTF8Type} 
... ]
... ;
86ed51a4-e3b1-36b7-9cb9-93e4bf1a5fde
Waiting for schema agreement...
... schemas agree across the cluster

※追記:org.apache.cassandra.db.marshal.MarshalException: cannot parse ...とエラーになる場合は、
create column family User with comparator = UTF8Type and default_validation_class=UTF8Type and key_validation_class=UTF8Type;
のようにするといいようです。

データの投入

それではデータを保存してみます。
MySQLでのINSERTやUPDATEにあたります。
この例では'memorycraft'というキーでemail,genderというcolumnを保存します。
このようにCassandraでは、連想配列をつくるようにコマンドをセットするため、INSERTとUPDATEは同じ処理になります。
[default@Hogebook] set User['memorycraft']['email'] = 'memorycraft@gmail.com';
Value inserted.
Elapsed time: 47 msec(s).
[default@Hogebook] set User['memorycraft']['gender'] = 'male';
Value inserted.
Elapsed time: 2 msec(s).
[default@Hogebook] set User['memorycraftgirl']['gender'] = 'female';
Value inserted.
Elapsed time: 3 msec(s).
[default@Hogebook] set User['memorycraftgirl']['email'] = 'memorycraft+girl@gmail.com';
Value inserted.
Elapsed time: 17 msec(s).


データの取得

ここで、投入したデータを取得してみます。
MySQLでいうPKを条件にしたSELECTにあたります。
 
[default@Hogebook] get User['memorycraft'];                   
=> (column=email, value=memorycraft@gmail.com, timestamp=1347799163739000)
=> (column=gender, value=male, timestamp=1347799233322000)
Returned 2 results.
Elapsed time: 2 msec(s).


データの検索

キー以外のカラムの条件でデータを検索したい場合があります。
以下のように行います。
 
[default@Hogebook] get User where gender = 'male';
No indexed columns present in index clause with operator EQ
しかし、エラーになりました。インデックスがないことが原因のようです。
Cassandraではキー以外で検索をかける場合、そのカラムにインデックスを張る必要があります。
そこで、カラムの設定にインデックスを設定します。
[default@Hogebook] update column family User with column_metadata =  
... [
... {column_name: email, validation_class: UTF8Type},
... {column_name: gender, validation_class: UTF8Type, index_type: KEYS} 
... ]
... ;
86ed51a4-e3b1-36b7-9cb9-93e4bf1a5fde
Waiting for schema agreement...
... schemas agree across the cluster

このように、index_type: KEYSとすることで、genderカラムで検索をかけることが可能になります。

[default@Hogebook] get User where gender = 'male';
-------------------
RowKey: memorycraft
=> (column=email, value=memorycraft@gmail.com, timestamp=1347799163739000)
=> (column=gender, value=male, timestamp=1347799233322000)

1 Row Returned.
Elapsed time: 119 msec(s).


データの削除

データを削除してみます。
MySQLでのDELETEに該当します。
[default@Hogebook] del User['memorycraftgirl'];   
row removed.


とりあえず、駆け足で簡単なCRUD処理をさらってみました。
ここまでは、一風変わったデータストアというところまでしかわかりません。次はもう少しCassandraらしい部分を調べてみます。

2012年9月5日水曜日

S3ってなんじゃ?(CORS対応)

ごぶさたしております。
S3がCORS対応になったので、少し触れてみたいと思います。

CORSはCrossDomainResourceSharingの略です。
JSONPなどの特殊なケースを除いて通常ajaxなどではクロスドメイン通信は認められていません。
CORSは、通信先のサーバーで条件付きで許可をすることでクロスドメインアクセスを可能にするための仕組みで、W3Cで策定されいてる仕様です。モダンブラウザであればほぼサポートされているかと思います。

CORSではブラウザがクロスドメインのサーバーにリクエストする際に、事前にそのサーバーがこれから行おうとしているリクエストを許可しているかどうかをHTTPメソッドのひとつであるOPTIONSメソッドといくつかのHTTPリクエストヘッダを用いて問い合わせます。これをPreflightリクエストと言います。
そしてそのレスポンスをもってブラウザは通信可能かどうかを判断します。

Preflightリクエストで使用されるヘッダは
  • Origin 
    • リクエスト元のオリジン
    • (http://www.example.com/foo/barからリクエストされる場合、http://www.example.com/)
  • Access-Control-Request-Method 
    • これからリクエストするときに使用するHTTPメソッド
  • Access-Control-Request-Headers
    • これからリクエストするときに使用するHTTPヘッダー

などです。これらは通常ブラウザが自動で判別してリクエストヘッダに付与します。
それに対してレスポンスヘッダには以下の内容がセットされます。
  • Access-Control-Allow-Origin
    • この項目で指定されたOriginにリクエストが許可されます。
  • Access-Control-Allow-Credentials
    • trueならCookieなどユーザーの資格情報をレスポンスに含めることを許可します
  • Access-Control-Expose-Headers
    • CORS API仕様として公開して良いヘッダーフィールド
  • Access-Control-Max-Age
    • このPreflightリクエストの結果のキャッシュ保持秒数
  • Access-Control-Allow-Methods
    • 許可するHTTPメソッドが含まれます
  • Access-Control-Allow-Headers
    • 許可するリクエストヘッダーが含まれます

ブラウザはこのレスポンスヘッダを見てリクエスト可能かどうかを判断します。
通常REST APIなどを実装しWEBサービスを行うサーバーでは、このCORSに対応するような実装をすることで許可した相手にのみAPIを利用できるように実装を行う必要がありますが、今回S3ではそれが簡単な設定を行うだけで利用できるようになりました。

それでは実際にS3のCORS機能を使って具体的にどんなことが行われているのかを見てみます。

まずS3にバケットをつくります。
そこに例として以下のJSONファイルをアップロードします。

{
   "id": "1405028029",
   "name": "Satoru Miura",
   "first_name": "Satoru",
   "last_name": "Miura",
   "link": "https://www.facebook.com/memocra",
   "username": "memocra",
   "gender": "male",
   "locale": "ja_JP"
}

そしてEC2などで立ち上げた別のホストにこのjsonファイルをajaxで読み込む以下のようなHTMLを配置します。

<html>
  <head>
    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js" ></script>
    <script type="text/javascript">
    $(function(){
      $.ajax({
        url:'http://s3-ap-northeast-1.amazonaws.com/memorycraft/memocra.json',
        type:'GET',
        contentType:"application/json",
        error:function(XMLHttpRequest, textStatus, errorThrown){
          alert("ERROR " + textStatus+ " " + errorThrown);
        },
        success:function(data){
          for(var p in data){
            $("#data").append("<li>"+p+" : "+data[p]+"</li>");
          }
        },
        dataType:'json',
        processData: false
      });
    });
    </script>
  </head>
  <body>
    <h1>Graph</h1>
    <ul id="data"></ul>
  </body>
</html>


WEBブラウザのアドレスバーに直接
http://s3-ap-northeast-1.amazonaws.com/memorycraft/memocra.json
と入力するとロードできますが、別ホストで上記のようなHTMLページからのajaxではドメインが異なるためロードに失敗します。



これはchromeブラウザですが、通信をみるとOPTIONSメソッドで送信しており、以下のヘッダを送信していることがわかります。

  • Access-Control-Request-Headers
    • origin, content-type, accept
  • Access-Control-Request-Method
    • GET
  • Host
    • s3-ap-northeast-1.amazonaws.com
  • Origin
    • http://176.34.47.135


これがPreflightリクエストです。
このあとOrigin、Content-Type、AcceptヘッダをGETメソッドで送りますよと伝えています。
結果は403で返ってきました。

バケットのプロパティを見るとPermissionsタブに、「Edit CORS Configuration」というボタンがあるのでクリックすると以下のようなXMLの設定が表示されます。



<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
    <CORSRule>
        <AllowedOrigin>*</AllowedOrigin>
        <AllowedMethod>GET</AllowedMethod>
        <MaxAgeSeconds>3000</MaxAgeSeconds>
        <AllowedHeader>Authorization</AllowedHeader>
    </CORSRule>
</CORSConfiguration>

CORSRuleにある設定がこのS3バケットで受け付けている設定になります。
これらは前述の
  • Access-Control-Allow-Origin
  • Access-Control-Allow-Methods
  • Access-Control-Max-Age
  • Access-Control-Allow-Headers

レスポンスヘッダに該当し、許可する条件を示しています。
このデフォルト設定をみると、アクセス元はどこでもよく、許可するHTTPでメソッドはGETのみ、認証キャッシュは3000秒で、Authorizationヘッダーのみを許可しています。

Authorizationヘッダーのみの許可なので、本リクエストで送る予定のAccept, Content-Type, Originヘッダーが許可されず403エラーとなっているようです。
ここで、AllowdHeaderを増やしてみます。

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
    <CORSRule>
        <AllowedOrigin>*</AllowedOrigin>
        <AllowedMethod>GET</AllowedMethod>
        <MaxAgeSeconds>3000</MaxAgeSeconds>
        <AllowedHeader>Authorization</AllowedHeader>
        <AllowedHeader>Accept</AllowedHeader>
        <AllowedHeader>Content-Type</AllowedHeader>
        <AllowedHeader>Origin</AllowedHeader>
</CORSRule>
</CORSConfiguration>

もしくはすべてのヘッダーを受け付けるようにワイルドカード*を指定します。

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
    <CORSRule>
        <AllowedOrigin>*</AllowedOrigin>
        <AllowedMethod>GET</AllowedMethod>
        <MaxAgeSeconds>3000</MaxAgeSeconds>
        <AllowedHeader>*</AllowedHeader>
    </CORSRule>
</CORSConfiguration>

すると、GETであればアクセス可能になるので、ajaxリクエストは成功します。



この状態では、どのホストからでもGETリクエストであればアクセスできてしまうので、
接続するOriginを特定してみます。

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
    <CORSRule>
        <AllowedOrigin>http://176.34.47.135</AllowedOrigin>
        <AllowedMethod>GET</AllowedMethod>
        <MaxAgeSeconds>3000</MaxAgeSeconds>
        <AllowedHeader>*</AllowedHeader>
    </CORSRule>
</CORSConfiguration>

ここで先ほどとはまた別のホストから同様にajaxでリクエストしてみると、Preflightリクエストで403が返り、アクセスできないことがわかります。



いままではS3のパーミッションはAWSアカウントを基準としたアクセス制御でしたが、
このようにHTTPレベルでもアクセス制御ができるようになりました。

これによって、S3の使用の仕方がとても広がるのではないかと思います。
以上です。

2012年3月4日日曜日

FuelPHPってなんじゃ?(Eclipseとxamppでローカル開発編)

WindowsでFuelPHPを開発する方法をまとめてみました。
前回の記事で作成したように、FuelPHPのアプリケーションリポジトリは自前のGitlabでホストしているものとします。

今回はEclipseを利用します。


Eclipseの最新(Indigo)をインストール

Eclipseの最新のJ2EEをダウンロードします。
AptanaStudio3でも構いません。





アップデートサイトでEgitをインストール

Help > Install New Software...
から
Indigoのアップデートサイト
http://download.eclipse.org/releases/indigo

Collaboration > Eclipse Egit
を選択してインストール





SSHの公開鍵を作成

Preference > General > Network Connections > SSH2のGeneralタブでSSHのディレクトリを確認しておきます。
次に「Key Management」タブで「Generate RSA Key..」ボタンをクリックして、出力された内容をコピーしておきます。
また、「Save Private Key...」ボタンを押して、SSHのディレクトリに保存します。




公開鍵をGitlabに登録

GitlabのMy ProfileのKeysで、さきほどコピーした内容を適当なタイトルをつけて保存します。
Githubでも同様です。





Gitのユーザー設定

Preference > Team > Git > ConfigurationのUserSettingタブで、「New Entry...」ボタンをクリックし、以下の内容を入力します。
  • user.name:gitlabで登録したユーザー名
  • user.email:gitlabで登録したメールアドレス




Gitのクローン

EclipseのProject Explorerで右クリック、Import > Import... > Git > Projects From Git を選択します。


リポジトリソースでURIを選択します。


URIに該当するプロジェクトのgit@IPアドレス:Git名を入力します。
すると、Hostやリポジトリパスやユーザー名に自動的に各項目が入力されます。
そのまま「Next」ボタンをクリックします。



ブランチを選択します。ここではmasterしかないのでそのまま「Next」ボタンをクリックします。



自動的にGitのデフォルトディレクトリにダウロードするように設定されています。適宜変更しても構いません。
ここではC:\Users\memorycraft\git\bookstoreに保存されるように設定しました。
また、「Clone Submodules」にチェックを入れておきます。


「Next」ボタンをクリックすると、プロジェクトに関しての画面になるので、「Import existing project」を選択します。もしアプリケーションリポジトリがエクリプスプロジェクトでない場合は、「Import as general project」を選択して、後でPHPプロジェクトに変更するか、「Use the New Project wizard」を選択します。

また、Mac版Eclipseではプロジェクトウィザードを選択すると正常終了しなかったので、あらかじめアプリケーションリポジトリに.projectをコミットしておくとスムースに進むと思います。


「Next」ボタンを押すと、リポジトリに含まれているEclipseプロジェクトが表示されるので、そのまま「Finish」ボタンをクリックして、インポートを完了します。



すると、図のようにFuelPHPアプリがプロジェクトとして登録されているのが確認できました。




XAMPPのインストール

FuelPHPをローカルで実行できるようにするために、以下のリンクからXAMPPをインストールします。
http://www.apachefriends.org/jp/xampp-windows.html


インストールしたらApacheとMySQLを起動しておきます。



シンボリックリンクの作成

FuelPHPはpublicディレクトリ以外がDocumentRoot配下にあることはセキュリティ上好ましくないので、基本はpublicディレクトリのシンボリックリンクをDocumentRoot配下に配置するのですが、ショートカットはApacheからはシンボリックリンクだと認識されません。
そこで、Windowsでシンボリックリンクを作成できるツールを使用します。

コマンドプロンプトを管理者権限で実行します。



DocumentRootに移動し、mklinkコマンドでFuelPHPのpublicディレクトリのシンボリックリンクをbookstoreという名前で作成します。
C:\Windows\system32>cd c:\xampp\htdocs
c:\xampp\htdocs>mklink /d bookstore c:\Users\memorycraft\git\bookstore\public
bookstore <<===>> c:\Users\memorycraft\git\bookstore\public のシンボリック リン
クが作成されました


 すると、GUIでは以下のようにショートカットのようなアイコンでbookstoreという項目が作成されていることがわかります。




FuelのローカルDB設定

DB設定ですが、デフォルトではdevelopmentモードで動作します。
例えばdevelopmentモードのDB設定(fuel/app/config/development/db.php)で、localhostのDBを設定している場合はそのままdevelopmentモードでローカル実行すれば問題ありません。

また、開発サーバーでアプリケーションとは別のホストのDBを参照していて、ローカル実行ではXAMPPのMySQLを使用したい、もしくは、開発サーバーではlocalhostのDBを参照していて、ローカル実行ではXAMPPのDBを使わずに、開発サーバーのMySQLを利用したい場合などは、developmentモードとは別にローカルホスト用の環境設定をする必要があります。
その場合は、以下の手順で設定をおこないます。

  • development/db.phpとは別にlocalhost/db.phpを用意する
  • Windowsの環境設定でFUEL_ENVという名前でlocalhostを登録する
  • XAMPPのhttpd.confでSetEnv FUEL_ENV localhostと設定する

ここでは、developmentモードがlocalhostのDBなので、XAMPPのMySQLを使用することにします。


XAMPPのMySQLでデータベース作成

XAMPPのMySQLにデータベースとユーザーを作成します。
環境変数PATHにxampp¥mysql¥binを設定しておくとMySQLの実行に便利です。

mysql -u root
mysql> create database bookstore;
mysql> GRANT ALL PRIVILEGES ON bookstore.* TO 'bs_user'@localhost IDENTIFIED BY 'bs_pass';



oilコマンド

環境変数PATHにxampp¥phpを設定しておきます。
次に、oilコマンドで、Scaffoldをつくって、migrateしてみます。

cd C:¥Users¥memorycraft¥git¥bookstore
php oil generate scaffold book title:varchar[255] auther_id:int publisher_id:int
php oil refine migrate



ブラウザで確認


おお!無事動きました。



リモートリポジトリにcommit, push

いままで変更した分をローカルのリポジトリにコミットします。
まずプロジェクト自体を選択して、右クリック>Team > Add to Index



すると、変更ファイルがコミット候補になります。
次に、プロジェクトを選択して右クリック>Team > Commit ...


コミットダイアログが表示されるので、コメントを記入して、「Commit」ボタンをクリックします。
するとローカルのリポジトリに変更が反映されます。


次に、リモートリポジトリに反映するには、プロジェクトを選択して、右クリック > Team > Push to Upstreamを選択します。



すると、Gitlabのリポジトリツリーにも、変更が反映されました。



これで、WindowsでのFuelPHPの開発環境が整いました。
今回は以上です。