カテゴリー別アーカイブ: サーバ連携

PHPでCurlコードを動的生成

サーバサイドのテクノロジーを利用して、Curlコードを動的に生成し、アプレットを実行することができます。ここではPHPの例を説明したいと思います。(もちろんASPやJSP・サーブレットでも同様のことが実現可能です。)

サンプルコードは以下のようになります。

dynamic02.jpg

必要な点は、
 1. MIMEタイプにtext/vnd.curlを指定します。(Content-typeヘッダーに左記タイプを指定)
 2. Curlのコードを出力します。(PHPの場合は、echoを使います。)

例えば、ApacheのDocumentRootに上記ファイル(例えば、curl.php)とCurlライセンスファイルを配置し、ブラウザからhttp://localhost/curl.php へアクセスしますと以下のような画面が表示されます。

dynamic01.jpg

サーバ・データベース操作(MySQL、Oracle)

Curlではデータベースサーバに接続し、RecordSetを生成することができる機能を持っています。これは、CDBC(Curl DataBase Connectivity)と呼ばれており、下図のようにJDBC経由でCurlからOracleやMySQLなどのサーバサイドデータベースに接続することができます。

cdbc01.jpg

 

この機能の操作方法を説明していきます。

事前準備

これを利用するには、以下のものが必要となります。

  • Javaサーブレットエンジン:Tomcat or JBoss
  • データベースサーバ:MySQL or Oracle
  • JDBCドライバ

これらのセットアップ及びserver.xml等のセッティング方法は、以下のページを参照してください。

サーブレットエンジンの構成

RecordSetの作成

まずは、CDBCを利用するために2つのパッケージをインポートします。 

{import * from CURL.DATA-ACCESS.BASE}
{import * from CURL.DATA-ACCESS.CONNECTED}

次にデータベースに接続するために、Connectionクラスを継承したBasicConnectionクラスのインスタンスを生成します。この際、BasicConnectionのコンストラクタの引数にCDBCサーバのURLを渡します。 

