1
1

(Python)CGIモジュール廃止への抗い

Last updated at Posted at 2023-09-16

はじめに

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パラメタがそのまま表示される
スクリーンショット 2023-09-16 190306.png

バックエンドの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データが返ってきたことを確かめてみよう
スクリーンショット 2023-09-16 222044.png
GETの時もちゃんと取れてる
スクリーンショット 2023-09-16 222156.png

とりあえずこいつをたたき台にしていろんなPOST、GETを試してみる
画像データとCSVのPOST方法も考えた方がいいかもしれない

1
1
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1