RM-BLOG

IT系技術職のおっさんがIT技術とかライブとか日常とか雑多に語るブログです。* 本ブログに書かれている内容は個人の意見・感想であり、特定の組織に属するものではありません。/All opinions are my own.*

【jquery】jqueryによる非同期通信(Ajax)の基本的な実装について(+ちょっとだけJava/Servlet)

jqueryによる非同期通信(Ajax)の基本的な実装サンプル


 

 


【もくじ】

1.基本的なこと
2.サンプル
3.実験結果




 1.基本的なこと

非同期通信の実装方法には、他にもいろいろあるみたいだが、
基本的には「$.ajaxという関数を使って実装する。
以下は実装例。


$.ajax({
	type: 'GET',
	url: "/ajaxtest/AjaxTestServlet",
	dataType: 'HTML',
	data: {
		mode: 1,
		sleep_time: 10
	}
}).done(function(data) {
	$("#test1").css("background-image","none");
	$("#test1").html(data);
});


「type」はサーバへのデータ送信方法で、GETとかPOSTとかを指定する。
↑の例はGET。
デフォルトはGETだそうだ。
「url」はサーバ側のリクエスト先URL。
↑の例は「ajaxtest」というWebアプリケーションの「AjaxTestServlet」というサーブレットを呼び出す。
「dataType」はサーバからのレスポンスの形式。
↑の例はHTML形式でレスポンスを受領する。
「data」はサーバに送信するパラメータ。
↑の例は「mode=1」「sleep_time=10」という値をサーバに送信する。
ここで送信した値はサーバ側(サーブレット側)でHttpServletRequest#getParameterで取得できる。

「done」は非同期通信完了後に実行される関数。
サーバのレスポンスを「data」という変数値予約語的に扱われている?のか?よくわからない)で受け取り、
ドキュメント内の特定のオブジェクトのCSSやDOMを操作する。
→「data」で特定の要素を書き換える

「非同期通信完了後に実行される関数」には、通信に成功した場合と失敗した場合とで種類がわかれているようだ。
成功した場合は「done」、失敗した場合は「fail」を使うらしい。
成功だの失敗だのの場合分けは実装する側としてはどう判断して切り分けてコーディングすればいいんだ?って思ったが、
なんかそのまま続けて書けばいいんだそうだ。
※ちなみに試してはいない

$.ajax({
  ...(省略)...

}).done(function(data){
}).fail(function(data){
})


参考URL:
http://semooh.jp/jquery/api/ajax/jQuery.ajax/options/
http://blog.toshimaru.net/jquery-ajaxdeferredajax/




 2.サンプル

実験用に以下のようなHTMLを用意する。

<!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">
	<meta HTTP-EQUIV='Cache-Control' CONTENT='no-cache'>
	<meta HTTP-EQUIV='Pragma' CONTENT='no-cache'>
	<meta HTTP-EQUIV='Expires' CONTENT='-1'>
	
	<title>ajax test</title>
	
	<style type="text/css">
		.divtest {
			font-weight:bold;
			background-image:url(gif-load.gif);
			background-repeat:no-repeat;
			background-position:center center;
			width:500px;
			height:100px;
		}
	</style>
	
	<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
	<script type="text/javascript">
	<!--
		$(document).ready(function() {
			
			// test1
			$.ajax({
				type: 'GET',
				url: "/ajaxtest/AjaxTestServlet",
				dataType: 'HTML',
				data: {
					mode: 1,
					sleep_time: 10
				}
			}).done(function(data) {
				$("#test1").css("background-image","none");
				$("#test1").html(data);
			});
			
			// test2
			$.ajax({
				type: 'POST',
				url: "/ajaxtest/AjaxTestServlet",
				dataType: 'HTML',
				data: {
					mode: 2,
					sleep_time: 20
				}
			}).done(function(data) {
				$("#test2").css("background-image","none");
				$("#test2").html(data);
			});

			// test3
			$.ajax({
				type: 'GET',
				url: "/ajaxtest/AjaxTestServlet",
				dataType: 'HTML',
				data: {
					mode: 3,
					sleep_time: 30
				}
			}).done(function(data) {
				$("#test3").css("background-image","none");
				$("#test3").html(data);
			});
			
		});
	// -->
	</script>
