Fast API - Middleware

2025. 9. 10. 14:17Programming Language/Python

반응형

1. Middleware

미들웨어는 FastAPI 애플리케이션의 모든 요청과 응답 사이에서 실행되는 함수로 웹 서버에 들어오는 요청을 가로채서 처리를 한 후에 다음 단계로 전달하거나 응답을 수정하는 역할을 한다.

 

미들웨어는 여러 API 엔드포인트에서 공통적으로 적용해야하는 로직을 한 곳 에서 처리가 가는하다는 점에서 유용하다.

미들웨어가 없다면 모든 라우트 함수에 동일한 코드를 복,붙해서 써야하기에 코드가 중복되고 관리가 어려워진다.

 

보통 미들웨어에서는 인증 및 권한 부여(요청 헤더 토큰 검사를 통한 로그인 여부 확인, 접근 권한 확인), 로깅(모든 요청에 대한 IP, 요청시간, 응답 상태 코드 등을 기록), CORS(다른 도메인에서 오는 요청을 허용하거나 차단하는 규칙을 설정), 압축(클라이언트에게 보내는 응답 데이터를 압축해서 전송 시간을 단축), 예외처리(특정 오류에 대한 일관된 형식의 응답을 반환)등의 기능을 한다

 

미들웨어는 데코레이터를 app.middleware()를 사용하면 된다.

 

근데 보면 Fast API는 ASGI(비동기 서버)에서 돌아가나 동기와 비동기를 모두 사용이 가능한데 미들웨어의 경우는 많은 요청/응답을 통과하는 입구의 관문 같은 것이기에 블로킹 작업이 진행된다면 서버가 멈춘것 처럼 보일 수 도 있다.

그래서 미들웨어의 진입점은 async로 두고 I/O작업의 경우는 기다리게 하고 다른 요청을 동시에 처리할 수 있도록 설정한다.

 

근데 미들웨어는 관문이라고 했었는데 미들웨어를 돌리는 동안 다른 처리를 하면 안되는거 아니냐고 생각할 수 있지만 미들웨어가 걸린 요청에 대해서 미들웨어 처리하는 동안 해당 요청에 대해서 처리한다는게 아니라 다른 요청에 대한 미들웨어를 먼저 처리하거나 다른 요청을 처리하는 것으로 미들웨어가 걸린 곳은 멈춰서 I/O를 대기하고 다른 처리를 하고 I/O작업이 끝나면 미들웨어가 종료된 이후에 해당 미들웨어를 걸어둔 요청에 대한 라우팅 메서드로 가서 요청을 처리하도록 하기 때문에 문제가 없다.

 

이제 미들웨어를 한번 만들어보면서 확인해보자.

 

2. 미들웨어 생성 : @app.middleware()

먼저 기본적인 간단한 엔드포인트 세개를 생성해주자.

엔드포인트는 각각 진짜 단순하게 요청할때 전달한 값을 함수명과 함께 반환하는 것으로 만들었다.

이랬을때 보면 모든 요청이 정상적으로 그냥 오고 갈 수 있도록 되어 있다.

 

이제 미들웨어를 작성해보자.

미들웨어는 middleware라는 데코레이터를 사용해서 만들어 줄 수 있다.

먼저 비동기 함수를 하나 생성해주고

해당 함수에 데코레이터를 얹어 미들웨어로 만들어주자.

 

여기서 @app.middleware()는 FastAPI 애플리케이션에서 모든 요청과 응답을 처리하는 함수를 등록하는데 사용되는 데코레이터로 해당 데코레이터를 쓰면 라우트 핸들러 함수가 실행되기 전, 후로 특정 로직을 삽입할 수 있다.

이때 @app.middleware의 ()내부에 들어가는 값은 주로 프로토콜의 종류를 내타내는 문자열들이 들어가는데 현재는 사실 FastAPI가 공식적으로 지원하는건 "http"뿐이다.(그냥 이 내부에는 무조건 http만 들어간다고 봐도 될듯 싶다.)

