2013年12月23日月曜日

Spring の JMS サンプルを試す

この記事は Spring Framework Advent Calendar 2013 の 12/23 の記事です。 

このエントリでは、Spring Guide で紹介されている JMS サンプルを試してみたいと思います。
# なんども掲載日を変更してしまい、すみません。
# 本当は WildFly/HornetQ で何かしら動かしてみたかったのですが、次回リベンジします。。

Spring では Guides として、用途別にたくさんのサンプルを載せています。

http://spring.io/guides

その中で JMS を利用したメッセージングのサンプルがあったので、試してみました。

http://spring.io/guides/gs/messaging-jms/

サンプルを clone し、内容を確認してみます。
$ git clone https://github.com/spring-guides/gs-messaging-jms.git
$ cd gs-messaging-jms
$ tree --dirsfirst
.
# 完成形
├── complete
│   ├── gradle
│   │   └── wrapper
│   │       ├── gradle-wrapper.jar
│   │       └── gradle-wrapper.properties
│   ├── src
│   │   └── main
│   │       └── java
│   │           └── hello
│   │               ├── Application.java
│   │               └── Receiver.java
│   ├── build.gradle
│   ├── gradlew
│   ├── gradlew.bat
│   └── pom.xml
# 最初の状態
├── initial
│   ├── gradle
│   │   └── wrapper
│   │       ├── gradle-wrapper.jar
│   │       └── gradle-wrapper.properties
│   ├── src
│   │   └── main
│   │       └── java
│   │           └── hello
│   ├── build.gradle
│   ├── gradlew
│   ├── gradlew.bat
│   └── pom.xml
# その他
├── test
│   └── run.sh
├── LICENSE.code.txt
├── LICENSE.writing.txt
├── README.adoc
├── SIDEBAR.ftl.md
└── SIDEBAR.md

complete に完成済みの答えが格納されています。initial はある程度の構成ができてここからソースや少々の設定を加えて complete に近づけていく、という形になっています。
今回作成する必要があるのは、
  • Application.java
  • Receiver.java
ですね。あとは Maven を利用するならば pom.xml を、Gradle であれば build.gradle を一部追記しておしまいです。では、ガイド に沿って、initial 以下を編集作成していきたいと思います。

Receiver.java

hello パッケージに Receiver.java を作成します。
MDB ? と見まごうばかりですが、これは Message Driven Pojo(MDP) であり、javax.jms.MessageListener を実装する必要はありません。onMessage ではなく、任意の名前のメソッドを作成し、メッセージコンシューマの実装を記述していきます。
最後の FileSystemUtils.deleteRecursively(new File("activemq-data")); で組込 ActiveMQ のデータを削除しています。

Application.java

hello パッケージに Application.java を作成します。

まず目をひくのが、Class 宣言のところにある @Configuration と @EnableAutoConfiguration ですね。このプロジェクトは、Spring Boot というスタンドアロンなアプリケーションを迅速につくる仕組みを利用しており、この 2 つの設定のおかげで、だいたい Spring が設定をよきにはからってくれるというもののようです。確かに、プロジェクト中に applicationContext.xml などは見当たりません。すごい。

Spring Boot - AutoConfigure

とはいえ、自分で設定するところも必要です。

L27 - 30 では先ほど作成した Receiver を Bean 定義しています。Java クラス中に定義できてしまうんですね。
L32 - 39 では、メッセージ受信時に実行されるメソッドが定義するために、Receiver#receiveMessage をリスナ用メソッドに指定しています。Receiver が MDP でいられるのはこういった設定ができるからなんですねえ。
L41 - 52 では、SimpleMessageListenerContainer を返す container メソッドを定義しています。ここで MDP の設定はひと通り定義しています。

main() メソッドが、送信クライアントになっています。JmsTemplate を使っているので、かなりコードが短いですね。
# 送信に関しては、JMS2.0 もかなりシンプルに使える API ですので、EE 7 が待ち遠しいですね。

アプリケーションのビルド・実行

initial にある pom.xml/build.gradle に plugin を追加して、実行可能な jar を作成できるようにします。pom.xml の場合は spring-boot-maven-plugin プラグインを追加し、build.gradle の場合は dependencies と apply plugin を追加します。

Maven の場合


Gradle の場合


こう並べてみると Gradle は記述がさっぱりしていて、ぐらついてしまいますねえ。。

これでビルドできるようになったので、Maven または Gradle を使ってビルドします。

Maven の場合

$ mvn clean package

Gradle の場合

$ ./gradlew build

ビルドが完了したら実行します。

Maven の場合

$ java -jar target/gs-messaging-jms-0.1.0.jar

Gradle の場合

$ java -jar build/libs/gs-messaging-jms-0.1.0.jar

実行結果

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::             (v0.5.0.M6)

[...]
Sending a new message.
Received <ping>
[...]

Spring + JMS いい感じ

Spring の JMS クライアントはかなりいい感じということがわかりました。純粋に jms クライアントを書くよりシンプルに記述できますね。
また、MDP を使うことで EJB なしで非同期受信ができるのは特筆すべき点と思います。

今回の JMS プロバイダは組込みの ActiveMQ でしたが、今後は WildFly の JMS プロバイダである HornetQ を利用するなどもしてみようかと思います。

2013年12月13日金曜日

Brunch を使ってみる

今ちまちまと作っているブログアプリケーションである MoreCat ですけれども、フロントエンドの開発に Brunch というフレームワークを使おうと考えています。名前は前から聞いていたのですが実際に触るのは初めてなので、メモがてら練習してみます。
なお、おれの JavaScript 力(ジャバスクリプトちから)は皆無に等しいので、JS もこれからがんばります。
利用環境は Fedora 19 です。

Brunch ってなんじゃい

Brunch is an ultra-fast HTML5 build tool
だそうです。CoffeScript や Backbone.js、Stylus など、いろいろなフレームワークを組み合わせたフレームワーク + ビルド環境って感じでしょうか。

Brunch の導入

とりあえずトップページの Getting Started に沿ってやってみます。

Node.js/npm のインストール


brunch のインストールに必要です。
$ sudo yum install npm
$ npm -v              
1.3.6

Bower のインストール


あとで必要になるので Bower もインストールしておきます。
$ sudo npm install -g bower 
$ bower -v
1.2.8

Brunch のインストール

$ sudo npm install -g brunch

スケルトンからプロジェクトを作成

$ brunch new gh:paulmillr/brunch-with-chaplin brunch-practice

スケルトンはいろいろあるようですが、とりあえず一番メジャーそうなものを選びました。brunch new <skeleton-URL> [optional-output-dir] の書式で作成します。[optional-output-dir] を指定しない場合は、カレントディレクトリに資材が展開されます。資材は以下のような内容です。
$ cd brunch-practice
$ tree -L 1 --dirsfirst
.
├── app
├── bower_components
├── generators
├── node_modules
├── public
├── README.md
├── bower.json
├── brunch-config.coffee
└── package.json

スケルトンをそのまま動かしてみて、ちょっといじる

何も変更せずにそのまま動かしてみました。
$ cd brunch-practice
$ brunch watch --server
12 Dec 23:30:19 - info: application started on http://localhost:3333/
12 Dec 23:30:20 - info: compiled 29 files and 1 cached into 3 files, copied index.html in 769ms
localhost:3333 でリスンしてるよ、と表示されるのでブラウザで確認してみます。
このページは app/assets/index.html のようなので、適当に body 要素に hogehoge と書いてみました。すると変更を検知して自動的にビルドしてくれます。
12 Dec 23:33:35 - info: copied index.html in 68ms
再度 localhost:3333 にアクセスすると、変更が反映されていることがわかります。しかもブラウザも勝手に更新されてました。すごい。こうやって開発を進めていくわけですね。

プロダクション環境のビルド

以下のコマンドで、プロダクション環境のビルドが行われ、public ディレクトリに出力されます。デフォルトで js は全てミニファイされています。
brunch build --production

Brunch, 使ってみよう

自分が JavaScript の周辺フレームワークに疎いため、ひとつひとつを理解するのに時間がかかりそうですが、開発そのものはすごくスムーズに行えそうです。がんばるぞ。

2013年12月9日月曜日

WildFly の Web 基盤、Undertow の紹介

この記事は JavaEE Advent Calendar 2013 の 12/9 の記事です。

昨日は @n_agetsu さんの CDIでアプリケーション設定をインジェクション でした。
明日は @sk44_ さんの JSF で日本語ファイル名のファイルダウンロード? です。

このエントリでは、WildFly の Web コンテナである、Undertow のご紹介をしたいと思います。

WildFly って何?

WildFly は、OSS の Java EE アプリケーションサーバです。2013-12-09 時点でのリリースバージョンは 8.0.0.Beta1 であり、Java EE 7 の仕様がひと通り実装されています。
以前は JBoss Application Server(JBoss AS) と呼ばれていたものですが、商用サポート版である JBoss Enterprise Application Platform(JBoss EAP)と区別がつきづらいことや、JBoss を冠するプロダクトが複数あることから、アプリケーションサーバのランタイム固有の名称として WilfFly と改名されました。JBoss AS としての最後のメジャーバージョンが 7 であったため、WildFly はその数字を引き継いで 8 から始まっていて、基本的なアーキテクチャはそのまま踏襲されています。

その他 WildFly については @nekop さんの JBoss / WildFly (全部俺) Advent Calendar 2013 をご参考ください。全部俺て。

Undertow って何?

今回の本題の Undertow ですが、WildFly の Web サブシステムです。JBoss AS 7 から、Web や EJB、データソースなどはサブシステムという単位で設定を行うようになっています。JBoss AS 7 では 7 より前の JBoss の通り、Web サブシステムは JBossWeb という Tomcat ベースの Web/Servlet コンテナ実装を利用していました。WildFly では新たに Undertow を Web サブシステムの実装に利用しています。また、設定上も Web サブシステムから Undertow サブシステムと名称が変わっています。

Undertow の特徴

Undertow は Java で実装されたフレキシブルで高パフォーマンスな Web サーバであり、NIO ベースのブロッキング/ノンブロッキング API を提供します。 
Undertow はコンポジションベースのアーキテクチャを持ち、小さく単一な用途のハンドラと結びつけることで Web サーバを構築できます。このことにより、完全な Java EE の Servlet 3.1 コンテナ、低レベルなノンブロッキングハンドラ、またはその中間の何か、といった選択ができるような柔軟性を持っています。
Undertow はビルダ API を利用することで簡単に組み込めるよう設計されています。Undertow のライフサイクルは組込先のアプリケーションで完全にコントロールすることができます。
Undertow は JBoss によりスポンサードされており、WildFly アプリケーションサーバのデフォルトの Web サーバです。
ノンブロッキング I/O ということで、よりスケーラブルになっています。また、簡単に Web サーバを構築でき、ビルダ API を利用することでアプリケーションへの組み込みも簡単とのこと。Jetty のような使い方もできそうですね。
次に Why Undertow ということで特徴をまとめています。

  • 軽量
    • Undertow は軽量で、Undertow Core の jar のサイズは 1MB 以下です。ランタイムとしても軽量であり、シンプルな埋込みサーバであれば、ヒープサイズは 4MB 以下で動きます。
  • HTTP Upgrade のサポート
    • HTTP Upgrade をサポートしており、複数のプロトコルを HTTP ポートを介して利用することができます。
  • Web Socket のサポート
    • JSR-356 を含む、Web Socket をフルサポートしています。
  • Servlet 3.1
    • 埋込み Servlet を含む、Servlet 3.1 をサポートしています。また、同じデプロイ資材に対して Servlet とネイティブな Undertow のノンブロッキングハンドラを組み合わせることも可能です。
  • 埋め込み可能
    • 少ないコード量でアプリケーションに Undertow を埋め込むこともできますし、スタンドアロン実行することも可能です。
  • 柔軟性
    • Undertow サーバはハンドラにより設定が変更可能です。必要な機能を任意に追加可能で、利用しないものに対して注意を払う必要がありません。
HTTP Upgrade をサポートしており、このおかげで WildFly では最終的に HTTP の 8080 と管理用の 9990 の 2 つのポートのみしか利用しなくなる予定[1]です。EJB や JMS などで利用するプロトコルも、HTTP Upgrade されて利用されます。

[1]https://community.jboss.org/wiki/WildFly800Beta1ReleaseNotes

