サーブレット3.0ノートの非同期要求Conetプッシュロングポーリング編

14008 ワード

サーブレット3.0ノートの非同期要求Conetプッシュロングポーリング編
Conetのもう一つの形式はロングポーリング(long polling)で、クライアントはサーバと永続的な接続を確立し、サーバ側にデータが送信されるまで、サーバ側は切断され、クライアントはプッシュされたデータを処理し、再び永続的な接続を開始し、ループします.
ストリーム(Streaming)との違いは、主に1回の長接続でサーバ側が1回だけプッシュし、接続を切断することです.
その実装形態は,AJAX長ポーリングとJAVASCRITPポーリングの2種類に大別できる.
  • AJAX方式要求長ポーリングサーバー側は元のデータを返すことができ、あるいはフォーマットされたJSON、XML、JAVASCRITT情報を返すことができ、クライアントはより多くの処理の自由を持つことができる.形式的には従来のAJAX GET方式とほぼ同じであるが、次回のポーリングは前回のポーリングデータが処理済みに戻るまで待つ必要がある.ここではクライアントの小さな例を示します:
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>jQuery 1.5 with long poll</title>
    </head>
    <body>
    <div id="tip"></div>
    <script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.5.min.js"></script>
    <script type="text/javascript">
    $(function (){
    function log(resp){
    $("#tip").html("<b>" + resp + "</b>");
    }

    log("loading");

    //
    $.ajaxSetup({ cache: false });

    function initGet(){
    $.get("getNextTime")
    .success(function(resp){
    log(resp);
    }).error(function(){
    log("ERROR!");
    }).done(initGet);
    }

    initGet();
    });
    </script>
    </body>
    </html>
    jQuery 1.5に基づいて、イベントチェーンは、従来よりも簡潔で明瞭で、特にdoneメソッドで再び自身を呼び出すのは素晴らしいです.サーバ側は簡単で、1秒ごとに現在のシステム時間を出力します:
    /**
    * ,
    *
    * @author yongboy
    * @date 2011-2-11
    * @version 1.0
    */
    @WebServlet("/getNextTime")
    public class GetNextTimeServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request,
    HttpServletResponse response) throws ServletException, IOException {
    try {
    Thread.sleep(1000L);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }

    response.setContentType("text/plain");
    PrintWriter out = response.getWriter();

    out.print(DateFormatUtils.format(System.currentTimeMillis(),
    "yyyy-MM-dd HH:mm:ss SSS"));
    out.flush();

    out.close();
    }
    }
  • JAVASCIPTタグポーリング(Script tag long polling)が参照するJAVASSIRIPTスクリプトファイルはドメインをまたぐことができ、JSONPを加えるとさらに強力になります.ここでは、上記のような機能を実装するためのプレゼンテーションを示します.クライアント・インベントリ:
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>jQuery 1.5 with JSONP FOR Comet</title>
    </head>
    <body>
    <div id="tip"></div>
    <script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.5.min.js"></script>
    <script type="text/javascript">
    $(function (){
    function log(resp){
    $("#tip").html("<b>" resp "</b>");
    }

    log("loading");

    function jsonp(){
    $.getJSON('http://192.168.0.99:8080/servlet3/getNextTime2?callback=?').success(function(resp){
    log("now time : " resp);
    }).done(jsonp);

    //
    /*
    $.getJSON('http://192.168.0.99:8080/servlet3/getNextTime2?callback=?',function(date){
    log(date);
    }).done(jsonp);
    */
    }

    jsonp();
    });
    </script>
    </body>
    </html>
    サーバ・エンド・インベントリ:
    /**
    * JSONP ,
    * @author yongboy
    * @date 2011-2-11
    * @version 1.0
    */
    @WebServlet("/getNextTime2")
    public class GetNextTimeServlet2 extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request,
    HttpServletResponse response) throws ServletException, IOException {
    try {
    Thread.sleep(1000L);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }

    String callback = request.getParameter("callback");

    if(StringUtils.isBlank(callback)){
    callback = "showResult";
    }

    PrintWriter out = response.getWriter();

    StringBuilder resultBuilder = new StringBuilder();
    resultBuilder
    .append(callback)
    .append("('")
    .append(DateFormatUtils.format(System.currentTimeMillis(), "yyyy-MM-dd HH:mm:ss SSS"))
    .append("');");

    out.print(resultBuilder);
    out.flush();

    out.close();
    }
    }
    要求されるたびに、HTMLヘッダにJSネットワーク・アドレス(ドメイン間実装)が生成されます.
    http://192.168.0.99:8080/servlet3/getNextTime2?callback=jQuery150832738454006945_1297761629067&_=1297761631777
    指定する必要はありません.jqueryはランダムな関数名を自動的に生成します.上記アドレスサーバ側に新たなデータ出力がある場合に生成されるコールバックJS関数を要求する.
    jQuery150832738454006945_1297761629067('2011-02-15 17:20:33 921');
    この点は下のスクリーンショットから見ることができます.ただし、長い接続の場合、ブラウザは現在のJSファイルがまだロードされていないと判断し、ロード中であることを表示します.

  • 上のJSONPの例は簡単すぎて、サーバーとクライアントの接続待ち時間は1秒ぐらいしかありません.実際の環境では、タイムアウト時間を設定する必要があります.
    これまで、クライアントにタイムアウト時間を設定する必要があると言われていたかもしれませんが、指定した期限内にサーバにデータが戻ってこない場合は、サーバ側プログラムに通知し、接続を切断し、自身が再び新しい接続要求を開始する必要があります.
    しかし、サーブレット3.0の非同期接続の仕様では、それほど気を遣う必要はありません.スワップして、サーバー側にクライアントがタイムアウトしたことを知らせて、急いで再接続しましょう.
    前のいくつかのブログでは、偽のリアルシーンを実証し、ブログの主がブログを追加し、端末ユーザーにアクティブにストリーム形式でプッシュします.ここではJSONPドメイン間ポーリング接続形式を採用する.
    クライアント:
    <html>
    <head>
    <title>Comet JSONP %u63A8%u9001%u6D4B%u8BD5</title>
    <meta http-equiv="X-UA-Compatible" content="IE=8" />
    <meta http-equiv="content-type" content="text/html;charset=UTF-8"/>
    <meta name="author" content="[email protected]"/>
    <meta name="keywords" content="servlet3, comet, ajax"/>
    <meta name="description" content=""/>
    <link type="text/css" rel="stylesheet" href="css/main.css"/>
    <script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.5.min.js"></script>
    <script type="text/javascript">
    String.prototype.template=function(){
    var args=arguments;
    return this.replace(/\{(\d )\}/g, function(m, i){
    return args[i];
    });
    }
    var html = '<div class="logDiv">'
    '<div class="contentDiv">{0}</div>'
    '<div class="tipDiv">last date : {1}</div>'
    '<div class="clear">&nbsp;</div>'
    '</div>';

    function showContent(json) {
    $("#showDiv").prepend(html.template(json.content, json.date));
    }
    function initJsonp(){
    $.getJSON('http://192.168.0.99/servlet3/getjsonpnew?callback=?').success(function(json){
    if(json.state == 1){
    showContent(json);
    }else{
    initJsonp();
    return;
    }
    }).done(initJsonp)
    .error(function(){
    alert("error!");
    });
    }
    $(function (){
    initJsonp();
    });
    </script>
    </head>
    <body style="margin: 0; overflow: hidden">
    <div id="showDiv" class="inputStyle">loading ...</div>
    </body>
    </html>

    サーバ側は、長いポーリング接続要求を受信します.
    /**
    * JSONP
    *
    * @author yongboy
    * @date 2011-2-17
    * @version 1.0
    */
    @WebServlet(urlPatterns = "/getjsonpnew", asyncSupported = true)
    public class GetNewJsonpBlogPosts extends HttpServlet {
    private static final long serialVersionUID = 565698895565656L;
    private static final Log log = LogFactory.getLog(GetNewJsonpBlogPosts.class);

    protected void doGet(HttpServletRequest request,
    HttpServletResponse response) throws ServletException, IOException {
    response.setHeader("Cache-Control", "private");
    response.setHeader("Pragma", "no-cache");
    response.setHeader("Connection", "Keep-Alive");
    response.setHeader("Proxy-Connection", "Keep-Alive");
    response.setContentType("text/javascript;charset=UTF-8");
    response.setCharacterEncoding("UTF-8");

    String timeoutStr = request.getParameter("timeout");

    long timeout;

    if (StringUtils.isNumeric(timeoutStr)) {
    timeout = Long.parseLong(timeoutStr);
    } else {
    // 1
    timeout = 1L * 60L * 1000L;
    }

    log.info("new request ...");
    final HttpServletResponse finalResponse = response;
    final HttpServletRequest finalRequest = request;
    final AsyncContext ac = request.startAsync(request, finalResponse);
    //
    ac.setTimeout(timeout);
    ac.addListener(new AsyncListener() {
    public void onComplete(AsyncEvent event) throws IOException {
    log.info("onComplete Event!");
    NewBlogJsonpListener.ASYNC_AJAX_QUEUE.remove(ac);
    }

    public void onTimeout(AsyncEvent event) throws IOException {
    // , /blogpush,
    log.info("onTimeout Event!");

    //
    final PrintWriter writer = finalResponse.getWriter();

    String outString = finalRequest.getParameter("callback")
    + "({state:0,error:'timeout is now'});";
    writer.println(outString);
    writer.flush();
    writer.close();

    NewBlogJsonpListener.ASYNC_AJAX_QUEUE.remove(ac);
    }

    public void onError(AsyncEvent event) throws IOException {
    log.info("onError Event!");
    NewBlogJsonpListener.ASYNC_AJAX_QUEUE.remove(ac);
    }

    public void onStartAsync(AsyncEvent event) throws IOException {
    log.info("onStartAsync Event!");
    }
    });

    NewBlogJsonpListener.ASYNC_AJAX_QUEUE.add(ac);
    }
    }

    ブログデータが到着すると、登録されているクライアントに通知されるのは明らかです.
    データをプッシュした後、現在の非同期コンテキストcomplete()関数を呼び出す必要があります.
    /**
    *
    *
    * @author yongboy
    * @date 2011-2-17
    * @version 1.0
    */
    @WebListener
    public class NewBlogJsonpListener implements ServletContextListener {
    private static final Log log = LogFactory.getLog(NewBlogListener.class);
    public static final BlockingQueue BLOG_QUEUE = new LinkedBlockingQueue ();
    public static final Queue ASYNC_AJAX_QUEUE = new ConcurrentLinkedQueue ();

    public void contextDestroyed(ServletContextEvent arg0) {
    log.info("context is destroyed!");
    }

    public void contextInitialized(ServletContextEvent servletContextEvent) {
    log.info("context is initialized!");
    //
    new Thread(runnable).start();
    }

    private Runnable runnable = new Runnable() {
    public void run() {
    boolean isDone = true;

    while (isDone) {
    if (!BLOG_QUEUE.isEmpty()) {
    try {
    log.info("ASYNC_AJAX_QUEUE size : "
    + ASYNC_AJAX_QUEUE.size());
    MicBlog blog = BLOG_QUEUE.take();

    if (ASYNC_AJAX_QUEUE.isEmpty()) {
    continue;
    }

    String targetJSON = buildJsonString(blog);

    for (AsyncContext context : ASYNC_AJAX_QUEUE) {
    if (context == null) {
    log.info("the current ASYNC_AJAX_QUEUE is null now !");
    continue;
    }
    log.info(context.toString());
    PrintWriter out = context.getResponse().getWriter();

    if (out == null) {
    log.info("the current ASYNC_AJAX_QUEUE's PrintWriter is null !");
    continue;
    }

    out.println(context.getRequest().getParameter(
    "callback")
    + "(" + targetJSON + ");");
    out.flush();

    // ,
    context.complete();
    }
    } catch (Exception e) {
    e.printStackTrace();
    isDone = false;
    }
    }
    }
    }
    };

    private static String buildJsonString(MicBlog blog) {
    Map info = new HashMap ();
    info.put("state", 1);
    info.put("content", blog.getContent());
    info.put("date",
    DateFormatUtils.format(blog.getPubDate(), "HH:mm:ss SSS"));

    JSONObject jsonObject = JSONObject.fromObject(info);

    return jsonObject.toString();
    }
    }

    長い接続のタイムアウト時間は、「心拍数」と適切に形容され、両端を連動させ、環境に応じて具体的な値を設定する必要があります.
    JSONP方式はポーリングでもロングポーリングでも、ターゲットプログラムを第三者サイトに容易に融合させることができ、現在の個人用ページやブログなど多くの小さなストラップもこのような形式でドメイン間でデータを取得することが多い.