Django에서 Stdout을 스트리밍 응답으로 리디렉션하는 방법

27925 단어 djangopythontutorial
때로는 백엔드에서 긴 작업을 실행해야 하며 작업이 복잡하고 오류가 발생하기 쉽습니다. 따라서 사용자가 실시간 콘솔 로그를 볼 수 있기를 바랍니다. 따라서 함수의 stdout을 사용자의 브라우저로 리디렉션해야 합니다.

다음과 같은 기능이 주어집니다. 브라우저에서 실시간으로 stdout을 보는 방법은 무엇입니까?

import time


def job(times):
    for i in range(times):
        print(f'Task #{i}')
        time.sleep(1)
    print('Done')
    time.sleep(0.5)




스트리밍 응답



일반적으로 응답은 모든 데이터가 수집된 후에 전송됩니다. 그러나 때로는 데이터가 준비될 때까지 기다릴 수 없습니다. 이 경우 스트리밍 응답을 사용합니다. Django에서는 StreamingHttpResponse입니다. 다음 문서에서는 SHR이라고 하겠습니다. StreamingHttpResponse는 반복자를 입력으로 받아들입니다. iterator에서 새 값을 가져올 때마다 값을 보냅니다. 이를 사용하려면 반복자 함수만 구현하면 됩니다. yield의 값을 실시간으로 사용자의 브라우저로 전송합니다.

# Example of StreamingHttpResponse

from django.http.response import StreamingHttpResponse
def example():
    for i in range(5):
        # Add <br> to break line in browser
        yield f'{i}<br>'

def stream(request):
    return StreamingHttpResponse(example())



출력(브라우저에서):

0
1
2
3
4


스레딩



작업과 스트림 출력을 동시에 실행하므로 동시성이 필요합니다. Python에는 스레딩, 다중 처리 등과 같은 여러 선택 사항이 있습니다. 이 기사에서는 스레딩이 더 쉽기 때문에 스레딩을 사용하겠습니다.

# Example of threading

from threading import Thread
import time

def example(times):
    for i in range(times):
        print(i)
        time.sleep(1)

# Create Thread
thread = Thread(target=example, args=(5,))

# Start Thread
thread.start()

time.sleep(2)
print("This is printed in the main thread")

# Waiting thread to be done
thread.join()


산출:

0
1
This is printed in the main thread
2
3
4


표준 출력 리디렉션



Python이 인쇄하는 위치를 변경하려면 변경해야 합니다sys. stdout. 모든 파일류 객체를 허용합니다. 특히 write 메서드로 개체를 정의해야 합니다.

# Example of redirect stdout
import sys

class Printer:
    def __init__(self):
        self.contents = []

    def write(self, value):
        self.contents.append(value)

printer = Printer()
sys.stdout = printer

print('This should be saved in printer')

sys.stdout = sys.__stdout__

print('This should be printed to stdout')

print(printer.contents)


산출:

This should be printed to stdout
['This should be saved in printer', '\n']


스트리밍 응답으로 리디렉션 Stdout 구현



환경



파이썬 3.8.5

장고 3.2

먼저 Django 프로젝트를 생성합니다.

pip install django
django-admin startproject console_streaming
cd console_streaming
python manage.py startapp web


웹 설치

# console_streaming/settings.py

INSTALLED_APPS = [
    ...
    # Add web
    'web',
]


보기 만들기

# web/views.py

def stream(request):
    # Implement later
    pass


URL에 바인딩

# console_streaming/urls.py

from django.urls import path
from web import views

urlpatterns = [
    path('stream/', views.stream),
]


테스트 작업



이것은 우리가 사용할 테스트 기능입니다. 한 줄을 인쇄하고 1초 동안 n번 기다린 다음 "Done"을 인쇄합니다.

# web/views.py
import time


def job(times):
    for i in range(times):
        print(f'Task #{i}')
        time.sleep(1)
    print('Done')
    time.sleep(0.5)


프린터 등급