トップページの最後に Show me the code として、Hello World を返すだけの、しかし非同期 IO であるサーバを実行するコード例がありますので、試してみましょう。

以下にサンプルを作っています。

https://github.com/emag/undertow-practice

依存ライブラリは、undertow-core のみです。

  io.undertow
  undertow-core
  1.0.0.Beta25


サーバのコードとして、以下を作成します。
public class HelloWorldServer {

    public static void main(String[] args) {

        Undertow server = Undertow.builder()
            .addListener(8080, "localhost")
            .setHandler(new HttpHandler() {
                @Override
                public void handleRequest(final HttpServerExchange exchange) throws Exception {

                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
                    exchange.getResponseSender().send("Hello World");

                }
            }).build();
        server.start();
        System.out.println("HelloWorldServer is running!");
    }

}

非常にわかりやすいですね。localhost:8080 でリスンし、何をするかをハンドラとして設定します。ここでは Content-Type: text/plain として Hello World という内容のレスポンスを返します。
では、実際に実行してみます。
$ git clone https://github.com/emag/undertow-practice
$ cd undertow-practice
$ mvn clean compile exec:java -Dexec.mainClass=org.emamotor.undertow.practice.HelloWorldServer
[...]
HelloWorldServer is running!

サーバが立ち上がりました。リクエストしてみます。
$ curl localhost:8080 -v                           
* About to connect() to localhost port 8080 (#0)
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.29.0
> Host: localhost:8080
> Accept: */*
> 
< HTTP/1.1 200 OK
< Connection: keep-alive
< Content-Type: text/plain
< Content-Length: 11
< 
* Connection #0 to host localhost left intact
Hello World%

おお、ちゃんと Hello World が返ってきています。Hello World しか返せませんが、これは確かに Web サーバです。API もわかりやすいですし、10数行程度でこれはすごいですね。残念ながら自分の環境では非同期 I/O なのかを確認するには至っていないので、いずれ高負荷時にどうなるのか確認してみたいです。
また、興味がある方は main メソッドからデバッグしてみるのも良いかと思います。依存関係からもわかるように、Undertow は XNIO という JBoss プロジェクトの 1 つを基盤として利用して I/O を処理しています。XNIO は NIO を補完するような低レイヤの API を提供しているようです。なんだか難しいフレームワークのようで、がんばって理解したいところです。。

この他にも、Undertow のリポジトリにサンプルが何種類か用意されていますので、お試しいただければと思います。Undertow 1台をリバースプロキシにして、他の Undertow サーバ 3 台をロードバランスする例などもあり、おもしろいですね。定番の WebSocket による Chat サンプルもあります。

Undertow Examples

また、Undertow にはいくつかのドキュメントもあります。いくつかは本ブログで和訳もしているので、ご参考いただければと思います。

http://undertow.io/documentation/index.html

WildFly と Undertow

ここまでは Undertow 単体の内容を見てきましたが、実際には Undertow 単体というよりは、WildFly の Web コンテナとして利用が主かと思いますので、最後にいくつか設定を見ていきたいと思います。

まず、以下より WildFly をダウンロードします。

http://wildfly.org/downloads/

現在(2013-12-09)時点では 8.0.0.Beta1 が最新版です。ダウンロードした wildfly-8.0.0.Beta1.zip を適当なディレクトリに展開すればインストール完了です。

 以下を実行し、WildFly をスタンドアロンモードで起動します。標準出力に起動ログが出力されますが、以下では Undertow に関連してそうなところのみ抜き出しました。
$ cd <WildFly のインストールディレクトリ>/bin
$ ./standalone.sh 
=========================================================================

  JBoss Bootstrap Environment

  JBOSS_HOME: /home/wildfly/wildfly-8.0

  JAVA: java

  JAVA_OPTS:  -server -XX:+UseCompressedOops -Xms1g -Xmx1g -XX:MaxPermSize=256m -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true

=========================================================================

[...]
06:12:02,534 INFO  [org.jboss.as] (MSC service thread 1-6) JBAS015899: WildFly 8.0.0.Beta1 "WildFly" starting
[...]
06:12:03,319 INFO  [org.xnio] (MSC service thread 1-10) XNIO version 3.1.0.CR7
06:12:03,325 INFO  [org.xnio.nio] (MSC service thread 1-10) XNIO NIO Implementation Version 3.1.0.CR7
[...]
06:12:03,425 INFO  [org.wildfly.extension.undertow] (MSC service thread 1-2) JBAS017502: Undertow 1.0.0.Beta17 starting
06:12:03,431 INFO  [org.wildfly.extension.undertow] (ServerService Thread Pool -- 49) JBAS017502: Undertow 1.0.0.Beta17 starting
[...]
06:12:03,529 INFO  [org.wildfly.extension.undertow] (MSC service thread 1-15) JBAS017525: Started server default-server.
06:12:03,549 INFO  [org.wildfly.extension.undertow] (ServerService Thread Pool -- 49) JBAS017527: Creating file handler for path /home/wildfly/wildfly-8.0/welcome-content
06:12:03,561 INFO  [org.wildfly.extension.undertow] (MSC service thread 1-13) JBAS017531: Host default-host starting
06:12:03,591 INFO  [org.wildfly.extension.undertow] (MSC service thread 1-11) JBAS017519: Undertow HTTP listener default listening on /127.0.0.1:8080
[...]
06:12:03,867 INFO  [org.jboss.as] (Controller Boot Thread) JBAS015874: WildFly 8.0.0.Beta1 "WildFly" started in 1856ms - Started 183 of 220 services (63 services are lazy, passive or on-demand)

WildFly が立ち上がったら、管理 CLI を起動します。プロンプトが変われば管理対象サーバ(localhost:9990)に接続できています。
$ cd <WildFly のインストールディレクトリ>/bin
$ ./jboss-cli.sh -c
[standalone@localhost:9990 /]

余談ですが、JBoss AS7 では CLI 接続に 9999 ポートを指定していましたが、上述したポート削減のため 9990(http-remoting) をデフォルトで利用するようになっています。なお、Beta1 の時点ではまだ 9999 でも接続することが可能です。9999 ポートを利用したい場合は以下のように、明示的にプロトコル(remote)を指定する必要があります。
$ ./jboss-cli.sh -c --controller=remote://localhost:9999
[standalone@localhost:9999 /]

全体設定


CLI に接続し、以下コマンドを実行します。
[standalone@localhost:9990 /] /subsystem=undertow:read-resource
{
    "outcome" => "success",
    "result" => {
        "default-server" => "default-server",
        "default-servlet-container" => "default",
        "default-virtual-host" => "default-host",
        "instance-id" => undefined,
        "buffer-cache" => {"default" => undefined},
        "configuration" => {
            "filter" => undefined,
            "handler" => undefined
        },
        "error-page" => undefined,
        "server" => {"default-server" => undefined},
        "servlet-container" => {"default" => undefined}
    }
}

以前の AS7 までの web サブシステムと、設定項目が変わっています。WildFly がベースになるであろう EAP 7 を利用する場合は設定の見直しが必要になると考えられます。
なお、AS 7.1.1.Final の web サブシステムは以下です。
[standalone@localhost:9999 /] /subsystem=web:read-resource
{
    "outcome" => "success",
    "result" => {
        "default-virtual-server" => "default-host",
        "instance-id" => undefined,
        "native" => "false",
        "configuration" => {
            "container" => undefined,
            "static-resources" => undefined,
            "jsp-configuration" => undefined
        },
        "connector" => {"http" => undefined},
        "virtual-server" => {"default-host" => undefined}
    }
}

また、先日リリースされた商用版である EAP 6.2.0(AS 7.3.0.Final-redhat-14)では以下です。
[standalone@localhost:9999 /] /subsystem=web:read-resource                
{
    "outcome" => "success",
    "result" => {
        "default-virtual-server" => "default-host",
        "instance-id" => undefined,
        "native" => false,
        "configuration" => {
            "container" => undefined,
            "static-resources" => undefined,
            "jsp-configuration" => undefined
        },
        "connector" => {"http" => undefined},
        "valve" => undefined,
        "virtual-server" => {"default-host" => undefined}
    }
}

valve 設定が増えていますね。

Handler


Undertow サブシステムには Filter や Handler という見慣れない設定が存在しています。ここでは Handler について少しだけ見てみます。
[standalone@localhost:9990 /] /subsystem=undertow/configuration=handler/
file           reverse-proxy

handler として file と reverse-proxy という設定が用意されています。file では localhost:8080 でアクセスしたときのウェルカムファイルの設定が行われています。
変わり種は reverse-proxy ですね。Undertow 単体で reverse-proxy の設定ができるようになっています。今後別エントリで試してみたいと思います。すぐに試してみたい方は、以下のビデオで設定が載っていましたので、ご覧ください。

  • Dive Into WildFly 8
    • Reverse Proxy については 45:05 くらいから言及されています。

アクセスログの設定


アクセスログの設定も以前とパラメータが変わっています。設定後は反映に再起動が必要です。
[standalone@localhost:9990 /] /subsystem=undertow/server=default-server/host=default-host/setting=access-log:add
[standalone@localhost:9990 /] /subsystem=undertow/server=default-server/host=default-host/setting=access-log:read-resource
{
    "outcome" => "success",
    "result" => {
        "directory" => expression "${jboss.server.log.dir}",
        "pattern" => "common",
        "prefix" => "access_log",
        "rotate" => true,
        "worker" => "default"
    }
}
[standalone@localhost:9990 /] :reload

最後に

Java EE アプリケーションサーバにとって、Web コンテナはその根幹をなす非常に大切なものです。今まで JBoss AS は、Tomcat という伝統ある Web コンテナをベースにしてきましたが、WildFly にて刷新されました。刷新の理由はノンブロッキング API であったり、HTTP Upgrade のためだったりと様々ですが、近年の Web に対応するために色々な需要を取り込んでいることが伺えます。
今後、WildFly が正式リリースされるまでにどう仕上がっていくか楽しみですね。

参考

  • WildFly 8 Webinar Recording
    • Undertow について、30:05 くらいからスペックリードの  Stuart Douglas 氏が解説しています。

2013年12月1日日曜日

WildFly に Byteman をアタッチさせる

WildFly に Byteman をアタッチさせる手順メモ。
環境はスタンドアロンを想定しています。

Byteman のダウンロードとインストール

個人的な趣味で恐縮ですが /opt 以下にインストールし、byteman とエイリアスをさせます。ダウンロードする URL やファイル名が変わらない限りは、BYTEMAN_VERSION を変更するだけで動作すると思います。
BYTEMAN_VERSION="2.1.3"
BYTEMAN_BINARY="byteman-download-$BYTEMAN_VERSION"
BYTEMAN_DOWNLOAD_URL="http://downloads.jboss.org/byteman/$BYTEMAN_VERSION/byteman-download-$BYTEMAN_VERSION-bin.zip"
cd /opt
wget -qO- -O tmp.zip $BYTEMAN_DOWNLOAD_URL && unzip tmp.zip && rm -f tmp.zip && ln -s $BYTEMAN_BINARY byteman

WidlFly 起動時に Byteman をアタッチ

以下の JVM 起動オプションを <WildFly のインストールディレクトリ>/bin/standalone.conf に追記します。
BYTEMAN_HOME=/opt/byteman
BYTEMAN_RULE=/path/to/somerule.btm
BYTEMAN_OPTS="-javaagent:$BYTEMAN_HOME/lib/byteman.jar=listener:true,boot:$BYTEMAN_HOME/lib/byteman.jar"
if [ "x$BYTEMAN_RULE" != "x" ]; then
  BYTEMAN_OPTS="${BYTEMAN_OPTS},script:$BYTEMAN_RULE"
fi
BYTEMAN_OPTS="$BYTEMAN_OPTS -Dorg.jboss.byteman.transform.all
-Dorg.jboss.byteman.debug"
JAVA_OPTS="$BYTEMAN_OPTS $JAVA_OPTS"
後は BYTEMAN_RULE で指定しているパスを適宜適用したいルールのファイルを設定すれば完了です。試しに WildFly の起動/終了時にログを出させるルールを wildfly-begin-end.btm 等として適用してみます。ルールの内容は以下です。WildFly の起動と停止メソッドの終了に合わせてログを出力しています。

起動および停止を行うと、以下のようなログが出力されます。
# 起動時
[...] ...
[...] INFO  [org.jboss.as] (Controller Boot Thread) JBAS015874: WildFly 8.0.0.Beta1 "WildFly" started in 2058ms - Started 181 of 218 services (62 services are lazy, passive or on-demand)
[...] INFO  [stdout] (Controller Boot Thread) Default helper activated
[...] INFO  [stdout] (Controller Boot Thread) Installed rule using default helper : WILDFLY_STARTED
[...] INFO  [stdout] (Controller Boot Thread) WildFly started with Byteman!

# 停止時
[...] ...
[...] INFO  [org.jboss.as] (MSC service thread 1-2) JBAS015950: WildFly 8.0.0.Beta1 "WildFly" stopped in 18ms
[...] INFO  [stdout] (MSC service thread 1-2) Installed rule using default helper : WILDFLY_STOPPED
[...] INFO  [stdout] (MSC service thread 1-2) WildFly stopped with Byteman!

参考

2013年11月24日日曜日

Undertow Handler Authors Guide 和訳

このエントリは WildFly(旧名 JBoss AS)の Web サブシステムとしても利用される Undertow の以下設計ドキュメントの和訳です。実際に存在するクラス名も出てきますので、ソースコードもお手元にあるとよいかと思います。

Undertow Handler Authors Guide

原本は https://github.com/undertow-io/undertow-io-site の eda33a 時点のものを利用しています。更新が確認され次第、随時反映します。

なお、自分が理解が足りていないところが多いため、変な日本語になっています。。ちょくちょく直していく予定です。

イントロダクション


このガイドでは、Undertow のネイティブハンドラの作成方法の概要を説明します。HttpServerExchange オブジェクトの全ての API に言及している訳ではありません。API の多くは自明であったり、javadoc で理解可能なものです。そのかわり、このガイドでは Undertow のハンドラを記述する際に必要な概念の説明を中心に行います。

シンプルな例から始めましょう。
public class HelloWorldServer {

    public static void main(final String[] args) {
        Undertow server = Undertow.builder()                                                                                //Undertow ビルダ
                .addListener(8080, "localhost")                                                                                //リスナバインディング
                .setHandler(new HttpHandler() {                                                                                //デフォルトハンドラ
                    @Override
                    public void handleRequest(final HttpServerExchange exchange) throws Exception {
                        exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");  //レスポンスヘッダ
                        exchange.getResponseSender().send("Hello World");                                        //レスポンスセンダ
                    }
                }).build();
        server.start();
    }

}
大部分は自明です。

Undertow ビルダ


この API により、Undertow の設定と起動をすぐに行うことができます。この API は組込みや試験環境での用途を想定しています。現時点で、この API は変更される可能性があります。

リスナバインディング


次の行では Undertow が localhost の 8080 ポートにバインドするようにしています。

デフォルトハンドラ


このハンドラは Undertow に登録されたパスにどれもマッチしない URL に対して割り当てられます。このケースでは他にハンドラを登録していないので、常にこのハンドラが実行されます。

レスポンスヘッダ


自明ですが、これは Content-Type ヘッダに値を設定しています。1つ注意すべきなのは、Undertow ではヘッダのマップのキー値として String 型ではなく、ケースインセンティブな文字列である io.undertow.util.HttpString を利用します。io.undertow.utils.Headers クラスは一般的なヘッダーをあらかじめ定数で定義しています。

レスポンスセンダ


Undertow のセンダ API の役割はレスポンスを送ることのみです。センダの詳細は後ほど述べますが、このケースでは完了に対応するコールバックが指定されていないので、センダは、与えられた文字列が完全なレスポンスであると認識します。そして Content-Length ヘッダの設定を行い、レスポンス処理を完了します。

今後のコード例ではハンドラ自体に焦点を当て、サーバのセットアップ部分は割愛します。

リクエストのライフサイクル


(このことはリクエストライフサイクルのドキュメントでも 取り上げています。)

クライアントがサーバへ接続する際、Undertow は io.undertow.server.HttpServerConncection を生成します。クライアントがリクエストを送ると、リクエストは Undertow のパーサによってパースされ、その結果の io.undertow.server.HttpServerExchange がルートハンドラへ渡されます。ルートハンドラの処理が完了すると、以下の4つのいずれかの状態となります。

Exchange がすでに完了している


リクエスト及びレスポンスチャンネルの完全に読込み/書込み終わっている場合、Exchange は完了したとみなされます。(GET や HEAD などで) No Content が返されるようなリクエストに対しては、リクエスト側は自動的に完全に読込み終わったと見なします。ハンドラが完全なレスポンスを書込み、クローズし、レスポンスチャネルをフラッシュすると読込み側は完了したとみなされます。Exchange がすでに完全な状態となっているならば、Exchange は完了したとして何もしません。

ルートハンドラが Exchange の完了なしに正常に返る


このケースでは Exchange は HttpServerExchange.endExchange() が呼ばれることで完了します。endExchange() のセマンティクスについては後述します。

ルートハンドラが例外を伴って返る


このケースではレスポンスコードとして 500 が設定され、Exchange はHttpServerExchange.endExchange() を使って終了します。

ルートハンドラが HttpServerExchange.dispatch() が呼ばれた後、または非同期 IO が開始された後に返る


このケースではディスパッチされるタスクがディスパッチエグゼキュータへと渡されるか、非同期 IO がリクエストまたはレスポンスチャネル上で開始していれば、それから処理が始まります。このケースでは Exchange は完了せず、いつ処理が終わって Exchange が完了するかは、非同期タスク次第です。

もっとも一般的な HttpServerExchange.dispatch() の利用用途は、ブロッキング処理の行えない IO スレッドから、ブロッキング処理の行えるワーカースレッドへ処理を委譲することです。この例は典型的には以下のようになります。

ワーカースレッドへの処理の委譲
public void handleRequest(final HttpServerExchange exchange) throws Exception {
    if (exchange.isInIoThread()) {
      exchange.dispatch(this);
      return;
    }
    // ハンドラのコード
}
Exchange は実際にはコールスタックが返るまでディスパッチされないため、ある Exchange 中において同時に 2 つ委譲のスレッドが動作することはないと考えてよいでしょう。この Exchange はスレッドセーフでないですが、複数のスレッドが同時に Exchange を変更しようとしない限り、複数スレッド間で共有される可能性があります。また、1つ目と2つ目のスレッドアクセスの間に、スレッドプールディスパッチのようなことが起こりえます。

Exchange の完了


上で説明した通り、Exchange はリクエストおよびレスポンスチャネルのクローズとフラッシュすると完了したとみなされます。

Exchange の完了には2種類あり、リクエストチャネルの読込みが完了し、レスポンスチャネルで shutdownWrites() が呼ばれ、レスポンス内容がフラッシュされたときか、HttpServerExchange.endExchange() が呼ばれた時です。endExchange() が呼ばれた時、Undertow はコンテンツが生成されたかどうかチェックします。生成されていればリクエストチャネルを開放し、レスポンスチャネルのクローズとフラッシュを行います。コンテンツが生成されていない場合、Exchange にデフォルトのレスポンスリスナが登録されていれば、Undertow はレスポンスリスナにデフォルトのレスポンスを設定します。こうしてデフォルトのエラーページを返す機構を提供します。

Undertow のバッファプール


Undertow は NIO をベースとしているので、バッファリングが必要な場合は java.nio.ByteBuffer が使われます。これらのバッファは要求ごとに割り当てると性能に重大な影響を与えるため、プールされます。このバッファプールは HttpServerConnection.getBufferPool() を実行することで取得できます。

プールされたバッファは利用後はガベージコレクトの対象にならないため、開放されなくてはなりません。プール中のバッファのサイズはサーバが作成された際に設定されます。経験則として、最大限のパフォーマンスを出すサイズは 16kb です(これは Linux のデフォルトのソケットバッファサイズと一致します)。

ノンブロッキング IO


デフォルトでは Undertow はノンブロッキングの XNIO チャネルを利用し、リクエストは最初 XNIO の IO スレッド上で実行されます。これらのチャネルは受信データを送るために直接利用されます。これらのチャネルはかなり低レベルですが、Undertow はよい扱いやすくするために抽象化したものを提供します。

ノンブロッキング IO を利用したレスポンスを送信するもっとも簡単な方法は、上述したセンダ API を利用することです。センダ API は byte や String 両方に対応した send() メソッドを様々な用途にあった形で提供しています。メソッドの中には、送信が完了したらコールバック処理を実行するものや、コールバックをしない代わりに送信が完了した際に Exchange を終了させるものもあります。

センダ API はキューイングをサポートしないことに注意してください。send() をコールバックが通知されるまで呼ぶことはおそらくできません。

コールバックを取らない send() メソッドは、Content-Length ヘッダが自動的に設定されます。そうでなければ chunked エンコーディングを避けるために、自分で Content-Length ヘッダを設定する必要があります。

センダ API はまた、ブロッキング IO をサポートします。Exchange が HttpServerExchange.startBlocking() の実行によりブロッキングモードになった場合、センダは Exchange のアウトプットストリームを利用してデータを送信します。

ブロッキング IO


Undertow はブロッキング IO もサポートしています。XNIO のワーカスレッド中ではブロキング IO を使えないため、リード/ライトする前にリクエストをワーカスレッドプールへディスパッチする必要があります。

ワーカスレッドをディスパッチするコードは、前述のとおりです。

ブロッキング IO を利用するために HttpServerExchange.startBlocking() を実行します。このメソッドは 2 つあり、1 つは引数を取らず Undertow のデフォルトのストリーム実装を利用します。もう 1 つの HttpServerExchange.startBlocking(BlockingHttpServerExchange blockingExchange) では、利用中のストリームをカスタマイズして使うことができます。例えば サーブレット実装では Undertow のデフォルトストリームの代わりに Servlet(Input/Output)Stream を使うためにこの 2つ目のメソッドを利用しています。

ブロッキングモード Exchange が完了すると HttpServerExchnage.getInputStream() と HttpServerExchange.getOutputStream() を実行することができ、通常通りデータを書き込むことができます。上で述べた sender API も利用できますが、この場合 sender の実装はブロッキング IO が利用されます。

デフォルトで Undertow はバッファリングストリームを利用し、バッファはバッファプールから取り出されます。レスポンスがバッファに適合するほど十分小さければ、Content-Length ヘッダが自動的に設定されます。

ヘッダ


リクエストとレスポンスヘッダは HttpServerExchange.getRequestHeaders() と HttpServerExchange.getResponseHeaders() を介してアクセスできます。これらのメソッドは最適化sれたマップ実装である HeaderMap を返します。

ヘッダは最初のデータがチャネルへ書き込まれたときに HTTP レスポンスヘッダに書きだされます(バッファリングされていた場合、最初にデータが書き込まれた時刻とは一致しない可能性があります)。

ヘッダへの書き込みを矯正したい場合は、レスポンスチャネルまたはストリームで flush() メソッドを利用できます。

HTTP Upgrade


HTTP upgrade を実行するために HttpServerExchange.upgradeChannel(ExchangeComplietionListener upgradeComplitionListern) が利用できます。レスポンスコードは 101 に設定され、Exchange が完了するとリスナに通知されます。ハンドラは upgrade クライアントが期待するような適切なヘッダを設定することが可能です。


2013年11月10日日曜日

JJUG CCC 2013 Fall メモ

JJUG CCC 2013 Fall に参加しました。どのセッションも非常におもしろく、あっという間に1日が過ぎてしまいました。

WebSocket のハンズオンは社内システムに応用できそうなアーキテクチャ(WebSocket + JMS)なので、ぜひ WildFLy 上で動作するようにしてみたいと思います。

JVM コードリーディング入門は、大規模ソースコードのリーディングに普遍的に通用するテクニックを教えていただきました。めげずにいろいろと読んで行こうと思います。

Virt.x も気になるプロダクトです。まずは Acroquest さんのブログで入門したいと思います。

以下、メモをはりつけ(手抜きですみません)。発表スライドは公表されたら順次リンクしていきます。

参加セッションリスト



K-2 2013 エンタープライズ Java 最前線

GlassFish のロードマップ


  • 商用ライセンスサポートはバージョン 3.1.2.2 までで終了
  • バージョン 4 以降は OSS 版のみ提供
  • 今後 Oracle が提供する Java アプリケーションサーバは WLS のみ
  • 今後も Java EE の RI であるという GlassFIsh の位置づけは変わらない

Java EE 7 2013.6 リリース


  • EE 7 は EE 6 を拡張したもの。Web のトレンド(HTML5, WebSocket など)に対応したもの。
    • EE 6 は EoD の集大成と言えるリリースであった。
  • WebSocket の注目が高い(寺田さん調べ)
  • CDI + JTA で EJB をかなり置き換えられるようになった。
  • Bean Validation がいろいろなところで使えるようになった。
  • Concurancy Utility で、ユーザアプリケーション側でスレッドを起こすことが可能になった。
  • JAX-RS が Full Profile から Web Profile へ移った。

Project Avatar


  • JDK8_b103 以降で利用できる。 
  • 今年の Java One で OSS 化された旨が発表された。
  • Avatar を試してみたい場合、自力でセットアップするよりも、Avatar 公式サイトからダウンロードできる Avatar が同梱された GlassFish を使うのがおすすめ(どこにあるかわからない...)。 
  • Avatar は JS のエンジン(Nashorn)上で動作する。
    • Nashorn は JVM 上で動作する JS エンジンで、JDK8 の 1 プロジェクト。旧来の JS エンジンである Rhino より性能がよいらしい。 
      • 性能が良いのは、JDK8 における Invoke Dynamic 命令の恩恵らしい。 
  • Avatar で TSA を実現する場合。// TODO 寺田さんの絵がほしい。 
  • Avatar はデータバインディングに EL 式を使える。 
    • Avatar コンパイラでコンパイルする。 
  • Avatar でビルドしたアプリケーションは、GlassFish にそのままデプロイできる。 
  • 同じ AP サーバ上にほかの Java EE アプリケーションもデプロイされるので、Java <-> JS の相互呼び出しが可能。 
  • Avatar のサンプルアプリケーションは寺田さんのブログを参照。

Java EE 8 とその将来


  • まだまだ決まっていないことがたくさんあって、これからの状態
    • JSON バインディング、JCache は確実に入ってきそう。

R5-1 Java EE 7 WebSocket ハンズオン

// TODO WIldFLy で動かせるようにしたい!

  • 作成するアプリケーションは WebSocket による大規模リアルタイム情報配信
    • チャットとかだとバックエンドのサーバがあまり企業システムらしくないので、それらしいものを作る。
    • 利用テクノロジは WebSocket + JSF + JMS WebSocket
    • サーバはクラスタ構成にする。
    • JMS アプリケーションと WS アプリケーションは別のアプリケーションとして提供する。
  • 寺田さんは public フィールドはお好みでないようだ。
  • JSF のエッセンスは ManagedBean と EL 式。
  • Java EE 7 の WebSocket API は簡単に使えるように設計されている。
    • POJO
    • ポイントになるアノテーションは以下
      • @ServerEndpoint
      • @OnMessage
      • @OnOpen
      • @OnClose
      • etc.
    • Session#getOpenSessions で接続してきているすべてのクライアント情報を取得できる。 
  • GlassFish の JMS 実装は OpenMQ
  • JMS2.0 かんたん!
  • 途中で出てきた 4001 ってなに?


H-3 ユニットテスト改善ガイド


  • ユニットテストを組織に広めるためのヒントを紹介
  • (参考)渡辺さんのクラスメソッドのブログ記事
    • JUnit はあくまでテストするためのツールであり、「JUnit を使うこと」が目的ではない
    • ユニットテストを作るにはそれ相応のスキルが必要である
    • etc.
  • ユニットテストは適材適所であるべき
    • 全てやる派 vs 薄くやる派
  • テストは誰でもできる簡単なものではなく、トレーンングが必要
    • テスティングフレームワークの使い方やテスト手法を学ぶ
    • プロジェクトのコストとして扱わず、業務時間にトレーニングを実施する。
    • コーチができる人を探す。
  • テストが難しいコード、簡単なコードがある。いきなりすべてをやろうとせず、できるところからやる。
  • テストがしにくいAPI=使いにくいAPI。
    • テスト対象を直してはならないという勘違い。
    • 一度書いたものは変えたくないという抵抗感を減らす1つの方法は、テストファーストを行うこと。
  • CI を導入する前に、テスト文化を根付かせるのが先。
    • CI 導入にもノウハウが必要。一緒にテストも学ぶのは大変。
  • CI の効果
    • リグレッションをすぐに検知できる
    • 自動化が「見える」
      • モチベーションが上がる
    • 見える化のおかげで、管理者にも説明しやすい。
  • テストコード自体のメンテナンス・リファクタリングが必要。
    • テストは使い捨てでない
    • DRY をサボると確実にコピペ地獄になる。
    • 動かないテストは思い切って捨てるべき
    • テストは常にきれいにしておくこと。
      • ただし、整理しすぎるとかえって可読性が落ちることがあるので、あくまでテストとして読みやすいバランスを保つこと。
  • ユニットテストの効果を伝えずに押し付けても反発されるだけ。
    • メンバの手間を増やさない
    • 導入する目的を説明する
    • デバッガの代わりに紹介してみる
  • 上司の説得方法

R2-4 Javaアプリケーションサーバ構築・運用の勘所

ログ管理


  • トラブルシューティングの基本
    • GC ログ
      • 取っていないケースも多い。
      • 標準出力に出すのもあり
        • 普通の出力と混じってしまうのでは?
    • スレッドダンプ
      • ThreadLogic
        • WLS に特化した機能も含むものの、ほかの製品でも使える。
        • 侍のほうが直感的な UI だが、ThreadLogic は診断結果などが出
        • 大きなスレッドダンプも読み込み可能。
    • アクセスログ
      • JBoss/Tomcat はデフォルトで出るようになってほしい。
      • 性能劣化している時の切り分けとして、前段の Apache のレスポンスタイムと比べる。
    • CPU
      • ps auxwww -L プロセス単位でなく、スレッドごとのCPU使用率を知りたい。
      • コネクションプール不要論に対する反論
        • プールするのは性能だけでなく、リソースを制限することにも意味がある。

R5-5 JVMコードリーディング入門 ~JVMのOS抽象化レイヤーについて

対象のソースコード


  • jdk7u

読む前の準備


  • ビルドするかしないか
    • デバッグ実行しながら動作を調べるなら必要
    • 動かす前の調査としてコードを読むなら不要

JVM を読む基本的な方法


  • わからなくても全部をざっくり何週も読む
  • メモ・図解をする
    • わかったこともわからないこともメモしないと忘れてしまう。
  • 名言「理解とは真実との相対的な距離を縮める行為である。」
    • 仮設と懸賞を繰り返す。
  • 鳥の目と虫の目を交互に繰り返すことでわかることもある
  • ファイルサイズの大きい物を把握する
    • 重要 or ゴミの可能性大
  • 抽象度の高い名前を付けられたファイルを把握する
    • より重要度が高いファイルである可能性大
  • クラスの関係性を把握する
  • いろいろなクラスから継承されていたり、フレンド関数としてアクセスされてたり
  • C, C++ のレイヤを切り分ける
    • システムコールを行う層とそれを使う層
    • OS との境界
  • ざっくり読む
    • 読む部分と読み飛ばしても良い部分
      • assert -> 読む。なんらかの重要なチェックが行われている
      • non-PRODUCT -> 読み飛ばして OK。デバッグ情報など。
  • ソースコードのコメントをどう読むか
    • コード中のものはメンテされていない可能性もあるため、完全に信頼は置けない。
    • ヘッダファイルは基本的に読む。設計指針や専門用語の解説あり
    • ソース中でも以下のコメントは読む
      • 署名がついているもの
      • バグトラックの番号がついているもの。セキュリティホール対応だったりする。
      • 日付が書いてあるもの
    • JVM の抽象クラスの特性
    • メモリ周りのコードに億出てくる略語を押さえる
      • oop, RO, TLAB, BOT...
    • os <- 猫に見える

JVM の OS 抽象化レイヤ


  • memory <- 寝てる猫に見える
  • os.hpp
  • os.cpp
  • os:: を呼び出しているクラスは share/vm/memory にある
  • memory ディレクトリに含まれるクラスの継承関係を図示する。
  • allocation.hpp|cpp 中のクラスが大事そう。

R5-6 [BOF] Over the Node.js. An Introduction to Vert.x

Vert.x の概要


  • Vert.x は JVM 上で動くアプリケーションプラットフォーム。
  • ドキュメントが充実している(vertx.io)
  • Virt.x の作者 timfox は Red Hat で HornetQ とか関わっている。
  • 特徴
    • High Perf.
    • Distributed Event Bus
    • Polyglot
    • Module System
  • High Perf.
    • C10K問題。一般的に1つのスレッドあたり、256kb - 1mb 必要
    • EventLoop でノンブロッキング+非同期処理
      • EventLoop を止めてはいけない。
        • どうしてもブロックしたいときは、WorkerThread を利用する。
    • Java のスレッドを利用するので、デフォルトで CPU 数に応じたスケーリングが可能
      • node.js では、クラスタ機能を使わないとだめ。単体だと 1 CPU での動作になる。
  • Distributed Event Bus
    • JMS みたいな通信(P2P, Pub/Sub)
    • クラスタリングも可能
  • Polyglot
    • JVM 上で扱える様々な言語をサポート
    • Java JS Groovy JRuby Jython が公式でサポート
    • Scala Clojure PHP も開発中
  • Module System
    • node.js でいう npm のような機構
    • モジュールは Maven や Bintray に公開し、共有できる
    • モジュール間は Event Bus を介した疎結合
    • モジュールはいろいろな言語で実装されているが、Event Bus を介しているため、言語の差を意識しないで済む。

Vert.x による開発


  • JDK7 or later(must)
  • Project Template は、gradle を使うのがおすすめ。
  • Auto Reload
  • Remote Debug も可能
  • Node.js and the new web front-end
    • Backend は Virt.x

Vert.x の最新情報


  • Fat Jar(executable jar)
    • Virt.x Core を同梱しているので、実行に Virt.x のインストールが不要。
  • HA
    • HA Group を作る。HornetQ っぽい。
    • QUORUM スプリットブレイン対策
  • DNS / UDP Core API

Netty, Virt.x, Java EE アプリケーションサーバ

  • Netty, Virt.x, Java EE アプリケーションサーバの住み分けは?
    • Netty は低レイヤ向きのアプリケーション向け(ゲートウェイとか)
    • Virt.x は Netty より高レイヤかつ、M2M などのサービスに向いているのでは
      • あくまで LightWight を目指し、Java EE アプリケーションサーバの範囲を全て網羅するような作りにはならないように思える。

参考


R1-7 [BOF] Spring Frameworkの今(2013年版)


  • Spring は生きている。
    • ホストしている会が移り(Pivotal社)、どう扱われるかという意味で未来にはちょっと不安な部分もある。
  • spring.io にメインサイトが変更された。
    • 40 種類のサンプルを用意されている。
  • Spring Boot
    • 複雑化した Spring モジュールを簡単に扱えるようにする。
  • CDI(JSR-330)対応
  • SockJS 対応
    • レガシーブラウザでも利用できる WebSocket
  • STOMP に対応
  • Spring vs Java EE
    • Spring もどんどん JSR に対応していき、Java EE と差がなくなってきた。
    • JTA が使えるのでグローバルトランザクションもできる。
    • 外部 JMS との連携も可能。

その他


  • Tomcat 7.0.47 では JSR 標準の WebSocket API を制限付きで同梱している。
  • JSR356は低レイヤ過ぎる。なんらかのフレームワークが必要。

2013年11月9日土曜日

ブログアプリケーションを作ろう

@making さんのブログお手製であることに衝撃を受けて、自分でも作ってみたくなりました。

リポジトリは以下です。

https://github.com/emag/morecat

アプリケーション名は "MoreCat"

名前が無いと説明に不便(おれのブログアプリとか、あの例のやつだけどみたいになっちゃう)なので、MoreCat というアプリ名にしました。こちらから拝借しました。

以下、仕様を挙げていきます。

機能

ブログシステムというよりは、WordPress に近いもの


blogger や はてなダイアリーのように、開設者個々人のブログ環境を提供するのではなく、WordPress のように、MoreCat をインストールすると 1 つのブログ環境を構築するイメージです。WordPress のデータ構造が参考になるのかな。

デフォルトの記法は Markdown


なんだかんだ Markdown がいいですよね。凝った表とかが必要な場合は、生 HTML を書いてもらうか、Redmine みたいな textile を参考に Markdown を拡張する感じでしょうか。

管理クライアントは GUI と CUI をデフォルトで用意する


ブログエントリ投稿方法として、Web ブラウザによる GUI だけでなく、CUI も用意したいと思っています。イメージ的には blogger でいう GoogleCL とか blogger.vim です。more とか cat とかで読み進められるといいなあ。

アーキテクチャと実装

コンポーネントは3つ

  • morecat-backend
    • 全ての API を提供するサーバ。MoreCat のコア。
  • morecat-frontend
    • フロントエンド UI。
  • morecat-admin
    • 管理コンソール UI。

Backbone.js + Java EE(JAX-RS, CDI, JPA)


フロントエンドは Single Page Application(Backbone.js 、たぶん Brunch を利用) を提供し、バックエンド(サーバサイド)はすべて REST API(JAX-RS を利用) のみの提供とします。細かいライブラリは、作りながら必要に応じて採用していきます。アプリケーションサーバは WildFly です。

プラグイン機構


ブログの外観テーマや便利ツールなどは、外から差し替えできて、かつ、サーバの再起動不要だと嬉しいですね。後からこういうプラグイン機構を入れ込むのは難しい気がするので、最初から考えておきたいと思います。
なお、どうやって実現するのかは全然イメージできてません!! 参考になりそうな Java アプリケーションだと、Eclipse, IntelliJ IDEA, Jenkins とかでしょうか。Eclipse は OSGi ですよね。

データベースは好みのものが選べるようにする


といっても、RDMBS のみとなるかと思いますが。。ひとまず PostgreSQL, MySQL の2つで動作確認を行おうと思います。なるべく JPA で NativeQuery を使わないようにすれば大丈夫かなあ。開発用には WildFLy 同梱の H2 を利用します。

...

とりあえずここまで。仕様が変更されたら随時更新していきます。のんびり作っていきたいと思います。

2013年10月18日金曜日

DELL XPS 15 L521X に Fedora 19 をインストール

DELL XPS 15 L521X を購入し、Fedora 19 をインストールしました。自分がハードウェアに疎いため、インストールするまでにいろいろと手間取りましたので、はまったポイントをメモ。

なお、インストールには Live DVD を使っています。

CD/DVD Drive からのブートをどうすればいいのか悩んだ


Live DVD を焼いてドライブに入れても CD/DVD Drive からのブートになりませんので、BIOS を立ち上げます。DELL のロゴが出ている間に、F2 を押すと BIOS 画面に遷移します。

Main, Advance Security, Boot, Exit とメニューが並んでいるので、Boot を選択します。UEFI BOOT になっているので、これを LEGACY BOOT に変更する必要があります(これに気がつくまで時間かかった...)。

まず、Secure Boot の項目を Disabled に変更します。次に、Boot List Option の項目を UEFI から Legacy に変更します。LEGACY BOOT -> Boot Type Order の項目で、CD/DVD-ROM Drive の位置を Hard Disk Drive より上に上げます(F6 で上がります)。F10 を押して BIOS 設定を保存・終了します。

上記ができると、無事 CD/DVD Drive からブートが行えます。

無線 LAN が有効にならなくて困った


ワイヤレスのネットワークアダプタが Killer N 1202 というちょっとあまり聞かないものなので、無線 LAN がうまく使えるかを確認のために Live DVD からブートした後もすぐにはインストールせずにいたのですが、無線 LAN が使えない状態でした。

右上の表示をクリックすると、Wi-Fi hardware disabled と表示されており、rfkill list を実施すると Hard blocked: yes となっていました。こうなってはコマンド的にどうこうじゃないようなので、Wi-Fi の On-Off 切り替えスイッチがどこかにあるのかと探したのですが見当たらなく、カタログにも特に表記がなく途方に暮れつつもしばらくググってみると、以下がヒットしました。


ここの回答に、「Fn + F2 を押しなはれ」と神の啓示があり、試しに押してみると Wi-Fi が利用できるようになりました。rfkill list 結果も Hard blocked: no となっています。わからんよ。。

と思ったら、F2 に、無線っぽいアイコンが刻印されてますし、ワイヤレスのオン/オフですよと説明書にもきちんと書いてありました。えへへ。

2013年10月3日木曜日

全 DOM 要素の数を確認する

いいこと聞いたのでメモ。

jQuery でリソース内の全 DOM 要素の数を確認したい時は、
$("*").length
でいけます。

例えば、要素数に比例して描画処理に時間がかかってることが予想されるときなどに目安になります。

2013年9月26日木曜日

Undertow Design Document 和訳

このエントリは WildFly(旧名 JBoss AS)の Web サブシステムとしても利用される Undertow の以下設計ドキュメントの和訳です。実際に存在するクラス名も出てきますので、ソースコードもお手元にあるとよいかと思います。

Undertow Design Document

原本は https://github.com/undertow-io/undertow-io-site の a839203 時点のものを利用しています。更新が確認され次第、随時反映します。

なお、自分が理解が足りていないところが多いため、変な日本語になっています。。ちょくちょく直していく予定です。

Undertow 設計ドキュメント

これは Web サーバ Undertow の設計ドキュメントです。アーキテクチャ全般と設計思想をカバーしています。要求仕様書ではありません。

概要


Undertow のアーキテクチャのコアは軽量非同期ハンドラという考えに基づいています。これらハンドラ群は完全な HTTP サーバをなすためにチェーンされます。ハンドラはまた、1 つのスレッドプールによって戻されたブロッキングハンドラへ自身を渡します。

このアーキテクチャはエンドユーザがサーバ設定の変更を行う際に完全な柔軟性を持たせるために設計されています。例えばユーザが単にサーバへ静的ファイルをアップしたい場合、そのタスクを必要とするハンドラだけでサーバを設定できます。

ハンドラチェーンの例を以下に挙げます。



















図1. ハンドラチェーンの例

Servlet の機能は非同期サーバのコアの最上位に構築されます。Servlet モジュールはServlet の特定の機能に対するハンドラを提供することでコアと統合されます。Serlvet 実装では可能な限り非同期ハンドラを利用し、どうしても必要な場合だけブロッキングハンドラへと変更します。これは、ある Servlet 中にパッケージされた静的なリソースが、非同期 IO を通して提供されることを意味します。

コアサーバ


リクエストの受け取り


標準的な HTTP リクエストはサーバへ HTTPOpenListener 経由で送られます。この HTTPOpenListener は 1 つの PushBackStreamChannel 中のチャネルをラップし、 HTTPReadListener へ渡します。HTTPReadListener はリクエストを受け取るとパースを行い、いったん全てのヘッダを読込み、HTTPServerExchange インスタンスを作成し、ルートハンドラを実行します。このリスナに読まれるどのメッセージボディや次に来るリクエストも、ストリームへと先延ばしにされます。

HTTP パース処理はバイトコード生成ステートマシンにより行われ、一般的なヘッダや HTTP メソッドが認識されます。これは一般的なヘッダに対するパース処理はより高速かつ省メモリで実行されうることを意味し、あたかもヘッダの値がステートマシン、つまり Srting や StringBuilder インスタンスに割り当てられる必要なしに直接返却される内部に持っている文字列として認識されているようにふるまいます。

注意: このことが実際の運用においてパフォーマンスの向上に大きく寄与するかはまだ示されていません。もしそうでないのであれば、我々はこれ以上の複雑さを避けるため、よりシンプルなパーサへと移行する可能性もあります。

HTTPS や AJP、SPDY といった他のプロトコルのサポートはチャネルの実装を通して提供され、ハンドラに対して可能な限りプロトコルの詳細を切り離された抽象的なものとして実装されます。

ハンドラ


ベーシックなハンドラインターフェースは以下のようなものです。
public interface HttpHandler {
  /**
   * Handle the request.
   *
   * @param exchange the HTTP request/response exchange
   */
   void handleRequest(HttpServerExchange exchange) throws Exception;
}

