5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Python WSGIでマルチバイト文字が含まれるURLをリダイレクトしたらエラーが出る

Last updated at Posted at 2021-12-16

はじめに

かなりニッチな状況に対応した内容です。
特別なテクニックじゃなく、ググってもなかなか見つけられなかったニッチな問題を、
もしかしたら困っているかもしれない人に向けて贈ります。

アドベントカレンダーなのに?
僕も書き終わったときに、そう思いました。でも、書いちゃったので許してください

このエラーで困っている人の助けになりたくて

こんなエラーを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でリダイレクトすればいいんです。

でも、どうしてもマルチバイト文字を含めないといけないのっぴきならない事情があってやり方を考えました。

かなりググっても この件で困っている人は見つけられなかったので、相当ニッチな事情なのは承知の上で、
一人でも助かる人がいればと共有します。

5
0
0

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
5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?