はじめに
Webフレームワークが嫌いな弊社
APIもDjangoやFlaskは使わずにCGI化したPythonで作ってる
(ホントに今時の自社サービス系の会社か?)
ブラウザから何かしら受け取る時って辞書に加工したい時、cgiモジュールのFieldStorage使ってるけど
sample.py
input = cgi.FieldStorage()
diction = {n: input.getvalue(n) for n in input.keys()}
こいつがPython3.13から廃止になるらしいのでなんとかしてみた
コード
画面はこんな感じ、とりあえずGETとPOSTができる作りならなんでもいい
(HTMLとJSなんも知らんから適当に書いてる)
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テスト</title>
</head>
<body>
<p>POST値がそのまま返ってきます</p>
<p>
<label><input type="text" id="id" placeholder="id"></label>
<label><input type="text" id="name" placeholder="氏名"></label>
<label><input type="text" id="age" placeholder="年齢"></label>
<label><input type="text" id="bikou" placeholder="なんでも"></label>
<input type="button" value="POST" id="PostButton" onclick="ButtonClick(this)">
</p>
<p>URLパラメタがそのまま返ってきます</p>
<p>
<input type="button" value="GET" id="GetButton" onclick="ButtonClick(this)">
</p>
<p id="msg"></p>
<script type="text/javascript">
function ButtonClick(button){
let formData = new FormData();
formData.append("id", encodeURIComponent(document.getElementById('id').value));
formData.append("name", encodeURIComponent(document.getElementById('name').value));
formData.append("age", encodeURIComponent(document.getElementById('age').value));
formData.append("bikou", encodeURIComponent(document.getElementById('bikou').value));
let xhl = new XMLHttpRequest();
if (button.id == 'PostButton') {
xhl.open('POST', 'test.py', false);
xhl.setRequestHeader( 'Content-Type', 'Multipart/form-data' );
xhl.send(formData);
}
if (button.id == 'GetButton') {
//hoge=huga foo=baa piyo=baz qux=netagire をパラメタにしてGETする
xhl.open('GET', 'test.py?hoge=huga&foo=baa&piyo=baz&qux=netagire', false);
xhl.setRequestHeader( 'Content-Type', 'Multipart/form-data' );
xhl.send();
}
document.getElementById('msg').innerText = xhl.response;
}
</script>
</body>
</html>
できあがった画面がこれ
バカデカい余白にPOSTデータ、GETパラメタがそのまま表示される
バックエンドのPython
hoge.py
#!/usr/local/bin/python3.9
import os
import json
import sys
import urllib.parse
if os.environ['REQUEST_METHOD'] == 'GET' :
#環境変数QUERY_STRINGから取り出したURLパラメタを辞書にパース
url_dict = urllib.parse.parse_qs(os.environ['QUERY_STRING'])
'''
url_dict = {'hoge': ['huga'], 'foo': ['baa'], 'piyo': ['baz'], 'qux': ['netagire']}
辞書のバリュー部が長さ1のリストなので、頭だけ抽出すれば
{'hoge': 'huga', 'foo': 'baa', 'piyo': 'baz', 'qux': 'netagire'} みたいな辞書ができるね
'''
diction = {n: url_dict[n][0] for n in url_dict.keys()}
if os.environ['REQUEST_METHOD'] == 'POST' :
#標準入力を環境変数CONTENT_LENGTHだけ読み込み、splitlines()で改行文字を''にする
stdin = sys.stdin.read(int(os.environ['CONTENT_LENGTH'])).splitlines()
'''
このときのstdinの中身は
------WebKitFormBoundaryBJHNmZMBUlxGg07O
Content-Disposition: form-data; name="id"
POSTデータ
------WebKitFormBoundaryBJHNmZMBUlxGg07O
.
.
.
の繰り返し
こいつを4行ずつ区切ったものを重ねて奥行きを持たせる(この程度ならNumPy使わんでいいか)
'''
stdin = [stdin[n:n+4] for n in range(len(stdin)) if ((n == 0 or n % 4 ==0))]
'''
このときのstdinの中身は
[
['------WebKitFormBoundaryWVRIhGMy4qjlrgQA', 'Content-Disposition: form-data; name="フォームid"', '', 'POSTデータ'],
['------WebKitFormBoundaryWVRIhGMy4qjlrgQA', 'Content-Disposition: form-data; name="フォームid"', '', 'POSTデータ'],
(繰り返し)
]
なお末尾はPOST値が入ってないので削除
'''
stdin.pop(-1)
#各リストの2番目がキー、4番目が値になる辞書を作成
#2番目の内、Content-Disposition...は固定なのでトリミング
diction = {n[1][38:-1] : n[3] for n in stdin}
print('Status: 200 OK')
print('Content-Type: application/json\n')
print(json.dumps(diction))
クラスのプロパティとして扱いたいならこんな記述になるのかな
hoge.py
#!/usr/local/bin/python3.9
import os
import json
import sys
import urllib.parse
class Hoge :
#selfって打つのがめんどくさいからthisのtにしちゃう
def __init__(t) :
if os.environ['REQUEST_METHOD'] == 'GET' :
#同様にURLパラメタを辞書にパース
url_dict = urllib.parse.parse_qs(os.environ['QUERY_STRING'])
#各プロパティを辞書のキーで命名、辞書の各値の最初の要素を代入
for n in url_dict.keys() :
setattr(t,n,url_dict[n][0])
#安直にt.n = url_dict[n][0]とかやっちゃうとキーで命名されないので注意(tのnになっちゃう)
if os.environ['REQUEST_METHOD'] == 'POST' :
#標準入力を読み込んで2次元のリストにする→4行区切りで重ねる→末尾を削除 までは同様
stdin = sys.stdin.read(int(os.environ['CONTENT_LENGTH'])).splitlines()
stdin = [stdin[n:n+4] for n in range(len(stdin)) if ((n == 0 or n % 4 ==0))]
stdin.pop(-1)
#各プロパティをリストの2番目の要素で命名、4番目の値を代入
for n in stdin :
setattr(t,n[1][38:-1],n[3])
#ここでもt.n[1][38:-1] = n[3] とか目論んではいけない
#コンストラクタ内で出力メソッド呼ぶ
t.out()
def out(t) :
print('Status: 200 OK')
print('Content-Type: application/json\n')
#以下、拘りがない人は print(json.dumps(vars(t))) で終わり
#プロパティを辞書に加工、値部分をデコード
diction = {n: urllib.parse.unquote(vars(t)[n]) for n in vars(t).keys()}
#吐き出す時もensure_ascii=False渡してエンコード禁止
print(json.dumps(diction,ensure_ascii=False))
exit()
#インスタンス化
Hoge()
ボタン押してPOSTデータが返ってきたことを確かめてみよう
GETの時もちゃんと取れてる
とりあえずこいつをたたき台にしていろんなPOST、GETを試してみる
画像データとCSVのPOST方法も考えた方がいいかもしれない