이는 http 프로토콜을 사용하는 모든 요청에 대해서 작동하도록 등록하겠다는 의미이다.

 

그리고 이제 요청에 대한 확인을 할것이기 때문에 request를 매개변수로 받고

fastapi에서 Request를 import해야만 함

두번째 매개변수로는 call_next 함수를 받아주는데 


call_next함수는 다음단계(다음 미들웨어 체인 + 실제 라우트 핸들러)로 요청을 넘겨 응답을 만들어오는 비동기 함수로 FastAPI의 미들웨어 함수의 두번째 인자로 항상 주입한다.

미들웨어는 결국 엔드포인트 이전, 이후로 어떤 로직을 처리한 후에 다음 미들웨어 혹은 엔드포인트, 혹은 응답을 보내야하는데 그때 이 함수를 사용해서 요청에 대한 다음 작업을 전달한다.

만약 call_next함수를 사용하지 않으면 라우터로 가지 않고 내부에서 응답을 끝낼 수도 있다(차단 혹은 조기 반환)

 

아무튼 여기까지 하고 나서 미들웨어의 로직을 만들어 주자.

먼저 어떤 요청으로 미들웨어에 들어왔는지를 확인하고 이를 통해서 분기를 나눈다.

이때 요청에 대한 메서드, 요청 URL은 request객체 내부에 존재한다.

 

그리고 이제 call_next()를 호출할텐데 호출할때 request를 같이 던져준다.

이건 사실 미들웨어 이후의 다음 로직한테 요청을 전달하고 그 응답을 다시 받아서 response로 받아서 사용자한테 내가 응답하겠다는 의미이다.

그래서 사실 여기의 응답이 사용자에 대한 응답이 된다.

이렇게 요청을 다음 로직에게 전달한 응답을 return하면 미들웨어가 생성된 것이다.

 

한번 요청을 각각 보내보면 먼저 Get메서드로 구성된 /end1에 대한 요청은

이렇게 응답 및 로직 진행으로 인해 print가 출력되고 또 Get메서드로 구성된 /end2에 대한 요청은

이렇게 나오고 Post메서드로 구성된 /end3에 대한 요청은

이렇게 출력되는걸 볼 수 있다.

 

이제 특정 파라미터를 확인해서 해당 파라미터가 원하는 값을 가지고 있을때 요청에 대해 응답해주고 없다면 요청에 대해 응답하지 않고 바로 반환하는 로직을 만들어 보자.

 

먼저 request내부의 특정 파라미터를 확인하는 방법은 파라미터를 어떻게 담아왓는지에 따라 다르게 추출할 수 있다.

우선 파라미터를 담아오는 방식을 세가지로 분리해보자

그리고 각각의 엔드포인트에 대한 미들웨어 설정을 하면서 받아오는걸 확인해보자.

 

먼저 쿼리스트링으로 받는 경우는 

이렇게 request의 query_params.get() 함수를 통해서 확인이 가능하고 이 때 전달하는 파라미터의 명칭을 문자열로 전달하면 해당하는 파라미터를 찾아 반환한다.

출력문을 생성하고 요청에 

이렇게 isTrue를 쿼리스트링으로 던져보면

이렇게 파라미터를 확인할 수 있는 것을 볼 수 있다.

 

이제 전달받은 파라미터가 문자열인지 isinstance를 사용해서 확인하고 

맞다면 먼저 공백을 같이보냈을 수 도 있기에 strip을 사용해서 공백을 제거하고 

True, False, TRUE, FASLE을 보냈을 수 있기에 lower함수를 사용해서 모두 소문자로 변경하고

그리고 나서 실제 사용자가 true나 1이나 on으로 보냈을 수 있기에 이걸 포함한것을 확인하게 하기위해서 in을 사용해서 집합으로 리스트를 넣어 확인한다.

그리고 지금 조건은 True인 경우 내부로 들어오는 것이기 때문에 not으로 감싸서 true가 아닌 경우 해당 분기로 들어올 수 있도록 설정한다

그리고 이 분기로 들어왔을때 Exception을 던지게 만들어서 요청을 거부해주자.