</head>
<body>
	※ここはテスト1(10秒)です。<br/>
	<div id="test1" class="divtest">
	<br/>
	</div>
	<p></p>
	※ここはテスト2(20秒)です。<br/>
	<div id="test2" class="divtest">
	<br/>
	</div>
	<p></p>
	※ここはテスト3(30秒)です。<br/>
	<div id="test3" class="divtest">
	<br/>
	</div>
	<p></p>
</body>
</html>


これ自体は本当にただの静的HTMLである。
透明な3つのdivタグ(それぞれidを振っており、test1、test2、test3としている)だけがあり、
背景画像に昨今よく見るグルグルマーク(以下)が設定されているだけの静的ページ。

 

20180119_neta_gif-load.gif
※ちなみにこのグルグルマークは以下URLのものを使用させていただきました
http://loadergenerator.com/

ただしページ読込と同時(=$(document).ready)に3つの非同期通信が始まり、
それぞれサーバ側の「/ajaxtest/AjaxTestServlet」を呼び出す。
リクエストパラメータ(data:の引数値)は3つとも微妙に違うが、
これは、3つの通信を同時に開始し、それぞれ違うタイミングで終わらせるため
まさしく「非同期通信」の実態を確認するためであり、
「mode」の値はリクエストを特定するため
「sleep_time」の値は指定した秒数、サーブレット側でThread#sleepして処理を停滞させるため
に使用する。
1つ目の「sleep_time」は「10」(=通信終了まで10秒かかる)
2つ目の「sleep_time」は「20」(=通信終了まで20秒かかる)
3つ目の「sleep_time」は「30」(=通信終了まで30秒かかる)
ようにパラメータを設定する。
つまり、上から順に10秒ごとに通信が終了していく様を実現させる。

今回の実験のようなケースは、
非常に簡単な文字列を、ほぼ静的に近い形で生成してレスポンスするだけで、
どんなに非同期通信したところで処理時間にほとんど差異が出ない(それぞれほぼ一瞬で終わる)ため、
「それぞれ別の処理時間で終わる」ことを強制的に実現させる目的でThread#sleepを用いる。

3つの非同期通信は、処理終了後(done関数で実現)、
それぞれ対応するdivタグの背景画像のグルグルマークをけし(background-image:none)、
サーバからのレスポンス文字列をHTML形式でdivタグ内に書き出す。
これにより、
「通信中はグルグルマークが出ているが、通信終了次第グルグルマークは消える」
ことを再現させる。
逆に言うとdone関数内で背景画像を書き換えないと、
通信終了してもずーっとグルルグルし続けることになる。
なんだかシュール。
TwitterFacebook等の華やかでモダンなWEBページでよく見るグルグルマークの表現は、
結局のところこうした背景画像の使い方(背景画像を動的に書き換えている)に過ぎず、
フタを開けてみると「なんだそんな程度のことか」って安心する。

非同期通信の実装は、コード上は1つ目~3つ目まで順番に書くが、
それぞれ処理の終了を待たずして先の方に書かれているコードが実行されていく。
なので、非常に厳密に言えば、3つの非同期通信は、開始時刻が微っ妙~~~~に違うのかもしれない。
(普通に考えて、一番後ろに書かれている3つ目の通信開始が一番遅い、気がする)
通信数が増えてきたり、サーバ側のリソース状況とかにも依存するかもしれないが、
実際やってみた感じ、少なくともこの程度ならほとんど意識しないレベルで動いた。
このコーディングと実際の挙動は、
UNIXコマンドでバックグラウンドプロセスを実行するのと感覚的に似ている。
(それぞれの処理の終了を待たずして先に進む)