HttpServerExchange はリクエストとレスポンス、ヘッダ、レスポンスコード、チャネルなど、現在の全ての状態を保持します。任意のアタッチメントも付与でき、ハンドラチェーンの後でハンドラによって読込まれるオブジェクトのアタッチもハンドラで行えます。例で言えば、認証ハンドラでは認証されたアイデンティティのように、後で承認ハンドラによってあるユーザがリソースにアクセス可能かどうかを判断するために利用されるオブジェクトです。

HttpCompletionHandler はリクエストが完了した時に実行されます。どのハンドラも、このインスタンスをラップするいくつかのソートマップのクリーンナップ処理を次のハンドラへ渡す前に実施することが必要です。これらは非同期ハンドラであることから、コールチェーンはリクエストが行われている間に返却する可能性があり、そのため finally ブロック中でのクリーンナップ処理はできません(実際には、ハンドラは一般的には次のハンドラの実行後にいかなるコードも実行しません)。

最初ハンドラは XNIO の読込みスレッドで実行されます。これは、ハンドラは、サーバが書込みスレッドが返されるまで他のリクエストを処理できないようなブロッキング処理を行ってはいけないことを意味します。そのかわりに、ハンドラはコールバック可能な非同期処理の実施や、タスクの(XNIO ワーカープールのような)スレッドプールへの委譲を行います。

