쿼카러버의 기술 블로그

[Flask] Flask의 Request처리 방법과 Context 개념 심층 정리 본문

[Python]

[Flask] Flask의 Request처리 방법과 Context 개념 심층 정리

quokkalover 2022. 1. 31. 00:51

Flask로 웹서버를 구현하다보면 context라는 개념을 접하게 된다. 어떻게 보면 핵심 개념인데, 엄청 헷갈리고 잘 이해가 되지 않아서 고생하고 있던 찰나에, 아주 잘 정리된 글을 발견했다. 해당 글을 번역하고, 내가 이해하기 쉬운 방식으로 재구성해 정리했다. 필자가 추천하는 flask의 context 학습방법은 아래와 같다.

flask의 context 학습방법

1)  context에 대한 간단 이해 (https://etloveguitar.tistory.com/91)
2) 이 글을 읽기

3) context-local의 구현체 살펴보기 (https://etloveguitar.tistory.com/93)

 

이 글은 flask context가 무엇인지와 더불어, 전반적으로 Flask가 어떻게 request를 처리하는지 구체적으로 이해하는 것을 목적으로 한다.

 

시작한다!

 

Flask의 Request 처리 Abstract

Flask는 Request를 어떻게 처리할까? 구체적인 internal에 딥하게 들어가기 전에 먼저 추상적인 그림부터 보자.

request가 웹서버(nginx, apache)로 들어오게 되면,

1) 웹서버는 WSGI 서버에 request를 route하면서, 해당 요청을 처리해달라고 요청한다. (WSGI는 web server와 python based web application과의 통신을 중개하는 서버)

2) WSGI서버는 그럼 Flask app에게 request를 처리해달라고 요청한다.

3) 요청이 처리되면 flask → WSGI → 웹서버 순으로 요청이 전달된다.

자 그럼 이제 실제로 요청이 왔을 때 Flask에서 어떻게 요청을 처리하는지 살펴보자.

 

Context

요청이 오게되면 Flask는 두 개의 context를 생성한다.

  • Application Context
  • Request

Context와 관련된 간단한 설명은 이 글(https://etloveguitar.tistory.com/91)을 참고하기를 바란다. 이 Context들은 Flask가 요청을 처리하기 위한 정보들을 모두 담고 있는데, 이제 이 Context를 활용해서 Flask가 요청을 어떻게 처리하는지 살펴보자.

 

Overview Diagram

위 다이어그램에서 각 순번 별로 어떻게 데이터가 처리되는지 자세히 살펴보자.

 

1) 웹서버와 WSGI 서버

웹서버는 HTTP request를 WSGI서버에 전달한다.

  • 웹서버 예시 : Apache Nginx
  • WSGI 서버 예시 : Gunicorn, uWSGI, mod_wsgi

Flask를 Development서버로 실행하면 production level로는 사용할 수 없는 WSGI 서버가 같이 실행된다.

 

2) Spawn Worker

  • request를 처리하기 위해 WSGI서버는 worker를 spawn한다.
  • 이 때 worker는 구현체에 따라 thread, process나 coroutine이 될 수 있다. 무튼 하나의 request당 하나의 worker가 실행된다는 점만 유의하자

 

3) Context 생성

요청을 처리하기 위해 Flask app이 실행되면, Flask는 Application context와 Request context를 생성한다. 그리고, 각각 전역 객체인 stack에 push한다.

앞 글에서 설명했듯이, Application context는 database configuration과 같이 application-level의 데이터를 관리하고, Request context는 요청 응답에 필요한 request-specific한 데이터를 관리한다.

 

4) Proxy

위 3단계에서 요청을 처리하기 위한 Application Context와 Request Context들을 각각의 Stack에 담아두었는데, 이제는 이 정보에 접근하기 위한 매게가 필요하다. 그것이 바로 proxy다.

이때의 proxy란 앞 글에서 잠시 살펴보았던 current_apprequest 객체다.

  • current_app - proxy to the Application context for the worker
  • request - proxy to the Request context for the worker

이 때 한 가지 의문점이 생긴다. 이렇게 global한 객체에 proxy를 통해 접근한다 하더라도 thread-safe하지 않은데 flask는 이 문제를 어떻게 해결할까? 모든 worker가 이 global객체인 step들에 접근하게 되면 보안적으로도 문제가 생길 수 있기 때문이다.