만약에 Exception이 발생하지 않았을 경우 

이렇게 다음 체인에게 넘겨 그 응답을 return할 수 있도록 하자.

이러고 요청을 보내보면

이렇게 응답이 되는 것을 볼 수 있다.

True일때는 

이렇게 다 받아지는 것을 볼 수 있다.

 

 

경로 파라미터로 오는 경우는 조금 복잡할 수 있다.

이때는 아직 라우팅 이전이기에 path_params의 사용이 불가능하기에 전달하는 파라미터를 문자열로 직접 분해해서 파라미터를 찾아야한다.

또한 경로 파라미터로 요청이 전달되기 때문에 path의 경로가 

이런식으로 구성되게 된다.

그러면 

이 조건으로는 분기를 탈 수 없기 때문에 이때는 startswith라는 함수를 통해서 어떤 문자열로 path가 오면이라는 조건을 걸어줘야한다.

그리고 이렇게 전달하면 우리가 받을 url은 /end2/{param}인데 url이 그냥 /end2일때도 true로 받아들여 내부로 들어오기 때문에 /end2가 아니라 /end2/을 넣어주자.

이러고 경로 파라미터를 추가한 요청을 보내면서 내부에서 path를 출력해보면

이런식으로 받아 오는 것을 볼 수 있다.

이때 /를 구분자로 해서 분리되기에 먼저 제일 앞부분과 제일 뒷부분에 혹시 /(슬래시)가 들어올 수 있기에 이걸 strip으로 제거해주고

특정 구분자를 기준으로 문자열을 잘라 배열을 반환해주는 함수인 split을 사용해서 경로 파라미터에 해당하는 두번째 요소까지만 배열로 생성해서 반환하게 하면 

이렇게 배열이 반환된다.

여기서 배열의 크기가 2개 보다 작은 경우는 정상적이지 않은 경로로 접근한것이기에 Exception으로 잘못된 경로로 접근했다고 알리고 

그게 아니라면 두번째 요소의 파라미터의 값이 True인지를 확인하는데 이때도 쿼리스트링을 했을 때 처럼 조건을 넣어서 확인을 해주는데 이때는 무조건 문자열로 오기 때문에 str인지는 확인하지 않고 not in을 사용해서 값을 확인해주고 

만약 true가 아니라 다른 것이 왔다면 Exception으로 raise해주자.

이제 요청을 보내보면 먼저 /end2/ 로만으로 와서 경로 파라미터가 존재하지 않는 요청을 해보면

이렇게 404에러를 raise하는 것을 볼 수 있고 true가 아닌 다른 파라미터를 전달하면

이렇게 401로 에러를 벹는 것을 볼 수 있다.

이제 True, On, 1으로 요청을 전달해보면

이렇게 200으로 응답을 반환하는 것을 볼 수 있다.

 

이제 Post로 Body에 값을 담아오는 경우를 확인해볼텐데 body의 값을 읽어오는 방법은 request의 body()라는 함수를 사용하면 받아올 수 있다.

그리고 여기서 body를 읽어오는 작업은 클라이언트에서 전송된 요청 바디를 읽어오는 작업이기 때문에 네트워크를 통해서 데이터를 기다려야하기 때문에 시간이 걸릴 수 있다.

그렇기에 body를 사용하는 부분은 await을 걸어줘야한다.

이러고 한번 요청을 보내보면

이렇게 보내온 body 데이터를 가져오는것을 확인할 수 있다.

이제 이 데이터를 사용하기 편한 상태로 변경해줄 건데 이때 데이터가 정상적으로 들어오지 않을 가성성이 있기에 try-except으로 감싸주고 json.load()를 사용해서 body 데이터를 딕셔너리 형태로 변경해주자.

그리고 except 에서는 json의 JSONDecodeError에 대한 내용으로 except를 작성해주자.

여기서 하나 추가해준다면 raw_data에 아무런 값이 오지 않을 경우를 대비해서 raw_data 뒤에 or b"{}" 를 넣어주면