リクエストとレスポンスのストリームはハンドラや HttpServerExchange のChannelWrapper の登録によりラップされます。このラップは一般的に、transfer や content encoding を実装するハンドラによってのみ利用されます。例えば圧縮を実装するために、ハンドラは データを圧縮し、圧縮データを下層のチャネルへ書き出す ChannelWrapper を登録します。これらラッパは、レスポンスのボディへ書き出すのみに利用され、ステータスラインやヘッダ内容の変更のためには利用できないことに注意してください。

単一のハンドラのみでリクエストの読込みやボディへの書き出しが可能です。ハンドラが別のハンドラがすでにチャネルを取得した後にそのチャネルを取得しようとした場合、null が返却されます。

コネクションの永続化


コネクションの永続化は、チャンク形式か固定長のリクエストとレスポンスのチャネルをラップすることにより実装されます。一度リクエストが完全に読込まれると、次のリクエストが即座に開始され、次のレスポンスが提供され、ゲート化したストリームは現在のレスポンスが完了するまでレスポンス処理が始まる事を許可しません。

セッションのハンドリング


セッションはセッションハンドラにより実装されます。リクエストが処理される際、このハンドラがセッションクッキーの存在を調べ、もし存在するのであればセッションマネージャからセッションを受け取り、HttpServerExchange に付与します。また、セッションマネージャも HttpServerExchange に付与します。セッションの取得は非同期に行われる可能性があります(例えばセッションのデータベースへの格納や、クラスタにおける別のノードへの配置など)。

