はじめに
かなりニッチな状況に対応した内容です。
特別なテクニックじゃなく、ググってもなかなか見つけられなかったニッチな問題を、
もしかしたら困っているかもしれない人に向けて贈ります。
アドベントカレンダーなのに?
僕も書き終わったときに、そう思いました。でも、書いちゃったので許してください
このエラーで困っている人の助けになりたくて
こんなエラーをWSGIを使ったWEBアプリで見かけたら、この記事でお助け出来る可能性大です。
さあ、僕と一緒に解決していきましょう!
File "/usr/lib/python3.X/wsgiref/handlers.py", line 310, in finish_content
self.send_headers()
File "/usr/lib/python3.X/wsgiref/handlers.py", line 333, in send_headers
self._write(bytes(self.headers))
File "/usr/lib/python3.X/wsgiref/headers.py", line 142, in __bytes__
return str(self).encode('iso-8859-1')
UnicodeEncodeError: 'latin-1' codec can't encode characters in position 83-87: ordinal not in range(256)
まずは実際エラーを起こしているところの紹介
ここです。このHTTP HEADER文字列をiso-8859-1(latin-1)で(何故か)encodeしているところが元凶になります。
マルチバイト文字が含まれるURLをここでencodeするとUnicodeEncodeErrorが出るんですね。
UTF-8じゃだめなんですかね?
https://github.com/python/cpython/blob/main/Lib/wsgiref/headers.py#L142
def __bytes__(self):
return str(self).encode('iso-8859-1')
どうやって解決する?
今回、ご用意いたしました対応方法は力技です。
(スマートなやり方があれば逆にご教示ください。)
"""まずは 問題のmethodをoverrideします"""
from wsgiref.headers import Headers
from wsgiref.simple_server import WSGIServer, make_server, WSGIRequestHandler, ServerHandler
class UTF8Headers(Headers):
"""
override
wsgiref/headers.py
"""
def __bytes__(self):
"""
HTTP通信用途にフォーマットされたヘッダを、最終行まで含めてバイト列で返す。
"""
# wsgiref.headers.Headersでは ここが `iso-8859-1`で定義されている。
# ヘッダーにマルチバイト(utf-8)文字が含まれても良いようにencodeを変更
return str(self).encode('utf-8')
"""http requestのhandleで使われるServerHandlerでUTF8Headersが使われるようにする """
class UTF8ServerHandler(ServerHandler):
"""
override
wsgiref/simple_server.py
wsgiref/handlers.py
"""
def __init__(self,stdin,stdout,stderr,environ,
multithread=True, multiprocess=False
):
"""
コンストラクタ
"""
super().__init__(stdin, stdout, stderr, environ, multithread, multiprocess)
# クラスプロパティheaders_classを上記で用意しているUTF8Headersで上書き
self.headers_class = UTF8Headers
"""ここまで作ってきたUTF8ServerHandlerを使ってHttp Request時は自作ServerHandlerが使われるようにする"""
class UTF8RequestHandler(WSGIRequestHandler):
"""
override
https://github.com/python/cpython/blob/main/Lib/wsgiref/simple_server.py#L115
"""
def handle(self):
"""単一 HTTP requestの処理"""
self.raw_requestline = self.rfile.readline(65537)
if len(self.raw_requestline) > 65536:
self.requestline = ''
self.request_version = ''
self.command = ''
self.send_error(414)
return
if not self.parse_request(): # An error code has been sent, just exit
return
# UTF8でのHeaderHandlerが使われている自作ServerHandlerを生成
handler = UTF8ServerHandler(
self.rfile, self.wfile, self.get_stderr(), self.get_environ()
)
handler.request_handler = self # backpointer for logging
handler.run(self.server.get_app())
省略
# UTF8RequestHandlerを使ってサーバー起動したら完了
server = make_server(
'0.0.0.0',
80,
app,
server_class=WSGIServer,
handler_class=UTF8RequestHandler
)
server.serve_forever()
さいごに
取り急ぎエラーを解消する方法は上記に書きました。
でも、本当はマルチバイト文字をURLエンコードしたURLでリダイレクトすればいいんです。
でも、どうしてもマルチバイト文字を含めないといけないのっぴきならない事情があってやり方を考えました。
かなりググっても この件で困っている人は見つけられなかったので、相当ニッチな事情なのは承知の上で、
一人でも助かる人がいればと共有します。