RPGとYAJLでJSONのウェブ・サービスを提供する
RESTとJSONを使用して他のコンピュータ上で実行されているデータにアクセスする
どんな組織でも複数のコンピュータを備えているでしょう。ですから、企業で1台のコンピュータ上のプログラムが他のコンピュータ上で稼動しているプログラムを呼び出す方法が必要になるのは当たり前のことです。たとえば、自社の営業社員に対して指定された月にどれだけ営業報酬を支払えばよいかを決めるために、人事アプリケーションを実行しているサーバーが営業アプリケーションを稼動しているサーバーを呼び出す必要が出てくるかもしれません。
ここ数年に渡ってIT業界は他のコンピュータで実行されているプログラムを呼び出す方法をいくつか開発してきましたが、私が見た中で最善の方法はウェブサービスです。本稿では、データをJSON (JavaScript Object Notation)フォーマット式で返すREST(REpresentational State Transfer)ウェブサービスをRPGで記述する方法について説明します。その説明の中で、自分にとってはこれがなくては仕事ができないと思っているオープンソースのJSONツールであるYAJL (Yet Another JSON Library)を紹介します。
RESTとJSONの場合
私が自部門のために書くプログラムでRESTウェブサービスを好んで使うのは、RESTの方がオーバーヘッドが小さく、また他への依存度が少ないからです。一方、お客様、ベンダーやその他の自部門外の方が使うウェブ・サービスを書くのであれば、SOAP (Simple Object Access Protocol)を使います。SOAPのコーディングを支援してくれるツールは豊富にありますし、そうしたツールのおかげでウェブ・サービスの使い方を非常にたくさんの人に教える必要はなくなるのです。しかしSOAPのウェブサービスを書くのは、ツールが複雑さのレイヤーを加え、サービスを遅くするオーバーヘッドを伴うのでより複雑になります。SOAPではHTTPサーバー、アプリケーション・サーバー、ウェブ・サービス・サーバーと自分が書くプログラムという形になるのがほとんどです。これに対してRESTでは、RESTとHTTPサーバーと自分が書くプログラムだけとなります。ソフトウェアのレイヤーが少ないということはオーバーヘッドが小さいということになります。
多様性もRESTのもう一つの利点です。SOAPのウェブサービスはそのデータをSOAPフォーマットで送信しますが、これはXMLの一つのタイプです。JSONデータ(あるいは画像、PDF、ワード文書などといったその他のフォーマットのデータ)を送信する必要があるときは、そのデータをまずエンコードしてXML文書の中に組み入れられるようにします。これもまたウェブサービスにとってはオーバーヘッドになります。しかしRESTではどんなフォーマットでもデータを送信できるので、RAW画像、PDF文書、その他送る必要のあるデータならなんでも送信することができます。本稿では、XMLフォーマットにエンコードすることなくJSONフォーマットでデータを送信する方法について説明します(これはSOAPではできません)。
JSONのデータ・フォーマットはXMLのフォーマットに多くの点で似ています。JSONはXMLのように名前つきのデータ/値のペアと他のデータの中に入れ子になったデータが使用でき、プレーンなテキストのフォーマットです。しかし似ている点はここまでです。JSONの方が軽量です。すなわちXMLに比べて少ない文字数で同じデータを表現できます。さらにJSONは常にUTF-8でコーディングされます(他のエンコードはサポートしていません)ので、取り扱いがシンプルです。しかもいつもUTF-8ですから、時によってASCIIやその他のUnicodeの種類であるとか心配する必要がなく、プログラム中でそうした可能性を考える必要がありません。
私がJSONを気に入っているのはJavaScriptの一種で変数を初期化する方法として使われているからです。同様なものとしてRPGにはデータの初期化の方法がいくつかあります。スタンドアロンの変数を初期化するのにRPGではD仕様のINZキーワードがあります。配列やテーブルを初期化するのにはコンパイル時データ(プログラムの最後の**行の下においたデータ)があります。) しかしJavaScriptではJSONを使ってデータを初期化します。だから私はJSONをJavaScriptプログラムと通信するウェブサービスで使用するのです。というのもJSONはJavaScriptから簡単に使えXMLよりも性能が高いからです。
Yet Another JSONライブラリ
YAJLはロイド・ヒライエル氏が開発したオープンソースのJSONジェネレータであり構文解析機能です。同氏はMozillaに勤務していますので間違いなくJSONのようなブラウザ技術を熟知しています。YAJLにはJSONデータを構文解析する方法が2つあり、本稿で紹介するJSONジェネレータも備えています。では他のJSONツールではなくなぜYAJLを使うのでしょうか。昨年、私とProfound Logic社の同僚は自分たちのソフトウェア開発に使用する最高のJSON構文解析ツールを探していました。私たちが開発していたソフトウェアは性能の高さで知られていて、JSONの構文解析機能を使用すると実行速度が落ちるのではないかと心配していたため、最高速の構文解析機能が必要だったのです。数十個のさまざまなJSONツールを調査し、YAJLが飛びぬけて効率が高いということがわかりました。YAJLは2番目に速いツールの倍の速度だったのです。
YAJLはC言語で記述されておりC言語のプログラムから呼び出されるように設計されています。しかし私はRPGのフロントエンドを記述して、YAJLをRPGプログラムから使いやすいようにしました。YAJLと私が作ったRPGのフロントエンド、および本稿で紹介するコード例は私のウェブサイトhttp://www.scottklement.com/から入手可能です。
ウェブサービスを構築する
本稿のために私は株の状態を取得するウェブサービスをRPGで記述しました。株の個々の項目の状態を要求することができます(または項目が指定されていない場合は全ての項目の状態が返されます)。基本的な考え方としては、呼び出し側はhttp://your-system.com:8500/stockqty/itemnoという形式でユニフォーム・リソース識別子(URI: Uniform Resource Identifier)を提供します。ここで「your-system.com:8500」の部分を、このサービスを実行するコンピュータのIPアドレスまたはドメイン名とHTTPサーバーのポート番号で置き換えます。URLのitemno部分はオプションです。このオプションを指定すると株のその項目の状態のみを提供し、指定しないと下部の全ての項目の状態を提供します。 このウェブサービスのRPGコードの主要な部分を図1に示します。このプログラムはウェブサービスを呼び出すのに使われていたURIを取り出すことから始まっています(図1の呼び出しA)。ホスト名とポートは環境ごとに異なるものですから、URIの中で「stockqty」という文字列を探すことでホスト名とポートを読み飛ばし(呼び出しB)、その直後に来るデータの文字列の一部を取り出します(呼び出しC)。直後にデータがない、あるいはエラーが発生した場合は項目番号を空白に設定します(呼び出しD)。主要な部分の残りの部分はreadData、createJSON、sendReplyという3つのサブプロシージャを呼び出しています(呼び出しE)。
データ構造をテンプレートとして定義する
readDataデータ・プロシージャは項目番号を入力として受け取り、物理ファイルを読み込んで株の状態データを取得します。本稿ではRPGのファイルを読み込む方法をご存知であると仮定していますので、readDataサブプロシージャのコードは提供していません。そのデータ構造のレイアウトを示した出力を図2に示してありますのでおわかりいただけると思います。この出力はデータが正しく読み込まれた(「success」)か否かを追跡するサブフィールドを含んだ「data_t」という名前のメインのデータ構造テンプレート(呼び出しB)、正しく読み込まれなかった(「errmsg」)場合のエラーメッセージ、読み込んだ項目数(「count」)、そして最大999項目までの配列で構成されています。項目の配列はLIKEDSキーワードを使用してitem_tデータ構造を参照し(呼び出しA)、そこには返されてきた各項目の製品ID、製品名、価格、株数が含まれています。私はデータ構造をテンプレートとして定義し、LIKEDSキーワードで複製を作るのが好きです。なぜならそのテンプレート構造をサブプロシージャのプロトタイプとして使用できるからです。また私はこのテンプレートを使用して「data」というデータ構造も作り(呼び出しC)、サブプロシージャを呼び出すときのパラメータとして使用しています。
YAJLを使ってデータ構造からJSONを作成する
データをデータ構造に読み込んだら、次のステップはそのデータ構造と同じフォーマットのJSONデータを作成します。このようにして私が作成しているウェブサービスの(JavaScriptで記述されている)利用側は、RPGプログラムがデータを使用するのと同じようにこのデータを使用することができます。YAJLの呼び出し方を説明する前に図3を見てください。この図には送信するJSONデータの例が示されています。JSONは中括弧が新しいオブジェクトの始まりになるように構造化されています。JSONでいう「オブジェクト」とはRPGでいうところの「データ構造」に相当します。JSONデータの各オブジェクト内ではフィールド名が引用符で囲まれ、その後にコロン、そしてそのフィールドの値が続いているのがおわかりいただけます。各フィールドと次のフィールドの間はカンマで区切られています。図3の呼び出しAではオブジェクトの最初の3つのサブフィールドが示されています。「success」サブフィールドは2値のサブフィールドでtrueまたはfalseの値が入ります。この例ではtrueにセットされていてウェブサービスが正常に終了したことを示しています。「errmsg」サブフィールドはエラーメッセージを決定します(この場合はエラーがなかったので空の文字列となっています)。「count」サブフィールドは3にセットされていて3つの項目が返されたことを示しています。
返された項目は図3の呼び出しB、C、Dに示されています。呼び出しの前後の四角括弧は繰り返しデータの配列であることを示しています。この配列中の各項目は次の項目との間がカンマで区切られています。各項目が中括弧で始まって中括弧で終わっている点に注意してください。これは項目配列にはオブジェクトが含まれ各オブジェクトに4つのサブフィールドがあるからです。つまり、RPGのデータ構造と同じフォーマットであるということです。メインのデータ構造には配列が含まれており、その配列の中には4つのサブフィールドを含んだデータ構造があります。この4つのサブフィールドの出力がJSONフォーマットであるということは、このウェブサービスを呼び出したJavaScriptルーチンがRPGプログラムと同じフォーマットでデータを受け取るということを意味しています。
JSONのフォーマットを理解したら、次にそれを生成するRPGのコードを見てみましょう。図4にcreateJSONサブプロシージャを示していますが、このサブプロシージャはYAJLのルーチンを使用してJSONデータを生成しています。
最初のステップはyajl_genOpenサブプロシージャを呼び出すことで(図4の呼び出しA)、このサブプロシージャはYAJLのJSONジェネレータを初期化します。yajl_genOpenサブプロシージャに渡す必要のあるパラメータは、JSONデータを人間が読みやすいようにするには*ON、データをできるだけコンパクトにしたい場合は*OFFというインジケータだけです。私個人的には、プログラムをデバッグしているときはこのインジケータを*ONに設定します。そうすることで出力がずっと読みやすくなるからです。プログラムが正しく動いているとわかったらこのインジケータをオフにセットして、JSONデータを一行にまとめてコンパクトにします。コンパクトにしたデータはバイト数が少ないので、ネットワーク上で速く伝送され、自分のアプリケーションの実行速度が速くなるからです。
YAJLのJSONジェネレータには新しいオブジェクト(JSONでいうデータ構造)を開始し、新しい配列を開始し、文字、数値、2値(true/false)のフォーマットのフィールドを挿入するルーチンが含まれています。基本的な考え方は、オブジェクトを開始したら、追加した全てのフィールドはそのオブジェクトの一部とみなされるということです。同様に、配列を開始したら追加した全てのフィールドはその配列に追加されることになります。オブジェクトや配列を終了するルーチンもあり、常に最後に追加したオブジェクトを終了します。
返されるデータは1つのデータ構造で、JSONのコードも同様に動作して欲しいので、私はyajl_beginObjを呼び出してオブジェクトを開始しました(呼び出しB)。このサブルーチンはオブジェクトを開始する中括弧を出力します。これに続いてyajl_addBool、yajl_addChar、yajl_addNumを呼び出して(呼び出しC)、 success、errmsg、countの各サブフィールド(とその値)をJSONドキュメントに出力します。オブジェクトを開始しましたがまだ終了していないので、この3つのフィールドは開始したオブジェクトに追加されます。特殊文字はYAJLが処理しますので、このRPGでは特殊文字をエスケープする必要はありません。またYAJLはデータをUTF-8に変換します。新しいオブジェクトや配列を開始するたびに、それに含まれるサブフィールドをインデントするようにしています。こうするとサブフィールドが配列に追加されたことがすぐにわかるので、コードが読みやすくなると思うからです。
次のステップは項目からなる配列を同じオブジェクト内に挿入します。これはyajl_beginArrayルーチンを呼び出して(呼び出しD)配列を開始し、JSONドキュメントに四角括弧を出力することで行います。RPGでは私はFORループを使用して各項目オブジェクトを配列に追加します(呼び出しE)。というのはまだ配列を終了している状態ではないので、追加する各新しいオブジェクトは配列の一部とみなされ、それがわかるようにインデントしています。ループが終了しすべての項目を配列に追加したら、yajl_endArrayサブプロシージャを呼び出して配列を終了し(呼び出しF)、終わりの四角括弧を挿入してYAJLに対して配列が終了したことを伝えます。これ以後追加するフィールドはいずれもこの配列の一部とはなりません。1つのJSONドキュメント中に複数の配列を持たせることができるので、yajl_endArrayサブプロシージャは常に最後に開始された配列を終了します。また、配列の中に配列を入れ子にすることもできます。これは前の配列を終了せずにyajl_startArrayサブプロシージャを呼び出して最初の配列の中で2番目の配列を開始すれば良いのです。
最後のステップではcreateJSONルーチンがyajl_endObjを呼び出してJSONの応答全体を含むオブジェクトを終了させ(呼び出しG)、終了の中括弧を挿入します。
JSONデータの構築は終了しましたので、これをRPG変数に読み込んでHTTPサーバーに送り返すことができるようにする必要があります。これを受けてHTTPサーバーがウェブサービスの呼び出し側にそれを送り返してくれます。これを実行するにはyajl_copyBufという名前のルーチンを呼び出し、データをYAJLの内部JSONバッファからjsonDataという名前のRPG変数にコピーします。しかしjsonDataはVARYINGフィールドで、yajl_copyBufルーチンはデータをコピーする前の生のバッファに対して動作するので、jsonDataをその最大長にセットする必要があります(呼び出しH)。そしてyajl_copyBufを呼び出して(呼び出しI)YAJLの内部データ・バッファからRPGのフィールドへデータをコピーし、jsonDataの長さを適切な長さに設定して(呼び出しJ) VARYINGフィールドが正しく処理されるようにします。最後に、JSONドキュメントがjsonData中に入りましたので、yajl_genCloseを呼び出してYAJLに対してJSONジェネレータは完了したと伝えることできます(呼び出しK)。
呼び出しIで説明したyajl_copyBufルーチンは以下の4つのパラメータを受け取ります。
- 出力データのCCSID (Coded Character Set Identifier)。必要とするCCSIDにデータを変換するようYAJLに対して指示することができます。この場合CCSID 1208を使用しましたがこれはUTF-8のことです。
- データを受け取りたいバッファのアドレス。このアドレスはRPGの英数字変数(VARYINGまたは固定長)のアドレスでも構いませんし、メモリ中に自分で割り当てたバッファへのポインタでも構いません。
- 上記2のパラメータのバッファのサイズ。
- YAJLがバッファ中に保存するデータの長さ(バイト数)を受け取る変数。
ウェブサービスの呼び出し側に応答を送信する
sendReplyサブプロシージャは、JSONデータをHTTPサーバーに送信してJSONデータがウェブサービスの呼び出し側に送られるようにします。このサブプロシージャの一部を図5に示します。図5の呼び出しAでは、HTTPサーバーに対して送信しようとしているデータのタイプを表すHTTPヘッダを生成しています(application/jsonとするとデータをJSONとして認識します)。呼び出しBで、プログラムはIBMが提供するQtmhWrStout APIを呼び出してヘッダとJSONデータをIBM HTTPサーバー(Powered by Apache)へ送り、HTTPサーバーがそのデータをウェブサービスの呼び出し側に送信します。
ウェブサービスのインストール
ウェブサービスを呼び出すことができるようにする前に、IBM HTTPサーバーに対してこのウェブサービスのことを伝えなければなりません。私はSKWEBSRVという名前のライブラリを作成して、皆さんに使っていただきたいウェブサービスをこのライブラリの中に置くことにしました。次にHTTP Adminサーバー(ポート2001上で稼動)に行ってInternet Configurations、IBM Web Administration、All Serversを選択し、ウェブサービスを追加したいHTTPサーバーを選択します。
HTTPサーバーがまだ構築されていない場合はHTTPサーバーを構築することができます。Create HTTP Serverをクリックすると使い勝手の良いウィザードがHTTPサーバー構築のプロセスをガイドしてくれます。Create Web Services Serverをクリックしてはいけません。そうするとSOAPウェブサービスを作成するのにIBM Integrated Web Servicesツールを使用してしまうからです。本稿では通常のHTTPサーバーを使用しています。
STOCKQTYウェブサービスを追加したいHTTPサーバーの構成の中で、左側にあるナビゲーション・ペインを下にスクロールし、Edit Configuration Fileを選択してキーワードを追加します。これを図6に示します。
ScriptAliasキーワード(図6の呼び出しA)はHTTPサーバーに対して「stockqty」で始まるURLはすべてSKWEBSRVライブラリ中のSTOCKQTYという名前のプログラムを呼び出すように伝えます。ディレクティブの
ウェブサービスを呼び出す
ウェブサービスを呼び出すにはウェブサービスの呼び出し側プログラムを作成します。このプログラムは、Ajax (Asynchronous JavaScript and XML)を使用してウェブサービスを呼び出すJavaScriptコードを含んだ簡単なHTMLページで構成されています。
HTTPサーバーからJSONフォーマットの応答を受け取り、eval()関数を介してJavaScriptコンパイラを起動してJavaScript変数に変換するJavaScriptコードの一部を図7に示します(図7の呼び出しA)。ご覧の通りJSONデータをJavaScript変数に変換するのは非常に簡単です。このコードは次に返されたデータを繰り返し処理してHTMLのテーブルを作成します(図7の呼び出しB)。
試してみる
RPGプログラミングのスキルを使用してJSONフォーマットでデータを返すRESTウェブサービスを作成する方法がおわかりいただけたと思いますので、これをインストールして試してみるのが一番の道です。RPGコードをデバッグ・モードで実行してみてプログラムがどのように動作するのかをご自分で確認できます。そしてそのプログラムを自分のデータベース・ファイルを使用するように変換してみれば、YAJL JSONツールを使用してRPG中のRESTウェブサービスを作成するのが簡単だということがおわかりいただけるでしょう。