값이 오지 않을 경우에도 에러를 방지할 수 있게 할 수 있다.

 

그리고 이제 data에 담긴 값중 isTrue를 꺼내서 그 값이 Fasle인지 True인지 확인해주는 조건절을 만들어야하는데 body에서 들어오는 값의 경우는 타입에 대해서도 측정을 하기 때문에 이제 모든 조건에 대해서 조건을 걸어줘야 한다 

 

우리가 허용할 값은 True, true에 대해서 실제 bool값인 경우를 확인해야하고, 1일 경우 int 혹은 float 값인지를 확인해야하고 , 1, on, On, True인 string 값에 대해서도 확인해야 한다 

 

그러면 bool인 경우 단독으로 isTrue 자체로 확인해야하고 

그리고 isTrue가 int 나 float타입이면서 1인 경우에 대해서 조건을 확인해야하고

마지막으로 isTrue가 string일 경우 true, on, 1을 확인하기 위한 조건을

걸어주면 된다

그리고 이 전체 값에 not을 묶어서 true, on, 1이 아닌 경우에 대해서는 모두 exception을 날릴수 있도록 설정해주면 된다.

 

이렇게 하면 isTrue에 값을 True 혹은 On 혹은 1를 제외한 값을 담아 보낼 경우 에러로 처리해주는 미들웨어를 만드는 것을 완성했다.

 

사실상의 처리는 Header, Cookie, Body, QueryString, PathParameter 등등 전달하는 방법에 따라 값을 받아오는 방법에 대해서만 알고 있다면 미들웨어의 생성은 간단하게 할 수 있다.

본인이 만들었던 파라미터가 아니라 토큰을 확인한다거나 쿠키에 있는 세션을 확인하는 등의 방식으로 처리하면 권한이나 접근에 대해서 제한을 두거나 하는 방법이 가능해진다.

 

2. Middleware로 CORS 처리

기존에 사용했던 데코레이터를 통해서 미들웨어를 등록하는 방식은 함수형 미들웨어 등록 방식이다.

함수형 미들웨어 등록방식은 간단한 전/후처리(로그, 헤더 추가, 간단한 검증)에 대해 설정하기 좋은 방식이고 CROS와 같은 경우는 복잡한 처리가 필요한데 이걸 데코레이터를 사용해서 등록하기에는 안정적이지 못하고 빼먹을 만한 요소가 많기 때문에 함수형으로는 처리를 하지 않는다.

 

CORS의 처리는 FastAPI의 add_middleware()를 사용해서 처리한다

 

add_middleware

add_middleware는 데코레이터로 추가하던 미들웨어와는 다르게 미들웨어 클래스를 등록하거나 동적으로 미들웨어를 추가할 때 사용된다.

이 방식은 좀 더 유연하고 클래스 인스턴스의 인자를 전달할 수도 있는 방식이다.

add_middleware는 아래와 같은 형태를 가지고 있고 각 요소를 설명해보자면.

app.add_middleware(MiddlewareClass, **options)

 

1) MiddlewareClass

첫번째 인자로 들어가는 것은 미들웨어의 역할을 수행하는 클래스 자체이다.

미들웨어 클래스를 전달하면 클래스를 인스턴스화 해서 미들웨어 체인에 추가해준다.

이 안에 들어갈 수 있는 클래스의 예시로는 CORSMiddleware, HTTPSRedirectMiddleware등이 있다.

2) **kwargs

해당 옵션은 첫번째 인자인 미들웨어 클래스에 따라서 값이 다르게 설정될 수 있다.

add_middleware 함수의 경우는 내부적으로 MiddlewareClass의 인스턴스를 생성하고 두번째 인자부터 생성하고자 하는 미들웨어클래스의 __init__ 생성자에게 인자로 전달하게 되면서 인스턴스를 생성할때 생성자에 전달할 인자를 가지고 전달할 수 있도록 하는 인자들이다.

그래서 전달하는 미들웨어 클래스의 종류에 따라서 두번째 이후의 인자들이 변경될 수 밖에 없어진다라는 것을 알고 있자.

 

