簡単なオープンインタフェース(2)-コアエンジン(上)を作成する方法

15622 ワード

1、実現する機能
本書は前回に続き、オープンインタフェースプラットフォームのコアエンジンのマルチHandlerサポートメカニズムを完成します.
図1に示すように.
図1オープンインタフェースサーバ側アーキテクチャ
2、Filterかデコレーションモードか
装飾者モードはJavaのI/O実装のような実装の候補のようだ.多くの「装飾」が1階建てで、新しい機能が得られ、元の機能はまだあります.
私の今の応用シーンにとって、この実現方法は複雑すぎる.相対的にFilterの方が簡潔です.
現在のアプリケーションシーンはパフォーマンスに非常に要求されており、少し複雑なモードでも使用するのに適していません.
3、Handlerインタフェース定義
私のHandlerインタフェースは以下のように定義されています.
public interface Handler {
    public void inWay(HttpServletRequest request,HttpServletResponse response); 
    public void outWay(HttpServletRequest request,HttpServletResponse response);
}

さらに節操のある子供靴は、Request、Response 0、さらにContextオブジェクトを自分で定義します.Webコンテナの制約から脱し,さらに独自の下位通信プロトコルを実現する.
私のところはまず怠け者で、時間があればゆっくり来ます.
Handlerインタフェースでは,inWayメソッドは図1左側の下向き矢印,outWayは右側の上向き矢印に対応する.
このようにして,同じHandlerで入力,出力の論理を定義する.シーケンス化機能を実現するHandlerについてはinWayで逆シーケンス化,outWayでシーケンス化を実現する.暗号化機能を実現するHandlerではinWayで復号化を実現し,outWayでは暗号化を実現する.圧縮機能を実現するHandlerについては,inWayで解凍を実現する論理,outWayで圧縮を実現する論理である.
このように,あるHandlerを必要としない場合は,そのまま取り除くとよい.
もちろん、outWayではdo nothingが可能です.
また,Handler実装クラスは独自の属性を持つことができないことが重要である.Handlerインスタンスに「ステータス」はありません.Handlerはスレッドが安全である必要があります.
3、構成可能
複数のHandlerは構成可能であり、各Handlerチェーンは1つ以上のインタフェースにサービスすることができる.
Handlerのステータスなし、スレッドセキュリティに基づいて、各JVMにおけるHandlerの単一例を採用することで、Handlerオブジェクトの頻繁な作成、回収の損失を回避できます.
構成情報はpropertiesファイルに保存できます.xmlに保存できます.データに保存できます.例は次のとおりです.
openapi.handler.keys=surface,encrypt,auth
openapi.handler.surface=cn.hailong.common.openapi.handler.SurfaceHandler
openapi.handler.encrypt=cn.hailong.common.openapi.handler.EncryptHandler
openapi.handler.auth=cn.hailong.common.openapi.handler.AuthHandler

システムで使用可能なすべてのHandlerを構成し、システムの起動時にロードします.
上記の構成では、各Handlerのクラス名が構成されており、ロード時にクラス名に基づいてクラスのインスタンスを作成することができます.各Handlerには、Handlerチェーンを構成するときに参照しやすい短い名前が付けられています.
対応するロードコードは次のとおりです.

public class HandlerManager {   
    /**
     *         Handler,key handler  ,value Handler  。
     */
    private static Map handlersMap = new ConcurrentHashMap();

    static{
        reloadHandlers();
    }