一度セッションとセッションマネージャが HttpServerExchange へ付与されると、その後のハンドラはセッションへデータを格納できます。また、セッションが存在しない場合、新しいセッションを生成するためにこのセッションマネージャを利用できます。

設定


Undertow の Core 部では、プログラム上でハンドラーチェーンをくみ上げる事で設定するかわりになるような、設定用の API 提供しません。XML による設定は AS7 サブシステムにより提供されます。これはサーバが XML で全ての設定をすることなしに組み込みモードで利用できるということです。Tomcat や Jetty に対抗するスタンドアロンのサーブレットコンテナを提供するために、縮小版の AS7 インスタンスを利用します。このインスタンスは web サブシステムのみを提供します。完全な AS7 インスタンスよりもダウンロード量が少なく、軽量であるコンテナを利用する事で、モジュール機構やマネジメント機能といった AS7 のメリットの全てをユーザが得られます。

エラーハンドリング


エラーページの生成は HttpCompletionHandler にラップされ実施されます。このラッパはレスポンスがすでにコミットされたかどうかを確認でき、されていない場合はエラーページを書き出します。チェーン中で後に存在する HttpCompletionHandler は優先され、 HttpCompletionHandler のラッパが最初に実行されます。

Servlet


Undetow Servlet のコア


サーブレットのコードは、Undertow リポジトリに存在し、AS7 のJava EE インテグレーションコード中にもあります。サーブレットのコードは大まかに以下に分類されます。

  • ライフサイクルマネジメント
  • 全てのリクエストハンドリング機能を含むサーブレットハンドラ
  • セッションマネジメント
