« TP-LINK AC1200 Wi-Fi Range Extender RE300を設置してみた | トップページ | Apache Solrを使って形態素解析とBigramを併用してテキスト検索してみる »

2020.11.03

ECMAScript 2015とPython間におけるJSONPを用いたやりとり

以前、jQueryとPython間におけるJSONPを用いたやりとりでjQueryを使ったJSONP通信のサンプルを作りましたが、今回はjQueryを使わずに同じ機能を持つサンプルを作ってみます。

ここでは、これでできる! クロスブラウザJavaScript入門 第11回 JSONP入門を参考に、JavaScriptの標準仕様であるECMAScript2015の記法を用いたサンプルコードを書いてみました。

サンプルコードのHTMLファイルを用意します。まず、JSONPを用いるサンプルコードは以下のとおり。

index.html


<!DOCTYPE html>
<html>
<h1>ECMAScript2015 + Python (JSONP)</h1<
<form id="hoge">
<input name="foo" type="" /><br />
<input name="bar" type="" /><br />
<input name="baz" type="" /><br />
</form>

<a id="foo" href="#">show</a>
<a id="bar" href="#">clear</a></pre>
<div id="baz"></div>
<pre class="brush: text">

<script type="text/javascript" src="sample.js"></script>

</body>
</html>

JSONPを用いるサンプル

sample.js


const url = 'http://[host name]/sample.py?callback=';

// callback関数名に用いる乱数
let randomCount = Math.floor(Math.random() * 100000);

const waitLoad = new Promise((resolve, reject) => {
document.addEventListener('DOMContentLoaded', () => {resolve();}, false);
});

const click = new Promise((resolve, reject) => {
document.getElementById('foo').addEventListener('click', () => {
resolve(cws());
}, false);
});

const clear = new Promise((resolve, reject) => {
document.getElementById('bar').addEventListener('click', () => {
document.getElementById('baz').textContent = '';
resolve();
}, false);
});

const p2j = (d) => {
let text = [];
for (let i = 0; i < d.length; i++) {
text[i] = d[i].name + '=' + encodeURIComponent(d[i].value);
}
return text.join('&');
};

const cws = () => {
let mes = document.getElementById('baz');
let d = document.querySelectorAll('#hoge > input');
randomCount += 1;
let callbackName = 'callback\._' + randomCount;
let arg = url + callbackName + '&' + p2j(d);

window.callback = {
['_' + randomCount]: (data) => {
if (data === null) {
mes.textContent = 'データが存在しませんでした。';
} else {
let mesText = "";
for (let i in data) {
mesText += data[i].key + ';' + data[i].value + ' ';
}
mes.textContent = mesText;
}
}
};

let scr = document.createElement('script');
scr.src = arg;
scr.id = 'jsonp';
let scriptNode = document.getElementsByTagName('script').item(0);
if (document.getElementById('jsonp') !== null) {
scriptNode.parentNode
.replaceChild(scr, scriptNode.parentNode.lastChild);
} else {
scriptNode.parentNode.appendChild(scr);
}
};

// Main Routine
waitLoad
.then(click)
.catch((e) => console.log(e));

waitLoad
.then(clear)
.catch((e) => console.log(e));

// EOF

いかがでしょうか。jQueryの記法よりもコード量が増えてはいますが、ECMAScript2015の記法を用いることである程度すっきりと書けていると思います。
33行で変数dにinputタグに格納されている値を格納します。
35行で最初に決めた乱数を一回呼ぶごとにインクリメントした値を含むcallback関数名を決定します。
38-50行でcallback関数をグローバルオブジェクトとして定義します。
52-61行でドキュメントにscriptノードを追加することによりクロスドメインのsample.pyを呼び出し、callback関数を受け取ります。2回目以降scriptノードを入れ替えています。

JSON形式で機能を呼び出すサンプルコードも掲示しておきます。

index2.html


<!DOCTYPE html>
<html>
<h1>ECMAScript2015 + Python (JSON)</h1<
<form id="hoge">
<input name="foo" type="" /><br />
<input name="bar" type="" /><br />
<input name="baz" type="" /><br />
</form>

<a id="foo" href="#">show</a>
<a id="bar" href="#">clear</a></pre>
<div id="baz"></div>
<pre class="brush: text">

