본문 바로가기

개발하자

사용자 지정 미들웨어 때문에 Fast API Swager가 렌더링되지 않습니까?

반응형

사용자 지정 미들웨어 때문에 Fast API Swager가 렌더링되지 않습니까?

다음과 같은 맞춤형 미들웨어가 있습니다:

그것의 목적은 myFast의 모든 엔드포인트의 모든 응답에 meta_data 필드를 추가하는 것이다API 앱.


@app.middelware("http")
async def add_metadata_to_response_payload(request: Request, call_next):

    response = await call_next(request)

    body = b""
    async for chunk in response.body_iterator:
        body+=chunk


    data = {}
    data["data"] = json.loads(body.decode())
    data["metadata"] = {
        "some_data_key_1": "some_data_value_1",
        "some_data_key_2": "some_data_value_2",
        "some_data_key_3": "some_data_value_3"
    }

    body = json.dumps(data, indent=2, default=str).encode("utf-8")

    return Response(
        content=body,
        status_code=response.status_code,
        media_type=response.media_type
    )

하지만, 내가 uvicorn을 사용하여 앱을 서비스하고 swager URL을 실행했을 때, 내가 본 것은 다음과 같다:


Unable to render this definition

The provided definition does not specify a valid version field.

Please indicate a valid Swagger or OpenAPI version field. Supported version fields are
Swagger: "2.0" and those that match openapi: 3.0.n (for example, openapi: 3.0.0)

많은 디버깅을 통해 이 오류가 사용자 지정 미들웨어, 특히 다음 줄 때문이라는 것을 알게 되었습니다:

body = json.dumps(data, indent=2, default=str).encode("utf-8")

내가 이 대사를 간단히 언급한다면, 스웨거는 나에게 딱 좋다. 하지만, 저는 미들웨어로부터의 응답에서 내용 인수를 전달하기 위해 이 줄이 필요합니다. 어떻게 해결해야 할까요?

업데이트:

나는 다음을 시도했다: 기본 arg를 제거함으로써 swager가 성공적으로 로드되었다. 그러나 이제 내가 API 중 하나를 클릭하면, 화면의 응답 페이로드와 함께 swager가 나에게 알려주는 것은 다음과 같다:

추가 업데이트(2022년 4월 6일):

크리스가 문제의 한 부분을 해결할 해결책을 얻었지만, 스웨거는 여전히 로딩되지 않았습니다. 코드가 미들웨어 수준에서 무기한 중단되었으며 페이지가 여전히 로드되지 않았습니다.

그래서, 나는 이 모든 곳에서 발견했다:

사용자 지정 미들웨어를 추가하는 이 방법은 Base에서 상속함으로써 작동합니다Stallette의 HTTP 미들웨어는 자체적인 문제(내부 미들웨어 대기, 스트리밍 응답 및 정상 응답, 호출 방식과 관련이 있음)를 가지고 있다. 아직 이해가 안 돼요.




swagger html의 본문을 미들웨어와 응답에서 가져온 json 데이터(이 경우 html 응답)로 대체하고 있습니다.

당신은 결국 다음과 같은 일을 겪게 될 것이다

{
    "data": "<html>....</html>",
    "metadata": {
        "some_data_key_1": "some_data_value_1",
        "some_data_key_2": "some_data_value_2",
        "some_data_key_3": "some_data_value_3"
    }
}

물론 이것은 효과가 없을 것이다.

가능한 해결책

미들웨어에서 응답 내용 유형을 확인합니다. 응답이 있는 경우 응답을 확장하고, 그렇지 않은 경우 응답을 그대로 둡니다.

참고: 이 작업은 모든 응답에 가 추가되어야 하지만 내용 유형에는 추가되지 않는다고 가정할 수 있는 경우에만 수행할 수 있습니다. (필요에 따라 수표를 변경할 수 있습니다.)

다른 가능한 해결책

다음 문제가 현재 구현에 병합되고 이 버전의 사용을 시작할 때까지 기다립니다.

https://github.com/tiangolo/fastapi/issues/1174 https://github.com/encode/starlette/pull/1286