    public static synchronized void reloadHandlers(){

        handlersMap.clear();

        logger.info("Open Api Handlers load start ... ");

        long begin = System.currentTimeMillis();
        Properties props = ConfigManager.getProperties("openapi");
        String handlerKeys = props.getProperty("openapi.handler.keys");

        logger.debug("loading handlers : "+handlerKeys);

        Handler handler = null;

        if(!StringUtils.isEmpty(handlerKeys)){
            String[] handlerKeyArray = handlerKeys.split(",");
            if(handlerKeyArray!=null && handlerKeyArray.length>0){
                for (String handlerKey : handlerKeyArray) {
                    String propertiesKey = "openapi.handler."+handlerKey;
                    String handlerClassName = props.getProperty(propertiesKey);
                    if(StringUtils.isEmpty(handlerClassName)){
                        continue;
                    }
                    handlerClassName = handlerClassName.trim();
                    Class> clz = Class.forName(className);
                    handler = BeanUtil.newInstance(Handler.class,clz);
                    if(handler!=null){
                        handlersMap.put(handlerKey,handler);
                        logger.debug(String.format("handler[%s] loaded : %s ", handlerKey,handler));
                    }
                }
            }
        }

        logger.info("Open Api Handlers load end , time costs "+(System.currentTimeMillis()-begin));
    }

    /**
     *
     */
    public static Hanlder get(String shortName){
        return handlerMap.get(shortName);
    }
}

Handlerの構成を変更し、再ロードが必要な場合はサーバを再起動する必要もなく(本番環境では重要)、HandlerManager.reloadHandlers()を再度呼び出すことができます.
次はHandlerチェーンの構成です.構成例は次のとおりです.
openapi.handler.chain.keys=full,idle
openapi.handler.chain.full=surface,encrypt,auth,traffic,config,validate
openapi.handler.chain.idle=idle,idle,idle,idle,idle

ロードロジックはHandlerManagerのコードロジックと似ています.
4.Handler実行プロセス
Handlerの実行手順を図1に示す.
public class HandlerChain {

    protected List handlersList = null;
    protected List handlersReversedList = null;

    protected Iterator inIterator = null;
    protected Iterator outIterator = null;

    public HandlerChain(List handlers) {
        setHandlers(handlers);
        reset();
    }

    protected void setHandlers(List handlers) {
        if (handlers == null) {
            return;
        }
        //   
        this.handlersList = handlers;
        //   
        if (handlersReversedList == null) {
            handlersReversedList = new ArrayList();
        } else {
            handlersReversedList.clear();
        }
        for (int idx = handlers.size() - 1; idx > -1; --idx) {
            handlersReversedList.add(handlers.get(idx));
        }
    }

    public void reset() {
        if (handlersList != null) {
            inIterator = handlersList.iterator();
        }
        if (handlersReversedList != null) {
            outIterator = handlersReversedList.iterator();
        }
    }


    public void inWay(HttpServletRequest request, HttpServletResponse response) {
        Handler nextHandler = null;
        if (inIterator != null && inIterator.hasNext()) {
            nextHandler = inIterator.next();
        }
        if (nextHandler != null) {
            nextHandler.inWay(request, response);
            this.inWay(request, response);//    
        } else {
            logger.debug(String.format("In End Time:%s.",(System.currentTimeMillis() - this.time)));
        }
    }

    public void outWay(HttpServletRequest request, HttpServletResponse response) {
        Handler nextHandler = null;
        if (outIterator != null && outIterator.hasNext()) {
            nextHandler = outIterator.next();
        } 
        if (nextHandler != null) {
            nextHandler.outWay(request, response);
            this.outWay(request, response);
        } else {
            logger.debug(String.format("Out End Time:%s.",(System.currentTimeMillis() - this.time)));
        }
    }
}

HandlerChainはステータスがあり、リクエストごとにインスタンスを作成します.handlerChainInstanceを呼び出します.InWay(req,resp)は、対応するHandlerチェーンのすべてのinWayメソッドを実行する.handlerChainInstanceを呼び出します.outWay(req,resp)は、対応するHandlerチェーンのすべてのoutWayメソッドを実行する.
5、メッセージ形式の参考実現
インタフェースの呼び出しは、インタフェース名、パラメータ値、出力パラメータが戻り値、例外メッセージのいずれかを含むメソッドの呼び出しと同様です.
次のインタフェースを定義します.
interface RpcMessage{
    Object getMeta();
    /**
     * @return        。
     */
    String getMethod();
    /**
     * @return       。
     */
    List getParams();
    /**
     * @return    。
     */
    Object getResult();
    /**
     * @return     。
     */
    Object getError();
}