続いてサーブレットである。
ただしこれは、上記のHTMLからajaxで呼び出されるためだけに存在している色が強く、
少なくとも本項では、全体からすると「オマケ」である。
(目的はajaxでサーバ処理を非同期実行することなので、サーブレットに拘る必要はあまりない)

import java.io.*;
import java.text.*;
import java.util.*;
import java.util.regex.*;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.*;

public class AjaxTestServlet extends HttpServlet {

	private static final DateFormat LOG_FORMAT = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss.SSS");
	
	private static final String PARAM_KEY_SUBMIT = "submit";
	private static final String PARAM_KEY_MODE = "mode";
	private static final String PARAM_KEY_SLEEP_TIME = "sleep_time";
	
	private static final String MODE1 = "1";
	private static final String MODE2 = "2";
	private static final String MODE3 = "3";
	
	private static final String ENCODING_UTF8 = "UTF-8";
	private static final String CONTENT_TYPE_HTML = "text/html";
	
	public AjaxTestServlet()
	{
	}

	public void doGet(HttpServletRequest httpservletrequest, HttpServletResponse httpservletresponse)
		throws ServletException, IOException
	{
		httpservletrequest.setAttribute(PARAM_KEY_SUBMIT , "GET");
		doProcess(httpservletrequest, httpservletresponse);
	}

	public void doPost(HttpServletRequest httpservletrequest, HttpServletResponse httpservletresponse)
		throws ServletException, IOException
	{
		httpservletrequest.setAttribute(PARAM_KEY_SUBMIT , "POST");
		doProcess(httpservletrequest, httpservletresponse);
	}

	private void doProcess(HttpServletRequest httpservletrequest, HttpServletResponse httpservletresponse)
		throws ServletException, IOException
	{
		printLog("★開始");
		try
		{
		
			String paramSubmit = (String)httpservletrequest.getAttribute(PARAM_KEY_SUBMIT);
			String paramMode = httpservletrequest.getParameter(PARAM_KEY_MODE);
			String paramSleep = httpservletrequest.getParameter(PARAM_KEY_SLEEP_TIME);
			long sleepTimeMs = 0;
			try {
				sleepTimeMs = Long.parseLong(paramSleep);
				if (sleepTimeMs < 0) {
					throw new Exception("Parameter is less than 0[" + paramSleep + "]");
				}
			} catch(Exception e) {
				throw new ServletException(e);
			}
			printLog("モード:" + paramMode + "、サーバへの送信:" + paramSubmit + "、スリープ時間:" + paramSleep);
			
			// レスポンスを設定
			httpservletresponse.setCharacterEncoding(ENCODING_UTF8);
			httpservletresponse.setContentType(CONTENT_TYPE_HTML);
			
			PrintWriter pw = httpservletresponse.getWriter();
			pw.write(makeResponseHtml(paramMode , paramSubmit , sleepTimeMs));
			pw.flush();
			

		}
		catch(ServletException servletexception)
		{
			throw servletexception;
		}
		printLog("★終了");
	}
	
	private static String makeResponseHtml(String mode,String howToSubmit,long sleepTimeMs) {
	
		StringBuilder response = new StringBuilder();
		
		String backgroundColor = "";
		if (mode.equals(MODE1)) {
			backgroundColor = "pink";
		} else if (mode.equals(MODE2)) {
			backgroundColor = "lightblue";
		} else if (mode.equals(MODE3)) {
			backgroundColor = "lightgreen";
		} else {
			backgroundColor = "lightyellow";
		}
		try {
			Thread.sleep(sleepTimeMs * 1000);
		} catch(Exception e) {
			//nothing to do
		}
		
		response.append("<span style=\"background-color:" + backgroundColor + ";\">");
		response.append("MODE=" + mode + "/サーバへの送信は" + howToSubmit + "<br/>");
		response.append(LOG_FORMAT.format(new Date()) + "に処理完了しました。<br/>");
		response.append("</span>");
		
		
		return response.toString();
	}


