基本的なサーバ連携アプリケーション

Curlはクライアントサイドに特化した言語のため、サーバーサイドの技術に依存しません。そのため、サーバーサイドとのインターフェースをあわせればどんなサーバーサイドの技術とも連携できます。ここではサーバーサイドの技術としてServletを利用し、HTTPでの通信を行う基本的なCurlの実装方法を紹介していきます。

 

1. 環境構築

サンプルで使用する環境について説明します。
※サンプルでは以下の環境を前提としていますが、CurlではHTTP通信ができれば問題ないので、独自でその他の環境を構築しても問題ありません。ただし、レスポンスとしてXMLを返す必要がありますので、その点だけ注意が必要になります。

1.1. データベース
サンプルではデータベースとしてAccessを使用するのでデータソースの登録が必要になります。管理ツールより、ODBCデータソースの[システムDSN] or [ユーザDSN]に、サンプルのMDBファイル[sampledb.mdb]を[SampleData]の名前で登録してください

1.2. サーバーサイド
サンプルではサーバーサイドとしてServletを使用しますのでServletが動作する環境が必要になります。以下のものが動作するようにインストールおよび初期設定を行ってください。
・JDKのインストール
・Tomcatのインストール
インストール後、Tomcatをインストールしたフォルダのwebappsフォルダに/sampleフォルダをコピー後、Tomcatを起動してください。

