• ウェブサービスの応答をgzipで圧縮する - Axisクライアント編

    前回に引き続いて、Axisクライアント→サービス間の応答もgzipで圧縮する(サーバサイドの設定については前回参照)。よもや、pivotハンドラから書かなければならんのだろうか・・・と鬱々としていたら最初から実装されてんのね。
    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で逆コンパイルしたコードを読んでたから気がつきませんでした。ははは。
    後は、.NET Frameworkのクライアントと同じで、HTTPリクエストヘッダ中にAccept-Encoding: gzipが付与され、サーバ側が対応していれば応答本文がgzipで圧縮され送られてくる。CommonsHTTPSenderが伸張してくれておしまい。
    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で圧縮する

    サービス→クライアントへの応答をgzipで圧縮し、転送量を小さく。
    大雑把には以下の通り。
    • クライアント
      • HTTPヘッダにAccept-Encoding: gzipをつけて要求を投げる。
      • 応答がContent-Encoding: gzipの場合に、帰ってきた応答を伸張する
    • サービス側(必ずしもサービス自身が担当するわけではなく)
      • 要求のHTTPヘッダにAccept-Encoding: gzipが含まれる場合、HTTP応答本文をgzipで圧縮して返す
    前提とする環境は、サーバ側=Tomcat+AxisでApache+mod_jk。
    最も手数の少ない方法は、
    • クライアント=.NET Framework 2.0(手元の環境をVS2005にしたのでもうこれしか確認できない)
    • サービスの応答がクライアントに帰る直前、要はmod_deflateを用いて圧縮する
    というところか。Tomcat→Apache間のメッセージが圧縮されないのがイマイチだが、TomcatのFilterを用いて圧縮する方法で実現しようと思ったらやたら手数が多かったから・・・・。servlets-examplesにサンプルが付いているけど、十分複雑だよ。。。)

    クライアント編

    • 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クライアント編

    ここまでで、最終的な公開用エンドポイントでサービスを公開できているはず。 最後は.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/ にアクセスしてコネクタが働いていないことを確認する。