Ajadで非遷移ポスト

そんな訳で、今回はついに(?)非遷移ポストに挑戦してみたいと思います。処理の流れ的にはこんな感じ。

  • ボタンクリック時にアプレットのメソッドを実行
  • formタグのaction要素からポスト先のURLを、テキストボックスからポストデータを取得して、HttpURLConnectionを利用してポスト
  • サーバ側では、strutsのActionでポストを受けて、適当にポストデータをリクエストスコープに保存してJSPフォワード
  • JSPでは、strutsのbean:writeタグを使って適当にActionがリクエストスコープに保存したデータをレンダリング
  • アプレット側のDOM操作で、JSPが出力したHTMLをdivタグ内に表示

オライリーのAjaxデザインパターン的に言うと、こういう風なサーバ側がレンダリングしたHTMLを受け取ってそれを直接DOM操作で何かしらのタグのinnerHTMLにセットするのはHtmlMessageパターンと言うらしいですね。

それはさておき、今回のHTMLはこんな感じです。

<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib uri="http://struts.apache.org/tags-logic"  prefix="logic"  %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html lang="ja">
	<head>
		<meta http-equiv="Content-Style-Type" content="text/css" />
		<meta http-equiv="Content-Script-Type" content="text/javascript" />
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
		<script type="text/javascript">
			function getApplet() {
				return document.getElementById("applet");
			}
		</script>
		<title>トップ</title>
	</head>
	<body>
		<!-- アプレット読込み -->
		<applet id="applet" mayscript code="org/ajad/sample/web/applet/TopApplet" width="0" height="0"></applet>
		
		<!-- 文書 -->
		<ul>
			<li>
				非遷移ポスト<br />
				<form id="htmlMessageForm" action="<%= request.getContextPath() %>/htmlMessage.do">
					テキスト入力1 : <input type="text" id="htmlMessageText1" size="20" /><p />
					テキスト入力2 : <input type="text" id="htmlMessageText2" size="20" /><p />
					<input type="button" id="htmlMessageButton" value="送信" onclick="getApplet().onClickHtmlMessageButton();" />					
				</form>
				<p />
				以下にPOST先のHTMLを読込みます。
				<div id="htmlMessageTarget"></div>
			</li>
		</ul>
	</body>
</html>

もはやトクダンかわったところは無いですね(?)。で、それを受けるstruts-configの設定はこんな感じです。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts-config PUBLIC
        "-//Apache Software Foundation//DTD Struts Configuration 1.3//EN"
        "http://struts.apache.org/dtds/struts-config_1_3.dtd">
<struts-config>
	<form-beans>
		<form-bean name="htmlMessageForm" type="org.ajad.sample.web.form.HtmlMessageForm" />
	</form-beans>
	
	<action-mappings>
		<action path="/htmlMessage" type="org.ajad.sample.web.action.HtmlMessageAction" name="htmlMessageForm">
			<forward name="success" path="/pages/htmlMessage.jsp" />
		</action>
	</action-mappings>		
</struts-config>

トクダン何の変哲も無い普通のstruts-configですね。ちなみに、ActionFormも何の変哲も無さすぎて省略しようかと思ったのですが、こんな感じです。

package org.ajad.sample.web.form;

import org.apache.struts.action.ActionForm;

public class HtmlMessageForm extends ActionForm {
	private static final long serialVersionUID = 277434096628351935L;
	private String htmlMessageText1_;	
	private String htmlMessageText2_;

	public String getHtmlMessageText1() { return htmlMessageText1_;}
	public void setHtmlMessageText1(String htmlMessageText1) { htmlMessageText1_ = htmlMessageText1;}

	public String getHtmlMessageText2() {return htmlMessageText2_;}
	public void setHtmlMessageText2(String htmlMessageText2) {htmlMessageText2_ = htmlMessageText2;}
}

Actionも特に何の変哲も無いですが、こんな感じです。

package org.ajad.sample.web.action;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.ajad.sample.web.form.HtmlMessageForm;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