여기에 영감을 받아 그렇게 할 수 있는 방법이 있습니다. 유형인 경우에만 를 추가하여 수정할 수 있도록 아래와 같이 응답의 를 확인하십시오.

OpenAPI(Swagger UI)가 렌더링(및 둘 다)하려면 응답에 키가 없는지 확인하여 해당 경우에만 응답 수정을 진행할 수 있습니다. 응답 데이터에 이러한 이름을 가진 키가 있으면 OpenAPI에 대한 응답에 있는 추가 키를 사용하여 추가로 확인할 수 있습니다(예: , , ). 필요한 경우 해당 값과 비교하여 확인할 수도 있습니다.

from fastapi import FastAPI, Request, Response
import json

app = FastAPI()

@app.middleware("http")
async def add_metadata_to_response_payload(request: Request, call_next):
    response = await call_next(request)
    content_type = response.headers.get('Content-Type')
    if content_type == "application/json":
        response_body = [section async for section in response.body_iterator]
        resp_str = response_body[0].decode()  # converts "response_body" bytes into string
        resp_dict = json.loads(resp_str)  # converts resp_str into dict 
        #print(resp_dict)
        if "openapi" not in resp_dict:
            data = {}
            data["data"] = resp_dict  # adds the "resp_dict" to the "data" dictionary
            data["metadata"] = {
                "some_data_key_1": "some_data_value_1",
                "some_data_key_2": "some_data_value_2",
                "some_data_key_3": "some_data_value_3"}
            resp_str = json.dumps(data, indent=2)  # converts dict into JSON string
        
        return Response(content=resp_str, status_code=response.status_code, media_type=response.media_type)
        
    return response


@app.get("/")
def foo(request: Request):
    return {"hello": "world!"}

업데이트 1

또는 미들웨어 기능을 시작할 때 요청의 URL 경로를 확인하고(응답에 메타데이터를 추가하려는 미리 정의된 경로/경로 목록과 비교하여) 그에 따라 진행하는 것이 더 좋습니다. 다음은 예를 제시하겠습니다.

from fastapi import FastAPI, Request, Response, Query
from pydantic import constr
from fastapi.responses import JSONResponse
import re
import uvicorn
import json

app = FastAPI()
routes_with_middleware = ["/"]
rx = re.compile(r'^(/items/\d+|/courses/[a-zA-Z0-9]+)$')  # support routes with path parameters
my_constr = constr(regex="^[a-zA-Z0-9]+$")

@app.middleware("http")
async def add_metadata_to_response_payload(request: Request, call_next):
    response = await call_next(request)
    if request.url.path not in routes_with_middleware and not rx.match(request.url.path):
        return response
    else:
        content_type = response.headers.get('Content-Type')
        if content_type == "application/json":
            response_body = [section async for section in response.body_iterator]
            resp_str = response_body[0].decode()  # converts "response_body" bytes into string
            resp_dict = json.loads(resp_str)  # converts resp_str into dict 
            data = {}
            data["data"] = resp_dict  # adds "resp_dict" to the "data" dictionary
            data["metadata"] = {
                "some_data_key_1": "some_data_value_1",
                "some_data_key_2": "some_data_value_2",
                "some_data_key_3": "some_data_value_3"}
            resp_str = json.dumps(data, indent=2)  # converts dict into JSON string
            return Response(content=resp_str, status_code=response.status_code, media_type="application/json")

    return response

@app.get("/")
def root():
    return {"hello": "world!"}

@app.get("/items/{id}")
def get_item(id: int):
    return {"Item": id}

@app.get("/courses/{code}")
def get_course(code: my_constr):
    return {"course_code": code, "course_title": "Deep Learning"}

업데이트 2

또 다른 해결책은 설명된 대로 및 를 사용하여 본문의 변경 사항을 사용자가 지정한 경로에 적용할 수 있으며, 이를 통해 Swager UI의 문제를 더 쉽게 해결할 수 있습니다.

원하는 경우에도 미들웨어 옵션을 사용할 수 있지만 메인에 미들웨어를 추가하는 대신 본문에 일부 데이터를 추가하기 위해 에서 수정해야 하는 경로를 다시 포함하는 에 미들웨어 옵션을 추가할 수 있습니다( 참조).


반응형