Python3 + cgi.FieldStorage で typerror
嵌って結局友人に解決してもらったのでここにメモ.
概要
問題
Python 3 の cgi を使って cgi.FieldStorage() でデータを取得しようとする.
curl --data "n=32" とかだとうまく行くし,/?n=32 のようにして POST してもうまく行くが,
javascript から POST しようとすると(あろうことか)python が cgi.FieldStorage に起因して cgi.py のなかで
TypeError: must be str, not bytes といって死んでしまう.
解決
Content-type の設定が必要で,javascript からやると何もしない時に(僕の場合)text/plain として送ってしまうけれど,
例えば n=32 の形式なら curl がやっているように application/x-www-form-urlencoded とするのが正しい.
詳しく
構成
Python 3 で簡単な cgi を考えてみる.
こんなディレクトリ構成
.
├── index.html
├── test.js
├── serve.py
└── cgi-bin
└── cgi_test.py大本の serve.py
#!/usr/bin/env python3
# serve.py
import http.server
def main():
server_address = ("", 8000)
handler_class = http.server.CGIHTTPRequestHandler
server = http.server.HTTPServer(server_address, handler_class)
print("access: http://127.0.0.1:8000/")
server.serve_forever()
if __name__ == "__main__":
main()今から叩く cgi-bin/cgi_test.py, n の値をそのまま返す.
#!/usr/bin/env python3
# cgi-bin/cgi_test.py
import cgi
import cgitb
def main():
cgitb.enable()
print("Content-type: text/plain")
print("") # end of the header
fields = cgi.FieldStorage() # ここで落ちる
n = fields.getfirst('n', 0)
print(n)
if __name__ == "__main__":
main()index.html. Javascript 経由で cgi-bin/cgi_test.py を叩いて結果を表示する.
<!DOCTYPE html>
<!-- index.html -->
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<script src="./test.js"></script>
</head>
<body>
Hi there!
<pre id="response"></pre>
</body>
</html>上で呼ばれる test.js.
// test.js
window.addEventListener('load', fetchValue);
function fetchValue(){
var request = new XMLHttpRequest();
var params = "n=353";
// although this works
// request.open("GET", "cgi-bin/cgi_test.py?n=3232", true);
request.open("POST", "cgi-bin/cgi_test.py", true);
request.onreadystatechange = function(){
if (request.readyState == 4 && request.status == 200){
document.getElementById("response").innerHTML = request.responseText;
}
}
request.send(params);
}問題
これで index.html を見てみると cgi-bin/cgi_test.py が死んでいて
Traceback (most recent call last):
File "./cgi-bin/cgi_test.py", line 13, in main
fields = cgi.FieldStorage()
File "/usr/lib/python3.4/cgi.py", line 561, in __init__
self.read_single()
File "/usr/lib/python3.4/cgi.py", line 724, in read_single
self.read_binary()
File "/usr/lib/python3.4/cgi.py", line 746, in read_binary
self.file.write(data)
TypeError: must be str, not bytes……困った.
curl でうまく行くことを確認してみよう
$ curl --data "n=423" http://127.0.0.1:8000/cgi-bin/cgi_test.py
# => 423問題ない. javascript から GET してみよう.こういう感じで書き換える
// test.js
function fetchValue(){
var request = new XMLHttpRequest();
request.open("GET", "cgi-bin/cgi_test.py?n=3232", true);
request.onreadystatechange = function(){
if (request.readyState == 4 && request.status == 200){
document.getElementById("response").innerHTML = request.responseText;
}
}
request.send();
}これも問題なく 3232 が表示される.さてさて.
解決
友人の託宣により Content-type を x-www-form-urlencoded に設定する
request.open("POST", "cgi-bin/cgi_test.py", true);
// +++
request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");…で解決する.やった!!やった!!!!考えてみれば text/plain だと思ってたのになんか bytes が来てるじゃんってのも
符合するし,渡してるのは確かにそういう値でもある.
確認のため,cgi_test.py に cgi.print_environs() を仕込んで確認してみると,
curl --data のときは content-type は x-www-form-urlencoded に設定されていて,
何も考えない最初の javascript では text/plain になっていた.
さらに
ただ,いくらなんでも唐突な TypeError はどうかという気がするので,
どこでどう catch するのが適切かというのを考えていきたい.そもそも cgi.FieldStorage する前になにかしとくといいのかもしれないなあ…
というわけで,おわり.