AS7 の役割は以下です。
  • XML のパースとアノテーションの処理
  • インスタンスの注入、生成及び削除
  • クラスタリング
TODO: 分類の完全な定義

Undertow のサーブレットコンポーネントは AS7 の柔軟な API により設定され、別のインテグレータにも利用されます。この API は XML やアノテーションの代りとなり、コンテナはでぷろコンテナ自身のライフサイクルの制御にこの API を利用します。

Servlet ハンドラチェーン


サーブレット実行時用のハンドラーチェーンの役割は一般的にはとても簡単なものであり、サーブレットハンドラの前のノンブロッキングハンドラ層により提供される機能がほとんどです。サーブレットコアは以下のようなハンドラを提供します。
  • 全サーブレットとフィルタのパスに対するマッチルールを考量した上で、リクエストを適切なハンドラチェーンへディスパッチするハンドラ
  • リクエスト/レスポンスラッパオブジェクトが要求する仕様を生成し、そのラッパオブジェクトを HttpServerExchange へ付与するハンドラ
  • フィルタを実行するハンドラ
  • サーブレットを実行するハンドラ
各機能は可能な限りノンブロッキングなハンドラによってハンドリングされます。例えば、どのフィルタやサーブレットにも該当しないパスへのリクエストにはブロッキングなハンドラは利用されず、代替として静的なリソースが非同期ハンドラにより返されます。

設定とブートスラップ


基本的な設定は、以下の例のように柔軟なビルダー API により行われます。
final PathHandler root = new PathHandler();
final ServletContainer container = new ServletContainer(root);

ServletInfo.ServletInfoBuilder s = ServletInfo.builder()
        .setName("servlet")
        .setServletClass(SimpleServlet.class)
        .addMapping("/aa");

DeploymentInfo.DeploymentInfoBuilder builder = DeploymentInfo.builder()
        .setClassLoader(SimpleServletServerTestCase.class.getClassLoader())
        .setContextName("/servletContext")
        .setDeploymentName("servletContext.war")
        .setResourceLoader(TestResourceLoader.INSTANCE)
        .addServlet(s);

DeploymentManager manager = container.addDeployment(builder);
manager.deploy();
manager.start();

これらビルダーのディープコピーは、deploy() フェーズの間、ServletContainerInitializers により変更されます。ビルダーをクローンする意図ですが、MSC サービスに問題があった場合に備えて、元々の設定を常に保持しておきます。

deploy() フェーズが完了すると、ビルダーはすぐに Deploymentinfo のイミュータブルなコピーをビルドします。Deploymentinfo はデプロイ設定を全て保持しています。start() が呼ばれたとき、このメタデータは適切なハンドラチェーンを構築するのに利用されます。

JBoss アプリケーションサーバとのインテグレーション


このインテグレーションは Undertow チームでメンテナンスされる分割モジュールによって提供されます。このことによって AS7 サブシステムと Torquebox や Immutant のようなインストーラが提供され、既存の AS7 インスタンスへサブシステムが追加されます。AS7 リポジトリ外で開発するにはいくつか理由があります。
  • 衝突のリスクを減らすこと。AS7 のブランチ上で開発した場合、AS7 の開発ツリーと差分を解消するために、マージコミットか頻繁なリベースを実施する必要があり、どちらも望ましいものではありません。
  • ビルド実行時間を短くすること。AS7 のビルドと全テストを実施すると、約1時間程度かかります。異なるリポジトリで開発することでビルド/テスト実行時間をかなり短縮できます。
  • ユーザに選択しやすくすること。このアプローチによって簡単に AS7 ユーザが Undertow を既存のインスタンスにインストールして試すことができます。

セキュリティ


Undertow 中のセキュリティ機構は非同期ハンドラのセットと認証メカニズムのセットそれぞれを組み合わせて実装されます。
  • 認証機能を可能な限り早く呼び出すことができます。
  • 様々なシナリオ中で認証メカニズムの利用が可能であり、サーブレット以外でも適用できます。
ハンドラチェーンの初期段階で呼ばれるハンドラが SecurityInitialHandler であり、空の SecurityContext を現在の HttpServerExchange にセットし既存の SecurityContext を破棄することを保証します。この呼び出しは後でセットされた SecurityContext を破棄する役割をもつこのハンドラを返します。

SecurityContext は現在の認証ユーザに関する状態の保持と設定された AuthenticationMechanism の保持及び、これらに対してはたらきかけるメソッドを提供します。この SecurityContext は交換可能かつ復元可能であるため、一般的な設定をサーバに対して適用してから、後の呼び出しにおいて詳細な設定を行うことが可能です。

SecurityContext を確立した後で後続のハンドラが AuthenticationMechanism を SecurityContext へ追加することが可能であり、簡単に追加するために Undertow は AuthenticationMechanismsHandler というハンドラを用意しています。AuthenticationMechanism を追加するもう1つの方法として、カスタムハンドラを利用できます。

認証プロセスにおける次のハンドラは AuthenticationConstraintHandler です。このハンドラの役割は、現在のリクエストをチェックし、そのリクエストが認証が必要かどうかを特定することです。デフォルトの実装では、認証は全てのリクエストで必要なものとします。このハンドラーは拡張することができ、isAuthenticationRequired メソッドをオーバーライドすることでより詳細なチェックを行うことができます。

チェーン最後のハンドラは AuthenticationCallHandler であり、これにより SecurityContext の呼び出しによる認証処理の実行が保証されます。このハンドラが認証処理を委譲するか、設定されたメカニズムが適切な場合に認証処理を実施するかは、制約の内容に依存します。

これらのハンドラは連続して実行される必要はありませんが、最初に SecurityContext が確立された後は、AuthenticationMechanism と制約チェックはどの順番でもよく、最後に AuthenticationCallHandler が呼ばれる必要があります。しかし、それも制限されたリソースの処理が行われる前です。
図2. セキュリティチェーンの例

セキュリティメカニズムは以下のインタフェースを実装する必要があります。

public interface AuthenticationMechanism {
    IoFuture<authenticationresult> authenticate(final HttpServerExchange exchange);
    void handleComplete(final HttpServerExchange exchange, final HttpCompletionHandler completionHandler);
}

AuthenticationResult は試行された認証の状態を指定するためにメカニズムによって利用される他、認証後に認証されたアイデンティティのプリンシパルを返すメカニズムとしても利用されます。

public class AuthenticationResult {
    private final Principal principle;
    private final AuthenticationOutcome outcome;

    public AuthenticationResult(final Principal principle, final AuthenticationOutcome outcome) { ... }
}

認証処理は INBOUND フェーズと OUTBOUND フェーズの2つのフェーズに分かれます。INBOUND フェーズでは、クライアントから認証データが送られてきたかの確認を行い、データが送られてきている場合のみ、認証の試行に利用します。INBOUD フェーズの間はメカニズムは連続的に呼ばれます。複数のメカニズムの同時実行も1リクエストごとに複数スレッドを利用することで可能です。

AuthenticationMechanism の authenticate メソッドが呼ばれた時、その結果は以下の  AuthenticationOutcome とともに AuthenticationResult を利用して表します。

  • AUTHENTICATED - メカニズムはリモートユーザを認証した
  • NOT_ATTEMPTED - メカニズムは適切なセキュリティトークンを受け取っていないため、ユーザに対して認証を行わなかった
  • NOT_AUTHENTICATED - メカニズムは認証を行ったが失敗した、またはクライアントへ追加情報の要求を行っている
AuthenticationOutcome が AUTHENTICATED である場合、AuthenticationResult 中にプリンシパルが返されなければならず、残りの AuthenticationOutcome はプリンシパルを保持しません。

認証処理全体として以下のようにメカニズムが利用されます。

認証処理が要求されたかどうかに関わらず、リクエストが AuthenticationCallHandler に達した際、SecurityContext が処理を開始するために呼ばれます。認証処理の必要有無に限らず呼ばれる理由は、第1に、クライアントが追加で認証トークンを送る可能性があり、このことを考慮に入れたレスポンスが期待されるからです。第2に、追加のやりとりなしにリモートユーザを認証できる場合があります。特に認証がすでに行われているような場合です。第3に、認証メカニズムは更新途中の状態をクライアントへ渡す必要がある場合などを考えると、送られてきた全てのトークンが正当であることを保証する必要があります。

認証において設定された各 AuthenticationMechanism の authenticate メソッドが順番に実行され、以下の状態のいずれかになるなるまで実行され続けます。
  1. メカニズムがリクエストに対して認証し、AUTHENTICATED を返す
  2. メカニズムが認証を試行したが完了せず、NOT_AUTHENTICATED を返す
  3. リスト中のメカニズムを使い果たした
現時点では、レスポンスが AUTHENTICATED であった場合、そのリクエストは次のハンドラをそのまま通過できます。

リクエストが NOT_AUTHENTICATED である場合、認証が失敗したか、メカニズムが追加情報をクライアントに要求し、どちらの場合でも各 AuthenticationMethod で定義された handleComplete メソッドが順番に呼ばれ、クライアントにレスポンスが返されます。たとえ1つのメカニズムが中間認証であり、クライアントがこのメカニズムを中止して他のメカニズムに切り替えることができるとしても、全てのメカニズムが呼ばれるため、全てのチャレンジに再送が必要です。

リスト中のメカニズムを使い果たした場合、前に設定した認証制約のチェックが必要です。認証が必要でない場合はリクエストはチェーン中の次のハンドラへ処理を続行でき、このリクエストは認証されたものとします(後続のハンドラで認証処理が委譲されたり、陸リクエストが認証を再試行する場合を除く)。認証が必要である場合は NOT_AUTHENTICATED のレスポンスとともに各メカニズムの handleComplete メソッドが順番に呼ばれクライアントに対して認証チャレンジを送ります。

メカニズムを呼んだ後のリクエスト処理が後続のハンドラに許可される場合や、リクエストを認証するメカニズムがない場合、メカニズムは OUTBOUND フェーズの中で呼ばれません。しかし AuthenticationMechanism がクライアントを確証した場合、認証の必要有無に関わらず、メカニズムの handleComplete メソッドが呼ばれます。これはメカニズムがさらにメカニズム特有のトークンをクライアントに送る必要がある場合を想定しています。

メカニズムが一般的なチャレンジの送信、または任意の更新を行うために、以下のように handleComplete メソッド内でチェックすることができます。
if (Util.shouldChallenge(exchange)) { ... }

2013年8月31日土曜日

WildFly のログを Fluentd で扱う

WildFly(旧JBoss AS) のログ解析を、Fulentd を使って実施してみます。

今回の対象ログは、WildFly が出力する access.log とします。Apache httpd の access_log 相当のログです。

なお、本ブログは以下の記事を参考にしています。それぞれの設定の意味合いなどは、そちらのブログを参照いただければと思います。

ステップバイステップで学ぶ Fluentd + GrowthForecast でグラフ作成まとめ

目標

access.log ログを収集・解析し、レスポンスタイムの統計情報(最小値・最大値・平均値など)をグラフ化する。

大まかな流れ

  1. WildFly/JBoss AS7 の設定
  2. Fluentd の設定
    1. ログ送信サーバ(fluent-agent-lite) の設定
    2. ログ受信サーバ(td-agent)の設定
    3. グラフ化サーバ(GlowthForecast) の設定
  3. 動作確認

環境

  • CentOS 6.4 64bit
  • Maven 3.1.0
  • WildFly...と言いたいところですが、JBoss AS7.2.0 Final※
  • Fluentd(td-agent-1.1.14-0)
  • fluent-agent-lite-0.9
  • GlowthForecast
※ 2013/08/31 現在、WilfFly のバージョンは 8.0.0 Alpha4 ですが、access.log を出力する機能は次リリース版である Beta1 です。また、現在レスポンスタイムの出力機能が Undertow(WIldFly の Web 部)に入っていないため、Beta1 のリリースに含まれるか微妙なところです。WildFly においてレスポンスタイムの出力が確認され次第、ブログも反映いたします。

Access log support - WildFly JIRA

JBoss AS7 の設定

インストール


