ファイヤープロジェクト
Restletの主要なクラスをじっくり見てみる(その2 RouterとRoute)
2008-12-07T12:45+09:00   matsu
Routerは複数のRouteを持ち,Routeは要求に応じてスコアを返す.Routerはスコアとルーティングモードに応じてRouteを選択し動かす.
ルーティングというと,ネットワーク的なものを連想してしまうが,概念的にはその通りと思ってよいと思う. Routerは複数のRouteを持ち,Routeは要求に応じてスコアを返す.Routerはスコアに応じて適切なRouteを動かす. このとき,RouteはFilterの子クラスなので,beforeHandle,doHandle,afterHandleの流れで動く. そしてこのdoHandle内で,自身のnextのRestletのhandleを呼び出す.
RouterのgetNextメソッドは,内部でスコア算出を行うために,引数にリクエストやレスポンス情報を持つことに注意.
public Restlet getNext(Request request, Response response)
Routeは複数のRouteを持つわけだが,その登録は以下で行う.
public Route attach(String uriPattern, Restlet target)
public Route attach(Restlet target)
public Route attach(String uriPattern, Class targetClass)
最初のものが基本パターンである. 第一引数がURIパターンで,これをスコア算出の根拠とする. すなわち,要求がこのパターンに近ければ近いほど,スコアが高くなる. 要求に対してこのRouteのスコアが最も高ければ(※),第二引数のRestletのhandleが呼び出されることになる. 二つ目のメソッドは,URIパターンが""である設定となる. このURIパターンは,どのRouteもマッチしない場合に適用されるが,デフォルトRouteは,別に持っていたりもして,使い分けが良く分からない. 三つ目のメソッドは,第二引数にRestletではなく,Resourceを指定する. メソッド内部ではFinderを使用してResourceからRestletに変換(ラッピング)しているが,その仕組みは別途. 先程出て先程出て着た,デフォルトルートの設定は以下である.
public Route attachDefault(Class defaultTargetClass)
public Route attachDefault(Restlet defaultTarget)
これらは,getNextメソッド内で,どのRouteも選択されなかった場合のデフォルトRouteとして使用される. 二つのメソッドの違いは,attachメソッドと同様. また,attachしたRouteはdetachできる.
public void detach(Restlet target)
指定したRestletを持つRouteオブジェクト(デフォルトRouteも含む)を全てルーティング対象から削除する.
デフォルトのルーティングは,各Routeに設定したURIパターンと,リクエストURIとの適合度によって行われる. それは,
java.util.regex.Pattern
を利用して実現している. 場合によっては,これとは全くことなる,独自のルーティングルールでRestletを作成した場合もあるかもしれない. その場合は,RouterとRouteをそれぞれ拡張する. サンプルとして,SimpleRouterとSimpleRouteを作成してみた. まずはRouter.
スコア算出関数であるRouteだけを拡張すればできそうな気がするが,RouterはRouteと密結合されているので,Routerも拡張する必要がある. このちょっとイケていない状況は,Routerの登録メソッド
public Route attach(...
の引数がRouteではなく,Restletであることが端的に示している. つまり,RouterはRestletを受け取って,固定的にRouteを作成している. 独自のRouteを使うようにする余地がない(※). そこで,attachメソッド内で使用されるcreateRouteメソッドを拡張して,独自Routeを使用するようにする.
/**
 * 独自のRouteクラスを使用する場合は,このRouteのメソッドをオーバーライドする
 */
@Override
protected Route createRoute(String arg0, Restlet arg1) {
    return new SimpleRoute(arg1);
}
なお,このサンプルでは,第一引数は無意味である. 次にSimpleRoute.
SimpleRouteで実装するスコア関数では,今回はリクエストクエリの値をそのままスコア値とするようにしてみた.
/**
 * リクエストクエリのvalueの値をスコアとして使用する
 */
@Override
public float score(Request arg0, Response arg1) {
    float result = 0;
    String score = arg0.getResourceRef().getQueryAsForm().getFirstValue("value");
    try {
        result = new Float(score);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return result;
}
すなわち,リクエストクエリ中の
value=スコア値
がスコア値を決定する. さて,SimpleRouterは,SimpleRouteでルーティングモード(算出するスコア値をどのように扱うか)は,デフォルトのRouteと同じである. 今回はデフォルトので,スコア値がベストのRouteを選択する. ただし,スコア値は閾値を超えていなければならない. 閾値はRouterの,
private volatile float requiredScore;
に格納されており,
public float getRequiredScore()
public void setRequiredScore(float score)
で取得,設定できる. SimpleRouterでは,handleメソッド内で,リクエストクエリから値を取得し,変更するようにしておいた.
/**
 * リクエストクエリのrsの値を閾値として使用する
 */
@Override
public void handle(Request arg0, Response arg1) {
    String requiredScore = arg0.getResourceRef().getQueryAsForm().getFirstValue("rs");
    try {
        setRequiredScore(new Float(requiredScore));
    } catch (Exception e) {
        e.printStackTrace();
    }
    super.handle(arg0, arg1);
}
すなわちリクエストクエリ中の
rs=閾値
にて変更できる. 結局サンプルでは,リクケストクエリとして,
?rs=a&value=b
を持ち,a>bなら閾値を超えるRouteが見つからず404,a<bなら200が返る. 例.
http://localhost:8182/?rs=0.5&value=0.3 => 404
http://localhost:8182/?value=0.9&rs=0.1 => 200
なお,デフォルトではスコア値は0から1の間のfloatであるように作成されている. レンジチェックはないようなので,1を超えるものを作成できると思うが,その場合は全体の整合を再度確認していく必要があるかもしれない.
Routerには,スコア値の扱いを決めるルーティングモードがある(Restletのアーキテクチャと設定ファイル参照). ルーティングモードはRouterクラスの
public void setRoutingMode(int routingMode);
で指定する. デフォルトはBESTである. カスタムのルーティングモードを作成することができるようなので,作成してみた(※).
カスタムルーティングは,Routerクラスの
protected Route getCustom(Request request, Response response);
で実装する. 今回はRouteとしては先のSimpleRouteを使用し,そのスコア値の確立で200とするか,404とするかを決定している.
/**
 * カスタムルーティング
 * score値の確立で最初のRouteが使用される.それ以外はNOT FOUNDとなる.
 */
@Override
protected Route getCustom(Request request, Response response) {
    System.out.println("Custom routing.");
    Route result = getRoutes().get(0);
    float score = result.score(request, response);
    if (score < rand.nextFloat()) {
        result = null;
    }
    return result;
}
あとは,カスタムルーティングを使用するように,ルーティングモードを設定する.
// ルーティングモードの設定
router.setRoutingMode(Router.CUSTOM);
※ Routerクラスでは空実装となっている
protected Route getCustom(Request request, Response response) {
    return null;
}
Routerには,自作Routeのscore関数が呼び出す度に結果が変わるような場合のために,試行回数とリトライディレイ(インターバル)を設定する仕組みがある. 試行回数(の最大数)の取得と設定は
public int getMaxAttempts()
public void setMaxAttempts(int maxAttempts)
であり,ディレイの取得と設定は
public long getRetryDelay()
public void setRetryDelay(long retryDelay)
である. retryDelayはミリ秒である. この仕組みは,RouterのgetNextメソッドにて実装されているので,これをオーバーライドした際はこの仕組みが動かなくならないように注意する. この仕組みを使用して,1秒のリトライディレイで5回試行する(つまり回答を得るまでに4秒以上かかる)Routerを作成してみた.
「試行回数に達するまで次のRoute取得に失敗する」仕組みは,getCustomにて実現した.
/**
 * カスタムルーティング
 * 試行回数に達したら,最初のRouteを返す.
 */
@Override
protected Route getCustom(Request request, Response response) {
    Route result = null;
    
    if (attempts.get() == null) {
        attempts.set(1);
    }
    System.out.println("attempts = " + attempts.get());
    if (getMaxAttempts() == attempts.get()) {
        result = getRoutes().get(0);
    }
    attempts.set(attempts.get() + 1);
    return result;
}
... 試行回数を保持するためのプロパティはThreadLocalにしておく必要がある気がしたのだが,このサンプルはどうもシングルスレッドでしか動いていない雰囲気. ルーティングモードや試行回数とリトライディレイは,mainメソッドにて行っている.
// ルーティングモードの設定
router.setRoutingMode(Router.CUSTOM);
// 試行回数の設定
router.setMaxAttempts(5);
// 試行インターバル(ミリ秒)の設定
router.setRetryDelay(1000);
matsu(C)
Since 2002
Mail to matsu