우리는 stdout을 처리하기 위해 Printer 클래스를 구현하고 Whale 프로그램 수명 주기에서 하나의 인스턴스만 사용할 것입니다. sys.stdout는 스레드에 특정하지 않기 때문에 다른 요청에서 다른 stdout을 사용하면 다른 요청에서 stdout을 가져옵니다. 그래서 사전을 사용하여 다른 스레드에 대한 대기열을 저장하고 current_thread()를 사용하여 올바른 대기열을 식별하고 선택합니다. 현재 스레드가 Printer에 등록되지 않은 경우 기본 stdout을 사용합니다.

# web/views.py
from queue import Queue
from threading import current_thread
import sys


class Printer:
    def __init__(self):
        self.queues = {}

    def write(self, value):
        '''handle stdout'''
        queue = self.queues.get(current_thread().name)
        if queue:
            queue.put(value)
        else:
            sys.__stdout__.write(value)

    def flush(self):
        '''Django would crash without this'''
        pass

    def register(self, thread):
        '''register a Thread'''
        queue = Queue()
        self.queues[thread.name] = queue
        return queue

    def clean(self, thread):
        '''delete a Thread'''
        del self.queues[thread.name]

# Initialize a Printer instance
printer = Printer()
sys.stdout = printer


### 스트리머 클래스

다음으로 Streamer 클래스에 의한 동시 실행 및 스트리밍 응답을 구현하겠습니다. 스레드를 초기화하고 대기열을 얻기 위해 프린터에 등록합니다. 그런 다음 대기열에서 값을 읽고 스레드가 끝날 때까지 응답에 양보하는 것을 반복합니다.

from threading import Thread


class Steamer:
    def __init__(self, target, args):
        self.thread = Thread(target=target, args=args)
        self.queue = printer.register(self.thread)

    def start(self):
        self.thread.start()
        print('This should be stdout')
        while self.thread.is_alive():
            try:
                item = self.queue.get_nowait()
                yield f'{item}<br>'
            except Empty:
                pass
        yield 'End'
        printer.clean(self.thread)

def stream(request):
    streamer = Steamer(job, (10,))
    return StreamingHttpResponse(streamer.start())


Django 서버 실행

$ python manage.py runserver


오픈http://localhost:8000/stream/

그럼 당신은 볼 수 있습니다



요청할 때마다 터미널에서 하나의 출력을 볼 수 있습니다.

This should be stdout


전체 조회수.py




from django.http.response import StreamingHttpResponse
from queue import Queue, Empty
from threading import Thread, current_thread
import time
import sys


class Printer:
    def __init__(self):
        self.queues = {}

    def write(self, value):
        '''handle stdout'''
        queue = self.queues.get(current_thread().name)
        if queue:
            queue.put(value)
        else:
            sys.__stdout__.write(value)

    def flush(self):
        '''Django would crash without this'''
        pass

    def register(self, thread):
        '''register a Thread'''
        queue = Queue()
        self.queues[thread.name] = queue
        return queue

    def clean(self, thread):
        '''delete a Thread'''
        del self.queues[thread.name]


printer = Printer()
sys.stdout = printer


class Steamer:
    def __init__(self, target, args):
        self.thread = Thread(target=target, args=args)
        self.queue = printer.register(self.thread)

    def start(self):
        self.thread.start()
        print('This should be stdout')
        while self.thread.is_alive():
            try:
                item = self.queue.get_nowait()
                yield f'{item}<br>'
            except Empty:
                pass
        yield 'End'
        printer.clean(self.thread)


def job(times):
    for i in range(times):
        print(f'Task #{i}')
        time.sleep(1)
    print('Done')
    time.sleep(0.5)


def stream(request):
    streamer = Steamer(job, (10,))
    return StreamingHttpResponse(streamer.start())



코드 완료GitHub

wancat.cc에 게시된 원본

참조


  • Python Docs: threading
  • Python Docs: queue
  • Chase Seibert: Redirect console output to a Django HttpResponse
  • thread specific sys.stdout?
  • G. T. Wang: Python 多執行緒 threading 模組平行化程式設計教學
  • 좋은 웹페이지 즐겨찾기