JBoss AS7.2.0.Final のバイナリはコミュニティにて配布されていませんので、自分でビルドする必要があります。またビルドスクリプト(build.sh)の実行に Maven が必要ですので、あらかじめインストールください。なお、ここでは時間短縮のため、テストを省略してビルドしています。
$ git clone https://github.com/wildfly/wildfly.git
$ cd wildfly
$ git checkout refs/tags/7.2.0.Final
$ ./build.sh -DskipTests=true

wildfly/build/target/jboss-as-7.2.0.Final が作成されたバイナリです。適当なところにインストールください。このインストールパスを $JBOSS_HOME と表記します。

ログ設定

$ cd $JBOSS_HOME/bin
$ ./standalone.sh
$ ./jboss-cli.sh -c --command='/subsystem=web/virtual-server=default-host/access-log=configuration:add(pattern="%t,%a,%m,%U,%s,%S,%D",prefix="access.log.")'
念のため JBoss を再起動し、適当に Web アクセスします(wget localhost:8080 など)。$JBOSS_HOME/standalone/log/default-host に、access.log.YYYY-mm-dd とアクセスログが出力されていれば成功です。
cat $JBOSS_HOME/standalone/log/default-host/access.log.2013-08-31
[31/Aug/2013:13:06:59 +0000],127.0.0.1,GET,/,200,-,8

Fluentd の設定

実際には以下のような構成をイメージしていますが、今回は1サーバで全ての役割を持たせます。


ログ送信サーバ


WildFly/JBoss AS7 の access_log が出力されているサーバです。fluent-agent-light を利用して、ログ受信サーバへログを送信します。

インストール用のシェルもありますが、せっかくなので rpm を作成してインストールしてみます。

fluentd-agent-light のインストール

# yum install rpm-build
# useradd rpmbuilder
# su - rpmbuilder
$ mkdir -p ~/rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS}
$ wget http://tagomoris.github.io/tarballs/fluent-agent-lite.v0.9.tar.gz
$ tar zxvf fluent-agent-lite.v0.9.tar.gz && mv fluent-agent-lite* ~/rpmbuild/SOURCES
$ cp ~/rpmbuild/SOURCES/fluent-agent-lite/package/fluent-agent-lite.conf ~/rpmbuild/SOURCES
$ cp ~/rpmbuild/SOURCES/fluent-agent-lite/SPECS/fluent-agent-lite.spec ~/rpmbuild/SPECS
$ rpmbuild
$ exit
# rpm -ivh /home/rpmbuilder/rpmbuild/RPMS/x86_64/fluent-agent-lite-0.9-original.x86_64.rpm

fluentd-agent-lightの設定


以下の内容を /etc/fluentd-agent-lite.conf に記述します。なお、ログのパスは絶対パスに変更してください。
うーん、JBoss AS7 の場合、現在日時に利用されるファイルにすでに日付が入ってしまうため、パス名を日付の切り替えとともに書き換える処理が必要ですね。。

ログ受信サーバ


Fluentd の安定かつパッケージ版である、td-agent をインストールします。また、各種プラグインもインストールしておきます(このエントリ中では利用しないものも含まれています)。

td-agent のインストール

# echo "[treasuredata]\nname=TreasureData\nbaseurl=http://packages.treasure-data.com/redhat/\$basearch\nenabled=0\ngpgcheck=0\n" >> /etc/yum.repos.d/td.repo
# yum install -y td-agent --enablerepo=treasuredata
# /usr/lib64/fluent/ruby/bin/fluent-gem install fluent-plugin-file-alternative
# /usr/lib64/fluent/ruby/bin/fluent-gem install fluent-plugin-parser
# /usr/lib64/fluent/ruby/bin/fluent-gem install fluent-plugin-forest
# /usr/lib64/fluent/ruby/bin/fluent-gem install fluent-plugin-amplifier-filter
# /usr/lib64/fluent/ruby/bin/fluent-gem install fluent-plugin-numeric-monitor
# /usr/lib64/fluent/ruby/bin/fluent-gem install fluent-plugin-growthforecast

以下の内容を /etc/td-agent/td-agent.conf に記述します。

グラフ表示サーバ


グラフ化には GlowthForecat を利用します。
インストールから起動確認まで一気にやってみます。インストールする Perl のバージョンは、5.<偶数> のもので一番数字が大きいものにしました。5.<奇数>は開発版とのことです。
# yum -y groupinstall "Development Tools"
# yum -y install pkgconfig glib2-devel gettext libxml2-devel pango-devel cairo-devel
# useradd growthforecast
# su - growthforecast
$ curl -kL http://install.perlbrew.pl | bash
$ echo '[[ -s "$HOME/perl5/perlbrew/etc/bashrc" ]] && source "$HOME/perl5/perlbrew/etc/bashrc"' >> ~/.bash_profile
$ source "$HOME/perl5/perlbrew/etc/bashrc"
$ perlbrew available
  perl-5.19.3
  perl-5.18.1
  perl-5.16.3
  perl-5.14.4
  perl-5.12.5
  perl-5.10.1
  perl-5.8.9
  perl-5.6.2
  perl5.005_04
  perl5.004_05
  perl5.003_07
$ perlbrew install perl-5.18.1
$ perlbrew switch perl-5.18.1
$ perlbrew install-cpanm
$ cpanm -n GrowthForecast
$ mkdir /home/growthforecast/data
$ mkdir /home/growthforecast/log
$ growthforecast.pl --data-dir=/home/growthforecast/data > /home/growthforecast/log/growthforecast.log 2> /home/growthforecast/log/growthforecast.err &
$ curl -F number=10 http://<ip-addr>:5125/api/socialgame/member/register

動作確認

ここまでくれば完成です。WildFly/JBoss AS7 に適当に Web アクセスをして、/var/log/td-agent に以下のログが出力されている事をご確認ください。

  • access.log の生ログ(wildfly-access.log.*)
  • パースされたログ(parsed-wildfly-access.log.*)
  • GrowthForecat に送るレスポンスタイムの統計ログ(wildfly-response-time.*)

なにか問題が出ている場合は、同ディレクトリの td-agent.log にログが残っています。

GrowthForecat にレスポンスタイムのグラフが作成されているか、http://<ip-addr>:5125 にアクセスしてみましょう。以下のようなグラフが表示されていれば成功です。


参考リンク

2013年7月24日水曜日

Java におけるプロキシ経由のリモートIP 取得あれこれ

Servlet プログラミングで、リモートホストの IP の取得に HttpServletRequest#getRemoteAddr をしたところ、プロキシ経由からのアクセスだったためにプロキシサーバの IP が取得されてしまうという、さみしい事態になることがあります。

この対策として 3 パターンほど挙げます。
  1. HttpServletRequest#getHeader("x-forwaded-for") を利用
  2. (Tomcat6以降のみ)RemoteIpValve を利用
  3. Byteman を利用

プロキシの想定

今回はリバースプロキシとして、CentOS 6.4 に yum でインストールした Nginx を利用していると想定します。

インストールしていない場合は以下を実施ください。
# rpm -ivh http://nginx.org/packages/centos/6/noarch/RPMS/nginx-release-centos-6-0.el6.ngx.noarch.rpm
# yum install nginx

Nginx がインストールできたら /etc/nginx/conf.d/proxy.conf にプロキシ設定を記載します。
※ proxy-host, app-host の部分は環境に合わせて読み替えてください。
# cat /etc/nginx/conf.d/proxy.conf
server {
  listen 80;
  server_name proxy-host;

  location / {
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Server $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_pass http://app-host:8080;
  }
}

次節からリモートホストの IP 取得方法を記述していきます。

HttpServletRequest#getHeader("x-forwaded-for") を利用

Web で一番多く見かける方法です。HttpServletRequest#getRemoteAddr の代りに、 HttpServletRequest#getHeader("x-forwaded-for") の値を利用します。実際は以下のブログのようにフィルタにすると楽ですね(こちらは X-Real-IP の場合ですが)。

Keeping real user IP in Java web apps behind Nginx proxy

(Tomcat6以降のみ)RemoteIpValve を利用

@msfm さんに教えていただきました。ありがとうございます!

Tomcat 6 以降、または JBoss AS7/EAP6のみとなりますが、
org.apache.catalina.valves.RemoteIpValve を利用する方法です。

Tomcat であれば上記 API リファレンスを参考に、server.xml に Valve 要素を追加します。

JBoss AS7/EAP6 では、残念ながらCLI で設定できなさそうです。war アプリケーションの WEB-INF 配下に以下のような jboss-web.mxl を追加し、再ビルドします。

<jboss-web>
  <valve>
    <class-name>org.apache.catalina.valves.RemoteIpValve</class-name>
    <param>
      <param-name>remoteIpHeader</param-name>
      <param-value>x-forwarded-for</param-value>
    </param>
  </valve>
</jboss-web>

どちらの方法でも、ソースコードを変更することなく実施できます。

なお、WildFly の Web/Servlet コンテナであるところの Undertow では、まだこの機構はないようです。

Byteman を利用

Byteman を使って、アプリケーションのソースコードを変更せずに、
HttpServletRequest#getRemoteAddr の実装を書き換える方法です。
ザ・黒魔術という感じがしますね。

以下、手順です。

1. Byteman のインストール


2013/07/24 時点での最新版である 2.1.3 をインストールします。
# cd /opt
# wget -qO- -O tmp.zip http://downloads.jboss.org/byteman/2.1.3/byteman-download-2.1.3-bin.zip && unzip tmp.zip && rm -f tmp.zip && ln -s byteman-download-2.1.3 byteman

2. Byteman のルールを作成


x-forwarded-for.btm という名前で以下の内容で作成します。

今回作成した Byteman のルールですが、発想としては一番最初の HttpServletRequest#getHeader("x-forwaded-for")  を利用する方法と同じです。

リクエストヘッダに x-forwarded-for が存在する場合は、HttpServletRequest#getRemoteAddr の値を HttpServletRequest#getHeader("x-forwarded-for") の値に書き換えます。存在しない場合は何もしません。

3. Tomcat/JBoss 起動時に Byteman がアタッチされるように設定


Tomcat: $CATALINA_HOME/bin/setenv.sh
JBoss AS7/EAP6: $JBOSS_HOME/bin/standalone.conf

に以下を新規作成(または追記)します。
BYTEMAN_RULE 変数に代入する x-forwaded-for.btm のパスは適宜変更ください。

BYTEMAN_HOME=/opt/byteman
BYTEMAN_RULE=/path/to/x-forwarded-for.btm
BYTEMAN_OPTS="-javaagent:$BYTEMAN_HOME/lib/byteman.jar=listener:true,boot:$BYTEMAN_HOME/lib/byteman.jar"
if [ "x$BYTEMAN_RULE" != "x" ]; then
BYTEMAN_OPTS="${BYTEMAN_OPTS},script:$BYTEMAN_RULE"
fi
BYTEMAN_OPTS="$BYTEMAN_OPTS -Dorg.jboss.byteman.transform.all
-Dorg.jboss.byteman.debug"
JAVA_OPTS="$BYTEMAN_OPTS $JAVA_OPTS"

※ Tomcat の場合は、JAVA_OPTS を CATALINA_OPTS に変更してください。

4. Tomcat/JBoss AS7/EAP6 の起動


起動してみて、getRemoteAddr の返り値がクライアントのリモートホストの IP であれば成功です。

参考


課題

  • 多段プロキシに対応できない
  • 現状の Byteman ルールでは、X-Forwarded-For ヘッダを送ってくるプロキシが信頼できるプロキシかどうかチェック(IP アドレスの確認)を行っていない

2013年6月28日金曜日

Vagrant で CentOS 6.4 を利用する

発作的にまっさらな CentOS が欲しくて、でもはなからインストールするのはめんどくさい。
ということで Vagrant を試してみました。ちなみに、「ゔぇいぐらんと」と読むみたいですね。スクウェアにそんな感じのゲームがありましたね。

利用手順

VirtualBox のインストール


2013/06/27 現在、VirtualBox 4.2.14 だと動作しません。4.2.12 をご利用ください。

バックエンドの仮想化環境として、VirtualBox をインストールします。
なお、バックエンド環境は VirtualBox でなくてもよいようです。

Vagrant のインストール


http://downloads.vagrantup.com/ から最新版を選択し、各 OS に合った形式でインストール。

CentOS 6.4 32bit 版のゲストOSを作成する


