В Respons-е Starlette (FastAPI) пытаюсь передать кириллический текст, получаю UnicodeEncodeError: 'latin-1' codec can't encode characters

from fastapi import Response
@app.get(f'/customCyrillicHeader')
def test(resp: Response):
    resp.headers['X-API-Code'] = "Это киррилица, бро"
    return 'ok'

Получаю такой стэк:

ERROR:    Exception in ASGI application
Traceback (most recent call last):
  File "D:\Projects\RecalcAPIfast\venv\lib\site-packages\uvicorn\protocols\http\h11_impl.py", line 369, in run_asgi
    result = await app(self.scope, self.receive, self.send)
  File "D:\Projects\RecalcAPIfast\venv\lib\site-packages\uvicorn\middleware\proxy_headers.py", line 59, in __call__
    return await self.app(scope, receive, send)
  File "D:\Projects\RecalcAPIfast\venv\lib\site-packages\fastapi\applications.py", line 208, in __call__
    await super().__call__(scope, receive, send)
  File "D:\Projects\RecalcAPIfast\venv\lib\site-packages\starlette\applications.py", line 112, in __call__
    await self.middleware_stack(scope, receive, send)
  File "D:\Projects\RecalcAPIfast\venv\lib\site-packages\starlette\middleware\errors.py", line 181, in __call__
    raise exc from None
  File "D:\Projects\RecalcAPIfast\venv\lib\site-packages\starlette\middleware\errors.py", line 159, in __call__
    await self.app(scope, receive, _send)
  File "D:\Projects\RecalcAPIfast\venv\lib\site-packages\starlette\exceptions.py", line 82, in __call__
    raise exc from None
  File "D:\Projects\RecalcAPIfast\venv\lib\site-packages\starlette\exceptions.py", line 71, in __call__
    await self.app(scope, receive, sender)
  File "D:\Projects\RecalcAPIfast\venv\lib\site-packages\starlette\routing.py", line 580, in __call__
    await route.handle(scope, receive, send)
  File "D:\Projects\RecalcAPIfast\venv\lib\site-packages\starlette\routing.py", line 241, in handle
    await self.app(scope, receive, send)
  File "D:\Projects\RecalcAPIfast\venv\lib\site-packages\starlette\routing.py", line 52, in app
    response = await func(request)
  File "D:\Projects\RecalcAPIfast\venv\lib\site-packages\fastapi\routing.py", line 220, in app
    dependant=dependant, values=values, is_coroutine=is_coroutine
  File "D:\Projects\RecalcAPIfast\venv\lib\site-packages\fastapi\routing.py", line 154, in run_endpoint_function
    return await run_in_threadpool(dependant.call, **values)
  File "D:\Projects\RecalcAPIfast\venv\lib\site-packages\starlette\concurrency.py", line 40, in run_in_threadpool
    return await loop.run_in_executor(None, func, *args)
  File "C:\Python37\lib\concurrent\futures\thread.py", line 57, in run
    result = self.fn(*self.args, **self.kwargs)
  File "D:\Projects\RecalcAPIfast\app\main.py", line 18, in test
    resp.headers['X-API-Code'] = "Это киррилица, бро"
  File "D:\Projects\RecalcAPIfast\venv\lib\site-packages\starlette\datastructures.py", line 585, in __setitem__
    set_value = value.encode("latin-1")
UnicodeEncodeError: 'latin-1' codec can't encode characters in position 0-2: ordinal not in range(256)

В starlette\datastructures.py явное кодирование в latin-1

class MutableHeaders(Headers):
    def __setitem__(self, key: str, value: str) -> None:
        """
        Set the header `key` to `value`, removing any duplicate entries.
        Retains insertion order.
        """
        set_key = key.lower().encode("latin-1")
        set_value = value.encode("latin-1")

        found_indexes = []
        for idx, (item_key, item_value) in enumerate(self._list):
            if item_key == set_key:
                found_indexes.append(idx)

        for idx in reversed(found_indexes[1:]):
            del self._list[idx]

        if found_indexes:
            idx = found_indexes[0]
            self._list[idx] = (set_key, set_value)
        else:
            self._list.append((set_key, set_value))

Но насколько мне известно, header можно передавать объектом в base64. Но я не пойму как это сделать в FastAPI. Например Django rest framework сам кодирует в base64 и передает, ничего придумывать не нужно. Помогите плизз!


Ответы (1 шт):

Автор решения: insolor

Можно вручную закодировать строку в байты с помощью кодировки utf-8, потом перекодировать в base64, и потом уже присвоить в resp.headers['X-API-Code']:

from base64 import b64encode
from fastapi import FastAPI, Response


app = FastAPI()


@app.get(f'/customCyrillicHeader')
def test(resp: Response):
    resp.headers['X-API-Code'] = b64encode("Это киррилица, бро".encode('utf-8')).decode()
    return 'ok'

Вызов decode в конце нужен, чтобы превратить набор байт, который вернула функция b64encode, обратно в строку (без этого будет вылетать ошибка, что у байт нет метода encode).

→ Ссылка