<script type="text/javascript" src="sample2.js"></script>

</body>
</html>

sample2.js


const url = './sample.py?';

const waitLoad = new Promise((resolve, reject) => {
document.addEventListener('DOMContentLoaded', () => {resolve();}, false);
});

const click = new Promise((resolve, reject) => {
document.getElementById('foo').addEventListener('click', () => {
resolve(cws());
}, false);
});

const clear = new Promise((resolve, reject) => {
document.getElementById('bar').addEventListener('click', () => {
document.getElementById('baz').textContent = '';
resolve();
}, false);
});

const p2j = (d) => {
let text = [];
for (let i = 0; i < d.length; i++) {
text[i] = d[i].name + '=' + encodeURIComponent(d[i].value);
}
return text.join('&');
};

const cws = () => {
let mes = document.getElementById('baz');
let d = document.querySelectorAll('#hoge > input');
let arg = url + p2j(d);
const xhr = new XMLHttpRequest();

xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
// console.log(xhr.responseText);
let data = JSON.parse(xhr.responseText);
if (data === null) {
mes.textContent = 'データが存在しませんでした。';
} else {
let mesText = "";
for (let i in data) {
mesText += data[i].key + ';' + data[i].value + ' ';
}
mes.textContent = mesText;
}
} else {
window.alert('サーバエラーが発生しました。');
}
} else {
mes.textContent = '通信中...';
}
};

xhr.open('GET', arg, true);
xhr.send(null);
};

// Main Routine
waitLoad
.then(click)
.catch((e) => console.log(e));

waitLoad
.then(clear)
.catch((e) => console.log(e));

// EOF

こちらは素直なajaxコードになります。

次にサーバサイドに配置するPythonコードです。

sample.py (JSON, JSONP共用)


#!/usr/bin/env python
# coding: utf-8

import cgi
import cgitb; cgitb.enable()
import json
import re

form = cgi.FieldStorage()

foo = form.getfirst("foo", "")
bar = form.getfirst("bar", "")
baz = form.getfirst("baz", "")

r = json.dumps([{'key':'foo','value':foo}, \
{'key':'bar','value':bar}, \
{'key':'baz','value':baz}], \
sort_keys=True, indent=4)

if 'callback' in form:
callback = form.getfirst('callback', "callback")
callback = re.sub(r'[^a-zA-Z_0-9\.]', '', callback)
print ("Content-type: application/javascript; charset=utf-8\n\n")
print ('%s(%s)' % (callback, r))
else:
print ("Content-type: application/json; charset=utf-8\n\n")
print (r)

22行では、callback関数を返却するときに意図しないjavascriptコードを混入できないように英数字及びピリオド以外の文字をcallback関数名に含められないようにしています。

JSONP形式では、HTMLファイルにscriptタグの形で、例えばcallback._17831というcallback関数名を持つ、javascript内に定義した同名のcallback関数を実行します。次の関数がドキュメントノードのscriptタグに返されるイメージです。こうすることでHTMLファイルからクロスドメインに配置されたjsファイルを読み込むことができることと同様に、クロスドメインにおける要求に対する実行を実現しています。


(callback._17831([
{
"key": "foo",
"value": "\u7c73" // 米
},
{
"key": "bar",
"value": "\u308a\u3093\u3054" // りんご
},
{
"key": "baz",
"value": "\u8461\u8404" // 葡萄
}
])

json形式では、ajaxの形式でサーバプログラムを実行し、下のようなjson形式の値をクライアントに返されます。これをクライアント(JavaScript)でjsonパースして値を処理します。


[
{
"key": "foo",
"value": "\u7c73" // 米
},
{
"key": "bar",
"value": "\u308a\u3093\u3054" // りんご
},
{
"key": "baz",
"value": "\u8461\u8404" // 葡萄
}
]

 

|

« TP-LINK AC1200 Wi-Fi Range Extender RE300を設置してみた | トップページ | Apache Solrを使って形態素解析とBigramを併用してテキスト検索してみる »

パソコン・インターネット」カテゴリの記事

コメント

コメントを書く



(ウェブ上には掲載しません)




« TP-LINK AC1200 Wi-Fi Range Extender RE300を設置してみた | トップページ | Apache Solrを使って形態素解析とBigramを併用してテキスト検索してみる »