今回はお試しなので、サイズの小さい 32bit 版にしました(といっても 600 MB くらいしか違わないようですが)。なお、公式から配布されているイメージは以下から確認できます。


上記でお望みのイメージが見つからない場合は自作するか、有志で公開しているものを利用させてもらうといった手があります。

Box の追加


適当な作業用ディレクトリで行います。以下、このディレクトリをプロジェクトディレクトリと呼びます。
$ mkdir -p ~/vagrant/centos64_32 && cd ~/vagrant/centos64_32
centos64_32 という名前で Box (仮想サーバ、ゲストOS)を追加します。第4引数の URL は、http://www.vagrantbox.es/ で確認した URL です。
$ vagrant box add centos64_32 http://developer.nrel.gov/downloads/vagrant-boxes/CentOS-6.4-i386-v20130427.box

Box のセットアップ

$ vagrant init centos64_32
VagrantFile という設定ファイルが作成されます。

Box の起動

$ vagrant up
Virtual Box 側にもゲストOSが追加されたことが確認できます。

Box の利用

$ vagrant ssh
[vagrant@localhost ~]$
vagrant ユーザとしてログインできました。

基本的な操作

root になるには sudo -s または su - で


sudo -s であればノンパスで、su - であればパスワード vagrant で root ユーザになれます。

/vagrant は共有ディレクトリ


/vagrant で作られたファイルはプロジェクトディレクトリと共有されます。
$ ls
Vagrantfile
$ vagrant ssh
[vagrant@localhost ~]$ touch /vagrant/foo
[vagrant@localhost ~]$ ls /vagrant
Vagrantfile  foo
[vagrant@localhost ~]$ exit
$ ls
Vagrantfile foo

ssh の設定


現状ですと vagrant ssh を利用してログインする場合、カレントディレクトリはVagrantFile が存在するディレクトリである必要があります。これでは不便という事で、~/.ssh/config にホスト情報を追記して、どのディレクトリにいてもログインできるようにしておきます。
$ vagrant ssh-config --host centos >> ~/.ssh/config
$ cd /path/to # 適当なところへ移動
$ ssh centos
[vagrant@localhost ~]$

ネットワーク


VagrantFile を編集する事で、Box に任意の IP アドレスを設定する事ができます。以下の設定では 192.168.33.10 でホスト OS からアクセスすることができます。
# Create a private network, which allows host-only access to the machine
# using a specific IP.
config.vm.network :private_network, ip: "192.168.33.10" # コメントアウトを外す

プロビジョニング


Box を起動(vagrant up)する際に、
  1. yum upgrade  して
  2. epel レポジトリを追加して
  3. Apache httpd と nkf をインストール
するようにしてみたいと思います。サーバ環境構築の自動化の第一歩ですね。
以下のようなスクリプトを用意します。ファイル名は bootstrap.sh としておきます。
#!/usr/bin/env bash
yum upgrade -y
wget http://dl.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm
rpm -ivh epel-release-6-8.noarch.rpm
yum install -y httpd
yum install -y nkf

次に Vagrantfile に Box 起動時にこのシェルを実行するように記述します。
# Every Vagrant virtual environment requires a box to build off of.
  config.vm.box = "centos64_32"
  config.vm.provision :shell, :path => "bootstrap.sh"

Box を起動します。
$ vagrant destroy // すでにBoxを作成している場合
$ vagrant up

Box の起動とともに、bootstrap.sh に記述した内容が実行されていきます。
完了後、ログインします。
$ vagrant ssh
[vagrant@localhost ~]$ httpd -v
Server version: Apache/2.2.15 (Unix)
Server built:   May 13 2013 22:08:57
[vagrant@localhost ~]$ nkf --version
Network Kanji Filter Version 2.0.8 (2007-07-20)
[vagrant@localhost ~]$ sudo -s
[root@localhost vagrant]# yum upgrade
...
No Packages marked for Update
うまくいってるみたいですね。すごい。

参考

2013年6月23日日曜日

「JUnit実践入門」を読む会 第4回 参加報告

前回から Java 読書会 BOF に参加させていただいております。
現在は「JUnit 実践入門」が題材で、今回は第4回です。

Twitter ハッシュタグ
#javareading

参加者は(自分のテリトリの) Java EE 層は少なめで、地図ソフトウェアや組み込みのお話が聞けて非常に刺激的です。話題によっては高度な話まで突っ込まれることがあり、理解できなくて悔しい思いをすることも多いですが。。

読書会のスタイル

参加者各々が順番に、時間を区切って音読していくのが基本になります。
そしてここが肝ですが、途中で疑問に思ったところやメンバに聞いてみたいことなどを、遠慮なくぽろりと聞きます。すると議論が始まります。これが非常に楽しい。自分が口を挟むもよし、色々な意見を傾聴するもよし、です。

これは私だけかもしれませんが、自分ひとりで本を読んでいると、特に難しい内容になると顕著ですが、「まああとでわかるだろう」と読み飛ばしてしまいがちです。そして、だいたいきちんと理解せずに読了してしまいます。この読書会のやり方では、疑問に思ったところを相談できます。疑問がその場で解決することもありますし、もちろん書籍の内容だけでは判断できないものは保留になることもあります。そういった場合でも、あとでちゃんと調べてみようと指針を立てたり、議論のうちに自分が何を理解していないのか整理できることがあります。非常によいスタイルだと思います。

実は社内でも、Java 仮想マシン仕様 の読書会をこのやり方で実施しています。この仕様書は自分にとって相当難解で、気楽に周りに相談できるという場は非常にありがたいです。

第4回 所感

※書籍の内容とは関係ない内容も含みます。

初めて知ったこと

  • Java EE 7 の WebSocket はもう使われているシステムあり。LB や SSL アクセラレータをかましての動作実績もありそう。AP サーバは何使ってるか聞きそびれた。。Jetty あたりかなあ。次回聞いてみたい。
  • 匿名 Inner クラスでメソッドを実装する際に、実装しようとするメソッドの外側にあるローカル変数は final つきでないと読めない。例えば boolean をチェックしたい場合は Boolean bool = false; などと宣言してしまうと、Inner クラス中で bool = true; などとできなくなるので、AtomicBoolean などを使って凌ぐしかない。

困り事と提案

  • インターネット前提の Maven は使いづらい。
    • たしかにその通りだと思う。プログラマがインターネットの利用を制限されている環境は少なくないかもしれない。ただ、Sonatype などを使って、社内のプライベートリポジトリを用意することで、毎回だれか(インターネット接続許可のある一部のひと)がライブラリをダウンロードするのに走り回る必要はなくなるんじゃないかと思う。
  • システム依存や異常系のテストはどこまでやるか
    • 線引きが難しい。ただ、参加者の方も言ってらしたけど、結合試験で実施した方が早いものなんかは割り切りも大事かなと。

情報共有

Arquillian による DB 接続を伴うユニットテスト

書籍中では DBUnit を利用したコードが紹介されています(接続コードだけでなく、DB 起動・停止、テスト実行における前処理・後処理、テストデータの設定含む)。

JBoss などの Java EE AP サーバを利用して開発をする際に、DB 接続部分と開発用 DB の起動・停止に関して簡略化する方法をご紹介します。

Arquillian という、JUnit と協調するテスティングフレームワークがあります。
これは Java EE AP サーバを実際に起動させることで、Java EE の本物のコンテナ(EJB や CDI など)をテスト中で使えるようにするものです。

これが ユニットテストにおける DB接続をどう簡略化させるかというと、JPA(Java EE 標準の ORM 仕様) + インメモリデータベースの組み合わせが鍵になります。
JBoss や GlassFish であれば 、内部にインメモリデータベース(H2 / Derby)を持っており、サーバの起動とともにこれら DB が立ち上がります。これで起動・停止は自動化されます。あとは通常のデータソース設定として DB 接続定義を記載しておけば、プロダクションコードと同様の仕組みで DB 接続ができます。Maven の設定次第では テスト用と開発用の DB を分けておく事もできます。

テストコード中の DB 接続コードについては、JPA では EntityManager というインタフェースを利用してエンティティの操作を行いますが、このインスタンスは @PersistenceContext とアノテーションを付与することでインジェクションされます(参考)。JPA は Java SE でも利用できますが、こういったインジェクションが可能なのは AP サーバのコンテナがあってこそのものです。

JPA を使いたくない場合でも、@Inject(CDI) や @Resource といったインジェクション用アノテーションを利用して、javax.sql.Datasource を取得できますので、すぐに DB 処理に関するコードを書き始められます。

前処理・後処理に関しては、通常通り @Before・@After を利用します。

調べる事

  • Mockito の実装をちょっと読んでみたい。

2013年6月13日木曜日

PukiWiki Adv. を nginx で動かす

以下環境で PukiWiki Adv. を動かすためのメモ。
  • CentOS 6.4(さくらの VPS)
  • nginx 1.4.1
  • PHP 5.4.16
  • PukiWiki Adv. 1.0.3

なぜ Apache でなく nginx か

最初は PukiWiki Adv. を Apache httpd で動かそうと考えていましたが、.htaccess の設定がうまく読込めず、mod_rewrite を利用しての静的URL表現ができなかったため、nginx を試してみました。特にトラフィックが〜などの動機ではありませんので、性能面に関しての言及はありません。すんません。

nginx のインストール

今回の PukiWIki Adv. のために追加で必要そうなモジュールがいまいちよくわからなかったので、yum を使ってインストールしました。

# rpm -ivh http://nginx.org/packages/centos/6/noarch/RPMS/nginx-release-centos-6-0.el6.ngx.noarch.rpm
# vim /etc/yum.repos.d/nginx.repo
enabled の値を 0 に変更
# yum -y install nginx --enablerepo=nginx

チューニングなどはよしなに。

[参考]
CentOS-6へ最新版のnginxを3分でインストールする方法

PHP関連パッケージのインストール

現在、以下のパッケージがインストールされています。
remi や rpmforge、epel などのレポジトリを追加しておくと良いと思います。

# rpm -qa | grep php
php-common-5.4.16-1.el6.remi.x86_64
php-gd-5.4.16-1.el6.remi.x86_64
php-cli-5.4.16-1.el6.remi.x86_64
php-devel-5.4.16-1.el6.remi.x86_64
php-mbstring-5.4.16-1.el6.remi.x86_64
php-fpm-5.4.16-1.el6.remi.x86_64
php-5.4.16-1.el6.remi.x86_64

PHP-FPM のチューニング、がんばりましょう。

PukiWiki Adv. のインストール

# cd /var/www/html
# tar Jxvf /path/to/PukiWiki\ Adv.\ 1.0.3.tar.xz
# mv webroot pukiwiki
# mkdir misc
# mv COPYING.txt README.txt doc tools misc
# ls -l | awk '{ print $10 }'
misc
pukiwiki
wiki-common
wiki-data

PHP-FPM にプロキシさせる nginx の設定

/etc/nginx/conf.d/pukiwiki.conf を以下の内容で新規作成しました。
yourdomain は皆様のドメイン名に変更してください。

server {
    listen              80;
    server_name  yourdomain;
    root                /var/www/html/pukiwiki;
    index               index.php;

    location / {
        error_page      404 = @pukiwiki;
        log_not_found   off;
    }

    location @pukiwiki {
        rewrite ^/(.+)$         /index.php?$1   last;
        break;
    }

    location ~ \.php$ {
        include         fastcgi_params;

        fastcgi_pass    127.0.0.1:9000;
        fastcgi_index   index.php;
        fastcgi_param   SCRIPT_FILENAME  $document_root$fastcgi_script_name;
    }
}

[参考]
Rewriteを用いたサーチエンジン最適化 - nginx

nginx と PHP-FPM の起動

# /etc/init.d/php-fpm start
# /etc/init.d/nginx start

感想

自分は nginx を今日初めて触りましたが、設定書式がプログラムチックなためか非常に直感的で、ほとんど悩まずに設定することができました。Apache も別に詳しくなく、PHP に至ってはほとんどよくわかってないのですが、とりあえず公開する所までの敷居は低いと思います。

参考URL・書籍

感想でえらそうにいってましたが、スムーズに構築できたのは下記ページや書籍のおかげです。ありがとうございます。

  • nginx
    • nginx 本家
  • WEB+DB PRESS Vol.72
    • nginx の設定内容から PHP アプリケーションとの連携までわかりやすく解説されています。