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

## はじめに
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3225408/a0c23881-bb23-e129-1456-4e7a0d2c3848.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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3225408/58a13388-268c-ffcb-707b-348fd1adb43e.png)
GETの時もちゃんと取れてる
![スクリーンショット 2023-09-16 222156.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3225408/9160fa55-6958-4cff-788d-ae589b8031f3.png)


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