public class HtmlMessageAction extends Action {
	@Override
	public ActionForward execute(ActionMapping mapping, ActionForm form, 
			HttpServletRequest request, HttpServletResponse response) throws Exception {
		HtmlMessageForm htmlMessageForm = (HtmlMessageForm)form;
		request.setAttribute("htmlMessage1", htmlMessageForm.getHtmlMessageText1());
		request.setAttribute("htmlMessage2", htmlMessageForm.getHtmlMessageText2());
		return mapping.findForward("success");
	}
}

で、これらをつなぐ肝心のアプレットですが、こんな感じです。

package org.ajad.sample.web.applet;

import java.applet.Applet;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.HttpURLConnection;
import java.net.URL;
import netscape.javascript.JSObject;

public class TopApplet extends Applet {
	private static final long serialVersionUID = 3871816411119859125L;

	@Override
	public void init() {
		// NOP.
	}
	
	public void onClickHtmlMessageButton() {		
		// ポスト先のURLを取得
		JSObject win = JSObject.getWindow(this);
		JSObject document = (JSObject)win.getMember("document");
		JSObject link = (JSObject)document.call("getElementById", new Object[] {"htmlMessageForm"});
		String postUrl = (String)link.getMember("action");
		System.out.println("value = " + postUrl);

		// ポスト用のデータ1を取得
		String postPropertyName1 = "htmlMessageText1";
		JSObject text = (JSObject)document.call("getElementById", new Object[] {postPropertyName1});
		String postData1 = (String)text.getMember("value");
		System.out.println("value = " + postData1);

		// ポスト用のデータ2を取得
		String postPropertyName2 = "htmlMessageText2";
		JSObject text2 = (JSObject)document.call("getElementById", new Object[] {postPropertyName2});
		String postData2 = (String)text2.getMember("value");
		System.out.println("value = " + postData2);

		// ポスト実行
		StringBuilder builder = new StringBuilder();
		OutputStream os = null;
		PrintStream ps = null;
		InputStream is = null;
		BufferedReader reader = null;
		try {
			// URL接続取得
			URL url = new URL(postUrl);			
			HttpURLConnection con = (HttpURLConnection)url.openConnection();
			con.setDoOutput(true);
			con.setRequestMethod("POST");
			con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
			
			// POST実行
			builder.append(postPropertyName1);
			builder.append("=");
			builder.append(postData1);
			builder.append("&");
			builder.append(postPropertyName2);
			builder.append("=");
			builder.append(postData2);
			os = con.getOutputStream();
			ps = new PrintStream(os, false, "UTF-8");
			ps.print(builder.toString());
			
			// POST結果取得
			is = con.getInputStream();
			reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
			builder.setLength(0);
			while (true) {
				String line = reader.readLine();
				if (line == null) {
					break;
				}
				builder.append(line);
			}
		} catch (Exception e) {
			throw new IllegalStateException(e);
		} finally {
			// 各種ストリームのクローズ
			if (ps != null) {
				ps.close();
			}

			if (os != null) {				
				try {
					os.close();					
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			
			if (reader != null) {
				try {
					reader.close();					
				} catch (IOException e) {
					e.printStackTrace();
				}				
			}
			
			if (is != null) {
				try {
					is.close();					
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		
		// 取得したHTMLの出力
		JSObject div = (JSObject)document.call("getElementById", new Object[] {"htmlMessageTarget"});
		div.setMember("innerHTML", builder.toString());
	}
}

あいかわらずtry〜catchがちょっとウザイ感じですが、まぁこの辺は例によって共通化すればいかようにでもコードをすっきりさせることはできるでしょう。HttpURLConnectionを使えば、サーバ側で例外が発生してHTTPのステータスコード500がかえって来た場合はアプレット側でもきちんと例外が発生する等、わりときめ細かな例外処理ができそうです。システム例外はこれで対応できそうなので、後はStruts Validatorのバリデーションエラーを拾えるとかなりいい感じの非遷移ポストになるんじゃないでしょうか。