webserviceの最近のブログ記事
前回に引き続いて、Axisクライアント→サービス間の応答もgzipで圧縮する(サーバサイドの設定については前回参照)。よもや、pivotハンドラから書かなければならんのだろうか・・・と鬱々としていたら最初から実装されてんのね。
axis.jar内のorg.apache.axis.transport.http.CommonsHTTPSenderとcommons-httpclientの組み合わせでAccept-Encoding: gzipなリクエスト&応答伸張できるらしい。
CommonsHTTPSenderの場合、リクエストもgzip圧縮して送信できる感じ(試してない)。proxyサーバの設定ってどこでやるのかな・・・。 おまけ:応答の様子。
axis.jar内のorg.apache.axis.transport.http.CommonsHTTPSenderとcommons-httpclientの組み合わせでAccept-Encoding: gzipなリクエスト&応答伸張できるらしい。
導入
- The Jakarta Site - Commons Downloadsから「Commons Codec」「Commons HttpClient」をダウンロードする。今回使ったのは、それぞれ1.3と3.0。
- ダウンロードしたアーカイブからjarを取り出して、適当な場所に置く。クライアントプロジェクトのビルドパスに追加する。
設定
- client-config.wsdd内に以下の赤字部分のように記述してhttpトランスポートをCommonsHTTPSenderに置き換える。
<?xml version="1.0" encoding="UTF-8"?>
<deployment name="using Commons-httpclient"
xmlns="http://xml.apache.org/axis/wsdd/"
xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
<globalConfiguration>
<parameter name="disablePrettyXML" value="true"/>
<parameter name="enableNamespacePrefixOptimization" value="false"/>
</globalConfiguration>
<transport name="http" pivot="java:org.apache.axis.transport.http.CommonsHTTPSender"/>
<transport name="local" pivot="java:org.apache.axis.transport.local.LocalSender"/>
<transport name="java" pivot="java:org.apache.axis.transport.java.JavaSender"/>
</deployment>
- client-config.wsddをクラスパスのルートに配置する。クラスパスのルートかカレントディレクトリでもいいらしい("."ドットが適用されるから?)。./src/直下に置いた。ビルドすると./bin/直下に配置されるので。jarを作るときにbinまるごと一括で楽かと。
コード修正
- 以下のように、得られたスタブをorg.apache.axis.client.Stubにキャストし、_setProperty()メソッドでtransport.http.acceptGzipプロパティにtrueを指定する。
KeitaisoKaiseki binding; binding = new KeitaisoKaisekiServiceLocator().getKeitaisoKaisekiService();
((Stub)binding)._setProperty("transport.http.acceptGzip" , "true");
- 06/04/29追記:"transport.http.acceptGzip"は、org.apache.axis.transport.http.HTTPConstantsのMC_ACCEPT_GZIPとして定義があるので、これを用いるがベターという指摘。jadで逆コンパイルしたコードを読んでたから気がつきませんでした。ははは。
CommonsHTTPSenderの場合、リクエストもgzip圧縮して送信できる感じ(試してない)。proxyサーバの設定ってどこでやるのかな・・・。 おまけ:応答の様子。
≫POST /KeitaisoWS/services/KeitaisoKaisekiService HTTP/1.1
≫Content-Type: text/xml; charset=utf-8
≫SOAPAction: ""
≫User-Agent: Axis/1.3
≫Accept-Encoding: gzip
≫Host: passing.breeze.cc
≫Transfer-Encoding: chunked
≫
≫(リクエスト本文)
≫
≪HTTP/1.1 200 OK
≪Date: Tue, 28 Feb 2006 13:34:48 GMT
≪Server: Apache
≪Vary: Accept-Encoding
≪Content-Encoding: gzip
≪Connection: close
≪Transfer-Encoding: chunked
≪Content-Type: text/xml;charset=utf-8
≪
≪(応答本文)
サービス→クライアントへの応答をgzipで圧縮し、転送量を小さく。
大雑把には以下の通り。
最も手数の少ない方法は、
大雑把には以下の通り。
- クライアント
- HTTPヘッダにAccept-Encoding: gzipをつけて要求を投げる。
- 応答がContent-Encoding: gzipの場合に、帰ってきた応答を伸張する
- サービス側(必ずしもサービス自身が担当するわけではなく)
- 要求のHTTPヘッダにAccept-Encoding: gzipが含まれる場合、HTTP応答本文をgzipで圧縮して返す
最も手数の少ない方法は、
- クライアント=.NET Framework 2.0(手元の環境をVS2005にしたのでもうこれしか確認できない)
- サービスの応答がクライアントに帰る直前、要はmod_deflateを用いて圧縮する
クライアント編
- Web参照から生成したコードで、サービス呼び出しのスタブにあるEnableDecompressionプロパティにtrueをセットして呼び出すだけ。すばらしいシンプルさ。(06/03/01:と思ったらドキュメントによれば既定値trueらしい。あらら)
- 後は、Accept-Encodingを付けることもHTTP応答本文を伸張することも.NET Frameworkがやってくれる。
サーバ編
- 以下の通り、JkMountでApache→Tomcatに転送しているので、転送範囲の応答はmod_defalteで圧縮しちゃう。今のところウェブサービス以外のリソースはないのでブラウザのことは考えないで圧縮指定。
JkMount /KeitaisoWS/services/KeitaisoKaisekiService* ajp13
<Location "/KeitaisoWS/">
AddOutputFilterByType DEFLATE text/html text/plain text/xml
</Location>
- あとはSIGHUPを送るなどして設定を有効に。
応答を見てみる
見てみると言っても、クライアントコードがメッセージを受け取った時点で伸張されていてシームレスなので、通信内容をパケットキャプチャして確認する。- 応答圧縮を受け入れない場合
≫POST /KeitaisoWS/services/KeitaisoKaisekiService HTTP/1.1
≫User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; MS Web Services Client Protocol 2.0.50727.42)
≫Content-Type: text/xml; charset=utf-8
≫SOAPAction: ""
≫Host: passing.breeze.cc
≫Content-Length: 557
≫Expect: 100-continue
≫Connection: Keep-Alive
≫
≫HTTP/1.1 100 Continue
≫
≫(要求本文)
≪HTTP/1.1 200 OK
≪Date: Mon, 27 Feb 2006 17:32:20 GMT
≪Server: Apache
≪Vary: Accept-Encoding
≪Connection: close
≪Transfer-Encoding: chunked
≪Content-Type: text/xml;charset=utf-8
≪
≪(以下応答)
- 応答圧縮を受け入れる場合
≫POST /KeitaisoWS/services/KeitaisoKaisekiService HTTP/1.1
≫User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; MS Web Services Client Protocol 2.0.50727.42)
≫Content-Type: text/xml; charset=utf-8
≫SOAPAction: ""
≫Host: passing.breeze.cc
≫Content-Length: 557
≫Expect: 100-continue
≫Accept-Encoding: gzip
≫
≪HTTP/1.1 100 Continue
≪
≫(要求本文)
≪HTTP/1.1 200 OK
≪Date: Mon, 27 Feb 2006 17:32:30 GMT
≪Server: Apache
≪Vary: Accept-Encoding
≪Content-Encoding: gzip
≪Connection: close
≪Transfer-Encoding: chunked
≪Content-Type: text/xml;charset=utf-8
≪
≪(圧縮された応答)
- 今回の例では、約8000バイトの応答が、約2000バイトになった。
おわりに
次はAxisクライアントで同じことをやってみようと思うが、client-config.wsddでhttpトランスポートを置き換えないとダメなのかな?と考えただけで、面倒くせぇ~という気分になるな。- クライアントを作る分には手間も少なく、インタフェース仕様がサーバサイドで実際に使われているコードと対応するので齟齬が減らせるのではないかと思った。
- java/Eclipse/Tomcat/axisの組み合わせに関して言えば、本来やりたかったこと以外の部分で悩むことが多いんじゃないだろうか。java文化の特徴かも。
- ASP.NETの場合、メソッドの属性にメタデータが書けるせいか手間が少ない。今後アノテーションに対応してくればツールを使う部分でハマるケースを回避できるかもしれない。
- DLL HellよりもJAR Hellのがハマる
- MTでコードについて書くのは面倒。正直Wikiにしておけばよかった。
ここまでで、最終的な公開用エンドポイントでサービスを公開できているはず。
最後は.NETを用いてクライアントを作成する。VS2005でC#を用いた。
準備
- ウィンドウアプリケーションでもコンソールアプリケーションでもよいのでプロジェクト作成
- ソリューションエクスプローラのプロジェクトのコンテキストメニューから「Web参照の追加」
- WSDLのURLを与え、「参照の追加」
- 以上で、スタブコードが生成されるはず
クライアントコード
- フォームに入力した文字列をサービスに渡して表示するだけの単純なもの。
private void button1_Click(object sender, System.EventArgs e) { this.textBox2.Text = ""; cc.breeze.passing.KeitaisoKaisekiService svc = new cc.breeze.passing.KeitaisoKaisekiService(); cc.breeze.passing.KeitaisoToken[] results = svc.analyze( this.textBox1.Text ); for( int i = 0 ; i < results.Length ; i++ ) { this.textBox2.Text += results[i].token + "," + results[i].basicString + "," + results[i].hinshi + "," + results[i].reading + "," + results[i].pronunciation + "," + results[i].surface + "," + results[i].cost + "," + results[i].conjugationalForm + "," + results[i].additionalInfo + "," + "\r\n"; } } - こんな感じ
アクセス解析をapacheのログとまとめて行えると便利なので、コネクタを利用してapache→Tomcatへの接続ルートを用意する。
ウチのサーバの場合、外に公開するのはNAT内のapache(TCP:80)だけにしておきたいので、Tomcat(TCP:8080)を野ざらしにしない意味もある。
mod_jk
- Apache Tomcat - Apache Tomcatから、トップ→[Download/Tomcat Connectors]。
- JKをダウンロードする。今回使用したのは「JK 1.2.15 Source Release tar.gz」
- アーカイブを適当な場所に展開してビルドする。このエントリは事後に書いているので細かいことは忘れてしまったが、たぶん下のような感じ。
> ./configure --with-apxs=/usr/sbin/apxs
> make -
できあがった「mod_jk.so」をapacheのmodule置き場に配置し、confにモジュールのロードとログの設定を書く。/etc/httpd/conf.d/tomcat.conf
LoadModule jk_module modules/mod_jk.so
JkLogFile /var/log/httpd/mod_jk.log
JkLogLevel info - ウチのサーバの場合、仮想ホスト運用しているので、必要なVirtualHostディレクティブ内にJKのマウント設定を書く。何もかも外に出す必要はないので最低限の範囲で。そうしないとAdminServiceへも外からアクセスできてしまう。
JkMount /KeitaisoWS/services/KeitaisoKaisekiService* ajp13
起動
- apache→TomcatへTCPセッションが張られるので、Tomcatが先に起動している必要があるみたいだ。
- Tomcatを再起動した場合、apacheにSIGHUPを送るなどして再接続が必要。これってapacheが先に起動していた場合でもセッションが確立していなかったら改めて接続を試みる、みたいな動きに出来ないのかな。。
- ブラウザから、http://passing.breeze.cc/KeitaisoWS/services/KeitaisoKaisekiService?wsdl にアクセスしてコネクタが働いているか確認する。
- ブラウザから、http://passing.breeze.cc/KeitaisoWS/ にアクセスしてコネクタが働いていないことを確認する。
ここまででローカルPC内でウェブサービスクライアント~サービス間の疎通を行うことが出来た。
今度はサービスプロバイダを実際にサービスを公開するサーバに載せる。
今回使用したサーバは、OS=FedoraCore3、Java=JDK5.0 update5を使用している。
今度はサービスプロバイダを実際にサービスを公開するサーバに載せる。
今回使用したサーバは、OS=FedoraCore3、Java=JDK5.0 update5を使用している。
導入
- Windows上で環境構築した際に使用したzipアーカイブをlinux上でどしどし展開する。
- /home/
- apache-tomcat-5.5.15/
- apache-tomcat/ (シンボリックリンク)
- sen-1.2.1/
- sen/ (シンボリックリンク)
- /home/
- senについては、プロジェクトのホームページからダウンロードしたアーカイブでなくて、Windows上で「辞書の準備」までした状態当該ディレクトリをアーカイブし、そのままサーバ上に展開すると、辞書を改めて用意しなくていいので手間が省ける。
Tomcat
- ウェブアプリからsenを実行できるようにするため、conf/catalina.propertiesに以下の内容を追記する。
sen.home=/home/sen
- bin/startup.shを実行して起動。
デプロイ
- こんな感じのターゲットで、ローカルの資産をWARに固めてTomcatにデプロイする。そのまま、Axisでのデプロイも行って、サービスを登録する。
リモートからサービスをデプロイするには(AdminServiceを使うには)、server-config.wsddでenableRemoteAdminをtrueにしておく必要がある。org.apache.axis.utils.Adminを用いてserverサイドのconfigを「ローカルで」生成し、これを雛形にしてリモートAdminを有効にした状態で、WebContent/WEB-INF/sever-config.wsddとして放り込んだ。で、そのままTomcatにデプロイしてAdminClientからサービスをデプロイ、とゆー、大雑把な手段。。。<taskdef resource="axis-tasks.properties" /> <taskdef name="deploy" classname="org.apache.catalina.ant.DeployTask"/> <taskdef name="undeploy" classname="org.apache.catalina.ant.UndeployTask"/> <property name="dist" value="./dist" /> <property name="web" value="./WebContent" /> <property name="war" value="KeitaisoWS.war" /> <target name="deploy_ws" depends="deploy2server"> <axis-admin port="8080" hostname="passing.breeze.cc" failonerror="true" servletpath="/KeitaisoWS/services/AdminService" xmlfile="./src/cc/breeze/ws/keitaiso/deploy.wsdd" username="AdminServiceを使うためのユーザ。user.lstに書いておく" password="パスワード" /> </target> <target name="make_war"> <delete dir="${dist}" /> <mkdir dir="${dist}"/> <mkdir dir="${dist}/WEB-INF"/> <mkdir dir="${dist}/WEB-INF/classes"/> <mkdir dir="${dist}/WEB-INF/lib"/> <copy todir="${dist}"> <fileset dir="${web}"> <include name="**/*.*"/> <exclude name="**/*.java"/> <exclude name="**/jsp_servlet/*.class"/> <exclude name="**/build.xml"/> <exclude name="**/build.properties"/> </fileset> </copy> <copy todir="${dist}/WEB-INF/classes"> <fileset dir="build/classes"> <include name="**/*.*"/> <exclude name="**/jsp_servlet/*.class"/> <exclude name="**/*.java"/> <exclude name="**/*.wsdd"/> </fileset> </copy> </target> <target name="deploy2server" depends="make_war,undeploy" > <delete file="${war}" failonerror="false" /> <jar jarfile="${war}" basedir="${dist}" manifest="${dist}/META-INF/MANIFEST.MF"/> <deploy url="http://passing.breeze.cc:8080/manager" username="Tomcatのmanagerを使うためのユーザ" password="パスワード" path="/KeitaisoWS" war="${war}" /> </target> <target name="undeploy"> <undeploy url="http://passing.breeze.cc:8080/manager" username="Tomcatのmanagerを使うためのユーザ" password="パスワード" path="/KeitaisoWS" failonerror="false" /> </target> - Tomcatのmanagerを使うためのユーザは、サーバ側Tomcat導入ディレクトリのconf/tomcat-users.xmlに書いておく。
- ブラウザからhttp://passing.breeze.cc:8080/managaer/htmlにアクセスしてTomcatに配備されたか確認する。
- ブラウザからhttp://passing.breeze.cc:8080/KeitaisoWS/ →「リスト」にアクセスしてサービスが配備されたか確認する。
ここまでで、ローカル動作のTomcat上で動くウェブサービスを配備できているはず。
コアロジックの確認はしたものの、あくまで単一のJavaコード内に閉じていた。SOAP経由での疎通確認用ウェブサービスクライアントを作成する。
コアロジックの確認はしたものの、あくまで単一のJavaコード内に閉じていた。SOAP経由での疎通確認用ウェブサービスクライアントを作成する。
プロジェクト準備
- プロジェクトを作成する。
- [ファイル]-[新規]-[プロジェクト]から、「/Java/Javaプロジェクト」を選択
- この時点で作るクライアントはまだサービス利用者の立場で作成するわけではなくサービスの疎通確認用の意味合い。ゆえにインタフェース変更のセンもまだまだ捨てきれず、「/Webサービス/Webサービス・クライアント」ウィザードで作ってしまうと再度クライアントコードを生成するのが手間なのでplainなJavaプロジェクトを用いる。
- プロジェクト名を適当につけてプロジェクト作成。
- [ファイル]-[新規]-[プロジェクト]から、「/Java/Javaプロジェクト」を選択
- Axisのライブラリを参照できるようにする
- プロジェクトのプロパティ→「/Javaのビルド・パス」→「ライブラリ」タブへ移動
- 「ライブラリーの追加」ボタン→「ユーザー・ライブラリ」を選択
- Axis導入時に設定した「AXIS_LIB」を選択
クライアントコード生成
- WSDLからJavaコードを生成する。TomcatにデプロイされたWSDLを参照するのではなくローカルに生成したWSDLを直接参照する。前項同様、これから先WSDLを作り直すセンがまだあり得るので、デプロイしないでローカルでどんどん試せる方がよいと思う。
<taskdef resource="axis-tasks.properties" /> <target name="make_client_hinagata"> <wsdl2java serverside = "no" output = "./src" url = "../KeitaisoWS/WebContent/wsdl/KeitaisoKaiseki.wsdl" deployscope="application" /> </target> - 生成した後は例のごとく「パッケージエクスプローラ」上で「更新」を忘れないようにする。既存のファイルの内容が更新されるだけであれば問題ないが、ファイルが増えたり減ったりした場合は明示的にプロジェクトに追加された状態を作らなければならない。これってAntタスクで書けないのかなー。
実装
- サービスを呼び出すためのコードはWSDLから生成されているので、これを利用するコードを書く
- 単純な例。
public static void main(String[] args) throws Exception { KeitaisoKaiseki binding; try { binding = new KeitaisoKaisekiServiceLocator().getKeitaisoKaisekiService(); } catch (javax.xml.rpc.ServiceException e) { throw new Exception( "" , e ); } try { KeitaisoToken[] result = null; result = binding.analyze("やっとここまで来ましたね。"); for (int i = 0; i < result.length; i++) { KeitaisoToken token = result[i]; System.out.println(token.getToken() + "," + token.getBasicString() + "," + token.getHinshi() + "," + token.getReading() + "," + token.getPronunciation()); } } catch (java.rmi.RemoteException e) { throw new Exception( "" , e ); } } - サービスのバインディングを得る際、URLオブジェクトを渡すことで任意の場所にあるサービスにバインドできる。デフォルトではWSDLに書かれたURL。WSDLには最終的にサービスを公開するURLを書いてあるので、疎通用にバインドする。
URL url = new URL("http://localhost:8080/KeitaisoWS/services/KeitaisoKaisekiService");
binding = new KeitaisoKaisekiServiceLocator().getKeitaisoKaisekiService( url );