이제 CORSMiddleware 클래스에 대해서 미들웨어를 설정하도록 해보자.

코드를 생성하기 이전에 init에 해당하는 속성들에 대해서 먼저 확인해본다면 

1) allow_origins : 문자열 요소를 가진 List 객체를 전달한다

요청을 허용할 Origin을 설정한다.

여기에 *(와일드카드)를 넣으면 모든 출처를 허용하고 이는 보안상 위험할 수 있기에 특정 origin에 대해서 작성해두는게 좋다

 

2) allow_origin_regex : 정규 표현식을 문자열로 전달한다

allow_origins 대신에 정규 표현식을 사용해서 출처를 전달한다.

방식은 [r"https://.*\.myfrontend\.com"]와 같이 특정 도메인의 모든 서브 도메인에 대해서 허용할 때 사용된다

(중괄호는 구분하기 위해서지 중괄호가 필요하지는 않다)

 

3) allow_credentials : bool 값을 전달한다

True로 설정하면 쿠키, 인증헤더, 클라이언트 인증서 같은 자격 증명을 포함한 cross origin를 허용한다

다만, allow_origins가 *일 경우 True로 설정할수는 없다

 

4) allow_methods: 문자열 요소를 가진 List 객체를 전달한다

GET, POST, PUT 등 허용할 HTTP 메서드를 지정한다

*로 설정할 경우 모든 메서드를 허용한다

 

5) allow_headers: 문자열 요소를 가진 List 객체를 전달한다

X-Auth-Token, Content-Type 등 교차 출처 요청에서 허용할 HTTP 요청 헤더를 지정한다

*로 설정할 경우 모든 헤더를 허용한다

 

6) expose_headers: 문자열 요소를 가진 List 객체를 전달한다

클라이언트가 접근할 수 있도록 노출할 응답헤더를 지정한다.

기본적으로 브라우저는 몇몇 안전한 헤더만 접근할 수 있으므로 커스텀 헤더를 노출하려면 이 속성을 사용해야 한다

 

7) max_age: int 값으로 초에 해당한다

사전 요청(preflight request)의 유효기간을 설정한다.

이 시간동안 브라우저는 복잡한 CORS요청을 보낼 때마다 사전 요청을 보내지 않고 캐시된 정보를 사용하기에 성능향상에 도움이 된다.

 

이제 해당 설정을 추가해서 미들웨어를 하나 등록해보자.

 

먼저 CORS를 허가할 도메인 리스트를 생성해보자.

allow_origins설정으로 추가될것이기에 해당 인자가 받을 갑인 List[str]형태의 리스트로 생성해주도로고 하자.

이제 app에 add_middleware를 생성하고 

첫번째는 CORS에 대한 미들웨어 클래스를 넣기 위해서 먼저 CORSMiddleware를 import하고 

이제 클래스를 넣어주고

 

allow_origins에 origins를 넣어줘서 CORS를 허가할 도메인을 추가해주고 

allow_credentials를 True로 허용해서 쿠키, 인증해더등 자격 증명에 대한 요청을 허용하고 

그리고 메서드와 헤더도 모두 허가해주자.

 

 

이제 이렇게 설정한 다음에 CORS를 테스트하기 위해서 HTML에서 요청을 보내는 파일을 하나 생성하고

파이썬을 사용해서 해당 파일을 포트를9090으로 실행해보면

이렇게 화면이 열렸을때

이렇게 CORS가 발생하는 것을 볼 수 있다.

 

이걸 포트를 5500으로 다시키고 접속해보면

이렇게 CORS에러가 사라진것을 볼 수 있다.

 

이렇게 CORS 미들웨어 설정이 가능하다.

 

 

반응형

'Programming Language > Python' 카테고리의 다른 글

FastAPI - async with 구문  (0) 2025.09.10
Fast API - dependencies  (0) 2025.09.03
FastAPI - 의존성 주입  (0) 2025.09.03
Fast API - 라우팅 데코레이터의 속성  (1) 2025.09.02
FastAPI - Exception  (0) 2025.09.02