<動作確認>
ブラウザのアドレス欄に[http://localhost:8080/sample/SampleService] と入力します。
以下のXMLが表示されれば、正常に起動しています。

 1.3. クライアントサイド
任意のフォルダに/sample_curlフォルダをコピーしてください。Curlのアプレットをfile:で実行する場合、特権の設定が必要になるので、以下の設定を行ってください。
・「スタート」-「プログラム」-「CurlRTE」-「Curlコントロールパネル」を選択します。
・「セキュリティ」タブを選択し、「このコンピュータ」で「特権ディレクトリの追加」をクリックし、先ほどコピーしたフォルダのパスを入力します。

<動作確認>
/sample_curlフォルダ内の○○.curl(sample1.curl、sample2.curl)をダブルクリックしてブラウザにCurlのアプレットが表示されれば、正常に起動しています。

 

この状態から検索ボタンを押下することによってDBからデータを取得し、結果が画面に表示されます。

検索条件(name)に値(111)を入力し検索ボタンを押下すると以下のようになります。

2. 通信の流れ

通信をする際の流れは以下の通りになります。
Curlでリクエストデータを生成し、サーバサイドに対してHTTPリクエストを送信します。サーバーサイドでそのリクエストを受信し、ロジックを実行します。その後、クライアントサイドにレスポンス(XML)を返却し、クライアントサイドでそのレスポンスを解析およびデータオブジェクト(Curlのデータオブジェクト)を生成します。
通信の流れを以下の図で示します。

 

 3. リクエスト処理

3.1. HttpForm.submit-open
HttpForm内に配置されたコントロール(TextFieldやDropdownリストなど)のnameオプションにリクエストのパラメータ名を指定し、HttpForm.submit-openメソッドを使用することによりサーバーサイドにリクエストを送信することができます。このメソッドの戻り値がレスポンスになりますので、レスポンス処理にこの戻り値を渡し、レスポンスの解析を行います。

{let hf:HttpForm =
    {HttpForm
        ||通信先URL
        {url “http://localhost:8080/sample/SampleService“},

        ||HTTPリクエストメソッド
        ||HttpRequestMethod.post or HttpRequestMethod.get
        method = HttpRequestMethod.post,
       
        ||サーバーに送信される要求のエンコード タイプ
        ||HttpFormData.urlencoded-mime-type or HttpFormData.multipart-mime-type
        encoding = HttpFormData.urlencoded-mime-type,

        ||パラメータに使用する既定の文字エンコード
        default-character-encoding = “utf8”,
       
        width = {add-stretch},
        {spaced-vbox
            margin = 5pt,
            halign = “right”,
            {HBox spacing = 5pt, width = {add-stretch},
                {TextFlowBox “name”},
                {TextField name = “name”, width = 3cm},
                {TextFlowBox “address”},
                {TextField name = “address”, width = 3cm},
                {Fill}
            },
            {CommandButton
                label = “検索”,
                {on Action do
                    {with-busy-cursor
                        {try
                            {with-open-streams response:#TextInputStream =
                                ||リクエスト送信
                                {hf.submit-open
                                    character-encoding={get-character-encoding-by-name “utf8”}}
                             do
                                {if-non-null response then
                                    ||レスポンス解析→データオブジェクト生成
                                    let rs:#RecordSet = {parse-response response}
                                    set rg.record-source = rs
                                }
                            }
                         catch e:HttpException do
                            {popup-message “通信エラーが発生しました。:” & e.message}
                         catch e:Exception do
                            {popup-message “エラーが発生しました。:” & e.message}
                        }
                    }
                }
            }
        }
    }
}

 

3.2. HttpFile.http-read-open
HttpFormを使用せずにリクエストを送信する方法もあります。この方法を使用することによってより柔軟な通信が可能になります。HttpFormDataインスタンスを生成し、このインスタンスに送信したいリクエスト情報を追加します。その後、HttpFile.http-read-openメソッドを使用することによりサーバーサイドにリクエストを送信することができます。このメソッドの戻り値がレスポンスになりますので、レスポンス処理にこの戻り値を渡し、レスポンスの解析を行います。 

|| —————————————————————
|| HttpFormData生成プロシージャ
|| —————————————————————
{define-proc public {create-http-form-data
                        mime-type:String = HttpFormData.urlencoded-mime-type,
                        default-character-encoding:CharEncoding = CharEncoding.utf8,
                        request-parameter:{HashTable-of String, String} = {{HashTable-of String, String}}
                    }:HttpFormData
    ||デフォルトのHttpFormData作成
    let form-data:HttpFormData = {HttpFormData
                                     mime-type                  = mime-type,
                                     default-character-encoding = default-character-encoding
                                 }
    {for s:String key k:String in request-parameter do
        {form-data.append {HttpFormStringParam k, s}}
    }
   
    {return form-data}
}

 

|| —————————————————————
|| データ取得プロシージャ
|| —————————————————————
{define-proc public {get-data
                        url-string:String,
                        request-parameter:{HashTable-of String, String}
                    }:RecordSet
    let http-file:HttpFile = {{url url-string}.resolve} asa HttpFile
    let form-data:HttpFormData = {create-http-form-data request-parameter = request-parameter}
    let record-set:#RecordSet

    {with-busy-cursor
        {try
            {with-open-streams response:#TextInputStream =
                ||リクエスト送信
                {http-file.http-read-open
                    character-encoding = “utf8”,
                    request-method = HttpRequestMethod.post,
                    request-data = form-data
                }
             do
                {if-non-null response then
                    set record-set = {parse-response response}
                    {record-set.commit}
                }
            }
         catch he:HttpException do
            {popup-message “通信エラーが発生しました。:” & he.message}
         catch e:Exception do
            {popup-message “エラーが発生しました。:” & e.message}
        }
    }

    {return {non-null record-set}}
}

 

4. レスポンス処理

4.1. DefaultHandlerを継承
SAXParserを使用し、独自のXMLハンドラクラスのインスタンスをセットすることによりXMLの解析を行います。

|| —————————————————————
|| レスポンス解析プロシージャ
|| —————————————————————
{define-proc public {parse-response
                        tis:TextInputStream
                    }:#RecordSet
    ||SAXParserインスタンスを生成
    let parser:SAXParser = {SAXParser}
    ||XMLハンドラインスタンスを生成
    let response-handler:ResponseHandler = {ResponseHandler}
    ||SAXParserにXMLハンドラをセット
    {parser.set-content-handler response-handler}
    ||TextInputStreamをSAXParserのインプット用に変換
    let source:InputSource = {InputSource character-stream = tis}
    ||解析処理
    {parser.parse source}
    {return response-handler.record-set}
}

|| —————————————————————
|| XMLハンドラクラス
|| —————————————————————
{define-class public ResponseHandler {inherits DefaultHandler}
  field private _record-set:#RecordSet
  field private _data:StringBuf
 
  {getter {record-set}:#RecordSet
    {return self._record-set}
  }
 
  {constructor public {default}
    set self._data = {StringBuf}
    {construct-super}
  }
 
  {method public {start-document}:void}
 
  {method public {start-element url:String, name:String, qname:String, atts:Attributes}:void
    {switch name
     case “RecordSet” do
     case “Record” do
        {self.start-element-record atts}
    }
  }
 
  {method public {characters ch:StringBuf, start:int, length:int}:void
    {self._data.clear}
    {self._data.concat {ch.substr start, length}}
  }
 
  {method public {end-element uri:String, name:String, qname:String}:void
    {switch name
     case “RecordSet” do
        {if-non-null self._record-set then
            {self._record-set.commit}
        }
     case “Record” do
    }
  }
 
  {method public {end-document}:void}
 
  {method private {start-element-record atts:Attributes}:void
    {if self._record-set == null then
        let fields-array:{Array-of RecordField} = {{Array-of RecordField}}
        {for i:int = 0 below {atts.get-length} do
            {if-non-null {atts.get-local-name i} then
                {fields-array.append {RecordField {non-null {atts.get-local-name i}}}}
            }
        }
        set self._record-set = {RecordSet {RecordFields {splice fields-array}}}
    }
    let record:Record = {self._record-set.new-record}
    {for i:int = 0 below {atts.get-length} do
        {if-non-null {atts.get-local-name i} then
            {record.set {non-null {atts.get-local-name i}}, {atts.get-value i}}
        }
    }
    {self._record-set.append record}
  }
}

4.2. WSDKのエラボレーション使用
WSDKで提供されているAPIを使用することでXMLを解析することもできます。

|| —————————————————————
|| レスポンス解析プロシージャ
|| —————————————————————
{define-proc public {parse-response
                        tis:TextInputStream
                    }:RecordSet
    let response-xml:XDMElement = {build-xml preserve-whitespace? = true, tis}.root
    let e:XmlElaboration =
        {xml-elaboration
            {“RecordSet” {…:RecordData}:RecordSet
                {RecordSet
                    {RecordFields
                        {RecordField “name”, domain = String},
                        {RecordField “address”, domain = String},
                        {RecordField “phone”, domain = String},
                        {RecordField “number”, domain = String},
                        {RecordField “amount”, domain = String}
                    },
                    {splice …}}
            }
            {“Record” RecordData}
        }
    {return {e.elaborate response-xml} asa RecordSet}
}

5. サンプルソース

・ sample1.curl(HttpForm, DefaultHandler)
・ sample2.curl(HttpFile, WSDKエラボレーション) 

サンプルソースのダウンロード