📕

UnityでPythonサーバ(FastAPI)にJSON形式でPOST送信時の422エラー解決方法

2023/09/13に公開

前書き

Unity初心者の私が非常に悩まされたことをまとめて、今後の迷える子羊への参考になればいいと思い書きました。

至らぬ点やもっといい解決策があれば教えていただきたいです。

環境

  • Unity:2022.3.8f1
  • Python:3.10.13
    • FastAPI:0.103.0

解決できること

unity(c#)からJSON形式でPython(FastAPI)に送信

Python エラーと解決

FastAPIでPOSTリクエストで受け取るときには

エラー内容

POST送信された際422エラーが出力された

422エラーが出た時に使っていたコード

api.py
from fastapi import FastAPI
import subprocess
import os

@app.post("/text")
def testPost(text:str):

    return {"text":text}
    
if __name__ == "__main__":
    name, ex = os.path.splitext(os.path.basename(__file__))
    subprocess.run(f"uvicorn {name}:app --reload")

解決

  1. リクエストボディの定義
  2. 422エラーのデバック
全体のコード
api.py
from pydantic import BaseModel
from fastapi import FastAPI, Request, status
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
import subprocess
import os

@app.exception_handler(RequestValidationError)
async def handler(request:Request, exc:RequestValidationError):
    print(exc)
    return JSONResponse(content={}, status_code=status.HTTP_422_UNPROCESSABLE_ENTITY)

class Item(BaseModel):
    text:str

@app.post("/text")
def testPost(data:Item):

    return {"text":data.text}
    
if __name__ == "__main__":
    name, ex = os.path.splitext(os.path.basename(__file__))
    subprocess.run(f"uvicorn {name}:app --reload")

1.リクエストボディの定義

POSTリクエストはデータを送信するためにリクエストボディ使用します。

リクエストボディを定義するにはpydanticライブラリを使います。
pydanticからBaseModelをインポートして継承したクラスを作成します。

pippydanticライブラリのインストール

pip install pydantic
api.py
from pydantic import BaseModel
from fastapi import FastAPI
import subprocess
import os

# リクエストボディ
class Item(BaseModel):
    text:str

@app.post("/text")
def testPost(data:Item):

    return {"text":data.text}
    
if __name__ == "__main__":
    name, ex = os.path.splitext(os.path.basename(__file__))
    subprocess.run(f"uvicorn {name}:app --reload")

ItemクラスにBaseModelを継承して使います。

2.422エラーのデバック

以下の記事を参考にコードを追記する
https://qiita.com/kurumaebi65/items/74e2edf8a394cf086c9a

from pydantic import BaseModel
from fastapi import FastAPI, Request, status
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse

app = FastAPI()

@app.exception_handler(RequestValidationError)
async def handler(request:Request, exc:RequestValidationError):
    print(exc)
    return JSONResponse(content={}, status_code=status.HTTP_422_UNPROCESSABLE_ENTITY)

# 以下コード

追記して422エラーになると以下ものが出ます.
msgには「値は有効な辞書ではない」と出ている

422エラー
[{'loc': ('body',), 'msg': 'value is not a valid dict', 'type': 'type_error.dict'}]

Unity エラーと解決

エラー内容

  • APIに送信するデータが辞書型ではない
  • 辞書型で送信するのにform.AddFieldを使うのを間違っていた
TestPost.cs
using UnityEngine;
using UnityEngine.Networking;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;

public class TestPost : MonoBehaviour
{
    private string serverURL = "http://127.0.0.1:8000/"; // サーバーのURLに置き換える

    private void Start()
    {
        StartCoroutine("Test2voice");
    }

    IEnumerator Test2voice()
    {
        WWWForm form = new WWWForm();
	
	// ▼ここから▼
        form.AddField("text", "こんにちは、これはテストです");

        UnityWebRequest request = UnityWebRequest.Post(serverURL + "text", form);
        
        yield return request.SendWebRequest();
	// ▲ここまでが原因▲

        if (request.result == UnityWebRequest.Result.Success)
        {
            Debug.Log("フォームデータが正常に送信されました。");
            Debug.Log(request.downloadHandler.text);
            // 必要に応じてレスポンスを処理します。
        }
        else
        {
            Debug.LogError("フォームデータの送信中にエラーが発生しました: " + request.error);
        }
    }
}

解決

  • JSONデータのクラスを作成
  • JsonUtility.ToJsonを使いstring型のJSON形式で返す
  • ASCIIコードで送信しないようにする

https://qiita.com/s-sanosuke/items/3e0cd02ddc1b61de6cde

TestPost.cs
using UnityEngine;
using UnityEngine.Networking;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;

public class TestPost : MonoBehaviour
{
    private string serverURL = "http://127.0.0.1:8000/"; // サーバーのURLに置き換える

    // JSONデータのクラス
    [System.Serializable]
    private class RequestData
    {
        public string text;
    }

    private void Start()
    {
        StartCoroutine("Test2voice");
    }

    IEnumerator Test2voice()
    {
        RequestData requestData = new RequestData
        {
            text = "こんにちは、これはテストです",
        };

        // JSONデータを文字列に変換
        string json = JsonUtility.ToJson(requestData);
        // Debug.Log(json);

        WWWForm form = new WWWForm();

	// string型をbyte型配列に変換して送信
        byte[] postData = System.Text.Encoding.UTF8.GetBytes(json);
        var request = new UnityWebRequest(serverURL + "text", "POST");
        request.uploadHandler = (UploadHandler)new UploadHandlerRaw(postData);
        request.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer();
        request.SetRequestHeader("Content-Type", "application/json");
        yield return request.Send();


        if (request.result == UnityWebRequest.Result.Success)
        {
            Debug.Log("フォームデータが正常に送信されました。");
            Debug.Log(request.downloadHandler.text);
            // 必要に応じてレスポンスを処理します。
        }
        else
        {
            Debug.LogError("フォームデータの送信中にエラーが発生しました: " + request.error);
        }
    }
}
new UnityWebRequest(serverURL + "text", "POST");

上記のコードのserverURL + "text"はFastAPIのhttp://127.0.0.1:8000/textに送信する

最後に

こういったエラーはbing aiやChatGPTに聞くのも、解決の糸口になるのでガンガン使っていきましょう。

Discussion