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 アドレスの確認)を行っていない