Flask는 context-local라는 매우 우아하고 근사한 방법으로 해결한다. context-local은 모든 worker들이 같은 방법으로 접근하지만 각각 자신에게 부여된 고유한 object를 뜻한다.

context local

Python에는 thread-local data라는 개념이 있다. 이는 특정 thread만 접근할 수 있는 thread-safe하고 thread-unique한 데이터를 의미한다. 다르게 설명하자면 멀티 스레딩 환경에서 각각의 스레드는 자신의 고유한(unqiue) 데이터를 thread-safe한 방식으로 접근할 수 있다.

이와 다르게 Context local은 Flask에서 구현한 개념으로, thread-local과는 다르게 좀 더 generic한 개념이다. 여기서 generic이란 thread뿐 아니라 process, coroutine에서도 사용할 수 있다는 것을 의미한다. 참고로 context-local은 Flask를 구성하는 필수 패키지 중 하나인 Werkzeug에서 구현돼있다. 만약 하나의 context-local 객체에 데이터가 저장되면, 해당 데이터는 특정 worker만이 접근할 수 있다. 따라서 여러 worker가 context-local객체에 접근하게 되면 각각은 자신에게 부여된 data에 접근하게 된다.

즉, flask에서 current_app과 request proxy는 각각의 stack에 있는 context에접근할수있고,모든 데이터는 context-local 객체에 저장돼있다.

 

context local의 구현체와 동작과 관련해 더 구체적으로 알아보고 싶은 독자는 아래 글을 읽어보는 것을 추천한다.

https://etloveguitar.tistory.com/93

 

[Flask] context-local 구현체 살펴보기 (flask application context, request context)

이 글은 Flask의 context-local 의 구현체가 어떻게 생겼는지에 대해 알아본다. 따라서, Context Local에 대해 알아보는 독자는 Flask의 Application Context와 Request Context가 무엇인지, 그리고 Flask가 어떻..

etloveguitar.tistory.com

 

Proxy의 장점

만약 내가 직접 web framework를 구현하게 되면, application context와 request context를 아래 예시처럼 controller function에 넘겨주려고 할 것이다. (실제로 많은 웹 프레임워크들이 아래의 방식을 활용하고 있다.)

@app.route('/add_item', methods=['GET', 'POST'])
def add_item(application_context, request_context):  # contexts passed in!
   if request_context.method == 'POST':
       # Save the form data to the database
       ...
       application_context.logger.info(f"Added new item ({ request_context.form['item_name'] })!")
       ...

하지만 Flask를 사용하면 current_apprequest라는 proxy를 통해서 context를 argument로 넘길 필요가 없어진다. 이렇게 함을 통해서 좀더 심플하게 controller를 정의할 수 있다. 물론 이렇게 함으로써 초반에는 좀 헷갈릴 수 있지만 매우 흥미롭다.

한번 더 강조하자면 current_app과 request proxy는 전역 변수가 아니다. context-local로 정의된 전역 객체다. 따라서 이 객체들은 모두 각각 worker에게 고유한 객체들이다.

 

5) Clean Up

Flask Application이 request를 처리할 때 request context와 application context는 stack에서 pop되고, 이때 stack은 clean up된다.

 

마치며

이렇게 Flask에서 Application Context와 Request Context를 어떻게 활용하는지에 대해 알아보았다. 아무리 찾아봐도 잘 이해가 되지 않았던 개념인데, 이해하기 쉽게 정리해주신 Patrick Kennedy께 정말 감사하다.

 

 

 

참고자료:

https://www.codestudyblog.com/cnb11/1124181719.html

https://testdriven.io/blog/flask-contexts/

https://testdriven.io/blog/flask-contexts-advanced/

https://flask.palletsprojects.com/en/1.1.x/reqcontext/#notes-on-proxies

https://speakerdeck.com/mitsuhiko/advanced-flask-patterns-1

https://en.wikipedia.org/wiki/Context_(computing)

https://www.youtube.com/watch?v=fq8y-9UHjyk

https://www.linkedin.com/pulse/application-context-request-flask-shruthi-sagar-cr/?trk=articles_directory

Comments