	private static void printLog(Object obj)
	{
		StringBuilder stringbuilder = new StringBuilder();
		stringbuilder.append(LOG_FORMAT.format(new Date()));
		stringbuilder.append(" ");
		if(obj != null)
			stringbuilder.append(obj.toString());
		System.out.println(stringbuilder.toString());
	}


ajaxで以下赤太字のように指定したパラメータ

$.ajax({
	type: 'GET',
	url: "/ajaxtest/AjaxTestServlet",
	dataType: 'HTML',
	data: {
		mode: 1,
		sleep_time: 10
	}
}).done(function(data) {
	$("#test1").css("background-image","none");
	$("#test1").html(data);
});


は、サーブレット側では

private static final String PARAM_KEY_MODE = "mode";
private static final String PARAM_KEY_SLEEP_TIME = "sleep_time";
...(略)...

String paramMode = httpservletrequest.getParameter(PARAM_KEY_MODE);
String paramSleep = httpservletrequest.getParameter(PARAM_KEY_SLEEP_TIME);

...(略)...


で受け取れる。
あとはJavaの範疇で好きなように使えばいい。
このサーブレットの実装では、
「mode」はレスポンスの文字列および背景色の制御に
「sleep_time」は処理を途中で指定秒数停滞させる目的で
それぞれ使用する。
いずれも関連するのはレスポンスの文字列を生成するメソッド「makeResponseHtml」内である。
なお、modeによって背景色を変えることに特別な意図はない
(通信終了したことをパッと見でわかるようにしておきたいから、程度)

一つポイントとして挙げるとすると、レスポンスの返し方である。
HttpServletResponse#getWriterでPrintWriterを取得し、
それにレスポンスの文字列を書き込んでいく。
受け取る側(上記の静的HTML)では、レスポンスの受領形式を「HTML」としていたため、
文字列自体もHTML形式で編集して生成する。
↑のサーブレットの実装ではspanタグで囲っただけの簡単な文字列編集である。

ajaxでレスポンスを受け取ることができるのは調べてわかっていたが、
サーバ側で具体的にどうやってレスポンスを設定すればajaxで受け取れるのか?
が、ググっても見つからなかったので一応補足する。
(まあレスポンスの設定をするにあたってこれ以外の方法思いつかないが…)




 3.実験結果

上記諸々用意したうえで、warとかに固めてwebアプリとして配備し、
ローカルホストのドメインから静的HTMLにアクセスする。
アクセスした直後から非同期通信が始まるので、ブラウザでURL叩いたら後はほったらかしでいい。

上述した通り、上から順に10秒ごとに通信が終了していくわけだが、
とりあえず順を追って画像のハードコピーを貼っていく。

(1)アクセス直後

 

20180119_neta_experience_01.jpg

(2)1つ目の通信終了

 

20180119_neta_experience_02.jpg

(3)2つ目の通信終了

 

20180119_neta_experience_03.jpg

(4)3つ目の通信終了

 

20180119_neta_experience_04.jpg


通信が終了する度、グルグルマークが消えて、処理終了のメッセージに動的に書き換わる仕組み。
(1)の10秒後に(2)、(2)の10秒後に(3)...というように、
リクエストパラメータのsleep_timeに応じて終了タイミングが違っているのが確認できる。
通信終了後に書き換わるメッセージも、
(2)が2018/1/19 16:03:54
(3)が2018/1/19 16:04:04
(4)が2018/1/19 16:04:14
というように、それぞれ10秒ずれていることが確認できる。
(その他の処理により、ミリ秒以下を加味すると10秒以上間が空いているが、ほぼ誤差のレベル)




「非同期通信」っていう言葉だけ聞くと何か敷居の高い技術のような印象があったが、
いざ作ってしまえばそこまで難しくはない。
豊かなWebページ構築の手法の一つとして覚えておいて損はないだろう。