如何通过 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)
这个独立的例子将
- 从网络套接字读取
python
脚本
- 将脚本写入文件系统
- 运行 禁用输出缓冲的脚本
- 一次一行读取脚本输出
- 将每行输出写入网络套接字
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
选项传递给python
到disable 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()
用户上传了一个 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)
这个独立的例子将
- 从网络套接字读取
python
脚本 - 将脚本写入文件系统
- 运行 禁用输出缓冲的脚本
- 一次一行读取脚本输出
- 将每行输出写入网络套接字
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
选项传递给python
到disable 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()