-Laszlo Tutorial-
ここでは具体的にOpenLaszloでWebアプリケーションを作成する際に必要になってくるであろう諸問題への解決策を筆者の理解した範囲で
紹介します。OpenLaszloの基礎についてはLaszlo Japan等、他に素晴らしいサイトがありますのでそちらを参考にして下さいな。
1. LaszloからJavaのClassを呼び出す
Javaのサーバーサイドで使用するものと言えばServletなのだが、Laszloではというタグを使用することで呼び出すらしい。
Servletの場合にはHttpServletを継承したクラスしか呼べなかったのだが、Laszloではその必要がない。そこで問題になるのがセキュリティだ。
全てのクラスを呼べたとしたらこれは問題な訳で。そこで開発者が考えたのがタグ。例を出せば以下の通り。
<security>
<allow>
<pattern>hoge.Hoge</pattern>
</allow>
</security>
つまりこれはロードした段階で呼べるクラスを限定している(ここではhogeパッケージのHogeクラス)ということですな。
- javarpcタグの属性属性 -
とりあえず私の英語力で理解できて、且つ使いそうなものの説明。
・classname
必須属性(そりゃそうだ)。呼び出すクラスをここで宣言します。
・scope
クラスの有効期限みたいなもので。session, webapp, noneの三種類から選べます。
sessionはユーザのセッションが有効な限り同じClassを使うということですね。
例えば以下のようなクラスが定義されているとします。
package hoge
class Hoge {
private int num;
public void aaa() {
System.out.println("num --> " + num);
num++;
}
}
で、scopeがsessionだと、Classを呼び出す度にnumの値が増えていくという訳。但しセッションが切れたら0から再スタート。
webappにすれば誰がアクセスしても同じものが呼ばれる訳ですから、サーバーを止めない限り増え続けるという訳。
訪問回数を数えるカウンターとかに使えそうですよね。
・createargs
Classのインスタンスに渡す引数ですな。「[」と「]」でくくられたものをインスタンスの引数に使用するみたい。例えば以下の記述。
<javarpc name="aaa" scope="webapp" autoload="true" classname="hoge.Hoge" secure="false" createargs="[1, 'hoge']">
これはhoge.Hogeクラスのインスタンスを作成する際にint型で1, "hoge"という引数を渡すということ。対応するコンストラクタを
Classに定義してあげないとエラーになるようです。上の例なら以下のようなコンストラクタを作りましょう。
public Hoge(int num, String str)
・autoload
インスタンスを画面のロード時に作成するかどうかというフラグ。デフォルトではtrue。インスタンスを作成してあげないと
「constructor is not created」みたいなエラーが出ました。画面の描写を早くしたいという場合に使うのかな。
・secure
httpsプロトコルで通信するかどうかのフラグ。昨今のネット事情を考えればtrueにしたいところですが、とりあえずはfalseにしておくのが無難か。
デフォルトではfalseです。
・secureport
httpsで通信する場合に使用するポート番号。普通は443番ポートじゃないかな。
以上、JavaRPCの説明でしたがJavaでいうところのRMIっぽいので個人的にはあまり使いたいとは思いません(笑)。
後で出てくるJSPを介して実行するほうが好きです(あくまで個人的な見解です)。
2. MySQLとの連携(RPC編)
WebアプリっていうぐらいだからDBも使用したい訳で。上記の通りJavaのクラスが呼べるので
そこからDBへ接続するというのがやり方としては普通なのかなと。UI部分と切り離す為という意味でもそれでいいかと。
とりあえずDBとしてはMySQLを選択することにします。とりあえずMySQLのページからmysql-jdbc-connector-jを
ダウンロードしてきましょう。解凍してできたフォルダ内の「mysql-connector-java-3.1.8-bin.jar」というファイルを
${LASZLO_HOME}/WEB-INF/lib以下に配置します。Eclipseで使用する場合にはプロジェクトのBuildPathにも入れておいて
あげましょう。で、次に呼び出すClassを作成しましょう。かなり手抜きですが以下のようなクラスを作成して
${LASZLO_HOME}/WEB-INF/src/hogeに配置します(Eclipse + TomcatPluginの環境ではsrcが自動的に作成されます。多謝)。
package hoge;
import java.sql.*;
public class Hoge {
private static final long serialVersionUID = 1L;
public Hoge() {
System.out.println("Constructor is called.");
}
public void test() {
try {
Class.forName("com.mysql.jdbc.Driver");
String url =
"jdbc:mysql://localhost/mysql?"+
"user=root&password=<PASSWORD>"+
"&useUnicode=true&characterEncoding=<ENCODE>";
Connection conn = DriverManager.getConnection(url);
Statement stmt = conn.createStatement();
ResultSet result = stmt.executeQuery("SELECT user, host, password FROM user");
while(result.next()) {
System.out.println("user : "+result.getString("user"));
System.out.println("host : "+result.getString("host"));
System.out.println("password : "+result.getString("password"));
}
result.close();
stmt.close();
conn.close();
}
catch(Exception ex) {
ex.printStackTrace();
}
}
}
※<ENCODE>, <PASSWORD>は適宜読み替えて下さい。
作成したらコンパイルして${LASZLO_HOME}/WEB-INF/classes/hoge以下に配置します(この辺りもEclipse + TomcatPlugin環境だとデフォルトです)。
で、以下のようなtest.lzxを作成して${LASZLO_HOME}/my-apps以下に配置します。
<canvas debug="true">
<debug x="0" y="0" height="275"/>
<security>
<allow>
<pattern>^hoge.Hoge</pattern>
</allow>
</security>
<javarpc name="ce" scope="session" autoload="true" classname="hoge.Hoge" secure="false">
<method event="onerror" args="err">
Debug.write("----------");
Debug.write("onerror:", err);
</method>
<method event="onload">
Debug.write("----------");
Debug.write(this.classname);
Debug.write(this.proxy);
</method>
</javarpc>
<window closeable="true" resizable="true">
<button text="Connect!" onclick="this.test.invoke()">
<remotecall funcname="test" remotecontext="$once{canvas.ce}">
<method event="ondata">
Debug.write("----------");
Debug.write("method:", this.name);
</method>
</remotecall>
</button>
</window>
</canvas>
http://localhost:8080/laszlo/my-apps/test.lzxにアクセスてみましょう。デバックウィンドウにごちゃごちゃと出てくると
思います。Tomcatを立ち上げているコンソールを見れば「Constructor is called.」の文字が出ていることでしょう。これは
先程の説明の通り、javarpcタグのautoload属性をtrueにしているのでページが読み込まれると同時にクラスの初期化を行った
証拠ですね。「Connect!」というラベルのボタンがあると思いますので勇気を持ってクリックしてみましょう。Tomcatの
コンソールにMySQLに登録してあるユーザのID, ホスト、パスワードが表示されれば成功です。ブラウザのステータスバーが
「localhostを待っています…」というメッセージで固まっている点が気になりましたがとりあえずこれでDBへの接続も成功
しました。後は煮るなり焼くなり好きにできますね♪…が、少し問題が。Tomcatを落とすと分かりますが、NotSerializableException?
という警告が連発します。つまり、hoge.Hogeの同期が取れる保証がないぞゴルァ!ということでしょう(スレッドは苦手なんで自信なし。スマソ)。
そこでHogeにSerializableをimplementsして試したところ、今度は「desirializeの保証がないぞゴルァ!」とか言われます(しかも今度は
ERROR扱いです)。まぁ一応接続はできたのでキニシナイ!と言ってしまえばいいのですが、業務レベルではそうもいきませんよねぇ…。
回避策が分かり次第報告していきます。
(追記)
解決策判明。JavaDocumentを参照したところ、Serializeインターフェースをimplementして
private void writeObject(java.io.ObjectOutputStream out) throws IOException
と
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException
というシグニチャを持つメソッドを書いてあげればよいみたいです。詳しくはJavaDocumentを参照して下さい。わたくし厨房にはちと
ピンとこない内容だったのでヘタな説明はやめておきます。ご容赦。
3. MySQLとの連携(JSP編)
いろいろドキュメントを読んでいたところ、DB接続に関するページがあった…。上記のやり方でもいいのですがjspと連携して
DBを扱う方法が載っていたので紹介します。
1. JSPを使って動的にデータファイルを作成する。
先程のデータを取得してdatasetとして使用するためにJSPを一つ作る必要があります。JSPって何?という方はぐぐって下さい。
上記のデータを問い合わせてdatasetを作成するJSPは以下の通り。
<%@ page contentType="text/html; charset=UTF-8" import="java.sql.*"%>
<messagelist>
<%
Connection conn = null;
try {
System.out.println("Load Driver");
Class.forName("com.mysql.jdbc.Driver");
System.out.println("Connect DB");
String url =
"jdbc:mysql://localhost/mysql?"+
"user=root&password=<PASSWORD>"+
"&useUnicode=true&characterEncoding=<ENCODE>";
conn = DriverManager.getConnection(url);
System.out.println("Create Statement");
Statement stmt = conn.createStatement();
ResultSet result = stmt.executeQuery("SELECT user, host, password FROM user");
while(result.next()) {
%>
<item user="<%=result.getString("user")%>"
host="<%=result.getString("host")%>"
password="<%=result.getString("password")%>"/>
<%
}
result.close();
stmt.close();
}
finally {
try {
conn.close();
}
catch (SQLException e) {}
}
%>
</messagelist>
※<ENCODE>, <PASSWORD>は適宜読み替えて下さい。
2. データを表示するlzxファイル(connecttest.lzx)を作成する。
<canvas>
<dataset name="boarddata" src="boarddata.jsp" autorequest="true" type="http"/>
<grid width="100%" height="100%" multiselect="false" datapath="boarddata:/messagelist" contentdatapath="item">
<gridtext editable="false" datapath="@user" datatype="number" width="50" text="No" resizable="false" xoffset="10"/>
<gridtext editable="false" datapath="@host" width="300" text="Title"/>
<gridtext editable="false" datapath="@password" width="75" resizable="false" text="Date"/>
</grid>
</canvas>
connecttest.lzxを読み込めばデータが挿入されている表が出てくると思います。
4. サーバとのデータの受け渡し
Webアプリケーションとしては大事な要素です。ここではJavaRPC経由でデータをサーバに送信する手順を紹介します。
まず、先程紹介したhoge.Hogeクラスに以下のようなシグニチャを持つメソッドを追加します。
public void hoge(String hoge) {
System.out.println("hoge : " + hoge);
}
そして、以下のようなsenddata.lzxファイルを用意しましょう。
<?xml version="1.0" encoding="UTF-8"?>
<canvas debug="true">
<debug x="0" y="0" height="275"/>
<security>
<allow>
<pattern>^hoge.Hoge</pattern>
</allow>
</security>
<javarpc name="ce" scope="session" autoload="true" classname="hoge.Hoge" secure="false">
<method event="onerror" args="err">
Debug.write("----------");
Debug.write("onerror:", err);
</method>
<method event="onload">
Debug.write("----------");
Debug.write(this.classname);
Debug.write(this.proxy);
</method>
</javarpc>
<window closeable="true" resizable="true">
<button text="Connect!" onclick="this.hoge.invoke()">
<remotecall funcname="hoge" remotecontext="$once{canvas.ce}">
<param value="${txt.text}"/>
<method event="ondata">
Debug.write(txt.text);
Debug.write("----------");
Debug.write("method:", this.name);
</method>
</remotecall>
</button>
<edittext id="txt" multiline="true" height="100"/>
<simplelayout axis="y"/>
</window>
</canvas>
このファイルにアクセスしてテキストエリアに適当にデータを入れ、「Connect!」ボタンを押すとコンソールに送信データが
表示されると思います。尚、パラメータはparamタグを書いた順番に送られますのでご注意を。
で、これだけで終わればいいのですが、日本語のデータを送信すると見事に文字化けが発生します。恐らくLZXServlet内でsetEncodingを
ちゃんと呼んでいないからだと思われますが…。回避策としてはFilterというものを使用します。あるServlet(ここではLZXServlet)が
呼ばれる前に処理をはさみたい時に使用するServlet側の技術です。ログとか取る場合にも使えるものなので覚えておいて損はないかと。
1. Filterクラスを作成する
${LASZLO_HOME}/WEB-INF/src/hoge以下に次のようなクラスを作成します。
package hoge;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class EncodingFilter implements Filter {
public void init(FilterConfig arg0) throws ServletException {
//System.out.println("EncodingFilter initialized");
}
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
req.setCharacterEncoding("UTF-8");
chain.doFilter(req, res);
}
public void destroy() {
}
}
2. web.xmlにFilterの定義を書く
<filter>
<filter-name>EncodingFilter</filter-name>
<filter-class>hoge.EncodingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>EncodingFilter</filter-name>
<url-pattern>*.lzx</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>EncodingFilter</filter-name>
<url-pattern>*.lzo</url-pattern>
</filter-mapping>
*.lzx, *.lzoというのはLZXServletがマッピングされているURLパターンですね。これでTomcatを再起動すれば
めでたく日本語も正常に処理されます。全部デフォルトでUTF-8にしてくれよ…と思うのは私だけでしょうか?
因みに、src属性にjspを指定したdatasetを用いたデータのやりとりも可能なのですが、複数行のデータを送信する
際にバグが生じます(なんか途中でデータが切れてるっぽい)。現状では素直にJavaRPCを使用したほうが無難の
ようですね。この辺りも次期バージョンの3.1に期待したいところです。
5. RequestTypeについて
どうせローカル環境でテストしているのだから「View Source」はいらないし、生成したswfファイルとか考える前にガンガン作りたいじゃないですか。
ということで自分の作成したlzxファイルのみをブラウザに表示しようと思って調べてみた。なんかhoge.lzxの後にRequestTypeなるものを入れて
あげればよいらしい。例えばhttp://localhost:8080/laszlo/my-apps/hoge.lzx?lzt=swfとしてあげれば上記の御要望通りswfファイルのみが表示
されます。他のパラメタは以下の通り。(かなり適当にドキュメントを翻訳しているので大事なものが抜けている可能性があります。ドキュメントの場所は
「Guide to Deploying Laszlo Applications」→「III. Server Integration」→「4. LPS Request Types」です)
・lzt=html
lzxファイルをHTMLページにラップして表示すると。で、そのHTMLページがswfファイルを表示してくれるらしい。
canvasタグにtitleやbgcolor属性を指定した場合、それらが反映されると。で、swfファイルの返したHTMLページは
Laszloアプリを利用する為にJavaScriptを使用するらしい。
・lzt=html-object
基本的にはlzt=htmlと同じみたい。IEでは変更を伝える(イベントが通知されるってこと?IEあんまり使ってないのでシラネ)互換性がないとか。
・lzt=embed
HTMLページに挿入する形にする時に使用するのだと思われ。
・lzt=js
lzt=jsと同じなのだけれども、表示に必要なembed.jsとかいうものを自分でHTMLのHEAD部分に書かないといけないみたい。こんな感じだとさ。
<HTML>
<META http-equiv="Content-Type" type="text/html; charset=UTF-8">
<HEAD>
<TITLE>Laszlo BBS Examples</TITLE>
<script type="text/javascript" language="JavaScript"
src="http://localhost:8080/laszlo/lps/includes/embed.js"></SCRIPT>
</HEAD>
<BODY>
hoge
<script type="text/javascript" language="JavaScript"
src="http://localhost:8080/laszlo/my-apps/hoge.lzx?lzt=js">
</script>
hogehoge
</BODY>
</HTML>
※筆者はLaszloのルートURLを勝手にlaszloにしていますがデフォルトでは「lps-3.0」とかそんなもんです。ご注意。
・lzt=swf
swfファイルをそのまま返すとさ。HTMLページは一切使用しないとさ。で、このページのMimeTypeが「application/x-shockwave-flash」になるんだとさ。
・lzt=window
指定したlzxファイルをポップアップで表示するらしい。自分の環境では何も出なかった。後で確認しておきます。
まだ何個かありましたが疲れたのでやめ。とりあえず最初の目的を果たす為には「?lzt=swf」を後にくっつけてあげればよいみたいですな。
6. 外部への公開準備+SOLOについて
SOLO(Standalone OpenLaszlo Output)とは、簡単に言えばswf形式にして作成したFlashコンポーネントをデプロイすることです。
つまり、既存のWebサーバにOpenLaszloをインストールすることなく、swfファイルだけ配置すればOKということですね。但しこれには
制約があります。具体的には以下の通り(Laszloのドキュメントより抜粋)。
Here is a list of features that require OpenLaszlo Server:
*media types other than SWF, JPG, or MP3
*Persistent Connection
*SOAP
*XML-RPC
If your application relies on any of these features, you cannot deploy it SOLO.
要約すると、サーバ・クライアント型のデータ交換を行う機能は無理、ということですかね。恐らくFlash側の制限ではないでしょうか。
なので、少し凝ったものを作ろうと思ったらやっぱりOpenLaszlo毎サーバに配置する必要があると思われます。
次にTomcatの問題。TomcatはデフォルトでデプロイしたWebアプリの全てのディレクトリへアクセスできるようになっている
(但しWEB-INF以下は除く)。Laszloでは${LASZLO_HOME}/lps以下にbasecomponent等を定義してあるlzxファイルが配備されている。
流石にこれを外部に公開するのはどうかと思うのでアクセス拒否をする設定をしないとねぇ。Laszloとは余り関係ないのかも知れませんが、
ここにメモしておきます。
ⅰ) ディレクトリ一覧を表示しないようにする。
TomcatではAディレクトリへアクセスするとAディレクトリの一覧を表示してしまうという問題がある。開発者にとっては親切な設定な訳だが、
外部へ公開するとなるとハックして下さいと言わんばかりの設定です。ということでまずはディレクトリ一覧が見えないようにしましょう。
${TOMCAT_HOME}/conf以下のweb.xmlにて、以下の記述を変更しましょう。
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>listings</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
上記のようにlistingパラメータをfalseにしておくことでファイル一覧を見せなくすることができます。
ⅱ) lpsディレクトリへのアクセスを拒否する
${LASZLO_HOME}/lpsにはベースとなるlzxファイルが沢山ありますやっぱり見られたくないですよねぇ…。
こいつは作成した${LASZLO_HOME}/WEB-INF以下のweb.xmlに以下のように記述することで対処できます。
<security-constraint>
<web-resource-collection>
<web-resource-name>disableaccess</web-resource-name>
<url-pattern>/lps/admin/*</url-pattern>
<url-pattern>/lps/components/*</url-pattern>
</web-resource-collection>
<auth-constraint></auth-constraint>
</security-constraint>
後、データファイル等も見られたくないのであればそのファイルがあるディレクトリへのパス([DIR_PATH])を
<url-pattern>[DIR_PATH]</url-pattern>のようにして追加してあげればオケーです。
但し、このままだとブラウザに依存する404エラーor404エラーのページに飛んでしまう。ちょっとかっこ悪いなぁと思ったら
その辺りもweb.xmlに記述を足すことによって解決できるらしい。以下の記述を${LASZLO_HOME}/WEB-INF/web.xmlに足しましょう。
<error-page>
<error-code>404</error-code>
<location>/error/accesserror.html</location>
</error-page>
<error-page>
<error-code>403</error-code>
<location>/error/accesserror.html</location>
</error-page>
で、${LASZLO_HOME}以下にerrorディレクトリを作成し、accesserror.htmlなるファイルを作って適当な内容にします。
これで404or403エラーをTomcatが返したときにaccesserror.htmlページへとリダイレクトされます。何で403(権限エラー)
でも404にするのかって?だって権限エラーが返ってくるということはそこにファイルorディレクトリの存在を認めること
になるじゃないですか。分かる人には分かってしまうのでしょうが、少しでも存在を隠すための知恵だと思って納得頂けたら
と思います。ちなみにIEですと404エラーのほうが上手く動作してくれませんでした。理由はMicrosoftにでも聞いて下さい。シラネ。
7. datasetからServletを呼び出す
これまでサーバーとデータをやりとりするのに、JSPとRPCを使用する方法を紹介してきましたが、サーバーサイドのJava開発者ならば
「Servletはどうなんじゃ?」という質問が浮かぶと思います。結論を先に言うと「可能」です。
datasetのsrc属性には、戻り値?がXML形式のものであれば何を指定してもよいのですから、web.xmlで指定したurl-patternをsrc属性
に指定してあげればよいのです。例えばこんな感じ。
- ${LASZLO_HOME}/WEB-INF/web.xml
<servlet>
<servlet-name>hoge</servlet-name>
<servlet-class>hoge.HogeServlet</servlet-class>
</servlet>
…(中略)…
<servlet-mapping>
<servlet-name>hoge</servlet-name>
<url-pattern>/hogehoge</url-pattern>
</servlet-mapping>
- ${LASZLO_HOME}/WEB-INF/src/hoge/HogeServlet.java
package hoge;
import java.io.IOException;
import java.util.Enumeration;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class HogeServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
System.out.println("GETで呼ばれますた");
_service(req, res);
}
protected void doPost(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
System.out.println("POSTで呼ばれますた");
_service(req, res);
}
private void _service(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
// Header取得
Enumeration headers = req.getHeaderNames();
String headerkey = "";
while(headers.hasMoreElements()) {
headerkey = (String)headers.nextElement();
System.out.println("[HEADER] " + headerkey + " : " + req.getHeader(headerkey));
}
// パラメータ取得
Enumeration enums = req.getParameterNames();
String paramkey = "";
while(enums.hasMoreElements()) {
paramkey = (String)enums.nextElement();
System.out.println("[PARAM] " + paramkey + " : " + req.getParameter(paramkey));
}
req.setAttribute("HOGE", "Hello World!");
req.getRequestDispatcher("result.jsp").forward(req, res);
}
}
- ${LASZLO_HOME}/result.jsp
<?xml version="1.0" encoding="UTF-8"?>
<%@page language="java" contentType="text/html; charset=UTF-8"%>
<response>
<data text="<%=request.getAttribute("HOGE")%>"/>
</response>
- ${LASZLO_HOME}/servlettest.lzx
<?xml version="1.0" encoding="UTF-8"?>
<canvas>
<dataset name="helloservlet" src="hoge" request="false" type="http"/>
<view>
<button text="Submit">
<method event="onclick">
var params = new LzParam();
params.addValue("aaa", "クライアントから送られたデータです", true);
canvas.helloservlet.setQueryString(params);
canvas.helloservlet.setQueryType("POST");
canvas.helloservlet.doRequest();
</method>
</button>
<text datapath="helloservlet:/response/data/@text"/>
<simplelayout axis="y" spacing="10"/>
</view>
</canvas>
流れとしてはこんな感じ。
1.servlettest.lzxを読み込む
2.ボタンクリックでdatasetがリクエストを実行
3.HogeServletが呼び出される
4.result.jspにdispatchされる
5.result.jspをservlet.lzxが(勝手に)読み込んで結果をtextに表示する
※日本語データを送信する際にはPOSTで送らないと激しく文字化けするので注意。