오히려 좋아..

상황이 나쁘게만 흘러가는 것 같을 때 외쳐보자.. .

궁금한 마음으로 포트폴리오 보기

Web Programming/Django

[Django] python manage.py를 하면 무슨 일이?(runserver 편)

junha6316 2021. 5. 20. 15:50

 

지난 글에서는 python manage.py를 작성했을 때 어떤 과정으로 명령어가 실행되는지 알아보았다. 이번 포스트에서는 python manage.py 명령어 중에 가장 많이 사용한다고 해도 무방한 python manange.py runserver 를 사용했을 때 어떤 일이 발생하는지 알아보겠다.

 

1. 코드

먼저 지난 글에서 python manage.py [파일이름]을 작성하면 해당 파일을 실행시켜준다는 것을 알았다. 이를 통해 python manage.py runserver를 실행시키면 runserver.py를 실행 시켜주는 것을 알 수 있다. runserver.py 코드는 아래와 같다.  가시성을 위해 메인 로직만을 적어놓았으며 자세한 코드를 확인하고 싶다면 아래 링크나 VS code에서 Go to Definition을 사용하는 것을 추천한다.

https://github.com/django/django/blob/main/django/core/management/commands/runserver.pyhttps://github.com/django/django/blob/main/django/core/management/commands/runserver.py

 

django/django

The Web framework for perfectionists with deadlines. - django/django

github.com

#django/core/management/commands/runserver.py

#BaseCommand
#execute를 통해 command를 실행하는데
#self.execute는 다시 self.handle를 호출 한다.
class Command(BaseCommand):
    def add_arguments(self, parser):
        """ 명령어 작성시 입력한 추가 인자들을 받는 함수 """

    def execute(self, *arg, **options):
    	""" 명령어를 작성후 엔터시 실제로 명령을 수행하는 부분 """
        #BaseCommand를 확인해보면 handle를 호출하는 부분을 확인할 수 있다.
        super().execute(*args, **options) 
    
    def handle(self, *args, **options): 
        self.run(**options)
     
    def run(self, **oprtions):
        self.inner_run(None, **options)  

    def get_handler(self, *args, **options):
        return get_internal_wsgi_application()
    
    def inner_run(self, *args, **options):
        try:
           handler = self.get_handler(*args, **options)
           run(self.addr, int(self.port), handler, ipv6=self.use_ipv6,\
           threading=threading, server_cls=self.server_cls)
        except:
           pass

 

먼저 runserver.py는 Command 클래스에 의해서 구동되는데 Command 클래스는 BaseCommand를 상속받은 클래스이다.

BaseCommand는 기본적으로 execute함수에 의해 구동되며 executehandle 함수를 호출한다. 이러한 구동방식은 BaseCommand를 상속받은 모든 함수에서 동일하게 동작한다.

 

2. 구동 방식

flow Chart

 위에서 설명했다시피 실제로 명령어를 구동하는 부분은 execute함수이고 이 함수는 handle함수를 호출한다. handle함수에서는 ip와 관련된 몇몇 예외처리를 진행하고 run 함수를 호출한다. run함수에서는 use_reload에 따라 다른 함수를 실행하는데 일단 inner_run을 실행하는 것으로 생각하자. 마지막으로 inner_run에서는 WSGIhandler를 갖고 온 후에  run 함수를 실행시킨다. 여기서 run함수는  클래스 내부에 있는 run함수와는 다른 함수이다.  run 함수를 좀더 자세히 들여다 보자.

#django/core/servers/basehttp.py

def run(addr, port, wsgi_handler, ipv6=False, threading=False, server_cls=WSGIServer):
    server_address = (addr, port)
    if threading:
        httpd_cls = type('WSGIServer', (socketserver.ThreadingMixIn, server_cls), {})
    else:
        httpd_cls = server_cls
    httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)
    if threading:
        httpd.daemon_threads = True
    httpd.set_app(wsgi_handler)
    httpd.serve_forever()

이 함수는 WSGIServer에 wsgi_handler를 application으로 설정해주고 httpd.serve_forever()를 통해 서버를 구동하는 것을 알 수 있다. 그렇다면 serve_forever()는 어떤 동작을 하는 것일까? 먼저 이것에 대해 설명하기 전에 WSGIServer가 어떤 클래스인지 알아보자

 

3. WSGIServer

WSGIServer는 아래와 같은 상속관계를 갖고 있다. 

WSGIServer ← simple.WSGIServer ← HTTPServer ← socketserver.TCPServer ← BaseServer

위에서 확인한 serve_forever() 함수는 BaseServer에 작성되어 있는 함수인데  아래처럼 작성되어 있다.


#BaseServer
def serve_forever(self, poll_interval=0.5):
        """Handle one request at a time until shutdown.

        Polls for shutdown every poll_interval seconds. Ignores
        self.timeout. If you need to do periodic tasks, do them in
        another thread.
        """
        self.__is_shut_down.clear()
        try:
            # XXX: Consider using another file descriptor or connecting to the
            # socket to wake this up instead of polling. Polling reduces our
            # responsiveness to a shutdown request and wastes cpu at all other
            # times.
            with _ServerSelector() as selector:
                selector.register(self, selectors.EVENT_READ)
                while not self.__shutdown_request:
                    ready = selector.select(poll_interval)
                    # bpo-35017: shutdown() called during select(), exit immediately.
                    if self.__shutdown_request:
                        break
                    if ready:
                        self._handle_request_noblock()
                    self.service_actions()

 즉 self.__shutdown_requst가 False라면 while문을 돌면서 self.service_actions()를 실행시킨다. 한편 실제 server를 activate시키는 로직은 TCPServer에 구현되어 있는데 아래와 같다.

class TCPServer(BaseServer):

    address_family = socket.AF_INET

    socket_type = socket.SOCK_STREAM

    request_queue_size = 5

    allow_reuse_address = False

    def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True):
        """Constructor.  May be extended, do not override."""
        BaseServer.__init__(self, server_address, RequestHandlerClass)
        self.socket = socket.socket(self.address_family,
                                    self.socket_type)
        if bind_and_activate:
            try:
                self.server_bind()
                self.server_activate()
            except:
                self.server_close()
                raise
                
    def server_activate(self):
        self.socket.listen(self.request_queue_size)

server_activate() 함수를 보면 클래스 내부에 선언되어 있는 소켓을 listen하는 것을 볼 수 있다. 이 함수를 통해 해당 소켓에 request가 들어올때까지 기다리는 것을 볼 수 있다. 

 

이번 포스트에서는 runserver를 했을 때 어떤 과정을 통해 장고가 특정 소켓을 listen상태로 바꾸는지 알아보았다. 물론 여기서 언급한 내용은 참고용이기 때문에 반드시 직접 코드를 열어보는 것을 추천한다.