Metaには、可能な呼び出し者情報、ライセンス情報などが含まれています.
このようなデータ構造に対してjson−rpc(http://json-rpc.org/wiki/specification)は簡単で使いやすいシーケンス化スキームです.解析効率は高くないが,jsonは十分に簡単で,参照実装として良い選択である.生産環境において、より成熟した考慮が必要である.
持続化スキームとしてjson−rpcを用いた場合.
要求情報は次のようになります.
{meta:{token:'765959559266'},method:'getUserInfo',params:['32899688']}

戻り情報は次のようになります.
{result:{name:'   ',addr:'      '}}

または
{error:{code:'AUTH_ERR',msg:'Token  '}}

リクエストごとにRpcMessageを作成するインスタンスは、RequestまたはThreadLocalに保存できます.
6、異常処理
今は少し複雑な問題を考える必要があります.図1を見て,各段階で異常が投げ出された場合の対処を考える.ライセンスに失敗したり、トラフィック制御を超えたりした場合の対応を考慮します.
まず、クライアントがoutWayの構成に従ってカスタマイズされているという問題を明らかにします.outWayにシーケンス化と暗号化がある場合、クライアントは復号化と逆シーケンス化します.生産環境で採用されるシーケンス化方式は一般的にバイナリであり,開発環境ではJSONまたはXMLを採用する可能性がある.復号化は一般的に異なるユーザに対して異なるKeyを採用する.
返されたメッセージが、特定のフォーマットのシーケンス化または暗号化されていない場合、クライアントはメッセージを読み取ることができません.
だから、私たちが得た最初の結論は、異常メッセージもすべてのHandlerのoutWay方法に報告しなければならないということです.
次に、図1のフローの各段階を1つずつ考慮する.
6.1、inWay過程における異常
次のコードでinWayプロセスの例外をキャプチャし、rpcMessageインスタンスに保存できます.
try{
    handlerChainInstance.inWay(req,resp)
}catch(Throwable e){
    rpcMessage.addError("PRE_INVOKE_ERR",e.getMessage());
}

次に、inWayで例外が発生した場合、ビジネスロジックオブジェクトを呼び出すコードをスキップします.直接outWayを実行し、outWayの実行中に、構成に基づいて、どのようにシーケンス化するか、どのように暗号化するか、どのように暗号化するか.
暗号化に顧客のアイデンティティを明確にする必要がある場合、どのKeyを使用するかを知ることができますが、要求にユーザーのアイデンティティ情報が含まれていないか、ユーザーのアイデンティティ情報が無効な場合、どうすればいいですか?この問題を解決するには、第1に、ユーザ識別情報がメッセージボディに伝達されないようにする.さもなくば身分を識別することができなくて、どんなKeyで解読しても知らないで、読めません.第二に、アイデンティティが無効なエラーメッセージ、特殊なコードを定義し、明文伝達する.クライアントは、すべてのHandlerを実行する前に、このエラーかどうかを判断します.
6.2、業務ロジックの呼び出し中に発生した異常
これが一番簡単で、捕まえて、rpcMessageに置けばいいです.そしてどう行けばいいかoutWayはどう行けばいいですか.
6.3、outWay過程の異常
これが一番扱いにくい.
開発過程で極力排除し、避けるべきである.
万一発生した場合、例えば暗号化、圧縮中に異常が発生した場合、この場合も予定のメッセージを返して、呼び出し元に「サーバーが間違っています.お客様は店の二ちゃんに連絡してください」と伝えるしかありません.
あらかじめ定義されたメッセージは、Handlerチェーンの構成に従って自動的に生成され、コードは以下のようになります.
private static byte[] outWayErrorMessage = null;
outWayErrorMessage = buildResponseErrorMessage(handerList,"POST_ERR","      ,        ");