サンプル・プログラム
WebAPI利用イメージ
クライアント(ブラウザ)からインターネットを経由し、WikipediaやAmazon、Googleといったクラウドの WebAPI を利用する。
クライアントから検索キーワードなどをGET/POSTなどのhttp通信で WebAPI へ送信すると、結果が XML や JSON の形で返ってくる。WebAPI の裏側にはデータベースなどのシステムがあるかもしれないが、クライアントからはそれらを意識しないで済む。
郵便番号→住所検索
利用は無償だが、このWebAPIの 利用規約を遵守すること。
URL |
---|
https://api.thni.net/jzip/X0401/JSONP/{1}/{2}.js |
フィールド名 | 要否 | 内 容 |
---|---|---|
1 | 必須 | 郵便番号(上3桁) |
2 | 必須 | 郵便番号(下4桁) |
0071: /**
0072: * ZIP SEARCH API SERVICE API:郵便番号→住所検索(JSONP)
0073: * @param String zip1 郵便番号(ハイフンの前 3桁)
0074: * @param String zip2 郵便番号(ハイフンの後 4桁)
0075: * @return String リクエストURL
0076: */
0077: function getURL_Zip2Address(zip1, zip2) {
0078: let url = 'https://api.thni.net/jzip/X0401/JSONP/' + zip1 + '/' + zip2 + '.js';
0079:
0080: return url;
0081: }
0083: /**
0084: * 検索ボタン押下処理
0085: * @param なし
0086: * @return なし
0087: */
0088: function zip2address() {
0089: //エラー・クリア
0090: let errmsg = '';
0091: document.getElementById('error').innerHTML = errmsg;
0092:
0093: //変換後テキスト・クリア
0094: document.getElementById('state').value = '';
0095: document.getElementById('city').value = '';
0096: document.getElementById('street').value = '';
0097: document.getElementById('dest').value = '';
0098:
0099: //検索郵便番号
0100: let zip = document.getElementById('zip').value;
0101: //空白除去
0102: zip = zip.trim();
0103: //バリデーション
0104: const regex = new RegExp('^[0-9]{3}\-[0-9]{4}$')
0105: if (! regex.test(zip)) {
0106: errmsg = '郵便番号が間違っています.'
0107: console.error(errmsg);
0108: document.getElementById('error').innerHTML = 'エラー:' + errmsg;
0109: return;
0110: }
0111: //郵便番号をハイフンで分離
0112: let zips = zip.split('-');
0113:
0114: //XMLHttpRequestオブジェクト生成
0115: let request = new XMLHttpRequest();
0116:
0117: //WebAPIリクエスト
0118: let url = getURL_Zip2Address(zips[0], zips[1]);
0119: console.log(url);
0120: document.getElementById('WebAPI').innerHTML = '※WebAPI <a href="' + url + '">' + url + '</a>';
0121:
0122: //SOP回避
0123: let target = document.createElement('script');
0124: target.charset = 'utf-8';
0125: target.src = url;
0126: target.onerror = function() {
0127: errmsg = '郵便番号が間違っているかWebAPIに接続できません.'
0128: console.error(errmsg);
0129: document.getElementById('error').innerHTML = 'エラー:' + errmsg;
0130: }
0131: document.body.appendChild(target);
0132:
0133: //JSONP実行関数
0134: target = document.createElement('script');
0135: target.innerHTML = (
0136: function ZipSearchValue(result) {
0137: console.log(result);
0138: document.getElementById('state').value = result.stateName;
0139: document.getElementById('city').value = result.city;
0140: document.getElementById('street').value = result.street;
0141: document.getElementById('dest').value = result.stateName + result.city + result.street;
0142: }
0143: );
0144: target.onerror = function() {
0145: errmsg = '郵便番号が間違っているかWebAPIに接続できません.'
0146: console.error(errmsg);
0147: document.getElementById('error').innerHTML = 'エラー:' + errmsg;
0148: }
0149: document.body.appendChild(target);
0150: }
まず、入力した郵便番号をハイフンで3桁と4桁に分離する。これには split メソッドを利用した。
次に、WebAPI を非同期呼び出しするために XMLHttpRequest オブジェクトを生成する。これは、WebAPI のような非同期通信を行う Ajax(Asynchronous JavaScript + XML)通信を行うときに使うオブジェクトだ。XMLの名前が付いているが、JSONやJSONPでも利用できる。
ここで、JavaScriptには、同一オリジンのリソースにしかアクセスできないという制限 SOP(Same-Origin Policy;同一生成元ポリシー)が課されている。同一オリジンというのは、同じドメイン、同じポート番号であることを指す。
SOPはセキュリティ対策の一環で、JavaScriptが悪意のあるスクリプトを実行したり、異常なデータを参照することを回避するための仕組みである。
しかし、WebAPI にアクセスするには、この SOP が邪魔になる。そこで、サーバ側で特殊なHTTPヘッダ項目を追加することで、送り出したWebページ上のスクリプトがWebブラウザから別のサーバへアクセスできるようにする CORS(Cross-Origin Resource Sharing;クロスオリジンリソース共有)が施されていることが望ましい。
だが、今回利用する ZIP SEARCH API SERVICE を含め、CORS に対応している WebAPI は少ない。
ここでは次善の策として、JSONPを使って SOP を回避する。
具体的には、2つのscriptを動的に追加する。
1つは、WebAPI そのものを srcとするscriptだ。これにより、scriptが WebAPI と同一オリジンで実行されると見せかけることになる。
2つめは、JSONPで実行されるscriptを追加する。JSONP は、JSONデータを解析する関数を追加したデータ形式である。ZIP SEARCH API SERVICE ではJSOP実行関数は ZipSearchValue 固定だ。
scriptの追加には appendChild メソッドを用いた。エラー発生時には、console.logとHTMLにエラーを出力する。
JSONP実行関数は、JSON形式の応答データをそのままHTMLの要素に代入するだけである。
最後に、都道府県名、市町村名、町名などを結合し、クリップボードにコピーできるようにした。
このプログラムは、よくある会員登録で郵便番号ボタンをクリックすると自動的に住所が入力されるGUIに応用できる。
Wikipedia検索
URL |
---|
https://ja.wikipedia.org/w/api.php |
フィールド名 | 要否 | 内 容 |
---|---|---|
format | 省略可 | xml, json, yaml等 |
callback | 省略可 | コールバック関数名 |
action | 必須 | 操作:ここではquery |
prop | 省略可 | action固有のパラメータ。記事の各構成要素を取得する。ここではextractsを指定し、サマリを抽出する。 |
explaintext | 省略可 | 出力をHTMLではなくプレーンテキストにする。 |
redirects | 省略可 | リダイレクト記事を含める。 |
titles | 必須 | 見出し検索語。 |
0068: /**
0069: * Wikipedia API:サマリのリクエストURLを取得する
0070: * @param String query 検索キーワード
0071: * @return String リクエストURL
0072: */
0073: function getURL_WikipediaAPI_summary(query) {
0074: let url = 'https://ja.wikipedia.org/w/api.php?format=json&callback=callback&&action=query&prop=extracts&exintro&explaintext&redirects=1&titles=' + encodeURI(query);
0075:
0076: return url;
0077: }
前述の getURL_Zip2Address と異なり、パラメータを GETで渡す。
Wikipedia API には明示的にJSONP呼び出しはないが、コールバック関数を指定することによって同様の処理ができる。
0079: /**
0080: * 検索ボタン押下処理
0081: * @param なし
0082: * @return なし
0083: */
0084: function wikisearch() {
0085: //エラー・クリア
0086: let errmsg = '';
0087: document.getElementById('error').innerHTML = errmsg;
0088:
0089: //検索結果クリア
0090: document.getElementById('dest').value = '';
0091:
0092: //検索キーワード
0093: let query = document.getElementById('query').value;
0094: //空白除去
0095: query = query.trim();
0096: //入力文字のエスケープ
0097: query = htmlspecialchars(query);
0098:
0099: //XMLHttpRequestオブジェクト生成
0100: let request = new XMLHttpRequest();
0101:
0102: //WebAPIリクエスト
0103: let url = getURL_WikipediaAPI_summary(query);
0104: console.log(url);
0105: document.getElementById('WebAPI').innerHTML = '※WebAPI <a href="' + url + '">' + url + '</a>';
0106:
0107: //SOP回避
0108: let target = document.createElement('script');
0109: target.charset = 'utf-8';
0110: target.src = url;
0111: target.onerror = function() {
0112: errmsg = 'WebAPIに接続できません.'
0113: console.error(errmsg);
0114: document.getElementById('error').innerHTML = 'エラー:' + errmsg;
0115: }
0116: document.body.appendChild(target);
0117:
0118: //JSONP実行関数
0119: target = document.createElement('script');
0120: target.innerHTML = (
0121: function callback(result) {
0122: console.log(result);
0123: let key = Object.keys(result.query.pages)[0];
0124: let summary = result.query.pages[key].extract;
0125: //応答結果あり
0126: if (typeof summary != 'undefined') {
0127: document.getElementById('dest').value = summary;
0128: //応答結果なし
0129: } else {
0130: errmsg = '検索キーワードが見つかりません.'
0131: console.error(errmsg);
0132: document.getElementById('error').innerHTML = 'エラー:' + errmsg;
0133: }
0134: }
0135: );
0136: target.onerror = function() {
0137: errmsg = 'WebAPIに接続できません.'
0138: console.error(errmsg);
0139: document.getElementById('error').innerHTML = 'エラー:' + errmsg;
0140: }
0141: document.body.appendChild(target);
0142: }
留意点すべきは、応答で得られるJSONオブジェクトのページIDが可変である点。
IEでも動作するよう、Object.keys を利用し、応答で得られるJSONオブジェクト result.query.pages の最初の要素を取得する。これがページIDである。
ページIDから目的のサマリーが存在するかどうかを typedef 演算子で調べ、無ければHTMLとconsole.logにエラー出力する。
コラム:CORS問題
あるクライアントまたはすべてのクライアントに CORS を許可するには、サーバ側で Access-Control-Allow-Origin ヘッダを送信する必要がある。
自サーバやレンタルサーバであれば、この設定ができるだろうが、Wikipediaのような既存クラウドサービスで設定することはできない。そこで今回は、JSONPを使って回避をしている。
ただし、CORS 導入の目的から明らかなように、JSONPを使ったり CORS を回避することは、プログラムが脆弱性リスクを抱えることになる。
参考サイト
- ZIP SEARCH API SERVICE 「JIS X0401」対応版
- API:メイン ページ:MediaWiki
- PHPで「Wikipedia API」を利用する:ぱふぅ家のホームページ
- PHPで郵便番号から住所を求める:ぱふぅ家のホームページ
JavaScriptを使って、今回は、ZIP SEARCH API SERVICE を使って郵便番号を住所に変換するプログラムと、Wikipedia API を使ってWikipediaの見出し語検索を行うプログラムを作ってみよう。