如何通过 WebSocket 发送长 运行 Python 脚本的输出?

How to send the output of a long running Python script over a WebSocket?

用户上传了一个 python 文件,我需要在我的服务器上执行该文件并发回通过 WebSocket 创建的标准输出。执行的 python 文件将 运行 持续几分钟,我需要 return 套接字上的标准输出,因为它们是 "printed" 实时输出,而不是在完成时剧本。

我试过使用:Python. Redirect stdout to a socket,但这不是 WebSocket,我的 React 前端无法成功连接到它。 (如果你能解决这个问题,那也能解决我的问题)

我也尝试过使用 websocketd,但由于我无法在每个用户添加的打印语句后添加 sys.stdout.flush(),因此无法解决我的问题。

我也尝试过使用子进程的 PIPE 功能,但它有同样的刷新问题

async def time(websocket, path):
    while True:
        data = "test"
        await websocket.send(data)
        # Run subprocess to execute python file in here
        # sys.stdout => websocket.send             

start_server = websockets.serve(time, "127.0.0.1", 5678)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()

这是我正在使用的 python 测试脚本:

from time import sleep
for i in range(40):
    print(i)
    sleep(0.1)

这个独立的例子将

  1. 从网络套接字读取 python 脚本
  2. 将脚本写入文件系统
  3. 运行 禁用输出缓冲的脚本
  4. 一次一行读取脚本输出
  5. 将每行输出写入网络套接字
import asyncio
import websockets
import subprocess

async def time(websocket, path):
    script_name = 'script.py'
    script = await websocket.recv()
    with open(script_name, 'w') as script_file:
        script_file.write(script)
    with subprocess.Popen(['python3', '-u', script_name],
                          stdout=subprocess.PIPE,
                          bufsize=1,
                          universal_newlines=True) as process:
        for line in process.stdout:
            line = line.rstrip()
            print(f"line = {line}")
            await websocket.send(line)

start_server = websockets.serve(time, "127.0.0.1", 5678)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()

我使用这个 javascript 代码来测试服务器:

const WebSocket = require('ws');
let socket = new WebSocket("ws://127.0.0.1:5678");

socket.onopen = function(e) {
    let script = '\
import time\n\
for x in range(100):\n\
    print(f"x = {x}")\n\
    time.sleep(0.25)\n\
';
    console.log("sending data...");
    socket.send(script);
    console.log("done.");
};

socket.onmessage = function(event) {
    console.log(event.data.toString());
};

socket.onerror = function(event) {
    console.log(event);
};

Popen的使用是基于对这个问题的回答:

Read streaming input from subprocess.communicate()

-u选项传递给pythondisable output buffering

这个 class 将作为服务器的包装器

import sys


class ServerWrapper:
    def __init__(self, ws):
        self.__ws = ws
        sys.stdout = self

    def write(self, data):
        self.__ws.send(data)

    def close(self):
        sys.stdout = sys.__stdout__

您需要使用您的 websocket 对其进行初始化。

每次调用 print 时都会调用 write 函数(因为我们将 sys.stdout 更改为自定义输出。

然后,当你执行完脚本后,你可以用close

恢复标准输出

import asyncio
import websockets
import subprocess
import sys


class ServerWrapper:
    def __init__(self, ws):
        self.__ws = ws
        sys.stdout = self

    def write(self, data):
        self.__ws.send(data)

    def close(self):
        sys.stdout = sys.__stdout__


async def time(websocket, path):
    wrapper = ServerWrapper(websocket)
    # get commands and execute them as you would normally do
    # you don't need to worry about reading output and sending it


start_server = websockets.serve(time, "127.0.0.1", 5678)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()