def conn:BasicConnection =
    {BasicConnection {url http://localhost:8080/cdbc-servlet/CdbcServlet}}

単純にRecordSet(ここではConnectedRecordSetを継承したCDBC用クラスのBasicCeoonectedRecordSet)を生成するには、BasicConnectionのcreate-record-setメソッドを実行します。この引数には、データベース名とテーブル名(もしくはSQL文)を指定します。以下の例では、RecordGridに表示させています。

{RecordGrid
    record-source = {conn.create-record-set “example”,”employee”}
}

SQL文の実行

SQL文を実行する際、create-record-setを用いて、select文よりRecordSetを取得できます。

{RecordGrid
    record-source = {conn.create-record-set “example”,”select * from employee“}
}

それ以外に、insert、update、delete、create、drop等のSQLを実行する際は、BasicConnection.executeメソッドを実行します。このメソッドは一度に複数のSQL文も実行することができます。

{conn.create-record-set “example”, “delete from employee“}

直接SQL文を実行しなくても、BasicConnectedRecordSetのappend等のメソッドを用いたり、RecordGridの操作で、自動的にSQLを生成し、データの追加・更新・削除等を行いことができます。

データベースメタ情報の取得

いくつかのデータベース・メタ情報を取得するConnectionクラスのメソッドを以下に記載します。

メソッド名 説明
get-database-names 操作できるデータベースの一覧を取得できます。
get-tables 操作できるテーブル一覧を取得できます。
get-fields 対象テーブルのカラム情報を取得できます。(カラム名、サイズ、キー、タイプ等)
get-keys キーの一覧を取得できます。

 

その他機能

Curl6.0からいくつかの機能(圧縮、非同期通信、認証、独自バイナリフォーマット通信など)が追加されました。それらの一部を紹介します。

大量データを扱う場合に、パフォーマンスを向上させるための方法として、2つサポートしています。1つはHTTP圧縮機能です。これを用いますとサーバとの通信時に自動的に圧縮・解凍され、トラフィックが低減されます。利用するには、BasicConnectionのインスタンス生成時にcompress?キーワード引数にtrueをセットします。 

def conn:BasicConnection =
    {BasicConnection
        {url http://localhost:8080/cdbc-servlet/CdbcServlet},
        compress? = true
    }

もう一つのパフォーマンス向上機能として、通信フォーマットのシリアル化があります。通常CDBCはXMLで通信を行いますが、通信及びパースの高速化を行うため、Curl独自バイナリフォーマットのシリアル化・デシリアル化を自動的に行うという機能がサポートされました。これは通常のXMLと比べ5分の1から10分の1程度のスピードアップします。(ただし、データ量やデータ内容などによりスピードアップ率が変わる可能性があります。)これを利用するには、BasicConnectionのインスタンス生成時にserialize?キーワード引数にtrueをセットします。下位互換のためデフォルトはfalseとなっていますが、基本的にはこのパラメータをtrueにすることをお勧めします。

def conn:BasicConnection =
    {BasicConnection
        {url http://localhost:8080/cdbc-servlet/CdbcServlet},
        serialize? = true
    }

その他の機能として、非同期通信及びrealmを利用した認証があります。どちらもBasicConnectionのインスタンス生成時にキーワード引数として指定します。非同期通信の場合には、async?、realm認証の場合にはusernameとpasswordを指定することにより、この機能を利用することができます。

また、JDBCで扱われるデータタイプのほとんどが、Curlのデータタイプに自動的にマッピングされますので、開発者は簡単にアプリケーションの開発を行うことができます。例えば、Curl6.0からサポートされた機能ですと、OracleのBLOBタイプをCurlのByteArrayにマッピングしてくれます。

関連ドキュメント

データベースからレコードデータを取得

クライアントデータベース(SQLite)

 

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

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エラボレーション) 

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

 

CurlとRails2.0で作るRestfulアプリケーション

Ruby on Railsではバージョン1.2からRESTがサポートされました。これはPUT・GET・DELETE・PUTによるHTTPリクエストによりRailsにアクセスすることができます。これを利用し、CurlとRailsとの連携サンプルを紹介します。

環境セットアップ

まずはMySQLをインストールし、以下のSQL文を実行することでテーブルとデータを作成します。

CREATE DATABASE dev;
USE dev;

DROP TABLE employees;
CREATE TABLE employees (
  id INT auto_increment NOT NULL,
  login CHAR(16),
  firstname CHAR(32),
  lastname CHAR(32),
  phone CHAR(15),
  status INT,
  PRIMARY KEY (id)
);
INSERT INTO employees VALUES(default,’hmino’,’Hiroki’,’Mino’,’0123-45-6789′,1);
INSERT INTO employees VALUES(default,’mishikawa’,’Masaki’,’Ishikawa’,’03-1234-1234′,0);
INSERT INTO employees VALUES(default,’hokada’,’Hitoshi’,’Okada’,’00-0000-0000′,1);
INSERT INTO employees VALUES(default,’tyamamoto’,’Takeshi’,’Yamamoto’,’090-0909-0909′,1);
INSERT INTO employees VALUES(default,’amori’,’Akira’,’Mori’,’111-111-111′,0);

次にRubyとRails2.0をインストールします。Rubyはワンクリックインストーラで簡単にインストールできます。(http://www.rubyonrails.org/)その後、以下のコマンドを実行し、Railsをインストールします。

> gem install rails -y 

 インストールに成功しましたら、”rails demo”コマンドによりdemoディレクトリを作成します。

> rails demo 

demoディレクトリが作成されましたら、その配下のディレクトリにあるconfig/database.ymlファイルを開き、データベースの設定を行います。例えば以下のような設定にします。

development:
  adapter: mysql
  database: dev
  username: user1
  password: pass1
  host: localhost

さらに以下のコマンドを実行しますと簡単なRailsアプリケーションが出来上がります。

> ruby script/generate scaffold Employee 

最後にWebサーバ(WEBrick)を起動します。これは3000ポートで立ち上がります。 

> ruby script/server 

ブラウザで以下のURLを開き、稼動確認ができます。
http://localhost:3000/employees/

 また、RESTで稼動確認するためには以下のURLを開きます。
http://localhost:3000/employees.xml

CurlからRuby on Railsにアクセス

Railsでは以下のようなHTTPアクセスでCRUD(Create/Read/Update/Delete)を実現できるようです。

一覧表示  GET  /employees.xml 
1件表示 GET /employees/1.xml or 2.xml ….
新規作成 POST /employees
更新 PUT  /employees/1 
削除 DELETE /employees/1

※Curl6.0では、PUT、DELETEメソッドがサポートされていませんので、サンプルプログラム内ではGETやPOSTにて代用しています。(将来的にはサポートされる予定です。)

そこで、簡単にアクセスできるレコードセットRailsRecordSetをサンプルとして作成してみましたので、それを用いて連携させてみることとします。まずはRailsRecordSetクラスの入っているパッケージCOM.CURLAP.EXAMPLES.RAILSをimportします。次にRailsRecordSetを引数にRailsサーバのパス・エンティティ名・RecordFieldsを指定して生成します。

{import * from COM.CURLAP.EXAMPLES.RAILS,
    location = “rails/load.scurl”
}

{value
    let record-set:RecordSet =
        {RailsRecordSet
            “http://localhost:3000“,  || rails server
            “employee”,               || entity name
            {RecordFields
                {RecordField “id”, domain = int},
                {RecordField “login”, domain = String},
                {RecordField “firstname”, domain = String},
                {RecordField “lastname”, domain = String},
                {RecordField “phone”, domain = String},
                {RecordField “status”, domain = String}
            }
        } 

この生成したRailsRecordSetをRecordGridコントロールに紐付けることにより、簡単に画面表示させることができます。

        {RecordGrid
            record-source = record-set,
            alternate-row-background = “#ccffff”
        },

また、RecordGrid内でのレコード追加・修正後、commitボタンを押下することにより、データベースへの更新を行うこともできます。

コードサンプルのダウンロード

Ruby on Rails連携サンプル

 

cometdを用いたリアルタイム通信/サーバ・プッシュ

Webアプリケーションではサーバからのデータ配信をクライアント側でリアルタイムに受信する方法が必要となるケースがあります。従来これは画面のリロードやポーリング等による実装がなされてきました。しかし、さらなるリアルタイム性を実現するために「Comet(コメット)」と呼ばれるHTTPセッションを接続し続けて、疑似的にサーバ・プッシュ(サーバ配信)を実装する手法がAjaxとともに多く使われるようになってきました。これをCurlで実装するための方法を記載していきたいと思います。

当サンプルでは、BAYEUXプロトコル(1.0Draft1)というJSONフォーマットで定義されたプロトコルの実装であるcometdのクライアントサイドライブラリを用いて、サーバ・プッシュを実現できるようにしています。そこで、Javaの実装の一つであるJettyに搭載されているcometdを利用して、サンプルを説明していきたいと思います。ちなみにcometd/BAYEUXプロトコルの詳細については以下のサイトを参照してください。
  http://www.cometd.com/

cometd準備及びサンプル実行

1.以下のサイトからJetty6.1.7をダウンロードします。
  http://www.mortbay.org/

2.上記でダウンロードしたzipファイルを展開します。

3-1.Windowsの場合、Jettyを展開したディレクトリのbin/Jetty-Service.exeをダブルクリックし、Jettyを起動します。
3-2.UNISの場合、jetty.shによりJettyサーバを起動します。

4.サンプルpublish-stock-price.curlを実行しますと以下のような画面が表示されますので、「株価配信ボタン」を押下します。

5.別ブラウザにてsubscribe-stock-price.curlを起動し、以下の画面を表示させ、「リアルタイム株価受信開始」ボタンを押下しますと、株価をリアルタイムで受信することができます。

リアタイムアプリケーション・サンプル

これらのサンプルプログラムを元に、実装方法を説明していきます。

我々は、cometdのCurlクライアントであるCometdClientクラスを用意しています。このクラスは以下のメソッドを用意しています。(COM.CURL.MESSAGE-SERVICEパッケージを参照)←リンク

  || 初期化を行います。urlに対象のサーバを指定します。
|| ここでは、http://localhost:8080/cometdを指定しています。
  {method public {initialize url:Url}:void }

  || 購読を開始します。
  {method public {subscribe channel:String, …:EventHandler}:void }

  || 購読を終了します。
  {method public {unsubscribe channel:String}:void }

  || データを購読しているクライアントに対して送信します。
  {method public {publish channel:String, message:JsonValue}:void }

  || サーバとの接続を切断します。
  {method public {disconnect}:void }

まず、はじめにCometdClientクラスのインスタンスを生成し、initializeメソッドを実行します。これによりcometdサーバとの接続が確立されます。

    def client:Client = {CometdClient}
    {client.initialize {url cometd-server}}

次に受信する側(ここでは株価情報を受信するsubscribe-stock-price.curl)で、subscribeメソッドを用いて、配信(publishメソッド)データを受け取るための処理を開始します。その後、他のクライアントもしくはローカルからpublishメソッドが実行されますと(ここでは株価情報を配信しているpublish-stock-price.curl)、受信側ではsubscribeメソッドの引数として指定されたイベントMessageReceiveEventが発生します。publishによる送信されたデータはMessageReceiveEventのmessageゲッターで取得することができます。この値はJSONオブジェクトとなっています。また、subscribe/subscribe/unsubscribeメソッドの引数にある”channel”とは、グループのようなものです。この同一チャネル間の中のみでデータ送受信を行います。複数のチャネルに対してsubscribe/publishをすることが可能となっていますが、違うチャネル間ではデータ送受信を行うことができません。 

{client.subscribe
channel,
    {on e:MessageReceiveEvent do
        let msg:JsonValue = e.message
        || ….
     }
}

 

{client.publish
channel,
{JsonObject
“ticker”, tickers[{rand.next-in-range 0, 3}],
“price”, {rand.next-in-range -20, 20} & “”
    }
}

subscribeを中止する場合は、unsubscribeメソッドを実行します。また、コネクションを切断するためには、disconnectメソッドを実行します。

おまけ

Apache Tomcatでcometdを稼動させるには、Jettyに格納されている以下のファイルをTomcatのホームディレクトリ配下のwebappsにコピーしてください。

  cometd-bayeux-6.1.6rc1.jar
  cometd-api-0.9.20070918.jar
  jetty-util-6.1.6rc1.jar

サポートバージョン

RTE6.0以上

コードサンプルのダウンロード

cometdクライアントサンプル

comtedソースコード(Apps Galleryから)